summaryrefslogtreecommitdiff
path: root/src/mongo/s
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/s')
-rw-r--r--src/mongo/s/balance.cpp895
-rw-r--r--src/mongo/s/balance.h155
-rw-r--r--src/mongo/s/balancer_policy.cpp753
-rw-r--r--src/mongo/s/balancer_policy.h390
-rw-r--r--src/mongo/s/balancer_policy_tests.cpp909
-rw-r--r--src/mongo/s/bson_serializable.h113
-rw-r--r--src/mongo/s/catalog/catalog_cache.cpp66
-rw-r--r--src/mongo/s/catalog/catalog_cache.h94
-rw-r--r--src/mongo/s/catalog/catalog_manager.cpp171
-rw-r--r--src/mongo/s/catalog/catalog_manager.h686
-rw-r--r--src/mongo/s/catalog/dist_lock_catalog.cpp7
-rw-r--r--src/mongo/s/catalog/dist_lock_catalog.h212
-rw-r--r--src/mongo/s/catalog/dist_lock_catalog_impl.cpp580
-rw-r--r--src/mongo/s/catalog/dist_lock_catalog_impl.h64
-rw-r--r--src/mongo/s/catalog/dist_lock_catalog_impl_test.cpp1279
-rw-r--r--src/mongo/s/catalog/dist_lock_catalog_mock.cpp462
-rw-r--r--src/mongo/s/catalog/dist_lock_catalog_mock.h328
-rw-r--r--src/mongo/s/catalog/dist_lock_manager.cpp56
-rw-r--r--src/mongo/s/catalog/dist_lock_manager.h151
-rw-r--r--src/mongo/s/catalog/dist_lock_manager_mock.cpp115
-rw-r--r--src/mongo/s/catalog/dist_lock_manager_mock.h57
-rw-r--r--src/mongo/s/catalog/dist_lock_ping_info.cpp21
-rw-r--r--src/mongo/s/catalog/dist_lock_ping_info.h44
-rw-r--r--src/mongo/s/catalog/legacy/catalog_manager_legacy.cpp2507
-rw-r--r--src/mongo/s/catalog/legacy/catalog_manager_legacy.h245
-rw-r--r--src/mongo/s/catalog/legacy/cluster_client_internal.cpp293
-rw-r--r--src/mongo/s/catalog/legacy/cluster_client_internal.h43
-rw-r--r--src/mongo/s/catalog/legacy/config_coordinator.cpp637
-rw-r--r--src/mongo/s/catalog/legacy/config_coordinator.h33
-rw-r--r--src/mongo/s/catalog/legacy/config_upgrade.cpp789
-rw-r--r--src/mongo/s/catalog/legacy/config_upgrade.h200
-rw-r--r--src/mongo/s/catalog/legacy/config_upgrade_helpers.cpp127
-rw-r--r--src/mongo/s/catalog/legacy/config_upgrade_helpers.h52
-rw-r--r--src/mongo/s/catalog/legacy/config_upgrade_v0_to_v7.cpp104
-rw-r--r--src/mongo/s/catalog/legacy/config_upgrade_v6_to_v7.cpp131
-rw-r--r--src/mongo/s/catalog/legacy/distlock.cpp1262
-rw-r--r--src/mongo/s/catalog/legacy/distlock.h353
-rw-r--r--src/mongo/s/catalog/legacy/legacy_dist_lock_manager.cpp261
-rw-r--r--src/mongo/s/catalog/legacy/legacy_dist_lock_manager.h57
-rw-r--r--src/mongo/s/catalog/legacy/legacy_dist_lock_pinger.cpp482
-rw-r--r--src/mongo/s/catalog/legacy/legacy_dist_lock_pinger.h187
-rw-r--r--src/mongo/s/catalog/replset/catalog_manager_replica_set.cpp702
-rw-r--r--src/mongo/s/catalog/replset/catalog_manager_replica_set.h150
-rw-r--r--src/mongo/s/catalog/replset/catalog_manager_replica_set_test.cpp1413
-rw-r--r--src/mongo/s/catalog/replset/catalog_manager_replica_set_test_fixture.cpp182
-rw-r--r--src/mongo/s/catalog/replset/catalog_manager_replica_set_test_fixture.h104
-rw-r--r--src/mongo/s/catalog/replset/replset_dist_lock_manager.cpp527
-rw-r--r--src/mongo/s/catalog/replset/replset_dist_lock_manager.h166
-rw-r--r--src/mongo/s/catalog/replset/replset_dist_lock_manager_test.cpp2752
-rw-r--r--src/mongo/s/catalog/type_actionlog.cpp185
-rw-r--r--src/mongo/s/catalog/type_actionlog.h335
-rw-r--r--src/mongo/s/catalog/type_changelog.cpp271
-rw-r--r--src/mongo/s/catalog/type_changelog.h441
-rw-r--r--src/mongo/s/catalog/type_changelog_test.cpp262
-rw-r--r--src/mongo/s/catalog/type_chunk.cpp332
-rw-r--r--src/mongo/s/catalog/type_chunk.h193
-rw-r--r--src/mongo/s/catalog/type_chunk_test.cpp259
-rw-r--r--src/mongo/s/catalog/type_collection.cpp291
-rw-r--r--src/mongo/s/catalog/type_collection.h179
-rw-r--r--src/mongo/s/catalog/type_collection_test.cpp89
-rw-r--r--src/mongo/s/catalog/type_database.cpp118
-rw-r--r--src/mongo/s/catalog/type_database.h121
-rw-r--r--src/mongo/s/catalog/type_database_test.cpp52
-rw-r--r--src/mongo/s/catalog/type_settings.cpp430
-rw-r--r--src/mongo/s/catalog/type_settings.h317
-rw-r--r--src/mongo/s/catalog/type_settings_test.cpp351
-rw-r--r--src/mongo/s/catalog/type_shard.cpp238
-rw-r--r--src/mongo/s/catalog/type_shard.h160
-rw-r--r--src/mongo/s/catalog/type_shard_test.cpp85
-rw-r--r--src/mongo/s/catalog/type_tags.cpp164
-rw-r--r--src/mongo/s/catalog/type_tags.h117
-rw-r--r--src/mongo/s/catalog/type_tags_test.cpp196
-rw-r--r--src/mongo/s/chunk.cpp1184
-rw-r--r--src/mongo/s/chunk.h504
-rw-r--r--src/mongo/s/chunk_diff.cpp354
-rw-r--r--src/mongo/s/chunk_diff.h164
-rw-r--r--src/mongo/s/chunk_diff_test.cpp59
-rw-r--r--src/mongo/s/chunk_manager.cpp1179
-rw-r--r--src/mongo/s/chunk_manager.h490
-rw-r--r--src/mongo/s/chunk_manager_targeter.cpp1106
-rw-r--r--src/mongo/s/chunk_manager_targeter.h242
-rw-r--r--src/mongo/s/chunk_manager_targeter_test.cpp915
-rw-r--r--src/mongo/s/chunk_version.h677
-rw-r--r--src/mongo/s/chunk_version_test.cpp177
-rw-r--r--src/mongo/s/client/dbclient_multi_command.cpp333
-rw-r--r--src/mongo/s/client/dbclient_multi_command.h72
-rw-r--r--src/mongo/s/client/mock_multi_write_command.h223
-rw-r--r--src/mongo/s/client/multi_command_dispatch.h83
-rw-r--r--src/mongo/s/client/multi_host_query.cpp526
-rw-r--r--src/mongo/s/client/multi_host_query.h488
-rw-r--r--src/mongo/s/client/multi_host_query_test.cpp1128
-rw-r--r--src/mongo/s/client/scc_fast_query_handler.cpp324
-rw-r--r--src/mongo/s/client/scc_fast_query_handler.h68
-rw-r--r--src/mongo/s/client/shard.cpp218
-rw-r--r--src/mongo/s/client/shard.h179
-rw-r--r--src/mongo/s/client/shard_connection.cpp771
-rw-r--r--src/mongo/s/client/shard_connection.h228
-rw-r--r--src/mongo/s/client/shard_connection_test.cpp394
-rw-r--r--src/mongo/s/client/shard_registry.cpp410
-rw-r--r--src/mongo/s/client/shard_registry.h239
-rw-r--r--src/mongo/s/client/sharding_connection_hook.cpp142
-rw-r--r--src/mongo/s/client/sharding_connection_hook.h30
-rw-r--r--src/mongo/s/cluster_explain.cpp510
-rw-r--r--src/mongo/s/cluster_explain.h134
-rw-r--r--src/mongo/s/cluster_last_error_info.cpp113
-rw-r--r--src/mongo/s/cluster_last_error_info.h125
-rw-r--r--src/mongo/s/cluster_write.cpp399
-rw-r--r--src/mongo/s/cluster_write.h67
-rw-r--r--src/mongo/s/collection_metadata.cpp1120
-rw-r--r--src/mongo/s/collection_metadata.h542
-rw-r--r--src/mongo/s/collection_metadata_test.cpp2303
-rw-r--r--src/mongo/s/commands/cluster_add_shard_cmd.cpp163
-rw-r--r--src/mongo/s/commands/cluster_commands_common.cpp113
-rw-r--r--src/mongo/s/commands/cluster_commands_common.h48
-rw-r--r--src/mongo/s/commands/cluster_count_cmd.cpp301
-rw-r--r--src/mongo/s/commands/cluster_current_op.cpp302
-rw-r--r--src/mongo/s/commands/cluster_db_stats_cmd.cpp130
-rw-r--r--src/mongo/s/commands/cluster_drop_database_cmd.cpp126
-rw-r--r--src/mongo/s/commands/cluster_enable_sharding_cmd.cpp119
-rw-r--r--src/mongo/s/commands/cluster_explain_cmd.cpp171
-rw-r--r--src/mongo/s/commands/cluster_find_and_modify_cmd.cpp285
-rw-r--r--src/mongo/s/commands/cluster_find_cmd.cpp189
-rw-r--r--src/mongo/s/commands/cluster_flush_router_config_cmd.cpp71
-rw-r--r--src/mongo/s/commands/cluster_fsync_cmd.cpp119
-rw-r--r--src/mongo/s/commands/cluster_get_last_error_cmd.cpp283
-rw-r--r--src/mongo/s/commands/cluster_get_prev_error_cmd.cpp60
-rw-r--r--src/mongo/s/commands/cluster_get_shard_map_cmd.cpp77
-rw-r--r--src/mongo/s/commands/cluster_get_shard_version_cmd.cpp131
-rw-r--r--src/mongo/s/commands/cluster_index_filter_cmd.cpp220
-rw-r--r--src/mongo/s/commands/cluster_is_db_grid_cmd.cpp56
-rw-r--r--src/mongo/s/commands/cluster_is_master_cmd.cpp78
-rw-r--r--src/mongo/s/commands/cluster_kill_op.cpp143
-rw-r--r--src/mongo/s/commands/cluster_list_databases_cmd.cpp278
-rw-r--r--src/mongo/s/commands/cluster_list_shards_cmd.cpp98
-rw-r--r--src/mongo/s/commands/cluster_map_reduce_cmd.cpp894
-rw-r--r--src/mongo/s/commands/cluster_merge_chunks_cmd.cpp292
-rw-r--r--src/mongo/s/commands/cluster_move_chunk_cmd.cpp342
-rw-r--r--src/mongo/s/commands/cluster_move_primary_cmd.cpp365
-rw-r--r--src/mongo/s/commands/cluster_netstat_cmd.cpp71
-rw-r--r--src/mongo/s/commands/cluster_pipeline_cmd.cpp597
-rw-r--r--src/mongo/s/commands/cluster_plan_cache_cmd.cpp224
-rw-r--r--src/mongo/s/commands/cluster_profile_cmd.cpp64
-rw-r--r--src/mongo/s/commands/cluster_remove_shard_cmd.cpp162
-rw-r--r--src/mongo/s/commands/cluster_repair_database_cmd.cpp26
-rw-r--r--src/mongo/s/commands/cluster_repl_set_get_status_cmd.cpp88
-rw-r--r--src/mongo/s/commands/cluster_reset_error_cmd.cpp76
-rw-r--r--src/mongo/s/commands/cluster_shard_collection_cmd.cpp738
-rw-r--r--src/mongo/s/commands/cluster_shutdown_cmd.cpp45
-rw-r--r--src/mongo/s/commands/cluster_split_collection_cmd.cpp365
-rw-r--r--src/mongo/s/commands/cluster_user_management_commands.cpp1499
-rw-r--r--src/mongo/s/commands/cluster_whats_my_uri_cmd.cpp60
-rw-r--r--src/mongo/s/commands/cluster_write_cmd.cpp357
-rw-r--r--src/mongo/s/commands/commands_public.cpp2581
-rw-r--r--src/mongo/s/commands/run_on_all_shards_cmd.cpp213
-rw-r--r--src/mongo/s/commands/run_on_all_shards_cmd.h102
-rw-r--r--src/mongo/s/config.cpp1155
-rw-r--r--src/mongo/s/config.h226
-rw-r--r--src/mongo/s/cursors.cpp769
-rw-r--r--src/mongo/s/cursors.h167
-rw-r--r--src/mongo/s/d_merge.cpp504
-rw-r--r--src/mongo/s/d_merge.h50
-rw-r--r--src/mongo/s/d_migrate.cpp4627
-rw-r--r--src/mongo/s/d_split.cpp1443
-rw-r--r--src/mongo/s/d_state.cpp2145
-rw-r--r--src/mongo/s/d_state.h625
-rw-r--r--src/mongo/s/dbclient_shard_resolver.cpp108
-rw-r--r--src/mongo/s/dbclient_shard_resolver.h63
-rw-r--r--src/mongo/s/distlock_test.cpp639
-rw-r--r--src/mongo/s/grid.cpp120
-rw-r--r--src/mongo/s/grid.h157
-rw-r--r--src/mongo/s/metadata_loader.cpp427
-rw-r--r--src/mongo/s/metadata_loader.h224
-rw-r--r--src/mongo/s/metadata_loader_test.cpp1037
-rw-r--r--src/mongo/s/mock_ns_targeter.h307
-rw-r--r--src/mongo/s/mock_shard_resolver.h29
-rw-r--r--src/mongo/s/mongo_version_range.cpp208
-rw-r--r--src/mongo/s/mongo_version_range.h44
-rw-r--r--src/mongo/s/mongo_version_range_test.cpp272
-rw-r--r--src/mongo/s/mongos_options.cpp330
-rw-r--r--src/mongo/s/mongos_options.h76
-rw-r--r--src/mongo/s/mongos_options_init.cpp77
-rw-r--r--src/mongo/s/ns_targeter.h241
-rw-r--r--src/mongo/s/request.cpp177
-rw-r--r--src/mongo/s/request.h70
-rw-r--r--src/mongo/s/s_only.cpp213
-rw-r--r--src/mongo/s/server.cpp271
-rw-r--r--src/mongo/s/server.h6
-rw-r--r--src/mongo/s/shard_key_pattern.cpp631
-rw-r--r--src/mongo/s/shard_key_pattern.h355
-rw-r--r--src/mongo/s/shard_key_pattern_test.cpp835
-rw-r--r--src/mongo/s/shard_resolver.h35
-rw-r--r--src/mongo/s/stale_exception.h250
-rw-r--r--src/mongo/s/strategy.cpp1015
-rw-r--r--src/mongo/s/strategy.h133
-rw-r--r--src/mongo/s/type_config_version.cpp241
-rw-r--r--src/mongo/s/type_config_version.h414
-rw-r--r--src/mongo/s/type_config_version_test.cpp252
-rw-r--r--src/mongo/s/type_lockpings.cpp129
-rw-r--r--src/mongo/s/type_lockpings.h251
-rw-r--r--src/mongo/s/type_lockpings_test.cpp68
-rw-r--r--src/mongo/s/type_locks.cpp239
-rw-r--r--src/mongo/s/type_locks.h431
-rw-r--r--src/mongo/s/type_locks_test.cpp378
-rw-r--r--src/mongo/s/type_mongos.cpp225
-rw-r--r--src/mongo/s/type_mongos.h404
-rw-r--r--src/mongo/s/type_mongos_test.cpp271
-rw-r--r--src/mongo/s/version_manager.cpp628
-rw-r--r--src/mongo/s/version_manager.h27
-rw-r--r--src/mongo/s/version_mongos.cpp36
-rw-r--r--src/mongo/s/version_mongos.h6
-rw-r--r--src/mongo/s/write_ops/batch_downconvert.cpp448
-rw-r--r--src/mongo/s/write_ops/batch_downconvert.h78
-rw-r--r--src/mongo/s/write_ops/batch_downconvert_test.cpp359
-rw-r--r--src/mongo/s/write_ops/batch_upconvert.cpp329
-rw-r--r--src/mongo/s/write_ops/batch_upconvert.h39
-rw-r--r--src/mongo/s/write_ops/batch_upconvert_test.cpp213
-rw-r--r--src/mongo/s/write_ops/batch_write_exec.cpp547
-rw-r--r--src/mongo/s/write_ops/batch_write_exec.h136
-rw-r--r--src/mongo/s/write_ops/batch_write_exec_test.cpp431
-rw-r--r--src/mongo/s/write_ops/batch_write_op.cpp1338
-rw-r--r--src/mongo/s/write_ops/batch_write_op.h422
-rw-r--r--src/mongo/s/write_ops/batch_write_op_test.cpp3278
-rw-r--r--src/mongo/s/write_ops/batched_command_request.cpp528
-rw-r--r--src/mongo/s/write_ops/batched_command_request.h359
-rw-r--r--src/mongo/s/write_ops/batched_command_response.cpp895
-rw-r--r--src/mongo/s/write_ops/batched_command_response.h316
-rw-r--r--src/mongo/s/write_ops/batched_command_response_test.cpp75
-rw-r--r--src/mongo/s/write_ops/batched_delete_document.cpp193
-rw-r--r--src/mongo/s/write_ops/batched_delete_document.h124
-rw-r--r--src/mongo/s/write_ops/batched_delete_request.cpp468
-rw-r--r--src/mongo/s/write_ops/batched_delete_request.h226
-rw-r--r--src/mongo/s/write_ops/batched_delete_request_test.cpp77
-rw-r--r--src/mongo/s/write_ops/batched_insert_request.cpp505
-rw-r--r--src/mongo/s/write_ops/batched_insert_request.h224
-rw-r--r--src/mongo/s/write_ops/batched_insert_request_test.cpp220
-rw-r--r--src/mongo/s/write_ops/batched_request_metadata.cpp217
-rw-r--r--src/mongo/s/write_ops/batched_request_metadata.h109
-rw-r--r--src/mongo/s/write_ops/batched_request_metadata_test.cpp58
-rw-r--r--src/mongo/s/write_ops/batched_update_document.cpp312
-rw-r--r--src/mongo/s/write_ops/batched_update_document.h164
-rw-r--r--src/mongo/s/write_ops/batched_update_request.cpp522
-rw-r--r--src/mongo/s/write_ops/batched_update_request.h226
-rw-r--r--src/mongo/s/write_ops/batched_update_request_test.cpp85
-rw-r--r--src/mongo/s/write_ops/batched_upsert_detail.cpp184
-rw-r--r--src/mongo/s/write_ops/batched_upsert_detail.h124
-rw-r--r--src/mongo/s/write_ops/wc_error_detail.cpp223
-rw-r--r--src/mongo/s/write_ops/wc_error_detail.h144
-rw-r--r--src/mongo/s/write_ops/write_error_detail.cpp287
-rw-r--r--src/mongo/s/write_ops/write_error_detail.h164
-rw-r--r--src/mongo/s/write_ops/write_op.cpp391
-rw-r--r--src/mongo/s/write_ops/write_op.h332
-rw-r--r--src/mongo/s/write_ops/write_op_test.cpp509
252 files changed, 47941 insertions, 49494 deletions
diff --git a/src/mongo/s/balance.cpp b/src/mongo/s/balance.cpp
index 0ef5d3c42ce..dc429e85d80 100644
--- a/src/mongo/s/balance.cpp
+++ b/src/mongo/s/balance.cpp
@@ -64,588 +64,559 @@
namespace mongo {
- using std::map;
- using std::set;
- using std::shared_ptr;
- using std::string;
- using std::unique_ptr;
- using std::vector;
+using std::map;
+using std::set;
+using std::shared_ptr;
+using std::string;
+using std::unique_ptr;
+using std::vector;
- MONGO_FP_DECLARE(skipBalanceRound);
+MONGO_FP_DECLARE(skipBalanceRound);
- Balancer balancer;
+Balancer balancer;
- Balancer::Balancer()
- : _balancedLastTime(0),
- _policy(new BalancerPolicy()) {
+Balancer::Balancer() : _balancedLastTime(0), _policy(new BalancerPolicy()) {}
- }
+Balancer::~Balancer() = default;
- Balancer::~Balancer() = default;
+int Balancer::_moveChunks(const vector<shared_ptr<MigrateInfo>>& candidateChunks,
+ const WriteConcernOptions* writeConcern,
+ bool waitForDelete) {
+ int movedCount = 0;
- int Balancer::_moveChunks(const vector<shared_ptr<MigrateInfo>>& candidateChunks,
- const WriteConcernOptions* writeConcern,
- bool waitForDelete)
- {
- int movedCount = 0;
+ for (const auto& migrateInfo : candidateChunks) {
+ // If the balancer was disabled since we started this round, don't start new chunks
+ // moves.
+ const auto balSettingsResult =
+ grid.catalogManager()->getGlobalSettings(SettingsType::BalancerDocKey);
- for (const auto& migrateInfo : candidateChunks) {
- // If the balancer was disabled since we started this round, don't start new chunks
- // moves.
- const auto balSettingsResult =
- grid.catalogManager()->getGlobalSettings(SettingsType::BalancerDocKey);
+ const bool isBalSettingsAbsent =
+ balSettingsResult.getStatus() == ErrorCodes::NoMatchingDocument;
- const bool isBalSettingsAbsent =
- balSettingsResult.getStatus() == ErrorCodes::NoMatchingDocument;
+ if (!balSettingsResult.isOK() && !isBalSettingsAbsent) {
+ warning() << balSettingsResult.getStatus();
+ return movedCount;
+ }
- if (!balSettingsResult.isOK() && !isBalSettingsAbsent) {
- warning() << balSettingsResult.getStatus();
- return movedCount;
- }
+ const SettingsType& balancerConfig =
+ isBalSettingsAbsent ? SettingsType{} : balSettingsResult.getValue();
- const SettingsType& balancerConfig = isBalSettingsAbsent ?
- SettingsType{} : balSettingsResult.getValue();
+ if ((!isBalSettingsAbsent && !grid.shouldBalance(balancerConfig)) ||
+ MONGO_FAIL_POINT(skipBalanceRound)) {
+ LOG(1) << "Stopping balancing round early as balancing was disabled";
+ return movedCount;
+ }
- if ((!isBalSettingsAbsent && !grid.shouldBalance(balancerConfig)) ||
- MONGO_FAIL_POINT(skipBalanceRound)) {
- LOG(1) << "Stopping balancing round early as balancing was disabled";
- return movedCount;
- }
+ // Changes to metadata, borked metadata, and connectivity problems between shards
+ // should cause us to abort this chunk move, but shouldn't cause us to abort the entire
+ // round of chunks.
+ //
+ // TODO(spencer): We probably *should* abort the whole round on issues communicating
+ // with the config servers, but its impossible to distinguish those types of failures
+ // at the moment.
+ //
+ // TODO: Handle all these things more cleanly, since they're expected problems
- // Changes to metadata, borked metadata, and connectivity problems between shards
- // should cause us to abort this chunk move, but shouldn't cause us to abort the entire
- // round of chunks.
- //
- // TODO(spencer): We probably *should* abort the whole round on issues communicating
- // with the config servers, but its impossible to distinguish those types of failures
- // at the moment.
- //
- // TODO: Handle all these things more cleanly, since they're expected problems
+ const NamespaceString nss(migrateInfo->ns);
- const NamespaceString nss(migrateInfo->ns);
+ try {
+ auto status = grid.catalogCache()->getDatabase(nss.db().toString());
+ fassert(28628, status.getStatus());
+
+ shared_ptr<DBConfig> cfg = status.getValue();
- try {
- auto status = grid.catalogCache()->getDatabase(nss.db().toString());
- fassert(28628, status.getStatus());
+ // NOTE: We purposely do not reload metadata here, since _doBalanceRound already
+ // tried to do so once.
+ shared_ptr<ChunkManager> cm = cfg->getChunkManager(migrateInfo->ns);
+ invariant(cm);
- shared_ptr<DBConfig> cfg = status.getValue();
+ ChunkPtr c = cm->findIntersectingChunk(migrateInfo->chunk.min);
- // NOTE: We purposely do not reload metadata here, since _doBalanceRound already
- // tried to do so once.
- shared_ptr<ChunkManager> cm = cfg->getChunkManager(migrateInfo->ns);
+ if (c->getMin().woCompare(migrateInfo->chunk.min) ||
+ c->getMax().woCompare(migrateInfo->chunk.max)) {
+ // Likely a split happened somewhere, so force reload the chunk manager
+ cm = cfg->getChunkManager(migrateInfo->ns, true);
invariant(cm);
- ChunkPtr c = cm->findIntersectingChunk(migrateInfo->chunk.min);
+ c = cm->findIntersectingChunk(migrateInfo->chunk.min);
if (c->getMin().woCompare(migrateInfo->chunk.min) ||
- c->getMax().woCompare(migrateInfo->chunk.max)) {
-
- // Likely a split happened somewhere, so force reload the chunk manager
- cm = cfg->getChunkManager(migrateInfo->ns, true);
- invariant(cm);
-
- c = cm->findIntersectingChunk(migrateInfo->chunk.min);
+ c->getMax().woCompare(migrateInfo->chunk.max)) {
+ log() << "chunk mismatch after reload, ignoring will retry issue "
+ << migrateInfo->chunk.toString();
- if (c->getMin().woCompare(migrateInfo->chunk.min) ||
- c->getMax().woCompare(migrateInfo->chunk.max)) {
-
- log() << "chunk mismatch after reload, ignoring will retry issue "
- << migrateInfo->chunk.toString();
-
- continue;
- }
- }
-
- BSONObj res;
- if (c->moveAndCommit(migrateInfo->to,
- Chunk::MaxChunkSize,
- writeConcern,
- waitForDelete,
- 0, /* maxTimeMS */
- res)) {
-
- movedCount++;
continue;
}
+ }
- // The move requires acquiring the collection metadata's lock, which can fail.
- log() << "balancer move failed: " << res
- << " from: " << migrateInfo->from
- << " to: " << migrateInfo->to
- << " chunk: " << migrateInfo->chunk;
+ BSONObj res;
+ if (c->moveAndCommit(migrateInfo->to,
+ Chunk::MaxChunkSize,
+ writeConcern,
+ waitForDelete,
+ 0, /* maxTimeMS */
+ res)) {
+ movedCount++;
+ continue;
+ }
- if (res["chunkTooBig"].trueValue()) {
- // Reload just to be safe
- cm = cfg->getChunkManager(migrateInfo->ns);
- invariant(cm);
+ // The move requires acquiring the collection metadata's lock, which can fail.
+ log() << "balancer move failed: " << res << " from: " << migrateInfo->from
+ << " to: " << migrateInfo->to << " chunk: " << migrateInfo->chunk;
- c = cm->findIntersectingChunk(migrateInfo->chunk.min);
+ if (res["chunkTooBig"].trueValue()) {
+ // Reload just to be safe
+ cm = cfg->getChunkManager(migrateInfo->ns);
+ invariant(cm);
+
+ c = cm->findIntersectingChunk(migrateInfo->chunk.min);
- log() << "performing a split because migrate failed for size reasons";
+ log() << "performing a split because migrate failed for size reasons";
- Status status = c->split(Chunk::normal, NULL, NULL);
- log() << "split results: " << status;
+ Status status = c->split(Chunk::normal, NULL, NULL);
+ log() << "split results: " << status;
- if (!status.isOK()) {
- log() << "marking chunk as jumbo: " << c->toString();
+ if (!status.isOK()) {
+ log() << "marking chunk as jumbo: " << c->toString();
- c->markAsJumbo();
+ c->markAsJumbo();
- // We increment moveCount so we do another round right away
- movedCount++;
- }
+ // We increment moveCount so we do another round right away
+ movedCount++;
}
}
- catch (const DBException& ex) {
- warning() << "could not move chunk " << migrateInfo->chunk.toString()
- << ", continuing balancing round" << causedBy(ex);
- }
+ } catch (const DBException& ex) {
+ warning() << "could not move chunk " << migrateInfo->chunk.toString()
+ << ", continuing balancing round" << causedBy(ex);
}
-
- return movedCount;
}
- void Balancer::_ping(bool waiting) {
- grid.catalogManager()->update(
- MongosType::ConfigNS,
- BSON(MongosType::name(_myid)),
- BSON("$set" << BSON(MongosType::ping(jsTime()) <<
- MongosType::up(static_cast<int>(time(0) - _started)) <<
- MongosType::waiting(waiting) <<
- MongosType::mongoVersion(versionString))),
- true,
- false,
- NULL);
- }
+ return movedCount;
+}
+
+void Balancer::_ping(bool waiting) {
+ grid.catalogManager()->update(
+ MongosType::ConfigNS,
+ BSON(MongosType::name(_myid)),
+ BSON("$set" << BSON(MongosType::ping(jsTime())
+ << MongosType::up(static_cast<int>(time(0) - _started))
+ << MongosType::waiting(waiting)
+ << MongosType::mongoVersion(versionString))),
+ true,
+ false,
+ NULL);
+}
+
+/*
+* Builds the details object for the actionlog.
+* Current formats for detail are:
+* Success: {
+* "candidateChunks" : ,
+* "chunksMoved" : ,
+* "executionTimeMillis" : ,
+* "errorOccured" : false
+* }
+* Failure: {
+* "executionTimeMillis" : ,
+* "errmsg" : ,
+* "errorOccured" : true
+* }
+* @param didError, did this round end in an error?
+* @param executionTime, the time this round took to run
+* @param candidateChunks, the number of chunks identified to be moved
+* @param chunksMoved, the number of chunks moved
+* @param errmsg, the error message for this round
+*/
- /*
- * Builds the details object for the actionlog.
- * Current formats for detail are:
- * Success: {
- * "candidateChunks" : ,
- * "chunksMoved" : ,
- * "executionTimeMillis" : ,
- * "errorOccured" : false
- * }
- * Failure: {
- * "executionTimeMillis" : ,
- * "errmsg" : ,
- * "errorOccured" : true
- * }
- * @param didError, did this round end in an error?
- * @param executionTime, the time this round took to run
- * @param candidateChunks, the number of chunks identified to be moved
- * @param chunksMoved, the number of chunks moved
- * @param errmsg, the error message for this round
- */
-
- static BSONObj _buildDetails( bool didError, int executionTime,
- int candidateChunks, int chunksMoved, const std::string& errmsg ) {
-
- BSONObjBuilder builder;
- builder.append("executionTimeMillis", executionTime);
- builder.append("errorOccured", didError);
-
- if ( didError ) {
- builder.append("errmsg", errmsg);
- } else {
- builder.append("candidateChunks", candidateChunks);
- builder.append("chunksMoved", chunksMoved);
- }
- return builder.obj();
+static BSONObj _buildDetails(bool didError,
+ int executionTime,
+ int candidateChunks,
+ int chunksMoved,
+ const std::string& errmsg) {
+ BSONObjBuilder builder;
+ builder.append("executionTimeMillis", executionTime);
+ builder.append("errorOccured", didError);
+
+ if (didError) {
+ builder.append("errmsg", errmsg);
+ } else {
+ builder.append("candidateChunks", candidateChunks);
+ builder.append("chunksMoved", chunksMoved);
}
+ return builder.obj();
+}
- bool Balancer::_checkOIDs() {
- vector<ShardId> all;
- grid.shardRegistry()->getAllShardIds(&all);
+bool Balancer::_checkOIDs() {
+ vector<ShardId> all;
+ grid.shardRegistry()->getAllShardIds(&all);
- // map of OID machine ID => shardId
- map<int, string> oids;
+ // map of OID machine ID => shardId
+ map<int, string> oids;
- for (const ShardId& shardId : all) {
- const auto s = grid.shardRegistry()->getShard(shardId);
- if (!s) {
- continue;
- }
+ for (const ShardId& shardId : all) {
+ const auto s = grid.shardRegistry()->getShard(shardId);
+ if (!s) {
+ continue;
+ }
- BSONObj f = s->runCommand("admin", "features");
- if ( f["oidMachine"].isNumber() ) {
- int x = f["oidMachine"].numberInt();
- if (oids.count(x) == 0) {
- oids[x] = shardId;
+ BSONObj f = s->runCommand("admin", "features");
+ if (f["oidMachine"].isNumber()) {
+ int x = f["oidMachine"].numberInt();
+ if (oids.count(x) == 0) {
+ oids[x] = shardId;
+ } else {
+ log() << "error: 2 machines have " << x << " as oid machine piece: " << shardId
+ << " and " << oids[x];
+ s->runCommand("admin", BSON("features" << 1 << "oidReset" << 1));
+
+ const auto otherShard = grid.shardRegistry()->getShard(oids[x]);
+ if (otherShard) {
+ otherShard->runCommand("admin", BSON("features" << 1 << "oidReset" << 1));
}
- else {
- log() << "error: 2 machines have " << x
- << " as oid machine piece: " << shardId
- << " and " << oids[x];
- s->runCommand("admin", BSON("features" << 1 << "oidReset" << 1));
-
- const auto otherShard = grid.shardRegistry()->getShard(oids[x]);
- if (otherShard) {
- otherShard->runCommand("admin", BSON("features" << 1 << "oidReset" << 1));
- }
- return false;
- }
- }
- else {
- log() << "warning: oidMachine not set on: " << s->toString();
+ return false;
}
+ } else {
+ log() << "warning: oidMachine not set on: " << s->toString();
}
- return true;
}
-
- /**
- * Occasionally prints a log message with shard versions if the versions are not the same
- * in the cluster.
- */
- void warnOnMultiVersion( const ShardInfoMap& shardInfo ) {
-
- bool isMultiVersion = false;
- for ( ShardInfoMap::const_iterator i = shardInfo.begin(); i != shardInfo.end(); ++i ) {
- if ( !isSameMajorVersion( i->second.getMongoVersion().c_str() ) ) {
- isMultiVersion = true;
- break;
- }
- }
+ return true;
+}
- // If we're all the same version, don't message
- if ( !isMultiVersion ) return;
-
- warning() << "multiVersion cluster detected, my version is " << versionString;
- for ( ShardInfoMap::const_iterator i = shardInfo.begin(); i != shardInfo.end(); ++i ) {
- log() << i->first << " is at version " << i->second.getMongoVersion();
- }
+/**
+ * Occasionally prints a log message with shard versions if the versions are not the same
+ * in the cluster.
+ */
+void warnOnMultiVersion(const ShardInfoMap& shardInfo) {
+ bool isMultiVersion = false;
+ for (ShardInfoMap::const_iterator i = shardInfo.begin(); i != shardInfo.end(); ++i) {
+ if (!isSameMajorVersion(i->second.getMongoVersion().c_str())) {
+ isMultiVersion = true;
+ break;
+ }
}
- void Balancer::_doBalanceRound(vector<shared_ptr<MigrateInfo>>* candidateChunks) {
- invariant(candidateChunks);
+ // If we're all the same version, don't message
+ if (!isMultiVersion)
+ return;
- vector<CollectionType> collections;
- Status collsStatus = grid.catalogManager()->getCollections(nullptr, &collections);
- if (!collsStatus.isOK()) {
- warning() << "Failed to retrieve the set of collections during balancing round "
- << collsStatus;
- return;
- }
+ warning() << "multiVersion cluster detected, my version is " << versionString;
+ for (ShardInfoMap::const_iterator i = shardInfo.begin(); i != shardInfo.end(); ++i) {
+ log() << i->first << " is at version " << i->second.getMongoVersion();
+ }
+}
- if (collections.empty()) {
- LOG(1) << "no collections to balance";
- return;
- }
+void Balancer::_doBalanceRound(vector<shared_ptr<MigrateInfo>>* candidateChunks) {
+ invariant(candidateChunks);
- // Get a list of all the shards that are participating in this balance round along with any
- // maximum allowed quotas and current utilization. We get the latter by issuing
- // db.serverStatus() (mem.mapped) to all shards.
- //
- // TODO: skip unresponsive shards and mark information as stale.
- ShardInfoMap shardInfo;
- Status loadStatus = DistributionStatus::populateShardInfoMap(&shardInfo);
- if (!loadStatus.isOK()) {
- warning() << "failed to load shard metadata" << causedBy(loadStatus);
- return;
- }
+ vector<CollectionType> collections;
+ Status collsStatus = grid.catalogManager()->getCollections(nullptr, &collections);
+ if (!collsStatus.isOK()) {
+ warning() << "Failed to retrieve the set of collections during balancing round "
+ << collsStatus;
+ return;
+ }
- if (shardInfo.size() < 2) {
- LOG(1) << "can't balance without more active shards";
- return;
- }
-
- OCCASIONALLY warnOnMultiVersion( shardInfo );
+ if (collections.empty()) {
+ LOG(1) << "no collections to balance";
+ return;
+ }
- // For each collection, check if the balancing policy recommends moving anything around.
- for (const auto& coll : collections) {
- // Skip collections for which balancing is disabled
- const NamespaceString& ns = coll.getNs();
+ // Get a list of all the shards that are participating in this balance round along with any
+ // maximum allowed quotas and current utilization. We get the latter by issuing
+ // db.serverStatus() (mem.mapped) to all shards.
+ //
+ // TODO: skip unresponsive shards and mark information as stale.
+ ShardInfoMap shardInfo;
+ Status loadStatus = DistributionStatus::populateShardInfoMap(&shardInfo);
+ if (!loadStatus.isOK()) {
+ warning() << "failed to load shard metadata" << causedBy(loadStatus);
+ return;
+ }
- if (!coll.getAllowBalance()) {
- LOG(1) << "Not balancing collection " << ns << "; explicitly disabled.";
- continue;
- }
+ if (shardInfo.size() < 2) {
+ LOG(1) << "can't balance without more active shards";
+ return;
+ }
- std::vector<ChunkType> allNsChunks;
- grid.catalogManager()->getChunks(Query(BSON(ChunkType::ns(ns)))
- .sort(ChunkType::min()),
- 0, // all chunks
- &allNsChunks);
+ OCCASIONALLY warnOnMultiVersion(shardInfo);
- set<BSONObj> allChunkMinimums;
- map<string, vector<ChunkType>> shardToChunksMap;
+ // For each collection, check if the balancing policy recommends moving anything around.
+ for (const auto& coll : collections) {
+ // Skip collections for which balancing is disabled
+ const NamespaceString& ns = coll.getNs();
- for (const ChunkType& chunk : allNsChunks) {
- allChunkMinimums.insert(chunk.getMin().getOwned());
+ if (!coll.getAllowBalance()) {
+ LOG(1) << "Not balancing collection " << ns << "; explicitly disabled.";
+ continue;
+ }
- vector<ChunkType>& chunksList = shardToChunksMap[chunk.getShard()];
- chunksList.push_back(chunk);
- }
+ std::vector<ChunkType> allNsChunks;
+ grid.catalogManager()->getChunks(Query(BSON(ChunkType::ns(ns))).sort(ChunkType::min()),
+ 0, // all chunks
+ &allNsChunks);
- if (shardToChunksMap.empty()) {
- LOG(1) << "skipping empty collection (" << ns << ")";
- continue;
- }
+ set<BSONObj> allChunkMinimums;
+ map<string, vector<ChunkType>> shardToChunksMap;
- for (ShardInfoMap::const_iterator i = shardInfo.begin(); i != shardInfo.end(); ++i) {
- // This loop just makes sure there is an entry in shardToChunksMap for every shard
- shardToChunksMap[i->first];
- }
+ for (const ChunkType& chunk : allNsChunks) {
+ allChunkMinimums.insert(chunk.getMin().getOwned());
- DistributionStatus status(shardInfo, shardToChunksMap);
+ vector<ChunkType>& chunksList = shardToChunksMap[chunk.getShard()];
+ chunksList.push_back(chunk);
+ }
- // TODO: TagRange contains all the information from TagsType except for the namespace,
- // so maybe the two can be merged at some point in order to avoid the
- // transformation below.
- vector<TagRange> ranges;
+ if (shardToChunksMap.empty()) {
+ LOG(1) << "skipping empty collection (" << ns << ")";
+ continue;
+ }
- {
- vector<TagsType> collectionTags;
- uassertStatusOK(grid.catalogManager()->getTagsForCollection(ns.toString(),
- &collectionTags));
- for (const auto& tt : collectionTags) {
- ranges.push_back(TagRange(tt.getMinKey().getOwned(),
- tt.getMaxKey().getOwned(),
- tt.getTag()));
- uassert(16356,
- str::stream() << "tag ranges not valid for: " << ns.toString(),
- status.addTagRange(ranges.back()));
- }
- }
+ for (ShardInfoMap::const_iterator i = shardInfo.begin(); i != shardInfo.end(); ++i) {
+ // This loop just makes sure there is an entry in shardToChunksMap for every shard
+ shardToChunksMap[i->first];
+ }
- auto statusGetDb = grid.catalogCache()->getDatabase(ns.db().toString());
- if (!statusGetDb.isOK()) {
- warning() << "could not load db config to balance collection [" << ns << "]: "
- << statusGetDb.getStatus();
- continue;
+ DistributionStatus status(shardInfo, shardToChunksMap);
+
+ // TODO: TagRange contains all the information from TagsType except for the namespace,
+ // so maybe the two can be merged at some point in order to avoid the
+ // transformation below.
+ vector<TagRange> ranges;
+
+ {
+ vector<TagsType> collectionTags;
+ uassertStatusOK(
+ grid.catalogManager()->getTagsForCollection(ns.toString(), &collectionTags));
+ for (const auto& tt : collectionTags) {
+ ranges.push_back(
+ TagRange(tt.getMinKey().getOwned(), tt.getMaxKey().getOwned(), tt.getTag()));
+ uassert(16356,
+ str::stream() << "tag ranges not valid for: " << ns.toString(),
+ status.addTagRange(ranges.back()));
}
+ }
- shared_ptr<DBConfig> cfg = statusGetDb.getValue();
+ auto statusGetDb = grid.catalogCache()->getDatabase(ns.db().toString());
+ if (!statusGetDb.isOK()) {
+ warning() << "could not load db config to balance collection [" << ns
+ << "]: " << statusGetDb.getStatus();
+ continue;
+ }
- // This line reloads the chunk manager once if this process doesn't know the collection
- // is sharded yet.
- shared_ptr<ChunkManager> cm = cfg->getChunkManagerIfExists(ns, true);
- if (!cm) {
- warning() << "could not load chunks to balance " << ns << " collection";
- continue;
- }
+ shared_ptr<DBConfig> cfg = statusGetDb.getValue();
- // Loop through tags to make sure no chunk spans tags. Split on tag min for all chunks.
- bool didAnySplits = false;
+ // This line reloads the chunk manager once if this process doesn't know the collection
+ // is sharded yet.
+ shared_ptr<ChunkManager> cm = cfg->getChunkManagerIfExists(ns, true);
+ if (!cm) {
+ warning() << "could not load chunks to balance " << ns << " collection";
+ continue;
+ }
- for (const TagRange& range : ranges) {
- BSONObj min =
- cm->getShardKeyPattern().getKeyPattern().extendRangeBound(range.min, false);
+ // Loop through tags to make sure no chunk spans tags. Split on tag min for all chunks.
+ bool didAnySplits = false;
- if (allChunkMinimums.count(min) > 0) {
- continue;
- }
+ for (const TagRange& range : ranges) {
+ BSONObj min =
+ cm->getShardKeyPattern().getKeyPattern().extendRangeBound(range.min, false);
- didAnySplits = true;
+ if (allChunkMinimums.count(min) > 0) {
+ continue;
+ }
- log() << "ns: " << ns << " need to split on " << min
- << " because there is a range there";
+ didAnySplits = true;
- ChunkPtr c = cm->findIntersectingChunk(min);
+ log() << "ns: " << ns << " need to split on " << min
+ << " because there is a range there";
- vector<BSONObj> splitPoints;
- splitPoints.push_back( min );
+ ChunkPtr c = cm->findIntersectingChunk(min);
- Status status = c->multiSplit(splitPoints, NULL);
- if (!status.isOK()) {
- error() << "split failed: " << status;
- }
- else {
- LOG(1) << "split worked";
- }
+ vector<BSONObj> splitPoints;
+ splitPoints.push_back(min);
- break;
+ Status status = c->multiSplit(splitPoints, NULL);
+ if (!status.isOK()) {
+ error() << "split failed: " << status;
+ } else {
+ LOG(1) << "split worked";
}
- if (didAnySplits) {
- // State change, just wait till next round
- continue;
- }
+ break;
+ }
- shared_ptr<MigrateInfo> migrateInfo(_policy->balance(ns, status, _balancedLastTime));
- if (migrateInfo) {
- candidateChunks->push_back(migrateInfo);
- }
+ if (didAnySplits) {
+ // State change, just wait till next round
+ continue;
+ }
+
+ shared_ptr<MigrateInfo> migrateInfo(_policy->balance(ns, status, _balancedLastTime));
+ if (migrateInfo) {
+ candidateChunks->push_back(migrateInfo);
}
}
+}
- bool Balancer::_init() {
- try {
- log() << "about to contact config servers and shards";
+bool Balancer::_init() {
+ try {
+ log() << "about to contact config servers and shards";
- // contact the config server and refresh shard information
- // checks that each shard is indeed a different process (no hostname mixup)
- // these checks are redundant in that they're redone at every new round but we want to do them initially here
- // so to catch any problem soon
- Shard::reloadShardInfo();
- _checkOIDs();
+ // contact the config server and refresh shard information
+ // checks that each shard is indeed a different process (no hostname mixup)
+ // these checks are redundant in that they're redone at every new round but we want to do them initially here
+ // so to catch any problem soon
+ Shard::reloadShardInfo();
+ _checkOIDs();
- log() << "config servers and shards contacted successfully";
+ log() << "config servers and shards contacted successfully";
- StringBuilder buf;
- buf << getHostNameCached() << ":" << serverGlobalParams.port;
- _myid = buf.str();
- _started = time(0);
+ StringBuilder buf;
+ buf << getHostNameCached() << ":" << serverGlobalParams.port;
+ _myid = buf.str();
+ _started = time(0);
- log() << "balancer id: " << _myid << " started";
+ log() << "balancer id: " << _myid << " started";
- return true;
+ return true;
+ } catch (std::exception& e) {
+ warning() << "could not initialize balancer, please check that all shards and config "
+ "servers are up: " << e.what();
+ return false;
+ }
+}
+
+void Balancer::run() {
+ Client::initThread("Balancer");
+
+ // This is the body of a BackgroundJob so if we throw here we're basically ending the
+ // balancer thread prematurely.
+ while (!inShutdown()) {
+ if (!_init()) {
+ log() << "will retry to initialize balancer in one minute";
+ sleepsecs(60);
+ continue;
}
- catch ( std::exception& e ) {
- warning() << "could not initialize balancer, please check that all shards and config servers are up: " << e.what();
- return false;
- }
+ break;
}
- void Balancer::run() {
- Client::initThread("Balancer");
+ const int sleepTime = 10;
- // This is the body of a BackgroundJob so if we throw here we're basically ending the
- // balancer thread prematurely.
- while (!inShutdown()) {
- if (!_init()) {
- log() << "will retry to initialize balancer in one minute";
- sleepsecs(60);
- continue;
- }
+ while (!inShutdown()) {
+ Timer balanceRoundTimer;
+ ActionLogType actionLog;
- break;
- }
+ actionLog.setServer(getHostNameCached());
+ actionLog.setWhat("balancer.round");
+
+ try {
+ // ping has to be first so we keep things in the config server in sync
+ _ping();
- const int sleepTime = 10;
+ BSONObj balancerResult;
- while (!inShutdown()) {
- Timer balanceRoundTimer;
- ActionLogType actionLog;
+ // use fresh shard state
+ Shard::reloadShardInfo();
- actionLog.setServer(getHostNameCached());
- actionLog.setWhat("balancer.round");
+ // refresh chunk size (even though another balancer might be active)
+ Chunk::refreshChunkSize();
- try {
- // ping has to be first so we keep things in the config server in sync
- _ping();
+ auto balSettingsResult =
+ grid.catalogManager()->getGlobalSettings(SettingsType::BalancerDocKey);
+ const bool isBalSettingsAbsent =
+ balSettingsResult.getStatus() == ErrorCodes::NoMatchingDocument;
+ if (!balSettingsResult.isOK() && !isBalSettingsAbsent) {
+ warning() << balSettingsResult.getStatus();
+ return;
+ }
+ const SettingsType& balancerConfig =
+ isBalSettingsAbsent ? SettingsType{} : balSettingsResult.getValue();
- BSONObj balancerResult;
+ // now make sure we should even be running
+ if ((!isBalSettingsAbsent && !grid.shouldBalance(balancerConfig)) ||
+ MONGO_FAIL_POINT(skipBalanceRound)) {
+ LOG(1) << "skipping balancing round because balancing is disabled";
- // use fresh shard state
- Shard::reloadShardInfo();
+ // Ping again so scripts can determine if we're active without waiting
+ _ping(true);
- // refresh chunk size (even though another balancer might be active)
- Chunk::refreshChunkSize();
+ sleepsecs(sleepTime);
+ continue;
+ }
- auto balSettingsResult =
- grid.catalogManager()->getGlobalSettings(SettingsType::BalancerDocKey);
- const bool isBalSettingsAbsent =
- balSettingsResult.getStatus() == ErrorCodes::NoMatchingDocument;
- if (!balSettingsResult.isOK() && !isBalSettingsAbsent) {
- warning() << balSettingsResult.getStatus();
- return;
- }
- const SettingsType& balancerConfig = isBalSettingsAbsent ?
- SettingsType{} : balSettingsResult.getValue();
+ uassert(13258, "oids broken after resetting!", _checkOIDs());
- // now make sure we should even be running
- if ((!isBalSettingsAbsent && !grid.shouldBalance(balancerConfig)) ||
- MONGO_FAIL_POINT(skipBalanceRound)) {
+ {
+ auto scopedDistLock = grid.catalogManager()->getDistLockManager()->lock(
+ "balancer", "doing balance round");
- LOG(1) << "skipping balancing round because balancing is disabled";
+ if (!scopedDistLock.isOK()) {
+ LOG(1) << "skipping balancing round" << causedBy(scopedDistLock.getStatus());
// Ping again so scripts can determine if we're active without waiting
- _ping( true );
+ _ping(true);
- sleepsecs( sleepTime );
+ sleepsecs(sleepTime); // no need to wake up soon
continue;
}
- uassert( 13258 , "oids broken after resetting!" , _checkOIDs() );
-
- {
- auto scopedDistLock = grid.catalogManager()->getDistLockManager()->lock(
- "balancer", "doing balance round");
-
- if (!scopedDistLock.isOK()) {
- LOG(1) << "skipping balancing round"
- << causedBy(scopedDistLock.getStatus());
-
- // Ping again so scripts can determine if we're active without waiting
- _ping( true );
-
- sleepsecs( sleepTime ); // no need to wake up soon
- continue;
- }
-
- const bool waitForDelete = (balancerConfig.isWaitForDeleteSet() ?
- balancerConfig.getWaitForDelete() : false);
-
- std::unique_ptr<WriteConcernOptions> writeConcern;
- if (balancerConfig.isKeySet()) { // if balancer doc exists.
- writeConcern = std::move(balancerConfig.getWriteConcern());
- }
-
- LOG(1) << "*** start balancing round. "
- << "waitForDelete: " << waitForDelete
- << ", secondaryThrottle: "
- << (writeConcern.get() ? writeConcern->toBSON().toString() : "default")
- ;
-
- vector<shared_ptr<MigrateInfo>> candidateChunks;
- _doBalanceRound(&candidateChunks);
-
- if ( candidateChunks.size() == 0 ) {
- LOG(1) << "no need to move any chunk";
- _balancedLastTime = 0;
- }
- else {
- _balancedLastTime = _moveChunks(candidateChunks,
- writeConcern.get(),
- waitForDelete);
- }
-
- actionLog.setDetails(
- _buildDetails(false,
- balanceRoundTimer.millis(),
- static_cast<int>(candidateChunks.size()),
- _balancedLastTime,
- ""));
- actionLog.setTime(jsTime());
-
- grid.catalogManager()->logAction(actionLog);
-
- LOG(1) << "*** end of balancing round";
+ const bool waitForDelete =
+ (balancerConfig.isWaitForDeleteSet() ? balancerConfig.getWaitForDelete()
+ : false);
+
+ std::unique_ptr<WriteConcernOptions> writeConcern;
+ if (balancerConfig.isKeySet()) { // if balancer doc exists.
+ writeConcern = std::move(balancerConfig.getWriteConcern());
}
- // Ping again so scripts can determine if we're active without waiting
- _ping(true);
+ LOG(1) << "*** start balancing round. "
+ << "waitForDelete: " << waitForDelete << ", secondaryThrottle: "
+ << (writeConcern.get() ? writeConcern->toBSON().toString() : "default");
- sleepsecs(_balancedLastTime ? sleepTime / 10 : sleepTime);
- }
- catch ( std::exception& e ) {
- log() << "caught exception while doing balance: " << e.what();
-
- // Just to match the opening statement if in log level 1
- LOG(1) << "*** End of balancing round";
-
- // This round failed, tell the world!
- actionLog.setDetails(
- _buildDetails(true,
- balanceRoundTimer.millis(),
- 0,
- 0,
- e.what()));
+ vector<shared_ptr<MigrateInfo>> candidateChunks;
+ _doBalanceRound(&candidateChunks);
+
+ if (candidateChunks.size() == 0) {
+ LOG(1) << "no need to move any chunk";
+ _balancedLastTime = 0;
+ } else {
+ _balancedLastTime =
+ _moveChunks(candidateChunks, writeConcern.get(), waitForDelete);
+ }
+
+ actionLog.setDetails(_buildDetails(false,
+ balanceRoundTimer.millis(),
+ static_cast<int>(candidateChunks.size()),
+ _balancedLastTime,
+ ""));
actionLog.setTime(jsTime());
grid.catalogManager()->logAction(actionLog);
- // Sleep a fair amount before retrying because of the error
- sleepsecs(sleepTime);
-
- continue;
+ LOG(1) << "*** end of balancing round";
}
- }
+ // Ping again so scripts can determine if we're active without waiting
+ _ping(true);
+
+ sleepsecs(_balancedLastTime ? sleepTime / 10 : sleepTime);
+ } catch (std::exception& e) {
+ log() << "caught exception while doing balance: " << e.what();
+
+ // Just to match the opening statement if in log level 1
+ LOG(1) << "*** End of balancing round";
+
+ // This round failed, tell the world!
+ actionLog.setDetails(_buildDetails(true, balanceRoundTimer.millis(), 0, 0, e.what()));
+ actionLog.setTime(jsTime());
+
+ grid.catalogManager()->logAction(actionLog);
+
+ // Sleep a fair amount before retrying because of the error
+ sleepsecs(sleepTime);
+
+ continue;
+ }
}
+}
} // namespace mongo
diff --git a/src/mongo/s/balance.h b/src/mongo/s/balance.h
index 1b7bd55aa29..c62e05c18f4 100644
--- a/src/mongo/s/balance.h
+++ b/src/mongo/s/balance.h
@@ -35,85 +35,86 @@
namespace mongo {
- class BalancerPolicy;
- struct MigrateInfo;
- struct WriteConcernOptions;
+class BalancerPolicy;
+struct MigrateInfo;
+struct WriteConcernOptions;
+
+/**
+ * The balancer is a background task that tries to keep the number of chunks across all
+ * servers of the cluster even. Although every mongos will have one balancer running, only one
+ * of them will be active at the any given point in time. The balancer uses a distributed lock
+ * for that coordination.
+ *
+ * The balancer does act continuously but in "rounds". At a given round, it would decide if
+ * there is an imbalance by checking the difference in chunks between the most and least
+ * loaded shards. It would issue a request for a chunk migration per round, if it found so.
+ */
+class Balancer : public BackgroundJob {
+public:
+ Balancer();
+ virtual ~Balancer();
+
+ // BackgroundJob methods
+
+ virtual void run();
+
+ virtual std::string name() const {
+ return "Balancer";
+ }
+
+private:
+ // hostname:port of my mongos
+ std::string _myid;
+
+ // time the Balancer started running
+ time_t _started;
+
+ // number of moved chunks in last round
+ int _balancedLastTime;
+
+ // decide which chunks to move; owned here.
+ std::unique_ptr<BalancerPolicy> _policy;
/**
- * The balancer is a background task that tries to keep the number of chunks across all
- * servers of the cluster even. Although every mongos will have one balancer running, only one
- * of them will be active at the any given point in time. The balancer uses a distributed lock
- * for that coordination.
+ * Checks that the balancer can connect to all servers it needs to do its job.
+ *
+ * @return true if balancing can be started
*
- * The balancer does act continuously but in "rounds". At a given round, it would decide if
- * there is an imbalance by checking the difference in chunks between the most and least
- * loaded shards. It would issue a request for a chunk migration per round, if it found so.
+ * This method throws on a network exception
*/
- class Balancer : public BackgroundJob {
- public:
- Balancer();
- virtual ~Balancer();
-
- // BackgroundJob methods
-
- virtual void run();
-
- virtual std::string name() const { return "Balancer"; }
-
- private:
- // hostname:port of my mongos
- std::string _myid;
-
- // time the Balancer started running
- time_t _started;
-
- // number of moved chunks in last round
- int _balancedLastTime;
-
- // decide which chunks to move; owned here.
- std::unique_ptr<BalancerPolicy> _policy;
-
- /**
- * Checks that the balancer can connect to all servers it needs to do its job.
- *
- * @return true if balancing can be started
- *
- * This method throws on a network exception
- */
- bool _init();
-
- /**
- * Gathers all the necessary information about shards and chunks, and decides whether there are candidate chunks to
- * be moved.
- *
- * @param conn is the connection with the config server(s)
- * @param candidateChunks (IN/OUT) filled with candidate chunks, one per collection, that could possibly be moved
- */
- void _doBalanceRound(std::vector<std::shared_ptr<MigrateInfo>>* candidateChunks);
-
- /**
- * Issues chunk migration request, one at a time.
- *
- * @param candidateChunks possible chunks to move
- * @param writeConcern detailed write concern. NULL means the default write concern.
- * @param waitForDelete wait for deletes to complete after each chunk move
- * @return number of chunks effectively moved
- */
- int _moveChunks(const std::vector<std::shared_ptr<MigrateInfo>>& candidateChunks,
- const WriteConcernOptions* writeConcern,
- bool waitForDelete);
-
- /**
- * Marks this balancer as being live on the config server(s).
- */
- void _ping( bool waiting = false );
-
- /**
- * @return true if all the servers listed in configdb as being shards are reachable and are distinct processes
- */
- bool _checkOIDs();
-
- };
-
- extern Balancer balancer;
+ bool _init();
+
+ /**
+ * Gathers all the necessary information about shards and chunks, and decides whether there are candidate chunks to
+ * be moved.
+ *
+ * @param conn is the connection with the config server(s)
+ * @param candidateChunks (IN/OUT) filled with candidate chunks, one per collection, that could possibly be moved
+ */
+ void _doBalanceRound(std::vector<std::shared_ptr<MigrateInfo>>* candidateChunks);
+
+ /**
+ * Issues chunk migration request, one at a time.
+ *
+ * @param candidateChunks possible chunks to move
+ * @param writeConcern detailed write concern. NULL means the default write concern.
+ * @param waitForDelete wait for deletes to complete after each chunk move
+ * @return number of chunks effectively moved
+ */
+ int _moveChunks(const std::vector<std::shared_ptr<MigrateInfo>>& candidateChunks,
+ const WriteConcernOptions* writeConcern,
+ bool waitForDelete);
+
+ /**
+ * Marks this balancer as being live on the config server(s).
+ */
+ void _ping(bool waiting = false);
+
+ /**
+ * @return true if all the servers listed in configdb as being shards are reachable and are distinct processes
+ */
+ bool _checkOIDs();
+};
+
+extern Balancer balancer;
}
diff --git a/src/mongo/s/balancer_policy.cpp b/src/mongo/s/balancer_policy.cpp
index 7309261c902..cbfe1c8b9ab 100644
--- a/src/mongo/s/balancer_policy.cpp
+++ b/src/mongo/s/balancer_policy.cpp
@@ -46,511 +46,484 @@
namespace mongo {
- using std::map;
- using std::numeric_limits;
- using std::set;
- using std::string;
- using std::vector;
-
- string TagRange::toString() const {
- return str::stream() << min << " -->> " << max << " on " << tag;
+using std::map;
+using std::numeric_limits;
+using std::set;
+using std::string;
+using std::vector;
+
+string TagRange::toString() const {
+ return str::stream() << min << " -->> " << max << " on " << tag;
+}
+
+DistributionStatus::DistributionStatus(const ShardInfoMap& shardInfo,
+ const ShardToChunksMap& shardToChunksMap)
+ : _shardInfo(shardInfo), _shardChunks(shardToChunksMap) {
+ for (ShardInfoMap::const_iterator i = _shardInfo.begin(); i != _shardInfo.end(); ++i) {
+ _shardIds.insert(i->first);
}
+}
- DistributionStatus::DistributionStatus(const ShardInfoMap& shardInfo,
- const ShardToChunksMap& shardToChunksMap)
- : _shardInfo(shardInfo),
- _shardChunks(shardToChunksMap) {
+const ShardInfo& DistributionStatus::shardInfo(const ShardId& shardId) const {
+ ShardInfoMap::const_iterator i = _shardInfo.find(shardId);
+ verify(i != _shardInfo.end());
+ return i->second;
+}
- for (ShardInfoMap::const_iterator i = _shardInfo.begin(); i != _shardInfo.end(); ++i) {
- _shardIds.insert(i->first);
- }
- }
+unsigned DistributionStatus::totalChunks() const {
+ unsigned total = 0;
- const ShardInfo& DistributionStatus::shardInfo(const ShardId& shardId) const {
- ShardInfoMap::const_iterator i = _shardInfo.find(shardId);
- verify( i != _shardInfo.end() );
- return i->second;
+ for (ShardToChunksMap::const_iterator i = _shardChunks.begin(); i != _shardChunks.end(); ++i) {
+ total += i->second.size();
}
- unsigned DistributionStatus::totalChunks() const {
- unsigned total = 0;
+ return total;
+}
- for (ShardToChunksMap::const_iterator i = _shardChunks.begin();
- i != _shardChunks.end();
- ++i) {
-
- total += i->second.size();
- }
-
- return total;
+unsigned DistributionStatus::numberOfChunksInShard(const ShardId& shardId) const {
+ ShardToChunksMap::const_iterator i = _shardChunks.find(shardId);
+ if (i == _shardChunks.end()) {
+ return 0;
}
- unsigned DistributionStatus::numberOfChunksInShard(const ShardId& shardId) const {
- ShardToChunksMap::const_iterator i = _shardChunks.find(shardId);
- if (i == _shardChunks.end()) {
- return 0;
- }
+ return i->second.size();
+}
- return i->second.size();
+unsigned DistributionStatus::numberOfChunksInShardWithTag(const ShardId& shardId,
+ const string& tag) const {
+ ShardToChunksMap::const_iterator i = _shardChunks.find(shardId);
+ if (i == _shardChunks.end()) {
+ return 0;
}
- unsigned DistributionStatus::numberOfChunksInShardWithTag(const ShardId& shardId,
- const string& tag) const {
- ShardToChunksMap::const_iterator i = _shardChunks.find(shardId);
- if (i == _shardChunks.end()) {
- return 0;
- }
-
- unsigned total = 0;
+ unsigned total = 0;
- const vector<ChunkType>& chunkList = i->second;
- for (unsigned j = 0; j < i->second.size(); j++) {
- if (tag == getTagForChunk(chunkList[j])) {
- total++;
- }
+ const vector<ChunkType>& chunkList = i->second;
+ for (unsigned j = 0; j < i->second.size(); j++) {
+ if (tag == getTagForChunk(chunkList[j])) {
+ total++;
}
-
- return total;
}
- string DistributionStatus::getBestReceieverShard( const string& tag ) const {
- string best;
- unsigned minChunks = numeric_limits<unsigned>::max();
+ return total;
+}
- for ( ShardInfoMap::const_iterator i = _shardInfo.begin(); i != _shardInfo.end(); ++i ) {
- if ( i->second.isSizeMaxed() ) {
- LOG(1) << i->first << " has already reached the maximum total chunk size.";
- continue;
- }
+string DistributionStatus::getBestReceieverShard(const string& tag) const {
+ string best;
+ unsigned minChunks = numeric_limits<unsigned>::max();
- if ( i->second.isDraining() ) {
- LOG(1) << i->first << " is currently draining.";
- continue;
- }
+ for (ShardInfoMap::const_iterator i = _shardInfo.begin(); i != _shardInfo.end(); ++i) {
+ if (i->second.isSizeMaxed()) {
+ LOG(1) << i->first << " has already reached the maximum total chunk size.";
+ continue;
+ }
- if ( ! i->second.hasTag( tag ) ) {
- LOG(1) << i->first << " doesn't have right tag";
- continue;
- }
+ if (i->second.isDraining()) {
+ LOG(1) << i->first << " is currently draining.";
+ continue;
+ }
- unsigned myChunks = numberOfChunksInShard( i->first );
- if ( myChunks >= minChunks ) {
- LOG(1) << i->first << " has more chunks me:" << myChunks << " best: " << best << ":" << minChunks;
- continue;
- }
+ if (!i->second.hasTag(tag)) {
+ LOG(1) << i->first << " doesn't have right tag";
+ continue;
+ }
- best = i->first;
- minChunks = myChunks;
+ unsigned myChunks = numberOfChunksInShard(i->first);
+ if (myChunks >= minChunks) {
+ LOG(1) << i->first << " has more chunks me:" << myChunks << " best: " << best << ":"
+ << minChunks;
+ continue;
}
- return best;
+ best = i->first;
+ minChunks = myChunks;
}
- string DistributionStatus::getMostOverloadedShard( const string& tag ) const {
- string worst;
- unsigned maxChunks = 0;
+ return best;
+}
- for ( ShardInfoMap::const_iterator i = _shardInfo.begin(); i != _shardInfo.end(); ++i ) {
- unsigned myChunks = numberOfChunksInShardWithTag( i->first, tag );
- if ( myChunks <= maxChunks )
- continue;
+string DistributionStatus::getMostOverloadedShard(const string& tag) const {
+ string worst;
+ unsigned maxChunks = 0;
- worst = i->first;
- maxChunks = myChunks;
- }
+ for (ShardInfoMap::const_iterator i = _shardInfo.begin(); i != _shardInfo.end(); ++i) {
+ unsigned myChunks = numberOfChunksInShardWithTag(i->first, tag);
+ if (myChunks <= maxChunks)
+ continue;
- return worst;
+ worst = i->first;
+ maxChunks = myChunks;
}
- const vector<ChunkType>& DistributionStatus::getChunks(const ShardId& shardId) const {
- ShardToChunksMap::const_iterator i = _shardChunks.find(shardId);
- invariant(i != _shardChunks.end());
+ return worst;
+}
- return i->second;
- }
+const vector<ChunkType>& DistributionStatus::getChunks(const ShardId& shardId) const {
+ ShardToChunksMap::const_iterator i = _shardChunks.find(shardId);
+ invariant(i != _shardChunks.end());
- bool DistributionStatus::addTagRange( const TagRange& range ) {
- // first check for overlaps
- for ( map<BSONObj,TagRange>::const_iterator i = _tagRanges.begin();
- i != _tagRanges.end();
- ++i ) {
- const TagRange& tocheck = i->second;
+ return i->second;
+}
- if ( range.min == tocheck.min ) {
- LOG(1) << "have 2 ranges with the same min " << range << " " << tocheck;
- return false;
- }
+bool DistributionStatus::addTagRange(const TagRange& range) {
+ // first check for overlaps
+ for (map<BSONObj, TagRange>::const_iterator i = _tagRanges.begin(); i != _tagRanges.end();
+ ++i) {
+ const TagRange& tocheck = i->second;
- if ( range.min < tocheck.min ) {
- if ( range.max > tocheck.min ) {
- LOG(1) << "have overlapping ranges " << range << " " << tocheck;
- return false;
- }
+ if (range.min == tocheck.min) {
+ LOG(1) << "have 2 ranges with the same min " << range << " " << tocheck;
+ return false;
+ }
+
+ if (range.min < tocheck.min) {
+ if (range.max > tocheck.min) {
+ LOG(1) << "have overlapping ranges " << range << " " << tocheck;
+ return false;
}
- else {
- // range.min > tocheck.min
- if ( tocheck.max > range.min ) {
- LOG(1) << "have overlapping ranges " << range << " " << tocheck;
- return false;
- }
+ } else {
+ // range.min > tocheck.min
+ if (tocheck.max > range.min) {
+ LOG(1) << "have overlapping ranges " << range << " " << tocheck;
+ return false;
}
-
}
-
- _tagRanges[range.max.getOwned()] = range;
- _allTags.insert( range.tag );
-
- return true;
}
- string DistributionStatus::getTagForChunk( const ChunkType& chunk ) const {
- if ( _tagRanges.size() == 0 )
- return "";
+ _tagRanges[range.max.getOwned()] = range;
+ _allTags.insert(range.tag);
- const BSONObj min(chunk.getMin());
+ return true;
+}
- map<BSONObj,TagRange>::const_iterator i = _tagRanges.upper_bound( min );
- if ( i == _tagRanges.end() )
- return "";
+string DistributionStatus::getTagForChunk(const ChunkType& chunk) const {
+ if (_tagRanges.size() == 0)
+ return "";
- const TagRange& range = i->second;
- if ( min < range.min )
- return "";
+ const BSONObj min(chunk.getMin());
- return range.tag;
- }
+ map<BSONObj, TagRange>::const_iterator i = _tagRanges.upper_bound(min);
+ if (i == _tagRanges.end())
+ return "";
- void DistributionStatus::dump() const {
- log() << "DistributionStatus";
- log() << " shards";
+ const TagRange& range = i->second;
+ if (min < range.min)
+ return "";
- for ( ShardInfoMap::const_iterator i = _shardInfo.begin(); i != _shardInfo.end(); ++i ) {
- log() << " " << i->first << "\t" << i->second.toString();
+ return range.tag;
+}
- ShardToChunksMap::const_iterator j = _shardChunks.find(i->first);
- verify(j != _shardChunks.end());
+void DistributionStatus::dump() const {
+ log() << "DistributionStatus";
+ log() << " shards";
- const vector<ChunkType>& v = j->second;
- for (unsigned x = 0; x < v.size(); x++) {
- log() << " " << v[x];
- }
- }
+ for (ShardInfoMap::const_iterator i = _shardInfo.begin(); i != _shardInfo.end(); ++i) {
+ log() << " " << i->first << "\t" << i->second.toString();
- if ( _tagRanges.size() > 0 ) {
- log() << " tag ranges";
+ ShardToChunksMap::const_iterator j = _shardChunks.find(i->first);
+ verify(j != _shardChunks.end());
- for ( map<BSONObj,TagRange>::const_iterator i = _tagRanges.begin();
- i != _tagRanges.end();
- ++i )
- log() << i->second.toString();
+ const vector<ChunkType>& v = j->second;
+ for (unsigned x = 0; x < v.size(); x++) {
+ log() << " " << v[x];
}
}
- Status DistributionStatus::populateShardInfoMap(ShardInfoMap* shardInfo) {
- try {
- vector<ShardType> shards;
- Status status = grid.catalogManager()->getAllShards(&shards);
- if (!status.isOK()) {
- return status;
- }
+ if (_tagRanges.size() > 0) {
+ log() << " tag ranges";
- for (const ShardType& shardData : shards) {
- std::shared_ptr<Shard> shard = grid.shardRegistry()->getShard(shardData.getName());
+ for (map<BSONObj, TagRange>::const_iterator i = _tagRanges.begin(); i != _tagRanges.end();
+ ++i)
+ log() << i->second.toString();
+ }
+}
+
+Status DistributionStatus::populateShardInfoMap(ShardInfoMap* shardInfo) {
+ try {
+ vector<ShardType> shards;
+ Status status = grid.catalogManager()->getAllShards(&shards);
+ if (!status.isOK()) {
+ return status;
+ }
- // The shard must still exist in the registry. If it doesn't, which may happen in
- // the very low proability case that it gets dropped between the call to
- // getAllShards above and the call to getShard, just don't account for it since
- // it is missing anyways.
- if (!shard) {
- warning() << "Shard [" << shardData.getName() << "] was not found. Skipping.";
- continue;
- }
+ for (const ShardType& shardData : shards) {
+ std::shared_ptr<Shard> shard = grid.shardRegistry()->getShard(shardData.getName());
- ShardStatus shardStatus = shard->getStatus();
+ // The shard must still exist in the registry. If it doesn't, which may happen in
+ // the very low proability case that it gets dropped between the call to
+ // getAllShards above and the call to getShard, just don't account for it since
+ // it is missing anyways.
+ if (!shard) {
+ warning() << "Shard [" << shardData.getName() << "] was not found. Skipping.";
+ continue;
+ }
- std::set<std::string> dummy;
+ ShardStatus shardStatus = shard->getStatus();
- ShardInfo newShardEntry(shardData.getMaxSizeMB(),
- shardStatus.dataSizeBytes() / 1024 / 1024,
- shardData.getDraining(),
- dummy,
- shardStatus.mongoVersion());
+ std::set<std::string> dummy;
- for (const string& shardTag : shardData.getTags()) {
- newShardEntry.addTag(shardTag);
- }
+ ShardInfo newShardEntry(shardData.getMaxSizeMB(),
+ shardStatus.dataSizeBytes() / 1024 / 1024,
+ shardData.getDraining(),
+ dummy,
+ shardStatus.mongoVersion());
- shardInfo->insert(make_pair(shardData.getName(), newShardEntry));
+ for (const string& shardTag : shardData.getTags()) {
+ newShardEntry.addTag(shardTag);
}
- }
- catch (const DBException& ex) {
- return ex.toStatus();
- }
- return Status::OK();
+ shardInfo->insert(make_pair(shardData.getName(), newShardEntry));
+ }
+ } catch (const DBException& ex) {
+ return ex.toStatus();
}
- void DistributionStatus::populateShardToChunksMap(const ShardInfoMap& allShards,
- const ChunkManager& chunkMgr,
- ShardToChunksMap* shardToChunksMap) {
+ return Status::OK();
+}
- // Makes sure there is an entry in shardToChunksMap for every shard.
- for (ShardInfoMap::const_iterator it = allShards.begin(); it != allShards.end(); ++it) {
- (*shardToChunksMap)[it->first];
- }
+void DistributionStatus::populateShardToChunksMap(const ShardInfoMap& allShards,
+ const ChunkManager& chunkMgr,
+ ShardToChunksMap* shardToChunksMap) {
+ // Makes sure there is an entry in shardToChunksMap for every shard.
+ for (ShardInfoMap::const_iterator it = allShards.begin(); it != allShards.end(); ++it) {
+ (*shardToChunksMap)[it->first];
+ }
- const ChunkMap& chunkMap = chunkMgr.getChunkMap();
- for (ChunkMap::const_iterator it = chunkMap.begin(); it != chunkMap.end(); ++it) {
- const ChunkPtr chunkPtr = it->second;
+ const ChunkMap& chunkMap = chunkMgr.getChunkMap();
+ for (ChunkMap::const_iterator it = chunkMap.begin(); it != chunkMap.end(); ++it) {
+ const ChunkPtr chunkPtr = it->second;
- ChunkType chunk;
- chunk.setNS(chunkMgr.getns());
- chunk.setMin(chunkPtr->getMin().getOwned());
- chunk.setMax(chunkPtr->getMax().getOwned());
- chunk.setJumbo(chunkPtr->isJumbo()); // TODO: is this reliable?
+ ChunkType chunk;
+ chunk.setNS(chunkMgr.getns());
+ chunk.setMin(chunkPtr->getMin().getOwned());
+ chunk.setMax(chunkPtr->getMax().getOwned());
+ chunk.setJumbo(chunkPtr->isJumbo()); // TODO: is this reliable?
- const string shardName(chunkPtr->getShardId());
- chunk.setShard(shardName);
+ const string shardName(chunkPtr->getShardId());
+ chunk.setShard(shardName);
- (*shardToChunksMap)[shardName].push_back(chunk);
- }
+ (*shardToChunksMap)[shardName].push_back(chunk);
}
+}
- MigrateInfo* BalancerPolicy::balance( const string& ns,
- const DistributionStatus& distribution,
- int balancedLastTime ) {
+MigrateInfo* BalancerPolicy::balance(const string& ns,
+ const DistributionStatus& distribution,
+ int balancedLastTime) {
+ // 1) check for shards that policy require to us to move off of:
+ // draining only
+ // 2) check tag policy violations
+ // 3) then we make sure chunks are balanced for each tag
+ // ----
- // 1) check for shards that policy require to us to move off of:
- // draining only
- // 2) check tag policy violations
- // 3) then we make sure chunks are balanced for each tag
+ // 1) check things we have to move
+ {
+ for (const ShardId& shardId : distribution.shardIds()) {
+ const ShardInfo& info = distribution.shardInfo(shardId);
- // ----
+ if (!info.isDraining())
+ continue;
- // 1) check things we have to move
- {
- for (const ShardId& shardId : distribution.shardIds()) {
- const ShardInfo& info = distribution.shardInfo(shardId);
+ if (distribution.numberOfChunksInShard(shardId) == 0)
+ continue;
- if ( ! info.isDraining() )
- continue;
+ // now we know we need to move to chunks off this shard
+ // we will if we are allowed
+ const vector<ChunkType>& chunks = distribution.getChunks(shardId);
+ unsigned numJumboChunks = 0;
- if (distribution.numberOfChunksInShard(shardId) == 0)
+ // since we have to move all chunks, lets just do in order
+ for (unsigned i = 0; i < chunks.size(); i++) {
+ const ChunkType& chunkToMove = chunks[i];
+ if (chunkToMove.getJumbo()) {
+ numJumboChunks++;
continue;
-
- // now we know we need to move to chunks off this shard
- // we will if we are allowed
- const vector<ChunkType>& chunks = distribution.getChunks(shardId);
- unsigned numJumboChunks = 0;
-
- // since we have to move all chunks, lets just do in order
- for ( unsigned i=0; i<chunks.size(); i++ ) {
- const ChunkType& chunkToMove = chunks[i];
- if (chunkToMove.getJumbo()) {
- numJumboChunks++;
- continue;
- }
-
- string tag = distribution.getTagForChunk( chunkToMove );
- const ShardId to = distribution.getBestReceieverShard( tag );
-
- if ( to.size() == 0 ) {
- warning() << "want to move chunk: " << chunkToMove
- << "(" << tag << ") "
- << "from " << shardId
- << " but can't find anywhere to put it";
- continue;
- }
-
- log() << "going to move " << chunkToMove
- << " from " << shardId
- << "(" << tag << ")"
- << " to " << to;
-
- return new MigrateInfo(ns, to, shardId, chunkToMove.toBSON());
}
- warning() << "can't find any chunk to move from: " << shardId
- << " but we want to. "
- << " numJumboChunks: " << numJumboChunks;
- }
- }
+ string tag = distribution.getTagForChunk(chunkToMove);
+ const ShardId to = distribution.getBestReceieverShard(tag);
- // 2) tag violations
- if ( distribution.tags().size() > 0 ) {
-
- for (const ShardId& shardId : distribution.shardIds()) {
- const ShardInfo& info = distribution.shardInfo(shardId);
-
- const vector<ChunkType>& chunks = distribution.getChunks(shardId);
- for ( unsigned j = 0; j < chunks.size(); j++ ) {
- const ChunkType& chunk = chunks[j];
- string tag = distribution.getTagForChunk(chunk);
-
- if ( info.hasTag( tag ) )
- continue;
-
- // uh oh, this chunk is in the wrong place
- log() << "chunk " << chunk
- << " is not on a shard with the right tag: "
- << tag;
-
- if (chunk.getJumbo()) {
- warning() << "chunk " << chunk << " is jumbo, so cannot be moved";
- continue;
- }
-
- const ShardId to = distribution.getBestReceieverShard( tag );
- if ( to.size() == 0 ) {
- log() << "no where to put it :(";
- continue;
- }
- verify(to != shardId);
- log() << " going to move to: " << to;
- return new MigrateInfo(ns, to, shardId, chunk.toBSON());
+ if (to.size() == 0) {
+ warning() << "want to move chunk: " << chunkToMove << "(" << tag << ") "
+ << "from " << shardId << " but can't find anywhere to put it";
+ continue;
}
+
+ log() << "going to move " << chunkToMove << " from " << shardId << "(" << tag << ")"
+ << " to " << to;
+
+ return new MigrateInfo(ns, to, shardId, chunkToMove.toBSON());
}
+
+ warning() << "can't find any chunk to move from: " << shardId << " but we want to. "
+ << " numJumboChunks: " << numJumboChunks;
}
+ }
- // 3) for each tag balance
+ // 2) tag violations
+ if (distribution.tags().size() > 0) {
+ for (const ShardId& shardId : distribution.shardIds()) {
+ const ShardInfo& info = distribution.shardInfo(shardId);
- int threshold = 8;
- if ( balancedLastTime || distribution.totalChunks() < 20 )
- threshold = 2;
- else if ( distribution.totalChunks() < 80 )
- threshold = 4;
+ const vector<ChunkType>& chunks = distribution.getChunks(shardId);
+ for (unsigned j = 0; j < chunks.size(); j++) {
+ const ChunkType& chunk = chunks[j];
+ string tag = distribution.getTagForChunk(chunk);
- // randomize the order in which we balance the tags
- // this is so that one bad tag doesn't prevent others from getting balanced
- vector<string> tags;
- {
- set<string> t = distribution.tags();
- for ( set<string>::const_iterator i = t.begin(); i != t.end(); ++i )
- tags.push_back( *i );
- tags.push_back( "" );
+ if (info.hasTag(tag))
+ continue;
+
+ // uh oh, this chunk is in the wrong place
+ log() << "chunk " << chunk << " is not on a shard with the right tag: " << tag;
- std::random_shuffle( tags.begin(), tags.end() );
+ if (chunk.getJumbo()) {
+ warning() << "chunk " << chunk << " is jumbo, so cannot be moved";
+ continue;
+ }
+
+ const ShardId to = distribution.getBestReceieverShard(tag);
+ if (to.size() == 0) {
+ log() << "no where to put it :(";
+ continue;
+ }
+ verify(to != shardId);
+ log() << " going to move to: " << to;
+ return new MigrateInfo(ns, to, shardId, chunk.toBSON());
+ }
}
+ }
- for ( unsigned i=0; i<tags.size(); i++ ) {
- string tag = tags[i];
+ // 3) for each tag balance
- const ShardId from = distribution.getMostOverloadedShard(tag);
- if ( from.size() == 0 )
- continue;
+ int threshold = 8;
+ if (balancedLastTime || distribution.totalChunks() < 20)
+ threshold = 2;
+ else if (distribution.totalChunks() < 80)
+ threshold = 4;
- unsigned max = distribution.numberOfChunksInShardWithTag( from, tag );
- if ( max == 0 )
- continue;
+ // randomize the order in which we balance the tags
+ // this is so that one bad tag doesn't prevent others from getting balanced
+ vector<string> tags;
+ {
+ set<string> t = distribution.tags();
+ for (set<string>::const_iterator i = t.begin(); i != t.end(); ++i)
+ tags.push_back(*i);
+ tags.push_back("");
- string to = distribution.getBestReceieverShard( tag );
- if ( to.size() == 0 ) {
- log() << "no available shards to take chunks for tag [" << tag << "]";
- return NULL;
- }
+ std::random_shuffle(tags.begin(), tags.end());
+ }
- unsigned min = distribution.numberOfChunksInShardWithTag( to, tag );
+ for (unsigned i = 0; i < tags.size(); i++) {
+ string tag = tags[i];
- const int imbalance = max - min;
+ const ShardId from = distribution.getMostOverloadedShard(tag);
+ if (from.size() == 0)
+ continue;
- LOG(1) << "collection : " << ns;
- LOG(1) << "donor : " << from << " chunks on " << max;
- LOG(1) << "receiver : " << to << " chunks on " << min;
- LOG(1) << "threshold : " << threshold;
+ unsigned max = distribution.numberOfChunksInShardWithTag(from, tag);
+ if (max == 0)
+ continue;
- if ( imbalance < threshold )
- continue;
+ string to = distribution.getBestReceieverShard(tag);
+ if (to.size() == 0) {
+ log() << "no available shards to take chunks for tag [" << tag << "]";
+ return NULL;
+ }
- const vector<ChunkType>& chunks = distribution.getChunks(from);
- unsigned numJumboChunks = 0;
- for ( unsigned j = 0; j < chunks.size(); j++ ) {
- const ChunkType& chunk = chunks[j];
- if (distribution.getTagForChunk(chunk) != tag)
- continue;
+ unsigned min = distribution.numberOfChunksInShardWithTag(to, tag);
- if (chunk.getJumbo()) {
- numJumboChunks++;
- continue;
- }
+ const int imbalance = max - min;
- log() << " ns: " << ns << " going to move " << chunk
- << " from: " << from << " to: " << to << " tag [" << tag << "]"
- ;
- return new MigrateInfo(ns, to, from, chunk.toBSON());
- }
+ LOG(1) << "collection : " << ns;
+ LOG(1) << "donor : " << from << " chunks on " << max;
+ LOG(1) << "receiver : " << to << " chunks on " << min;
+ LOG(1) << "threshold : " << threshold;
+
+ if (imbalance < threshold)
+ continue;
+
+ const vector<ChunkType>& chunks = distribution.getChunks(from);
+ unsigned numJumboChunks = 0;
+ for (unsigned j = 0; j < chunks.size(); j++) {
+ const ChunkType& chunk = chunks[j];
+ if (distribution.getTagForChunk(chunk) != tag)
+ continue;
- if ( numJumboChunks ) {
- error() << "shard: " << from << " ns: " << ns
- << " has too many chunks, but they are all jumbo "
- << " numJumboChunks: " << numJumboChunks
- ;
+ if (chunk.getJumbo()) {
+ numJumboChunks++;
continue;
}
- verify( false ); // should be impossible
+ log() << " ns: " << ns << " going to move " << chunk << " from: " << from
+ << " to: " << to << " tag [" << tag << "]";
+ return new MigrateInfo(ns, to, from, chunk.toBSON());
}
- // Everything is balanced here!
- return NULL;
- }
-
+ if (numJumboChunks) {
+ error() << "shard: " << from << " ns: " << ns
+ << " has too many chunks, but they are all jumbo "
+ << " numJumboChunks: " << numJumboChunks;
+ continue;
+ }
- ShardInfo::ShardInfo(long long maxSizeMB,
- long long currSizeMB,
- bool draining,
- const set<string>& tags,
- const string& mongoVersion):
- _maxSizeMB(maxSizeMB),
- _currSizeMB(currSizeMB),
- _draining(draining),
- _tags(tags),
- _mongoVersion(mongoVersion) {
+ verify(false); // should be impossible
}
- ShardInfo::ShardInfo()
- : _maxSizeMB(0),
- _currSizeMB(0),
- _draining(false) {
- }
+ // Everything is balanced here!
+ return NULL;
+}
- void ShardInfo::addTag( const string& tag ) {
- _tags.insert( tag );
- }
+ShardInfo::ShardInfo(long long maxSizeMB,
+ long long currSizeMB,
+ bool draining,
+ const set<string>& tags,
+ const string& mongoVersion)
+ : _maxSizeMB(maxSizeMB),
+ _currSizeMB(currSizeMB),
+ _draining(draining),
+ _tags(tags),
+ _mongoVersion(mongoVersion) {}
- bool ShardInfo::isSizeMaxed() const {
- if (_maxSizeMB == 0 || _currSizeMB == 0)
- return false;
+ShardInfo::ShardInfo() : _maxSizeMB(0), _currSizeMB(0), _draining(false) {}
- return _currSizeMB >= _maxSizeMB;
- }
+void ShardInfo::addTag(const string& tag) {
+ _tags.insert(tag);
+}
- bool ShardInfo::hasTag( const string& tag ) const {
- if ( tag.size() == 0 )
- return true;
- return _tags.count( tag ) > 0;
- }
- string ShardInfo::toString() const {
- StringBuilder ss;
- ss << " maxSizeMB: " << _maxSizeMB;
- ss << " currSizeMB: " << _currSizeMB;
- ss << " draining: " << _draining;
- if ( _tags.size() > 0 ) {
- ss << "tags : ";
- for ( set<string>::const_iterator i = _tags.begin(); i != _tags.end(); ++i )
- ss << *i << ",";
- }
- ss << " version: " << _mongoVersion;
- return ss.str();
- }
+bool ShardInfo::isSizeMaxed() const {
+ if (_maxSizeMB == 0 || _currSizeMB == 0)
+ return false;
+
+ return _currSizeMB >= _maxSizeMB;
+}
- string ChunkInfo::toString() const {
- StringBuilder buf;
- buf << " min: " << min;
- buf << " max: " << max;
- return buf.str();
+bool ShardInfo::hasTag(const string& tag) const {
+ if (tag.size() == 0)
+ return true;
+ return _tags.count(tag) > 0;
+}
+
+string ShardInfo::toString() const {
+ StringBuilder ss;
+ ss << " maxSizeMB: " << _maxSizeMB;
+ ss << " currSizeMB: " << _currSizeMB;
+ ss << " draining: " << _draining;
+ if (_tags.size() > 0) {
+ ss << "tags : ";
+ for (set<string>::const_iterator i = _tags.begin(); i != _tags.end(); ++i)
+ ss << *i << ",";
}
+ ss << " version: " << _mongoVersion;
+ return ss.str();
+}
+
+string ChunkInfo::toString() const {
+ StringBuilder buf;
+ buf << " min: " << min;
+ buf << " max: " << max;
+ return buf.str();
+}
} // namespace mongo
diff --git a/src/mongo/s/balancer_policy.h b/src/mongo/s/balancer_policy.h
index b5203b37411..16614f424f1 100644
--- a/src/mongo/s/balancer_policy.h
+++ b/src/mongo/s/balancer_policy.h
@@ -37,198 +37,202 @@
namespace mongo {
- class ChunkManager;
-
- struct ChunkInfo {
- const BSONObj min;
- const BSONObj max;
-
- ChunkInfo(const BSONObj& chunk)
- : min(chunk[ChunkType::min()].Obj().getOwned()),
- max(chunk[ChunkType::max()].Obj().getOwned()) {
-
- }
-
- std::string toString() const;
- };
-
-
- struct TagRange {
- BSONObj min;
- BSONObj max;
- std::string tag;
-
- TagRange(){}
-
- TagRange( const BSONObj& a_min, const BSONObj& a_max, const std::string& a_tag )
- : min( a_min.getOwned() ), max( a_max.getOwned() ), tag( a_tag ) {}
-
- std::string toString() const;
- };
-
-
- class ShardInfo {
- public:
- ShardInfo();
- ShardInfo(long long maxSizeMB,
- long long currSizeMB,
- bool draining,
- const std::set<std::string>& tags = std::set<std::string>(),
- const std::string& _mongoVersion = std::string(""));
-
- void addTag( const std::string& tag );
-
- /** @return true if we have the tag OR if the tag is "" */
- bool hasTag( const std::string& tag ) const;
-
- /**
- * @return true if a shard cannot receive any new chunks because it reaches 'shardLimits'.
- * Expects the optional fields "maxSize", can in size in MB, and "usedSize", currently used size
- * in MB, on 'shardLimits'.
- */
- bool isSizeMaxed() const;
-
- /**
- * @return true if 'shardLimist' contains a field "draining". Expects the optional field
- * "isDraining" on 'shrdLimits'.
- */
- bool isDraining() const { return _draining; }
-
- long long getMaxSizeMB() const { return _maxSizeMB; }
-
- long long getCurrSizeMB() const { return _currSizeMB; }
-
- std::string getMongoVersion() const { return _mongoVersion; }
-
- std::string toString() const;
-
- private:
- long long _maxSizeMB;
- long long _currSizeMB;
- bool _draining;
- std::set<std::string> _tags;
- std::string _mongoVersion;
- };
-
-
- struct MigrateInfo {
- MigrateInfo(const std::string& a_ns,
- const ShardId& a_to,
- const ShardId& a_from,
- const BSONObj& a_chunk)
- : ns(a_ns),
- to(a_to),
- from(a_from),
- chunk(a_chunk) {
-
- }
-
- const std::string ns;
- const ShardId to;
- const ShardId from;
- const ChunkInfo chunk;
- };
-
- typedef std::map<ShardId, ShardInfo> ShardInfoMap;
- typedef std::map<ShardId, std::vector<ChunkType>> ShardToChunksMap;
-
-
- class DistributionStatus {
- MONGO_DISALLOW_COPYING(DistributionStatus);
- public:
- DistributionStatus(const ShardInfoMap& shardInfo,
- const ShardToChunksMap& shardToChunksMap);
-
- // only used when building
-
- /**
- * @return if range is valid
- */
- bool addTagRange( const TagRange& range );
-
- // ---- these methods might be better suiting in BalancerPolicy
-
- /**
- * @param forTag "" if you don't care, or a tag
- * @return shard best suited to receive a chunk
- */
- std::string getBestReceieverShard( const std::string& forTag ) const;
-
- /**
- * @return the shard with the most chunks
- * based on # of chunks with the given tag
- */
- std::string getMostOverloadedShard( const std::string& forTag ) const;
-
-
- // ---- basic accessors, counters, etc...
-
- /** @return total number of chunks */
- unsigned totalChunks() const;
-
- /** @return number of chunks in this shard */
- unsigned numberOfChunksInShard(const ShardId& shardId) const;
-
- /** @return number of chunks in this shard with the given tag */
- unsigned numberOfChunksInShardWithTag(const ShardId& shardId, const std::string& tag ) const;
-
- /** @return chunks for the shard */
- const std::vector<ChunkType>& getChunks(const ShardId& shardId) const;
-
- /** @return all tags we know about, not include "" */
- const std::set<std::string>& tags() const { return _allTags; }
-
- /** @return the right tag for chunk, possibly "" */
- std::string getTagForChunk(const ChunkType& chunk) const;
-
- /** @return all shard ids we know about */
- const std::set<ShardId>& shardIds() const { return _shardIds; }
-
- /** @return the ShardInfo for the shard */
- const ShardInfo& shardInfo(const ShardId& shardId) const;
-
- /** writes all state to log() */
- void dump() const;
-
- /**
- * Retrieves shard metadata information from the config server as well as some stats
- * from the shards.
- */
- static Status populateShardInfoMap(ShardInfoMap* shardInfo);
-
- /**
- * Note: jumbo and versions are not set.
- */
- static void populateShardToChunksMap(const ShardInfoMap& allShards,
- const ChunkManager& chunkMgr,
- ShardToChunksMap* shardToChunksMap);
-
- private:
- const ShardInfoMap& _shardInfo;
- const ShardToChunksMap& _shardChunks;
- std::map<BSONObj,TagRange> _tagRanges;
- std::set<std::string> _allTags;
- std::set<ShardId> _shardIds;
- };
-
-
- class BalancerPolicy {
- public:
-
- /**
- * Returns a suggested chunk to move whithin a collection's shards, given information about
- * space usage and number of chunks for that collection. If the policy doesn't recommend
- * moving, it returns NULL.
- *
- * @param ns is the collections namepace.
- * @param DistributionStatus holds all the info about the current state of the cluster/namespace
- * @param balancedLastTime is the number of chunks effectively moved in the last round.
- * @returns NULL or MigrateInfo of the best move to make towards balacing the collection.
- * caller owns the MigrateInfo instance
- */
- static MigrateInfo* balance( const std::string& ns,
- const DistributionStatus& distribution,
- int balancedLastTime );
- };
+class ChunkManager;
+
+struct ChunkInfo {
+ const BSONObj min;
+ const BSONObj max;
+
+ ChunkInfo(const BSONObj& chunk)
+ : min(chunk[ChunkType::min()].Obj().getOwned()),
+ max(chunk[ChunkType::max()].Obj().getOwned()) {}
+
+ std::string toString() const;
+};
+
+
+struct TagRange {
+ BSONObj min;
+ BSONObj max;
+ std::string tag;
+
+ TagRange() {}
+
+ TagRange(const BSONObj& a_min, const BSONObj& a_max, const std::string& a_tag)
+ : min(a_min.getOwned()), max(a_max.getOwned()), tag(a_tag) {}
+
+ std::string toString() const;
+};
+
+
+class ShardInfo {
+public:
+ ShardInfo();
+ ShardInfo(long long maxSizeMB,
+ long long currSizeMB,
+ bool draining,
+ const std::set<std::string>& tags = std::set<std::string>(),
+ const std::string& _mongoVersion = std::string(""));
+
+ void addTag(const std::string& tag);
+
+ /** @return true if we have the tag OR if the tag is "" */
+ bool hasTag(const std::string& tag) const;
+
+ /**
+ * @return true if a shard cannot receive any new chunks because it reaches 'shardLimits'.
+ * Expects the optional fields "maxSize", can in size in MB, and "usedSize", currently used size
+ * in MB, on 'shardLimits'.
+ */
+ bool isSizeMaxed() const;
+
+ /**
+ * @return true if 'shardLimist' contains a field "draining". Expects the optional field
+ * "isDraining" on 'shrdLimits'.
+ */
+ bool isDraining() const {
+ return _draining;
+ }
+
+ long long getMaxSizeMB() const {
+ return _maxSizeMB;
+ }
+
+ long long getCurrSizeMB() const {
+ return _currSizeMB;
+ }
+
+ std::string getMongoVersion() const {
+ return _mongoVersion;
+ }
+
+ std::string toString() const;
+
+private:
+ long long _maxSizeMB;
+ long long _currSizeMB;
+ bool _draining;
+ std::set<std::string> _tags;
+ std::string _mongoVersion;
+};
+
+
+struct MigrateInfo {
+ MigrateInfo(const std::string& a_ns,
+ const ShardId& a_to,
+ const ShardId& a_from,
+ const BSONObj& a_chunk)
+ : ns(a_ns), to(a_to), from(a_from), chunk(a_chunk) {}
+
+ const std::string ns;
+ const ShardId to;
+ const ShardId from;
+ const ChunkInfo chunk;
+};
+
+typedef std::map<ShardId, ShardInfo> ShardInfoMap;
+typedef std::map<ShardId, std::vector<ChunkType>> ShardToChunksMap;
+
+
+class DistributionStatus {
+ MONGO_DISALLOW_COPYING(DistributionStatus);
+
+public:
+ DistributionStatus(const ShardInfoMap& shardInfo, const ShardToChunksMap& shardToChunksMap);
+
+ // only used when building
+
+ /**
+ * @return if range is valid
+ */
+ bool addTagRange(const TagRange& range);
+
+ // ---- these methods might be better suiting in BalancerPolicy
+
+ /**
+ * @param forTag "" if you don't care, or a tag
+ * @return shard best suited to receive a chunk
+ */
+ std::string getBestReceieverShard(const std::string& forTag) const;
+
+ /**
+ * @return the shard with the most chunks
+ * based on # of chunks with the given tag
+ */
+ std::string getMostOverloadedShard(const std::string& forTag) const;
+
+
+ // ---- basic accessors, counters, etc...
+
+ /** @return total number of chunks */
+ unsigned totalChunks() const;
+
+ /** @return number of chunks in this shard */
+ unsigned numberOfChunksInShard(const ShardId& shardId) const;
+
+ /** @return number of chunks in this shard with the given tag */
+ unsigned numberOfChunksInShardWithTag(const ShardId& shardId, const std::string& tag) const;
+
+ /** @return chunks for the shard */
+ const std::vector<ChunkType>& getChunks(const ShardId& shardId) const;
+
+ /** @return all tags we know about, not include "" */
+ const std::set<std::string>& tags() const {
+ return _allTags;
+ }
+
+ /** @return the right tag for chunk, possibly "" */
+ std::string getTagForChunk(const ChunkType& chunk) const;
+
+ /** @return all shard ids we know about */
+ const std::set<ShardId>& shardIds() const {
+ return _shardIds;
+ }
+
+ /** @return the ShardInfo for the shard */
+ const ShardInfo& shardInfo(const ShardId& shardId) const;
+
+ /** writes all state to log() */
+ void dump() const;
+
+ /**
+ * Retrieves shard metadata information from the config server as well as some stats
+ * from the shards.
+ */
+ static Status populateShardInfoMap(ShardInfoMap* shardInfo);
+
+ /**
+ * Note: jumbo and versions are not set.
+ */
+ static void populateShardToChunksMap(const ShardInfoMap& allShards,
+ const ChunkManager& chunkMgr,
+ ShardToChunksMap* shardToChunksMap);
+
+private:
+ const ShardInfoMap& _shardInfo;
+ const ShardToChunksMap& _shardChunks;
+ std::map<BSONObj, TagRange> _tagRanges;
+ std::set<std::string> _allTags;
+ std::set<ShardId> _shardIds;
+};
+
+
+class BalancerPolicy {
+public:
+ /**
+ * Returns a suggested chunk to move whithin a collection's shards, given information about
+ * space usage and number of chunks for that collection. If the policy doesn't recommend
+ * moving, it returns NULL.
+ *
+ * @param ns is the collections namepace.
+ * @param DistributionStatus holds all the info about the current state of the cluster/namespace
+ * @param balancedLastTime is the number of chunks effectively moved in the last round.
+ * @returns NULL or MigrateInfo of the best move to make towards balacing the collection.
+ * caller owns the MigrateInfo instance
+ */
+ static MigrateInfo* balance(const std::string& ns,
+ const DistributionStatus& distribution,
+ int balancedLastTime);
+};
} // namespace mongo
diff --git a/src/mongo/s/balancer_policy_tests.cpp b/src/mongo/s/balancer_policy_tests.cpp
index 03ee8a8f6e0..b200576ee7b 100644
--- a/src/mongo/s/balancer_policy_tests.cpp
+++ b/src/mongo/s/balancer_policy_tests.cpp
@@ -37,594 +37,587 @@
namespace {
- using namespace mongo;
-
- using std::map;
- using std::string;
- using std::stringstream;
- using std::vector;
-
-
- TEST(BalancerPolicyTests, SizeMaxedShardTest) {
- ASSERT(!ShardInfo(0, 0, false).isSizeMaxed());
- ASSERT(!ShardInfo(100LL, 80LL, false).isSizeMaxed());
- ASSERT(ShardInfo(100LL, 110LL, false).isSizeMaxed());
- }
+using namespace mongo;
- TEST(BalancerPolicyTests, BalanceNormalTest) {
- ShardToChunksMap chunkMap;
- vector<ChunkType> chunks;
+using std::map;
+using std::string;
+using std::stringstream;
+using std::vector;
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << BSON("$minKey" << 1)));
- chunk.setMax(BSON("x" << 49));
- chunks.push_back(chunk);
- }
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << 49));
- chunk.setMax(BSON("x" << BSON("$maxKey" << 1)));
- chunks.push_back(chunk);
- }
+TEST(BalancerPolicyTests, SizeMaxedShardTest) {
+ ASSERT(!ShardInfo(0, 0, false).isSizeMaxed());
+ ASSERT(!ShardInfo(100LL, 80LL, false).isSizeMaxed());
+ ASSERT(ShardInfo(100LL, 110LL, false).isSizeMaxed());
+}
- chunkMap["shard0"] = chunks;
- chunkMap["shard1"] = vector<ChunkType>();
+TEST(BalancerPolicyTests, BalanceNormalTest) {
+ ShardToChunksMap chunkMap;
+ vector<ChunkType> chunks;
- // no limits
- ShardInfoMap info;
- info["shard0"] = ShardInfo(0, 2, false);
- info["shard1"] = ShardInfo(0, 0, false);
-
- DistributionStatus status(info, chunkMap);
- std::unique_ptr<MigrateInfo> c(BalancerPolicy::balance("ns", status, 1));
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << BSON("$minKey" << 1)));
+ chunk.setMax(BSON("x" << 49));
+ chunks.push_back(chunk);
+ }
- ASSERT(c);
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << 49));
+ chunk.setMax(BSON("x" << BSON("$maxKey" << 1)));
+ chunks.push_back(chunk);
}
+ chunkMap["shard0"] = chunks;
+ chunkMap["shard1"] = vector<ChunkType>();
- TEST(BalancerPolicyTests, BalanceJumbo) {
- ShardToChunksMap chunkMap;
- vector<ChunkType> chunks;
+ // no limits
+ ShardInfoMap info;
+ info["shard0"] = ShardInfo(0, 2, false);
+ info["shard1"] = ShardInfo(0, 0, false);
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << BSON("$minKey" << 1)));
- chunk.setMax(BSON("x" << 10));
- chunk.setJumbo(true);
- chunks.push_back(chunk);
- }
+ DistributionStatus status(info, chunkMap);
+ std::unique_ptr<MigrateInfo> c(BalancerPolicy::balance("ns", status, 1));
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << 10));
- chunk.setMax(BSON("x" << 20));
- chunk.setJumbo(true);
- chunks.push_back(chunk);
- }
+ ASSERT(c);
+}
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << 20));
- chunk.setMax(BSON("x" << 30));
- chunks.push_back(chunk);
- }
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << 30));
- chunk.setMax(BSON("x" << 40));
- chunk.setJumbo(true);
- chunks.push_back(chunk);
- }
+TEST(BalancerPolicyTests, BalanceJumbo) {
+ ShardToChunksMap chunkMap;
+ vector<ChunkType> chunks;
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << 40));
- chunk.setMax(BSON("x" << BSON("$maxKey" << 1)));
- chunks.push_back(chunk);
- }
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << BSON("$minKey" << 1)));
+ chunk.setMax(BSON("x" << 10));
+ chunk.setJumbo(true);
+ chunks.push_back(chunk);
+ }
- chunkMap["shard0"] = chunks;
- chunkMap["shard1"] = vector<ChunkType>();
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << 10));
+ chunk.setMax(BSON("x" << 20));
+ chunk.setJumbo(true);
+ chunks.push_back(chunk);
+ }
- // no limits
- ShardInfoMap info;
- info["shard0"] = ShardInfo(0, 2, false);
- info["shard1"] = ShardInfo(0, 0, false);
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << 20));
+ chunk.setMax(BSON("x" << 30));
+ chunks.push_back(chunk);
+ }
- DistributionStatus status(info, chunkMap);
- std::unique_ptr<MigrateInfo> c(BalancerPolicy::balance("ns", status, 1));
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << 30));
+ chunk.setMax(BSON("x" << 40));
+ chunk.setJumbo(true);
+ chunks.push_back(chunk);
+ }
- ASSERT(c);
- ASSERT_EQUALS(30, c->chunk.max["x"].numberInt());
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << 40));
+ chunk.setMax(BSON("x" << BSON("$maxKey" << 1)));
+ chunks.push_back(chunk);
}
- TEST(BalanceNormalTests, BalanceDrainingTest) {
- ShardToChunksMap chunkMap;
- vector<ChunkType> chunks;
+ chunkMap["shard0"] = chunks;
+ chunkMap["shard1"] = vector<ChunkType>();
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << BSON("$minKey" << 1)));
- chunk.setMax(BSON("x" << 49));
- chunks.push_back(chunk);
- }
+ // no limits
+ ShardInfoMap info;
+ info["shard0"] = ShardInfo(0, 2, false);
+ info["shard1"] = ShardInfo(0, 0, false);
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << 49));
- chunk.setMax(BSON("x" << BSON("$maxKey" << 1)));
- chunks.push_back(chunk);
- }
+ DistributionStatus status(info, chunkMap);
+ std::unique_ptr<MigrateInfo> c(BalancerPolicy::balance("ns", status, 1));
- chunkMap["shard0"] = chunks;
- chunkMap["shard1"] = vector<ChunkType>();
+ ASSERT(c);
+ ASSERT_EQUALS(30, c->chunk.max["x"].numberInt());
+}
- // shard0 is draining
- ShardInfoMap limitsMap;
- limitsMap["shard0"] = ShardInfo(0LL, 2LL, true);
- limitsMap["shard1"] = ShardInfo(0LL, 0LL, false);
+TEST(BalanceNormalTests, BalanceDrainingTest) {
+ ShardToChunksMap chunkMap;
+ vector<ChunkType> chunks;
- DistributionStatus status(limitsMap, chunkMap);
- std::unique_ptr<MigrateInfo> c(BalancerPolicy::balance("ns", status, 0));
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << BSON("$minKey" << 1)));
+ chunk.setMax(BSON("x" << 49));
+ chunks.push_back(chunk);
+ }
- ASSERT(c);
- ASSERT_EQUALS(c->to, "shard1");
- ASSERT_EQUALS(c->from, "shard0");
- ASSERT(!c->chunk.min.isEmpty());
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << 49));
+ chunk.setMax(BSON("x" << BSON("$maxKey" << 1)));
+ chunks.push_back(chunk);
}
- TEST(BalancerPolicyTests, BalanceEndedDrainingTest) {
- ShardToChunksMap chunkMap;
- vector<ChunkType> chunks;
+ chunkMap["shard0"] = chunks;
+ chunkMap["shard1"] = vector<ChunkType>();
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << BSON("$minKey" << 1)));
- chunk.setMax(BSON("x" << 49));
- chunks.push_back(chunk);
- }
+ // shard0 is draining
+ ShardInfoMap limitsMap;
+ limitsMap["shard0"] = ShardInfo(0LL, 2LL, true);
+ limitsMap["shard1"] = ShardInfo(0LL, 0LL, false);
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << 49));
- chunk.setMax(BSON("x" << BSON("$maxKey" << 1)));
- chunks.push_back(chunk);
- }
+ DistributionStatus status(limitsMap, chunkMap);
+ std::unique_ptr<MigrateInfo> c(BalancerPolicy::balance("ns", status, 0));
- chunkMap["shard0"] = chunks;
- chunkMap["shard1"] = vector<ChunkType>();
+ ASSERT(c);
+ ASSERT_EQUALS(c->to, "shard1");
+ ASSERT_EQUALS(c->from, "shard0");
+ ASSERT(!c->chunk.min.isEmpty());
+}
- // no limits
- ShardInfoMap limitsMap;
- limitsMap["shard0"] = ShardInfo(0, 2, false);
- limitsMap["shard1"] = ShardInfo(0, 0, true);
+TEST(BalancerPolicyTests, BalanceEndedDrainingTest) {
+ ShardToChunksMap chunkMap;
+ vector<ChunkType> chunks;
- DistributionStatus status(limitsMap, chunkMap);
- std::unique_ptr<MigrateInfo> c(BalancerPolicy::balance("ns", status, 0));
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << BSON("$minKey" << 1)));
+ chunk.setMax(BSON("x" << 49));
+ chunks.push_back(chunk);
+ }
- ASSERT(!c);
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << 49));
+ chunk.setMax(BSON("x" << BSON("$maxKey" << 1)));
+ chunks.push_back(chunk);
}
- TEST(BalancerPolicyTests, BalanceImpasseTest) {
- ShardToChunksMap chunkMap;
- vector<ChunkType> chunks;
+ chunkMap["shard0"] = chunks;
+ chunkMap["shard1"] = vector<ChunkType>();
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << BSON("$minKey" << 1)));
- chunk.setMax(BSON("x" << 49));
- chunks.push_back(chunk);
- }
+ // no limits
+ ShardInfoMap limitsMap;
+ limitsMap["shard0"] = ShardInfo(0, 2, false);
+ limitsMap["shard1"] = ShardInfo(0, 0, true);
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << 49));
- chunk.setMax(BSON("x" << BSON("$maxKey" << 1)));
- chunks.push_back(chunk);
- }
+ DistributionStatus status(limitsMap, chunkMap);
+ std::unique_ptr<MigrateInfo> c(BalancerPolicy::balance("ns", status, 0));
- chunkMap["shard0"] = vector<ChunkType>();
- chunkMap["shard1"] = chunks;
- chunkMap["shard2"] = vector<ChunkType>();
+ ASSERT(!c);
+}
- // shard0 is draining, shard1 is maxed out, shard2 has writebacks pending
- ShardInfoMap limitsMap;
- limitsMap["shard0"] = ShardInfo(0, 2, true);
- limitsMap["shard1"] = ShardInfo(1, 1, false);
- limitsMap["shard2"] = ShardInfo(0, 1, true);
+TEST(BalancerPolicyTests, BalanceImpasseTest) {
+ ShardToChunksMap chunkMap;
+ vector<ChunkType> chunks;
- DistributionStatus status(limitsMap, chunkMap);
- std::unique_ptr<MigrateInfo> c(BalancerPolicy::balance("ns", status, 0));
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << BSON("$minKey" << 1)));
+ chunk.setMax(BSON("x" << 49));
+ chunks.push_back(chunk);
+ }
- ASSERT(!c);
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << 49));
+ chunk.setMax(BSON("x" << BSON("$maxKey" << 1)));
+ chunks.push_back(chunk);
}
+ chunkMap["shard0"] = vector<ChunkType>();
+ chunkMap["shard1"] = chunks;
+ chunkMap["shard2"] = vector<ChunkType>();
- void addShard(ShardToChunksMap& shardToChunks, unsigned numChunks, bool last) {
- unsigned total = 0;
- for (const auto& chunk : shardToChunks) {
- total += chunk.second.size();
- }
+ // shard0 is draining, shard1 is maxed out, shard2 has writebacks pending
+ ShardInfoMap limitsMap;
+ limitsMap["shard0"] = ShardInfo(0, 2, true);
+ limitsMap["shard1"] = ShardInfo(1, 1, false);
+ limitsMap["shard2"] = ShardInfo(0, 1, true);
- stringstream ss;
- ss << "shard" << shardToChunks.size();
- string myName = ss.str();
+ DistributionStatus status(limitsMap, chunkMap);
+ std::unique_ptr<MigrateInfo> c(BalancerPolicy::balance("ns", status, 0));
- vector<ChunkType> chunksList;
+ ASSERT(!c);
+}
- for (unsigned i = 0; i < numChunks; i++) {
- ChunkType chunk;
- if (i == 0 && total == 0) {
- chunk.setMin(BSON("x" << BSON("$minKey" << 1)));
- }
- else {
- chunk.setMin(BSON("x" << total + i));
- }
+void addShard(ShardToChunksMap& shardToChunks, unsigned numChunks, bool last) {
+ unsigned total = 0;
+ for (const auto& chunk : shardToChunks) {
+ total += chunk.second.size();
+ }
- if (last && i == (numChunks - 1)) {
- chunk.setMax(BSON("x" << BSON("$maxKey" << 1)));
- }
- else {
- chunk.setMax(BSON("x" << 1 + total + i));
- }
+ stringstream ss;
+ ss << "shard" << shardToChunks.size();
+ string myName = ss.str();
+
+ vector<ChunkType> chunksList;
- chunksList.push_back(chunk);
+ for (unsigned i = 0; i < numChunks; i++) {
+ ChunkType chunk;
+
+ if (i == 0 && total == 0) {
+ chunk.setMin(BSON("x" << BSON("$minKey" << 1)));
+ } else {
+ chunk.setMin(BSON("x" << total + i));
}
- shardToChunks[myName] = chunksList;
+ if (last && i == (numChunks - 1)) {
+ chunk.setMax(BSON("x" << BSON("$maxKey" << 1)));
+ } else {
+ chunk.setMax(BSON("x" << 1 + total + i));
+ }
+
+ chunksList.push_back(chunk);
}
- void moveChunk(ShardToChunksMap& shardToChunks, MigrateInfo* m) {
- vector<ChunkType>& chunks = shardToChunks[m->from];
+ shardToChunks[myName] = chunksList;
+}
- for (vector<ChunkType>::iterator i = chunks.begin(); i != chunks.end(); ++i) {
- if (i->getMin() == m->chunk.min) {
- shardToChunks[m->to].push_back(*i);
- chunks.erase(i);
- return;
- }
- }
+void moveChunk(ShardToChunksMap& shardToChunks, MigrateInfo* m) {
+ vector<ChunkType>& chunks = shardToChunks[m->from];
- invariant(false);
+ for (vector<ChunkType>::iterator i = chunks.begin(); i != chunks.end(); ++i) {
+ if (i->getMin() == m->chunk.min) {
+ shardToChunks[m->to].push_back(*i);
+ chunks.erase(i);
+ return;
+ }
}
+ invariant(false);
+}
- TEST(BalancerPolicyTests, MultipleDraining) {
- ShardToChunksMap chunks;
- addShard(chunks, 5, false);
- addShard(chunks, 10, false);
- addShard(chunks, 5, true);
- ShardInfoMap shards;
- shards["shard0"] = ShardInfo(0, 5, true);
- shards["shard1"] = ShardInfo(0, 5, true);
- shards["shard2"] = ShardInfo(0, 5, false);
+TEST(BalancerPolicyTests, MultipleDraining) {
+ ShardToChunksMap chunks;
+ addShard(chunks, 5, false);
+ addShard(chunks, 10, false);
+ addShard(chunks, 5, true);
- DistributionStatus d(shards, chunks);
- std::unique_ptr<MigrateInfo> m(BalancerPolicy::balance("ns", d, 0));
+ ShardInfoMap shards;
+ shards["shard0"] = ShardInfo(0, 5, true);
+ shards["shard1"] = ShardInfo(0, 5, true);
+ shards["shard2"] = ShardInfo(0, 5, false);
- ASSERT(m);
- ASSERT_EQUALS("shard2", m->to);
- }
+ DistributionStatus d(shards, chunks);
+ std::unique_ptr<MigrateInfo> m(BalancerPolicy::balance("ns", d, 0));
+ ASSERT(m);
+ ASSERT_EQUALS("shard2", m->to);
+}
- TEST(BalancerPolicyTests, TagsDraining) {
- ShardToChunksMap chunks;
- addShard(chunks, 5, false);
- addShard(chunks, 5, false);
- addShard(chunks, 5, true);
- ShardInfoMap shards;
- shards["shard0"] = ShardInfo(0, 5, false);
- shards["shard1"] = ShardInfo(0, 5, true);
- shards["shard2"] = ShardInfo(0, 5, false);
+TEST(BalancerPolicyTests, TagsDraining) {
+ ShardToChunksMap chunks;
+ addShard(chunks, 5, false);
+ addShard(chunks, 5, false);
+ addShard(chunks, 5, true);
- shards["shard0"].addTag("a");
- shards["shard1"].addTag("a");
- shards["shard1"].addTag("b");
- shards["shard2"].addTag("b");
+ ShardInfoMap shards;
+ shards["shard0"] = ShardInfo(0, 5, false);
+ shards["shard1"] = ShardInfo(0, 5, true);
+ shards["shard2"] = ShardInfo(0, 5, false);
- while (true) {
- DistributionStatus d(shards, chunks);
- d.addTagRange(TagRange(BSON("x" << -1), BSON("x" << 7), "a"));
- d.addTagRange(TagRange(BSON("x" << 7), BSON("x" << 1000), "b"));
+ shards["shard0"].addTag("a");
+ shards["shard1"].addTag("a");
+ shards["shard1"].addTag("b");
+ shards["shard2"].addTag("b");
- std::unique_ptr<MigrateInfo> m(BalancerPolicy::balance("ns", d, 0));
- if (!m) {
- break;
- }
+ while (true) {
+ DistributionStatus d(shards, chunks);
+ d.addTagRange(TagRange(BSON("x" << -1), BSON("x" << 7), "a"));
+ d.addTagRange(TagRange(BSON("x" << 7), BSON("x" << 1000), "b"));
- if (m->chunk.min["x"].numberInt() < 7) {
- ASSERT_EQUALS("shard0", m->to);
- }
- else {
- ASSERT_EQUALS("shard2", m->to);
- }
+ std::unique_ptr<MigrateInfo> m(BalancerPolicy::balance("ns", d, 0));
+ if (!m) {
+ break;
+ }
- moveChunk(chunks, m.get());
+ if (m->chunk.min["x"].numberInt() < 7) {
+ ASSERT_EQUALS("shard0", m->to);
+ } else {
+ ASSERT_EQUALS("shard2", m->to);
}
- ASSERT_EQUALS(7U, chunks["shard0"].size());
- ASSERT_EQUALS(0U, chunks["shard1"].size());
- ASSERT_EQUALS(8U, chunks["shard2"].size());
+ moveChunk(chunks, m.get());
}
+ ASSERT_EQUALS(7U, chunks["shard0"].size());
+ ASSERT_EQUALS(0U, chunks["shard1"].size());
+ ASSERT_EQUALS(8U, chunks["shard2"].size());
+}
- TEST(BalancerPolicyTests, TagsPolicyChange) {
- ShardToChunksMap chunks;
- addShard(chunks, 5, false);
- addShard(chunks, 5, false);
- addShard(chunks, 5, true);
- ShardInfoMap shards;
- shards["shard0"] = ShardInfo(0, 5, false);
- shards["shard1"] = ShardInfo(0, 5, false);
- shards["shard2"] = ShardInfo(0, 5, false);
+TEST(BalancerPolicyTests, TagsPolicyChange) {
+ ShardToChunksMap chunks;
+ addShard(chunks, 5, false);
+ addShard(chunks, 5, false);
+ addShard(chunks, 5, true);
- shards["shard0"].addTag("a");
- shards["shard1"].addTag("a");
+ ShardInfoMap shards;
+ shards["shard0"] = ShardInfo(0, 5, false);
+ shards["shard1"] = ShardInfo(0, 5, false);
+ shards["shard2"] = ShardInfo(0, 5, false);
- while (true) {
- DistributionStatus d(shards, chunks);
- d.addTagRange(TagRange(BSON("x" << -1), BSON("x" << 1000), "a"));
+ shards["shard0"].addTag("a");
+ shards["shard1"].addTag("a");
- std::unique_ptr<MigrateInfo> m(BalancerPolicy::balance("ns", d, 0));
- if (!m) {
- break;
- }
+ while (true) {
+ DistributionStatus d(shards, chunks);
+ d.addTagRange(TagRange(BSON("x" << -1), BSON("x" << 1000), "a"));
- moveChunk(chunks, m.get());
+ std::unique_ptr<MigrateInfo> m(BalancerPolicy::balance("ns", d, 0));
+ if (!m) {
+ break;
}
- const size_t shard0Size = chunks["shard0"].size();
- const size_t shard1Size = chunks["shard1"].size();
-
- ASSERT_EQUALS(15U, shard0Size + shard1Size);
- ASSERT(shard0Size == 7U || shard0Size == 8U);
- ASSERT_EQUALS(0U, chunks["shard2"].size());
+ moveChunk(chunks, m.get());
}
+ const size_t shard0Size = chunks["shard0"].size();
+ const size_t shard1Size = chunks["shard1"].size();
- TEST(BalancerPolicyTests, TagsSelector) {
- ShardToChunksMap chunks;
- ShardInfoMap shards;
- DistributionStatus d(shards, chunks);
+ ASSERT_EQUALS(15U, shard0Size + shard1Size);
+ ASSERT(shard0Size == 7U || shard0Size == 8U);
+ ASSERT_EQUALS(0U, chunks["shard2"].size());
+}
- ASSERT(d.addTagRange(TagRange(BSON("x" << 1), BSON("x" << 10), "a")));
- ASSERT(d.addTagRange(TagRange(BSON("x" << 10), BSON("x" << 20), "b")));
- ASSERT(d.addTagRange(TagRange(BSON("x" << 20), BSON("x" << 30), "c")));
- ASSERT(!d.addTagRange(TagRange(BSON("x" << 20), BSON("x" << 30), "c")));
- ASSERT(!d.addTagRange(TagRange(BSON("x" << 22), BSON("x" << 28), "c")));
- ASSERT(!d.addTagRange(TagRange(BSON("x" << 28), BSON("x" << 33), "c")));
+TEST(BalancerPolicyTests, TagsSelector) {
+ ShardToChunksMap chunks;
+ ShardInfoMap shards;
+ DistributionStatus d(shards, chunks);
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << -4));
- ASSERT_EQUALS("", d.getTagForChunk(chunk));
- }
+ ASSERT(d.addTagRange(TagRange(BSON("x" << 1), BSON("x" << 10), "a")));
+ ASSERT(d.addTagRange(TagRange(BSON("x" << 10), BSON("x" << 20), "b")));
+ ASSERT(d.addTagRange(TagRange(BSON("x" << 20), BSON("x" << 30), "c")));
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << 0));
- ASSERT_EQUALS("", d.getTagForChunk(chunk));
- }
+ ASSERT(!d.addTagRange(TagRange(BSON("x" << 20), BSON("x" << 30), "c")));
+ ASSERT(!d.addTagRange(TagRange(BSON("x" << 22), BSON("x" << 28), "c")));
+ ASSERT(!d.addTagRange(TagRange(BSON("x" << 28), BSON("x" << 33), "c")));
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << 1));
- ASSERT_EQUALS("a", d.getTagForChunk(chunk));
- }
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << -4));
+ ASSERT_EQUALS("", d.getTagForChunk(chunk));
+ }
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << 10));
- ASSERT_EQUALS("b", d.getTagForChunk(chunk));
- }
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << 0));
+ ASSERT_EQUALS("", d.getTagForChunk(chunk));
+ }
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << 15));
- ASSERT_EQUALS("b", d.getTagForChunk(chunk));
- }
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << 1));
+ ASSERT_EQUALS("a", d.getTagForChunk(chunk));
+ }
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << 25));
- ASSERT_EQUALS("c", d.getTagForChunk(chunk));
- }
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << 10));
+ ASSERT_EQUALS("b", d.getTagForChunk(chunk));
+ }
- {
- ChunkType chunk;
- chunk.setMin(BSON("x" << 35));
- ASSERT_EQUALS("", d.getTagForChunk(chunk));
- }
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << 15));
+ ASSERT_EQUALS("b", d.getTagForChunk(chunk));
}
- /**
- * Idea for this test is to set up three shards, one of which is overloaded (too much data).
- *
- * Even though the overloaded shard has less chunks, we shouldn't move chunks to that shard.
- */
- TEST(BalancerPolicyTests, MaxSizeRespect) {
- ShardToChunksMap chunks;
- addShard(chunks, 3, false);
- addShard(chunks, 4, false);
- addShard(chunks, 6, true);
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << 25));
+ ASSERT_EQUALS("c", d.getTagForChunk(chunk));
+ }
- // Note that maxSize of shard0 is 1, and it is therefore overloaded with currSize = 3.
- // Other shards have maxSize = 0 = unset.
+ {
+ ChunkType chunk;
+ chunk.setMin(BSON("x" << 35));
+ ASSERT_EQUALS("", d.getTagForChunk(chunk));
+ }
+}
- ShardInfoMap shards;
- shards["shard0"] = ShardInfo(1, 3, false);
- shards["shard1"] = ShardInfo(0, 4, false);
- shards["shard2"] = ShardInfo(0, 6, false);
+/**
+ * Idea for this test is to set up three shards, one of which is overloaded (too much data).
+ *
+ * Even though the overloaded shard has less chunks, we shouldn't move chunks to that shard.
+ */
+TEST(BalancerPolicyTests, MaxSizeRespect) {
+ ShardToChunksMap chunks;
+ addShard(chunks, 3, false);
+ addShard(chunks, 4, false);
+ addShard(chunks, 6, true);
- DistributionStatus d(shards, chunks);
- std::unique_ptr<MigrateInfo> m(BalancerPolicy::balance("ns", d, 0));
+ // Note that maxSize of shard0 is 1, and it is therefore overloaded with currSize = 3.
+ // Other shards have maxSize = 0 = unset.
- ASSERT(m);
- ASSERT_EQUALS("shard2", m->from);
- ASSERT_EQUALS("shard1", m->to);
- }
+ ShardInfoMap shards;
+ shards["shard0"] = ShardInfo(1, 3, false);
+ shards["shard1"] = ShardInfo(0, 4, false);
+ shards["shard2"] = ShardInfo(0, 6, false);
- /**
- * Here we check that being over the maxSize is *not* equivalent to draining, we don't want
- * to empty shards for no other reason than they are over this limit.
- */
- TEST(BalancerPolicyTests, MaxSizeNoDrain) {
- ShardToChunksMap chunks;
+ DistributionStatus d(shards, chunks);
+ std::unique_ptr<MigrateInfo> m(BalancerPolicy::balance("ns", d, 0));
- // Shard0 will be overloaded
- addShard(chunks, 4, false);
- addShard(chunks, 4, false);
- addShard(chunks, 4, true);
+ ASSERT(m);
+ ASSERT_EQUALS("shard2", m->from);
+ ASSERT_EQUALS("shard1", m->to);
+}
- // Note that maxSize of shard0 is 1, and it is therefore overloaded with currSize = 4.
- // Other shards have maxSize = 0 = unset.
+/**
+ * Here we check that being over the maxSize is *not* equivalent to draining, we don't want
+ * to empty shards for no other reason than they are over this limit.
+ */
+TEST(BalancerPolicyTests, MaxSizeNoDrain) {
+ ShardToChunksMap chunks;
- ShardInfoMap shards;
- // ShardInfo(maxSize, currSize, draining, opsQueued)
- shards["shard0"] = ShardInfo(1, 4, false);
- shards["shard1"] = ShardInfo(0, 4, false);
- shards["shard2"] = ShardInfo(0, 4, false);
+ // Shard0 will be overloaded
+ addShard(chunks, 4, false);
+ addShard(chunks, 4, false);
+ addShard(chunks, 4, true);
- DistributionStatus d(shards, chunks);
- std::unique_ptr<MigrateInfo> m(BalancerPolicy::balance("ns", d, 0));
+ // Note that maxSize of shard0 is 1, and it is therefore overloaded with currSize = 4.
+ // Other shards have maxSize = 0 = unset.
- ASSERT(!m);
- }
+ ShardInfoMap shards;
+ // ShardInfo(maxSize, currSize, draining, opsQueued)
+ shards["shard0"] = ShardInfo(1, 4, false);
+ shards["shard1"] = ShardInfo(0, 4, false);
+ shards["shard2"] = ShardInfo(0, 4, false);
- /**
- * Idea behind this test is that we set up several shards, the first two of which are
- * draining and the second two of which have a data size limit. We also simulate a random
- * number of chunks on each shard.
- *
- * Once the shards are setup, we virtually migrate numChunks times, or until there are no
- * more migrations to run. Each chunk is assumed to have a size of 1 unit, and we increment
- * our currSize for each shard as the chunks move.
- *
- * Finally, we ensure that the drained shards are drained, the data-limited shards aren't
- * overloaded, and that all shards (including the data limited shard if the baseline isn't
- * over the limit are balanced to within 1 unit of some baseline.
- *
- */
- TEST(BalancerPolicyTests, Simulation) {
- // Hardcode seed here, make test deterministic.
- int64_t seed = 1337;
- PseudoRandom rng(seed);
-
- // Run test 10 times
- for (int test = 0; test < 10; test++) {
- // Setup our shards as draining, with maxSize, and normal
- int numShards = 7;
- int numChunks = 0;
-
- ShardToChunksMap chunks;
- ShardInfoMap shards;
-
- map<string, int> expected;
-
- for (int i = 0; i < numShards; i++) {
- int numShardChunks = rng.nextInt32(100);
- bool draining = i < 2;
- bool maxed = i >= 2 && i < 4;
-
- if (draining) {
- expected[str::stream() << "shard" << i] = 0;
- }
+ DistributionStatus d(shards, chunks);
+ std::unique_ptr<MigrateInfo> m(BalancerPolicy::balance("ns", d, 0));
- if (maxed) {
- expected[str::stream() << "shard" << i] = numShardChunks + 1;
- }
+ ASSERT(!m);
+}
- addShard(chunks, numShardChunks, false);
- numChunks += numShardChunks;
+/**
+ * Idea behind this test is that we set up several shards, the first two of which are
+ * draining and the second two of which have a data size limit. We also simulate a random
+ * number of chunks on each shard.
+ *
+ * Once the shards are setup, we virtually migrate numChunks times, or until there are no
+ * more migrations to run. Each chunk is assumed to have a size of 1 unit, and we increment
+ * our currSize for each shard as the chunks move.
+ *
+ * Finally, we ensure that the drained shards are drained, the data-limited shards aren't
+ * overloaded, and that all shards (including the data limited shard if the baseline isn't
+ * over the limit are balanced to within 1 unit of some baseline.
+ *
+ */
+TEST(BalancerPolicyTests, Simulation) {
+ // Hardcode seed here, make test deterministic.
+ int64_t seed = 1337;
+ PseudoRandom rng(seed);
- shards[str::stream() << "shard" << i] =
- ShardInfo(maxed ? numShardChunks + 1 : 0, numShardChunks, draining);
+ // Run test 10 times
+ for (int test = 0; test < 10; test++) {
+ // Setup our shards as draining, with maxSize, and normal
+ int numShards = 7;
+ int numChunks = 0;
+
+ ShardToChunksMap chunks;
+ ShardInfoMap shards;
+
+ map<string, int> expected;
+
+ for (int i = 0; i < numShards; i++) {
+ int numShardChunks = rng.nextInt32(100);
+ bool draining = i < 2;
+ bool maxed = i >= 2 && i < 4;
+
+ if (draining) {
+ expected[str::stream() << "shard" << i] = 0;
}
- for (ShardInfoMap::iterator it = shards.begin(); it != shards.end(); ++it) {
- log() << it->first << " : " << it->second.toString();
+ if (maxed) {
+ expected[str::stream() << "shard" << i] = numShardChunks + 1;
}
- // Perform migrations and increment data size as chunks move
- for (int i = 0; i < numChunks; i++) {
- DistributionStatus d(shards, chunks);
- std::unique_ptr<MigrateInfo> m(BalancerPolicy::balance("ns", d, i != 0));
- if (!m) {
- log() << "Finished with test moves.";
- break;
- }
+ addShard(chunks, numShardChunks, false);
+ numChunks += numShardChunks;
- moveChunk(chunks, m.get());
+ shards[str::stream() << "shard" << i] =
+ ShardInfo(maxed ? numShardChunks + 1 : 0, numShardChunks, draining);
+ }
- {
- ShardInfo& info = shards[m->from];
- shards[m->from] = ShardInfo(info.getMaxSizeMB(),
- info.getCurrSizeMB() - 1,
- info.isDraining());
- }
+ for (ShardInfoMap::iterator it = shards.begin(); it != shards.end(); ++it) {
+ log() << it->first << " : " << it->second.toString();
+ }
- {
- ShardInfo& info = shards[m->to];
- shards[m->to] = ShardInfo(info.getMaxSizeMB(),
- info.getCurrSizeMB() + 1,
- info.isDraining());
- }
+ // Perform migrations and increment data size as chunks move
+ for (int i = 0; i < numChunks; i++) {
+ DistributionStatus d(shards, chunks);
+ std::unique_ptr<MigrateInfo> m(BalancerPolicy::balance("ns", d, i != 0));
+ if (!m) {
+ log() << "Finished with test moves.";
+ break;
}
- // Make sure our balance is correct and our data size is low.
+ moveChunk(chunks, m.get());
- // The balanced value is the count on the last shard, since it's not draining or
- // limited.
- int balancedSize = (--shards.end())->second.getCurrSizeMB();
+ {
+ ShardInfo& info = shards[m->from];
+ shards[m->from] =
+ ShardInfo(info.getMaxSizeMB(), info.getCurrSizeMB() - 1, info.isDraining());
+ }
- for (ShardInfoMap::iterator it = shards.begin(); it != shards.end(); ++it) {
- log() << it->first << " : " << it->second.toString();
+ {
+ ShardInfo& info = shards[m->to];
+ shards[m->to] =
+ ShardInfo(info.getMaxSizeMB(), info.getCurrSizeMB() + 1, info.isDraining());
}
+ }
- for (ShardInfoMap::iterator it = shards.begin(); it != shards.end(); ++it) {
- log() << it->first << " : " << it->second.toString();
+ // Make sure our balance is correct and our data size is low.
- map<string, int>::iterator expectedIt = expected.find(it->first);
+ // The balanced value is the count on the last shard, since it's not draining or
+ // limited.
+ int balancedSize = (--shards.end())->second.getCurrSizeMB();
- if (expectedIt == expected.end()) {
- bool isInRange = it->second.getCurrSizeMB() >= balancedSize - 1
- && it->second.getCurrSizeMB() <= balancedSize + 1;
- if (!isInRange) {
- warning() << "non-limited and non-draining shard had "
- << it->second.getCurrSizeMB() << " chunks, expected near "
- << balancedSize;
- }
+ for (ShardInfoMap::iterator it = shards.begin(); it != shards.end(); ++it) {
+ log() << it->first << " : " << it->second.toString();
+ }
+
+ for (ShardInfoMap::iterator it = shards.begin(); it != shards.end(); ++it) {
+ log() << it->first << " : " << it->second.toString();
+
+ map<string, int>::iterator expectedIt = expected.find(it->first);
+
+ if (expectedIt == expected.end()) {
+ bool isInRange = it->second.getCurrSizeMB() >= balancedSize - 1 &&
+ it->second.getCurrSizeMB() <= balancedSize + 1;
+ if (!isInRange) {
+ warning() << "non-limited and non-draining shard had "
+ << it->second.getCurrSizeMB() << " chunks, expected near "
+ << balancedSize;
+ }
+
+ ASSERT(isInRange);
+ } else {
+ int expectedSize = expectedIt->second;
+ bool isInRange = it->second.getCurrSizeMB() <= expectedSize;
- ASSERT(isInRange);
+ if (isInRange && expectedSize >= balancedSize) {
+ isInRange = it->second.getCurrSizeMB() >= balancedSize - 1 &&
+ it->second.getCurrSizeMB() <= balancedSize + 1;
}
- else {
- int expectedSize = expectedIt->second;
- bool isInRange = it->second.getCurrSizeMB() <= expectedSize;
-
- if (isInRange && expectedSize >= balancedSize) {
- isInRange = it->second.getCurrSizeMB() >= balancedSize - 1
- && it->second.getCurrSizeMB() <= balancedSize + 1;
- }
-
- if (!isInRange) {
- warning() << "limited or draining shard had "
- << it->second.getCurrSizeMB() << " chunks, expected less than "
- << expectedSize << " and (if less than expected) near "
- << balancedSize;
- }
-
- ASSERT(isInRange);
+
+ if (!isInRange) {
+ warning() << "limited or draining shard had " << it->second.getCurrSizeMB()
+ << " chunks, expected less than " << expectedSize
+ << " and (if less than expected) near " << balancedSize;
}
+
+ ASSERT(isInRange);
}
}
}
+}
-} // namespace
+} // namespace
diff --git a/src/mongo/s/bson_serializable.h b/src/mongo/s/bson_serializable.h
index e957701df1c..a1d0695ee5c 100644
--- a/src/mongo/s/bson_serializable.h
+++ b/src/mongo/s/bson_serializable.h
@@ -34,81 +34,74 @@
namespace mongo {
+/**
+ * "Types" are the interface to a known data structure that will be serialized to and
+ * deserialized from BSON.
+ */
+class BSONSerializable {
+public:
+ virtual ~BSONSerializable() {}
+
/**
- * "Types" are the interface to a known data structure that will be serialized to and
- * deserialized from BSON.
+ * Returns true if all the mandatory fields are present and have valid
+ * representations. Otherwise returns false and fills in the optional 'errMsg' string.
*/
- class BSONSerializable {
- public:
-
- virtual ~BSONSerializable() {}
-
- /**
- * Returns true if all the mandatory fields are present and have valid
- * representations. Otherwise returns false and fills in the optional 'errMsg' string.
- */
- virtual bool isValid( std::string* errMsg ) const = 0;
-
- /** Returns the BSON representation of the entry. */
- virtual BSONObj toBSON() const = 0;
+ virtual bool isValid(std::string* errMsg) const = 0;
- /**
- * Clears and populates the internal state using the 'source' BSON object if the
- * latter contains valid values. Otherwise sets errMsg and returns false.
- */
- virtual bool parseBSON( const BSONObj& source, std::string* errMsg ) = 0;
-
- /** Clears the internal state. */
- virtual void clear() = 0;
-
- /** Returns a std::string representation of the current internal state. */
- virtual std::string toString() const = 0;
- };
+ /** Returns the BSON representation of the entry. */
+ virtual BSONObj toBSON() const = 0;
/**
- * Generic implementation which accepts and stores any BSON object
- *
- * Generally this should only be used for compatibility reasons - newer requests should be
- * fully typed.
+ * Clears and populates the internal state using the 'source' BSON object if the
+ * latter contains valid values. Otherwise sets errMsg and returns false.
*/
- class RawBSONSerializable : public BSONSerializable {
- public:
+ virtual bool parseBSON(const BSONObj& source, std::string* errMsg) = 0;
+
+ /** Clears the internal state. */
+ virtual void clear() = 0;
- RawBSONSerializable() {
- }
+ /** Returns a std::string representation of the current internal state. */
+ virtual std::string toString() const = 0;
+};
- explicit RawBSONSerializable(const BSONObj& raw)
- : _raw(raw) {
- }
+/**
+ * Generic implementation which accepts and stores any BSON object
+ *
+ * Generally this should only be used for compatibility reasons - newer requests should be
+ * fully typed.
+ */
+class RawBSONSerializable : public BSONSerializable {
+public:
+ RawBSONSerializable() {}
- virtual ~RawBSONSerializable() {
- }
+ explicit RawBSONSerializable(const BSONObj& raw) : _raw(raw) {}
- virtual bool isValid(std::string* errMsg) const {
- return true;
- }
+ virtual ~RawBSONSerializable() {}
- virtual BSONObj toBSON() const {
- return _raw;
- }
+ virtual bool isValid(std::string* errMsg) const {
+ return true;
+ }
- virtual bool parseBSON(const BSONObj& source, std::string* errMsg) {
- _raw = source.getOwned();
- return true;
- }
+ virtual BSONObj toBSON() const {
+ return _raw;
+ }
- virtual void clear() {
- _raw = BSONObj();
- }
+ virtual bool parseBSON(const BSONObj& source, std::string* errMsg) {
+ _raw = source.getOwned();
+ return true;
+ }
- virtual std::string toString() const {
- return toBSON().toString();
- }
+ virtual void clear() {
+ _raw = BSONObj();
+ }
- private:
+ virtual std::string toString() const {
+ return toBSON().toString();
+ }
- BSONObj _raw;
- };
+private:
+ BSONObj _raw;
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/catalog/catalog_cache.cpp b/src/mongo/s/catalog/catalog_cache.cpp
index db27e5b7778..416a0a7368a 100644
--- a/src/mongo/s/catalog/catalog_cache.cpp
+++ b/src/mongo/s/catalog/catalog_cache.cpp
@@ -38,51 +38,49 @@
namespace mongo {
- using std::shared_ptr;
- using std::string;
+using std::shared_ptr;
+using std::string;
- CatalogCache::CatalogCache(CatalogManager* catalogManager)
- : _catalogManager(catalogManager) {
+CatalogCache::CatalogCache(CatalogManager* catalogManager) : _catalogManager(catalogManager) {
+ invariant(_catalogManager);
+}
- invariant(_catalogManager);
- }
-
- StatusWith<shared_ptr<DBConfig>> CatalogCache::getDatabase(const string& dbName) {
- stdx::lock_guard<stdx::mutex> guard(_mutex);
+StatusWith<shared_ptr<DBConfig>> CatalogCache::getDatabase(const string& dbName) {
+ stdx::lock_guard<stdx::mutex> guard(_mutex);
- ShardedDatabasesMap::iterator it = _databases.find(dbName);
- if (it != _databases.end()) {
- return it->second;
- }
+ ShardedDatabasesMap::iterator it = _databases.find(dbName);
+ if (it != _databases.end()) {
+ return it->second;
+ }
- // Need to load from the store
- StatusWith<DatabaseType> status = _catalogManager->getDatabase(dbName);
- if (!status.isOK()) {
- return status.getStatus();
- }
+ // Need to load from the store
+ StatusWith<DatabaseType> status = _catalogManager->getDatabase(dbName);
+ if (!status.isOK()) {
+ return status.getStatus();
+ }
- shared_ptr<DBConfig> db = std::make_shared<DBConfig>(dbName, status.getValue());
- db->load();
+ shared_ptr<DBConfig> db = std::make_shared<DBConfig>(dbName, status.getValue());
+ db->load();
- invariant(_databases.insert(std::make_pair(dbName, db)).second);
+ invariant(_databases.insert(std::make_pair(dbName, db)).second);
- return db;
- }
+ return db;
+}
- void CatalogCache::invalidate(const string& dbName) {
- stdx::lock_guard<stdx::mutex> guard(_mutex);
+void CatalogCache::invalidate(const string& dbName) {
+ stdx::lock_guard<stdx::mutex> guard(_mutex);
- ShardedDatabasesMap::iterator it = _databases.find(dbName);
- if (it != _databases.end()) {
- _databases.erase(it);
- }
+ ShardedDatabasesMap::iterator it = _databases.find(dbName);
+ if (it != _databases.end()) {
+ _databases.erase(it);
}
+}
- void CatalogCache::invalidateAll() {
- stdx::lock_guard<stdx::mutex> guard(_mutex);
+void CatalogCache::invalidateAll() {
+ stdx::lock_guard<stdx::mutex> guard(_mutex);
- _databases.clear();
- }
+ _databases.clear();
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/catalog/catalog_cache.h b/src/mongo/s/catalog/catalog_cache.h
index 9d7c18cad76..a138c793c64 100644
--- a/src/mongo/s/catalog/catalog_cache.h
+++ b/src/mongo/s/catalog/catalog_cache.h
@@ -36,53 +36,55 @@
namespace mongo {
- class CatalogManager;
- class DBConfig;
- template<typename T> class StatusWith;
+class CatalogManager;
+class DBConfig;
+template <typename T>
+class StatusWith;
+/**
+ * This is the root of the "read-only" hierarchy of cached catalog metadata. It is read only
+ * in the sense that it only reads from the persistent store, but never writes to it. Instead
+ * writes happen thorugh the CatalogManager and the cache hierarchy needs to be invalidated.
+ */
+class CatalogCache {
+ MONGO_DISALLOW_COPYING(CatalogCache);
+
+public:
+ explicit CatalogCache(CatalogManager* catalogManager);
+
/**
- * This is the root of the "read-only" hierarchy of cached catalog metadata. It is read only
- * in the sense that it only reads from the persistent store, but never writes to it. Instead
- * writes happen thorugh the CatalogManager and the cache hierarchy needs to be invalidated.
+ * Retrieves the cached metadata for the specified database. The returned value is still
+ * owned by the cache and it should not be cached elsewhere, but instead only used as a
+ * local variable. The reason for this is so that if the cache gets invalidated, the caller
+ * does not miss getting the most up-to-date value.
+ *
+ * @param dbname The name of the database (must not contain dots, etc).
+ * @return The database if it exists, NULL otherwise.
*/
- class CatalogCache {
- MONGO_DISALLOW_COPYING(CatalogCache);
- public:
- explicit CatalogCache(CatalogManager* catalogManager);
-
- /**
- * Retrieves the cached metadata for the specified database. The returned value is still
- * owned by the cache and it should not be cached elsewhere, but instead only used as a
- * local variable. The reason for this is so that if the cache gets invalidated, the caller
- * does not miss getting the most up-to-date value.
- *
- * @param dbname The name of the database (must not contain dots, etc).
- * @return The database if it exists, NULL otherwise.
- */
- StatusWith<std::shared_ptr<DBConfig>> getDatabase(const std::string& dbName);
-
- /**
- * Removes the database information for the specified name from the cache, so that the
- * next time getDatabase is called, it will be reloaded.
- */
- void invalidate(const std::string& dbName);
-
- /**
- * Purges all cached database information, which will cause the data to be reloaded again.
- */
- void invalidateAll();
-
- private:
- typedef std::map<std::string, std::shared_ptr<DBConfig>> ShardedDatabasesMap;
-
-
- // Reference to the catalog manager. Not owned.
- CatalogManager* const _catalogManager;
-
- // Databases catalog map and mutex to protect it
- stdx::mutex _mutex;
- ShardedDatabasesMap _databases;
- };
-
-} // namespace mongo
+ StatusWith<std::shared_ptr<DBConfig>> getDatabase(const std::string& dbName);
+
+ /**
+ * Removes the database information for the specified name from the cache, so that the
+ * next time getDatabase is called, it will be reloaded.
+ */
+ void invalidate(const std::string& dbName);
+
+ /**
+ * Purges all cached database information, which will cause the data to be reloaded again.
+ */
+ void invalidateAll();
+
+private:
+ typedef std::map<std::string, std::shared_ptr<DBConfig>> ShardedDatabasesMap;
+
+
+ // Reference to the catalog manager. Not owned.
+ CatalogManager* const _catalogManager;
+
+ // Databases catalog map and mutex to protect it
+ stdx::mutex _mutex;
+ ShardedDatabasesMap _databases;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/catalog_manager.cpp b/src/mongo/s/catalog/catalog_manager.cpp
index 02da9c79c58..46acce91140 100644
--- a/src/mongo/s/catalog/catalog_manager.cpp
+++ b/src/mongo/s/catalog/catalog_manager.cpp
@@ -44,114 +44,109 @@
namespace mongo {
- using std::unique_ptr;
- using std::string;
+using std::unique_ptr;
+using std::string;
namespace {
- Status getStatus(const BatchedCommandResponse& response) {
- if (response.getOk() == 0) {
- return Status(static_cast<ErrorCodes::Error>(response.getErrCode()),
- response.getErrMessage());
- }
-
- if (response.isErrDetailsSet()) {
- const WriteErrorDetail* errDetail = response.getErrDetails().front();
-
- return Status(static_cast<ErrorCodes::Error>(errDetail->getErrCode()),
- errDetail->getErrMessage());
- }
-
- if (response.isWriteConcernErrorSet()) {
- const WCErrorDetail* errDetail = response.getWriteConcernError();
+Status getStatus(const BatchedCommandResponse& response) {
+ if (response.getOk() == 0) {
+ return Status(static_cast<ErrorCodes::Error>(response.getErrCode()),
+ response.getErrMessage());
+ }
- return Status(static_cast<ErrorCodes::Error>(errDetail->getErrCode()),
- errDetail->getErrMessage());
- }
+ if (response.isErrDetailsSet()) {
+ const WriteErrorDetail* errDetail = response.getErrDetails().front();
- return Status::OK();
+ return Status(static_cast<ErrorCodes::Error>(errDetail->getErrCode()),
+ errDetail->getErrMessage());
}
-} // namespace
+ if (response.isWriteConcernErrorSet()) {
+ const WCErrorDetail* errDetail = response.getWriteConcernError();
- Status CatalogManager::insert(const string& ns,
- const BSONObj& doc,
- BatchedCommandResponse* response) {
+ return Status(static_cast<ErrorCodes::Error>(errDetail->getErrCode()),
+ errDetail->getErrMessage());
+ }
- unique_ptr<BatchedInsertRequest> insert(new BatchedInsertRequest());
- insert->addToDocuments(doc);
+ return Status::OK();
+}
- BatchedCommandRequest request(insert.release());
- request.setNS(ns);
- request.setWriteConcern(WriteConcernOptions::Majority);
+} // namespace
- BatchedCommandResponse dummyResponse;
- if (response == NULL) {
- response = &dummyResponse;
- }
+Status CatalogManager::insert(const string& ns,
+ const BSONObj& doc,
+ BatchedCommandResponse* response) {
+ unique_ptr<BatchedInsertRequest> insert(new BatchedInsertRequest());
+ insert->addToDocuments(doc);
- // Make sure to add ids to the request, since this is an insert operation
- unique_ptr<BatchedCommandRequest> requestWithIds(
- BatchedCommandRequest::cloneWithIds(request));
- const BatchedCommandRequest& requestToSend =
- requestWithIds.get() ? *requestWithIds : request;
+ BatchedCommandRequest request(insert.release());
+ request.setNS(ns);
+ request.setWriteConcern(WriteConcernOptions::Majority);
- writeConfigServerDirect(requestToSend, response);
- return getStatus(*response);
+ BatchedCommandResponse dummyResponse;
+ if (response == NULL) {
+ response = &dummyResponse;
}
- Status CatalogManager::update(const string& ns,
- const BSONObj& query,
- const BSONObj& update,
- bool upsert,
- bool multi,
- BatchedCommandResponse* response) {
-
- unique_ptr<BatchedUpdateDocument> updateDoc(new BatchedUpdateDocument());
- updateDoc->setQuery(query);
- updateDoc->setUpdateExpr(update);
- updateDoc->setUpsert(upsert);
- updateDoc->setMulti(multi);
-
- unique_ptr<BatchedUpdateRequest> updateRequest(new BatchedUpdateRequest());
- updateRequest->addToUpdates(updateDoc.release());
- updateRequest->setWriteConcern(WriteConcernOptions::Majority);
-
- BatchedCommandRequest request(updateRequest.release());
- request.setNS(ns);
-
- BatchedCommandResponse dummyResponse;
- if (response == NULL) {
- response = &dummyResponse;
- }
-
- writeConfigServerDirect(request, response);
- return getStatus(*response);
+ // Make sure to add ids to the request, since this is an insert operation
+ unique_ptr<BatchedCommandRequest> requestWithIds(BatchedCommandRequest::cloneWithIds(request));
+ const BatchedCommandRequest& requestToSend = requestWithIds.get() ? *requestWithIds : request;
+
+ writeConfigServerDirect(requestToSend, response);
+ return getStatus(*response);
+}
+
+Status CatalogManager::update(const string& ns,
+ const BSONObj& query,
+ const BSONObj& update,
+ bool upsert,
+ bool multi,
+ BatchedCommandResponse* response) {
+ unique_ptr<BatchedUpdateDocument> updateDoc(new BatchedUpdateDocument());
+ updateDoc->setQuery(query);
+ updateDoc->setUpdateExpr(update);
+ updateDoc->setUpsert(upsert);
+ updateDoc->setMulti(multi);
+
+ unique_ptr<BatchedUpdateRequest> updateRequest(new BatchedUpdateRequest());
+ updateRequest->addToUpdates(updateDoc.release());
+ updateRequest->setWriteConcern(WriteConcernOptions::Majority);
+
+ BatchedCommandRequest request(updateRequest.release());
+ request.setNS(ns);
+
+ BatchedCommandResponse dummyResponse;
+ if (response == NULL) {
+ response = &dummyResponse;
}
- Status CatalogManager::remove(const string& ns,
- const BSONObj& query,
- int limit,
- BatchedCommandResponse* response) {
-
- unique_ptr<BatchedDeleteDocument> deleteDoc(new BatchedDeleteDocument);
- deleteDoc->setQuery(query);
- deleteDoc->setLimit(limit);
+ writeConfigServerDirect(request, response);
+ return getStatus(*response);
+}
- unique_ptr<BatchedDeleteRequest> deleteRequest(new BatchedDeleteRequest());
- deleteRequest->addToDeletes(deleteDoc.release());
- deleteRequest->setWriteConcern(WriteConcernOptions::Majority);
+Status CatalogManager::remove(const string& ns,
+ const BSONObj& query,
+ int limit,
+ BatchedCommandResponse* response) {
+ unique_ptr<BatchedDeleteDocument> deleteDoc(new BatchedDeleteDocument);
+ deleteDoc->setQuery(query);
+ deleteDoc->setLimit(limit);
- BatchedCommandRequest request(deleteRequest.release());
- request.setNS(ns);
+ unique_ptr<BatchedDeleteRequest> deleteRequest(new BatchedDeleteRequest());
+ deleteRequest->addToDeletes(deleteDoc.release());
+ deleteRequest->setWriteConcern(WriteConcernOptions::Majority);
- BatchedCommandResponse dummyResponse;
- if (response == NULL) {
- response = &dummyResponse;
- }
+ BatchedCommandRequest request(deleteRequest.release());
+ request.setNS(ns);
- writeConfigServerDirect(request, response);
- return getStatus(*response);
+ BatchedCommandResponse dummyResponse;
+ if (response == NULL) {
+ response = &dummyResponse;
}
-} // namespace mongo
+ writeConfigServerDirect(request, response);
+ return getStatus(*response);
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/catalog_manager.h b/src/mongo/s/catalog/catalog_manager.h
index ca3dd44dffa..bda4a1b79bd 100644
--- a/src/mongo/s/catalog/catalog_manager.h
+++ b/src/mongo/s/catalog/catalog_manager.h
@@ -37,354 +37,352 @@
namespace mongo {
- class ActionLogType;
- class BatchedCommandRequest;
- class BatchedCommandResponse;
- struct BSONArray;
- class BSONObj;
- class BSONObjBuilder;
- class ChunkType;
- class CollectionType;
- class ConnectionString;
- class DatabaseType;
- class DistLockManager;
- class OperationContext;
- class Query;
- class SettingsType;
- class ShardKeyPattern;
- class ShardType;
- class Status;
- template<typename T> class StatusWith;
- class TagsType;
+class ActionLogType;
+class BatchedCommandRequest;
+class BatchedCommandResponse;
+struct BSONArray;
+class BSONObj;
+class BSONObjBuilder;
+class ChunkType;
+class CollectionType;
+class ConnectionString;
+class DatabaseType;
+class DistLockManager;
+class OperationContext;
+class Query;
+class SettingsType;
+class ShardKeyPattern;
+class ShardType;
+class Status;
+template <typename T>
+class StatusWith;
+class TagsType;
+
+/**
+ * Used to indicate to the caller of the removeShard method whether draining of chunks for
+ * a particular shard has started, is ongoing, or has been completed.
+ */
+enum ShardDrainingStatus {
+ STARTED,
+ ONGOING,
+ COMPLETED,
+};
+
+/**
+ * Abstracts reads and writes of the sharding catalog metadata.
+ *
+ * All implementations of this interface should go directly to the persistent backing store
+ * and should avoid doing any caching of their own. The caching is delegated to a parallel
+ * read-only view of the catalog, which is maintained by a higher level code.
+ */
+class CatalogManager {
+ MONGO_DISALLOW_COPYING(CatalogManager);
+
+public:
+ virtual ~CatalogManager() = default;
+
+ /**
+ * Retrieves the connection string for the catalog manager's backing server.
+ */
+ virtual ConnectionString connectionString() const = 0;
+
+ /**
+ * Performs implementation-specific startup tasks including but not limited to doing any
+ * necessary config schema upgrade work. Must be run after the catalog manager
+ * has been installed into the global 'grid' object.
+ */
+ virtual Status startup(bool upgrade) = 0;
+
+ /**
+ * Performs necessary cleanup when shutting down cleanly.
+ */
+ virtual void shutDown() = 0;
+
+ /**
+ * Creates a new database or updates the sharding status for an existing one. Cannot be
+ * used for the admin/config/local DBs, which should not be created or sharded manually
+ * anyways.
+ *
+ * Returns Status::OK on success or any error code indicating the failure. These are some
+ * of the known failures:
+ * - DatabaseDifferCase - database already exists, but with a different case
+ * - ShardNotFound - could not find a shard to place the DB on
+ */
+ virtual Status enableSharding(const std::string& dbName) = 0;
+
+ /**
+ * Shards a collection. Assumes that the database is enabled for sharding.
+ *
+ * @param ns: namespace of collection to shard
+ * @param fieldsAndOrder: shardKey pattern
+ * @param unique: if true, ensure underlying index enforces a unique constraint.
+ * @param initPoints: create chunks based on a set of specified split points.
+ * @param initShardIds: if nullptr, use primary shard as lone shard for DB.
+ *
+ * WARNING: It's not completely safe to place initial chunks onto non-primary
+ * shards using this method because a conflict may result if multiple map-reduce
+ * operations are writing to the same output collection, for instance.
+ *
+ */
+ virtual Status shardCollection(const std::string& ns,
+ const ShardKeyPattern& fieldsAndOrder,
+ bool unique,
+ std::vector<BSONObj>* initPoints,
+ std::set<ShardId>* initShardIds = nullptr) = 0;
+
+ /**
+ *
+ * Adds a new shard. It expects a standalone mongod process or replica set to be running
+ * on the provided address.
+ *
+ * @param name is an optional string with the name of the shard.
+ * If empty, a name will be automatically generated.
+ * @param shardConnectionString is the connection string of the shard being added.
+ * @param maxSize is the optional space quota in bytes. Zeros means there's
+ * no limitation to space usage.
+ * @return either an !OK status or the name of the newly added shard.
+ */
+ virtual StatusWith<std::string> addShard(const std::string& name,
+ const ConnectionString& shardConnectionString,
+ const long long maxSize) = 0;
+
+ /**
+ * Tries to remove a shard. To completely remove a shard from a sharded cluster,
+ * the data residing in that shard must be moved to the remaining shards in the
+ * cluster by "draining" chunks from that shard.
+ *
+ * Because of the asynchronous nature of the draining mechanism, this method returns
+ * the current draining status. See ShardDrainingStatus enum definition for more details.
+ */
+ virtual StatusWith<ShardDrainingStatus> removeShard(OperationContext* txn,
+ const std::string& name) = 0;
+
+ /**
+ * Creates a new database entry for the specified database name in the configuration
+ * metadata and sets the specified shard as primary.
+ *
+ * @param dbName name of the database (case sensitive)
+ *
+ * Returns Status::OK on success or any error code indicating the failure. These are some
+ * of the known failures:
+ * - NamespaceExists - database already exists
+ * - DatabaseDifferCase - database already exists, but with a different case
+ * - ShardNotFound - could not find a shard to place the DB on
+ */
+ virtual Status createDatabase(const std::string& dbName) = 0;
+
+ /**
+ * Updates or creates the metadata for a given database.
+ */
+ virtual Status updateDatabase(const std::string& dbName, const DatabaseType& db) = 0;
+
+ /**
+ * Retrieves the metadata for a given database, if it exists.
+ *
+ * @param dbName name of the database (case sensitive)
+ *
+ * Returns Status::OK along with the database information or any error code indicating the
+ * failure. These are some of the known failures:
+ * - DatabaseNotFound - database does not exist
+ */
+ virtual StatusWith<DatabaseType> getDatabase(const std::string& dbName) = 0;
+
+ /**
+ * Updates or creates the metadata for a given collection.
+ */
+ virtual Status updateCollection(const std::string& collNs, const CollectionType& coll) = 0;
+
+ /**
+ * Retrieves the metadata for a given collection, if it exists.
+ *
+ * @param collectionNs fully qualified name of the collection (case sensitive)
+ *
+ * Returns Status::OK along with the collection information or any error code indicating
+ * the failure. These are some of the known failures:
+ * - NamespaceNotFound - collection does not exist
+ */
+ virtual StatusWith<CollectionType> getCollection(const std::string& collNs) = 0;
+
+ /**
+ * Retrieves all collections undera specified database (or in the system).
+ *
+ * @param dbName an optional database name. Must be nullptr or non-empty. If nullptr is
+ * specified, all collections on the system are returned.
+ * @param collections variable to receive the set of collections.
+ *
+ * Returns a !OK status if an error occurs.
+ */
+ virtual Status getCollections(const std::string* dbName,
+ std::vector<CollectionType>* collections) = 0;
+
+ /**
+ * Drops the specified collection from the collection metadata store.
+ *
+ * Returns Status::OK if successful or any error code indicating the failure. These are
+ * some of the known failures:
+ * - NamespaceNotFound - collection does not exist
+ */
+ virtual Status dropCollection(const std::string& collectionNs) = 0;
/**
- * Used to indicate to the caller of the removeShard method whether draining of chunks for
- * a particular shard has started, is ongoing, or has been completed.
+ * Retrieves all databases for a shard.
+ *
+ * Returns a !OK status if an error occurs.
*/
- enum ShardDrainingStatus {
- STARTED,
- ONGOING,
- COMPLETED,
- };
+ virtual Status getDatabasesForShard(const std::string& shardName,
+ std::vector<std::string>* dbs) = 0;
/**
- * Abstracts reads and writes of the sharding catalog metadata.
+ * Gets the requested number of chunks (of type ChunkType) that satisfy a query.
*
- * All implementations of this interface should go directly to the persistent backing store
- * and should avoid doing any caching of their own. The caching is delegated to a parallel
- * read-only view of the catalog, which is maintained by a higher level code.
+ * @param query The query to filter out the results.
+ * @param nToReturn The number of chunk entries to return. 0 means all.
+ * @param chunks Vector entry to receive the results
+ *
+ * Returns a !OK status if an error occurs.
+ */
+ virtual Status getChunks(const Query& query, int nToReturn, std::vector<ChunkType>* chunks) = 0;
+
+ /**
+ * Retrieves all tags for the specified collection.
+ */
+ virtual Status getTagsForCollection(const std::string& collectionNs,
+ std::vector<TagsType>* tags) = 0;
+
+ /**
+ * Retrieves the most appropriate tag, which overlaps with the specified chunk. If no tags
+ * overlap, returns an empty string.
+ */
+ virtual StatusWith<std::string> getTagForChunk(const std::string& collectionNs,
+ const ChunkType& chunk) = 0;
+
+ /**
+ * Retrieves all shards in this sharded cluster.
+ * Returns a !OK status if an error occurs.
*/
- class CatalogManager {
- MONGO_DISALLOW_COPYING(CatalogManager);
- public:
- virtual ~CatalogManager() = default;
-
- /**
- * Retrieves the connection string for the catalog manager's backing server.
- */
- virtual ConnectionString connectionString() const = 0;
-
- /**
- * Performs implementation-specific startup tasks including but not limited to doing any
- * necessary config schema upgrade work. Must be run after the catalog manager
- * has been installed into the global 'grid' object.
- */
- virtual Status startup(bool upgrade) = 0;
-
- /**
- * Performs necessary cleanup when shutting down cleanly.
- */
- virtual void shutDown() = 0;
-
- /**
- * Creates a new database or updates the sharding status for an existing one. Cannot be
- * used for the admin/config/local DBs, which should not be created or sharded manually
- * anyways.
- *
- * Returns Status::OK on success or any error code indicating the failure. These are some
- * of the known failures:
- * - DatabaseDifferCase - database already exists, but with a different case
- * - ShardNotFound - could not find a shard to place the DB on
- */
- virtual Status enableSharding(const std::string& dbName) = 0;
-
- /**
- * Shards a collection. Assumes that the database is enabled for sharding.
- *
- * @param ns: namespace of collection to shard
- * @param fieldsAndOrder: shardKey pattern
- * @param unique: if true, ensure underlying index enforces a unique constraint.
- * @param initPoints: create chunks based on a set of specified split points.
- * @param initShardIds: if nullptr, use primary shard as lone shard for DB.
- *
- * WARNING: It's not completely safe to place initial chunks onto non-primary
- * shards using this method because a conflict may result if multiple map-reduce
- * operations are writing to the same output collection, for instance.
- *
- */
- virtual Status shardCollection(const std::string& ns,
- const ShardKeyPattern& fieldsAndOrder,
- bool unique,
- std::vector<BSONObj>* initPoints,
- std::set<ShardId>* initShardIds = nullptr) = 0;
-
- /**
- *
- * Adds a new shard. It expects a standalone mongod process or replica set to be running
- * on the provided address.
- *
- * @param name is an optional string with the name of the shard.
- * If empty, a name will be automatically generated.
- * @param shardConnectionString is the connection string of the shard being added.
- * @param maxSize is the optional space quota in bytes. Zeros means there's
- * no limitation to space usage.
- * @return either an !OK status or the name of the newly added shard.
- */
- virtual StatusWith<std::string> addShard(const std::string& name,
- const ConnectionString& shardConnectionString,
- const long long maxSize) = 0;
-
- /**
- * Tries to remove a shard. To completely remove a shard from a sharded cluster,
- * the data residing in that shard must be moved to the remaining shards in the
- * cluster by "draining" chunks from that shard.
- *
- * Because of the asynchronous nature of the draining mechanism, this method returns
- * the current draining status. See ShardDrainingStatus enum definition for more details.
- */
- virtual StatusWith<ShardDrainingStatus> removeShard(OperationContext* txn,
- const std::string& name) = 0;
-
- /**
- * Creates a new database entry for the specified database name in the configuration
- * metadata and sets the specified shard as primary.
- *
- * @param dbName name of the database (case sensitive)
- *
- * Returns Status::OK on success or any error code indicating the failure. These are some
- * of the known failures:
- * - NamespaceExists - database already exists
- * - DatabaseDifferCase - database already exists, but with a different case
- * - ShardNotFound - could not find a shard to place the DB on
- */
- virtual Status createDatabase(const std::string& dbName) = 0;
-
- /**
- * Updates or creates the metadata for a given database.
- */
- virtual Status updateDatabase(const std::string& dbName, const DatabaseType& db) = 0;
-
- /**
- * Retrieves the metadata for a given database, if it exists.
- *
- * @param dbName name of the database (case sensitive)
- *
- * Returns Status::OK along with the database information or any error code indicating the
- * failure. These are some of the known failures:
- * - DatabaseNotFound - database does not exist
- */
- virtual StatusWith<DatabaseType> getDatabase(const std::string& dbName) = 0;
-
- /**
- * Updates or creates the metadata for a given collection.
- */
- virtual Status updateCollection(const std::string& collNs, const CollectionType& coll) = 0;
-
- /**
- * Retrieves the metadata for a given collection, if it exists.
- *
- * @param collectionNs fully qualified name of the collection (case sensitive)
- *
- * Returns Status::OK along with the collection information or any error code indicating
- * the failure. These are some of the known failures:
- * - NamespaceNotFound - collection does not exist
- */
- virtual StatusWith<CollectionType> getCollection(const std::string& collNs) = 0;
-
- /**
- * Retrieves all collections undera specified database (or in the system).
- *
- * @param dbName an optional database name. Must be nullptr or non-empty. If nullptr is
- * specified, all collections on the system are returned.
- * @param collections variable to receive the set of collections.
- *
- * Returns a !OK status if an error occurs.
- */
- virtual Status getCollections(const std::string* dbName,
- std::vector<CollectionType>* collections) = 0;
-
- /**
- * Drops the specified collection from the collection metadata store.
- *
- * Returns Status::OK if successful or any error code indicating the failure. These are
- * some of the known failures:
- * - NamespaceNotFound - collection does not exist
- */
- virtual Status dropCollection(const std::string& collectionNs) = 0;
-
- /**
- * Retrieves all databases for a shard.
- *
- * Returns a !OK status if an error occurs.
- */
- virtual Status getDatabasesForShard(const std::string& shardName,
- std::vector<std::string>* dbs) = 0;
-
- /**
- * Gets the requested number of chunks (of type ChunkType) that satisfy a query.
- *
- * @param query The query to filter out the results.
- * @param nToReturn The number of chunk entries to return. 0 means all.
- * @param chunks Vector entry to receive the results
- *
- * Returns a !OK status if an error occurs.
- */
- virtual Status getChunks(const Query& query,
- int nToReturn,
- std::vector<ChunkType>* chunks) = 0;
-
- /**
- * Retrieves all tags for the specified collection.
- */
- virtual Status getTagsForCollection(const std::string& collectionNs,
- std::vector<TagsType>* tags) = 0;
-
- /**
- * Retrieves the most appropriate tag, which overlaps with the specified chunk. If no tags
- * overlap, returns an empty string.
- */
- virtual StatusWith<std::string> getTagForChunk(const std::string& collectionNs,
- const ChunkType& chunk) = 0;
-
- /**
- * Retrieves all shards in this sharded cluster.
- * Returns a !OK status if an error occurs.
- */
- virtual Status getAllShards(std::vector<ShardType>* shards) = 0;
-
- /**
- * Returns true if host is being used as a shard.
- * Otherwise, returns false.
- */
- virtual bool isShardHost(const ConnectionString& shardConnectionString) = 0;
-
- /**
- * Runs a user management command on the config servers.
- * @param commandName: name of command
- * @param dbname: database for which the user management command is invoked
- * @param cmdObj: command obj
- * @param result: contains data returned from config servers
- * Returns true on success.
- */
- virtual bool runUserManagementWriteCommand(const std::string& commandName,
- const std::string& dbname,
- const BSONObj& cmdObj,
- BSONObjBuilder* result) = 0;
-
- /**
- * Runs a read-only user management command on a single config server.
- */
- virtual bool runUserManagementReadCommand(const std::string& dbname,
- const BSONObj& cmdObj,
- BSONObjBuilder* result) = 0;
-
- /**
- * Applies oplog entries to the config servers.
- * Used by mergeChunk, splitChunk, and moveChunk commands.
- *
- * @param updateOps: oplog entries to apply
- * @param preCondition: preconditions for applying oplog entries
- */
- virtual Status applyChunkOpsDeprecated(const BSONArray& updateOps,
- const BSONArray& preCondition) = 0;
-
- /**
- * Logs to the actionlog.
- * Used by the balancer to report the result of a balancing round.
- */
- virtual void logAction(const ActionLogType& actionLog) = 0;
-
- /**
- * Logs a diagnostic event locally and on the config server.
- *
- * NOTE: This method is best effort so it should never throw.
- *
- * @param opCtx The operation context of the call doing the logging
- * @param what E.g. "split", "migrate"
- * @param ns To which collection the metadata change is being applied
- * @param detail Additional info about the metadata change (not interpreted)
- */
- virtual void logChange(OperationContext* opCtx,
- const std::string& what,
- const std::string& ns,
- const BSONObj& detail) = 0;
-
- /**
- * Returns global settings for a certain key.
- * @param key: key for SettingsType::ConfigNS document.
- *
- * Returns ErrorCodes::NoMatchingDocument if no SettingsType::ConfigNS document
- * with such key exists.
- * Returns ErrorCodes::FailedToParse if we encountered an error while parsing
- * the settings document.
- */
- virtual StatusWith<SettingsType> getGlobalSettings(const std::string& key) = 0;
-
- /**
- * Directly sends the specified command to the config server and returns the response.
- *
- * NOTE: Usage of this function is disallowed in new code, which should instead go through
- * the regular catalog management calls. It is currently only used privately by this
- * class and externally for writes to the admin/config namespaces.
- *
- * @param request Request to be sent to the config server.
- * @param response Out parameter to receive the response. Can be nullptr.
- */
- virtual void writeConfigServerDirect(const BatchedCommandRequest& request,
- BatchedCommandResponse* response) = 0;
-
- virtual DistLockManager* getDistLockManager() = 0;
-
- /**
- * Directly inserts a document in the specified namespace on the config server (only the
- * config or admin databases). If the document does not have _id field, the field will be
- * added.
- *
- * NOTE: Should not be used in new code. Instead add a new metadata operation to the
- * interface.
- */
- Status insert(const std::string& ns,
- const BSONObj& doc,
- BatchedCommandResponse* response);
-
- /**
- * Updates a document in the specified namespace on the config server (only the config or
- * admin databases).
- *
- * NOTE: Should not be used in new code. Instead add a new metadata operation to the
- * interface.
- */
- Status update(const std::string& ns,
- const BSONObj& query,
- const BSONObj& update,
- bool upsert,
- bool multi,
- BatchedCommandResponse* response);
-
- /**
- * Removes a document from the specified namespace on the config server (only the config
- * or admin databases).
- *
- * NOTE: Should not be used in new code. Instead add a new metadata operation to the
- * interface.
- */
- Status remove(const std::string& ns,
- const BSONObj& query,
- int limit,
- BatchedCommandResponse* response);
-
- protected:
- CatalogManager() = default;
- };
-
-} // namespace mongo
+ virtual Status getAllShards(std::vector<ShardType>* shards) = 0;
+
+ /**
+ * Returns true if host is being used as a shard.
+ * Otherwise, returns false.
+ */
+ virtual bool isShardHost(const ConnectionString& shardConnectionString) = 0;
+
+ /**
+ * Runs a user management command on the config servers.
+ * @param commandName: name of command
+ * @param dbname: database for which the user management command is invoked
+ * @param cmdObj: command obj
+ * @param result: contains data returned from config servers
+ * Returns true on success.
+ */
+ virtual bool runUserManagementWriteCommand(const std::string& commandName,
+ const std::string& dbname,
+ const BSONObj& cmdObj,
+ BSONObjBuilder* result) = 0;
+
+ /**
+ * Runs a read-only user management command on a single config server.
+ */
+ virtual bool runUserManagementReadCommand(const std::string& dbname,
+ const BSONObj& cmdObj,
+ BSONObjBuilder* result) = 0;
+
+ /**
+ * Applies oplog entries to the config servers.
+ * Used by mergeChunk, splitChunk, and moveChunk commands.
+ *
+ * @param updateOps: oplog entries to apply
+ * @param preCondition: preconditions for applying oplog entries
+ */
+ virtual Status applyChunkOpsDeprecated(const BSONArray& updateOps,
+ const BSONArray& preCondition) = 0;
+
+ /**
+ * Logs to the actionlog.
+ * Used by the balancer to report the result of a balancing round.
+ */
+ virtual void logAction(const ActionLogType& actionLog) = 0;
+
+ /**
+ * Logs a diagnostic event locally and on the config server.
+ *
+ * NOTE: This method is best effort so it should never throw.
+ *
+ * @param opCtx The operation context of the call doing the logging
+ * @param what E.g. "split", "migrate"
+ * @param ns To which collection the metadata change is being applied
+ * @param detail Additional info about the metadata change (not interpreted)
+ */
+ virtual void logChange(OperationContext* opCtx,
+ const std::string& what,
+ const std::string& ns,
+ const BSONObj& detail) = 0;
+
+ /**
+ * Returns global settings for a certain key.
+ * @param key: key for SettingsType::ConfigNS document.
+ *
+ * Returns ErrorCodes::NoMatchingDocument if no SettingsType::ConfigNS document
+ * with such key exists.
+ * Returns ErrorCodes::FailedToParse if we encountered an error while parsing
+ * the settings document.
+ */
+ virtual StatusWith<SettingsType> getGlobalSettings(const std::string& key) = 0;
+
+ /**
+ * Directly sends the specified command to the config server and returns the response.
+ *
+ * NOTE: Usage of this function is disallowed in new code, which should instead go through
+ * the regular catalog management calls. It is currently only used privately by this
+ * class and externally for writes to the admin/config namespaces.
+ *
+ * @param request Request to be sent to the config server.
+ * @param response Out parameter to receive the response. Can be nullptr.
+ */
+ virtual void writeConfigServerDirect(const BatchedCommandRequest& request,
+ BatchedCommandResponse* response) = 0;
+
+ virtual DistLockManager* getDistLockManager() = 0;
+
+ /**
+ * Directly inserts a document in the specified namespace on the config server (only the
+ * config or admin databases). If the document does not have _id field, the field will be
+ * added.
+ *
+ * NOTE: Should not be used in new code. Instead add a new metadata operation to the
+ * interface.
+ */
+ Status insert(const std::string& ns, const BSONObj& doc, BatchedCommandResponse* response);
+
+ /**
+ * Updates a document in the specified namespace on the config server (only the config or
+ * admin databases).
+ *
+ * NOTE: Should not be used in new code. Instead add a new metadata operation to the
+ * interface.
+ */
+ Status update(const std::string& ns,
+ const BSONObj& query,
+ const BSONObj& update,
+ bool upsert,
+ bool multi,
+ BatchedCommandResponse* response);
+
+ /**
+ * Removes a document from the specified namespace on the config server (only the config
+ * or admin databases).
+ *
+ * NOTE: Should not be used in new code. Instead add a new metadata operation to the
+ * interface.
+ */
+ Status remove(const std::string& ns,
+ const BSONObj& query,
+ int limit,
+ BatchedCommandResponse* response);
+
+protected:
+ CatalogManager() = default;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/dist_lock_catalog.cpp b/src/mongo/s/catalog/dist_lock_catalog.cpp
index 44f024e2747..33d14a590e0 100644
--- a/src/mongo/s/catalog/dist_lock_catalog.cpp
+++ b/src/mongo/s/catalog/dist_lock_catalog.cpp
@@ -32,8 +32,7 @@
namespace mongo {
- DistLockCatalog::ServerInfo::ServerInfo(Date_t time, OID _electionId):
- serverTime(std::move(time)), electionId(std::move(_electionId)) {
- }
+DistLockCatalog::ServerInfo::ServerInfo(Date_t time, OID _electionId)
+ : serverTime(std::move(time)), electionId(std::move(_electionId)) {}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/catalog/dist_lock_catalog.h b/src/mongo/s/catalog/dist_lock_catalog.h
index 4bde917e763..3c83e0433a7 100644
--- a/src/mongo/s/catalog/dist_lock_catalog.h
+++ b/src/mongo/s/catalog/dist_lock_catalog.h
@@ -34,121 +34,121 @@
namespace mongo {
- class LockpingsType;
- class LocksType;
- class Status;
- template <typename T> class StatusWith;
+class LockpingsType;
+class LocksType;
+class Status;
+template <typename T>
+class StatusWith;
+/**
+ * Interface for the distributed lock operations.
+ */
+class DistLockCatalog {
+public:
/**
- * Interface for the distributed lock operations.
+ * Simple data structure for storing server local time and election id.
*/
- class DistLockCatalog {
+ struct ServerInfo {
public:
- /**
- * Simple data structure for storing server local time and election id.
- */
- struct ServerInfo {
- public:
- ServerInfo(); // TODO: SERVER-18007
- ServerInfo(Date_t time, OID electionId);
-
- // The local time of the server at the time this was created.
- Date_t serverTime;
-
- // The election id of the replica set member at the time this was created.
- OID electionId;
- };
-
- virtual ~DistLockCatalog() = default;
-
- /**
- * Returns the ping document of the specified processID.
- * Common status errors include socket errors.
- */
- virtual StatusWith<LockpingsType> getPing(StringData processID) = 0;
-
- /**
- * Updates the ping document. Creates a new entry if it does not exists.
- * Common status errors include socket errors.
- */
- virtual Status ping(StringData processID, Date_t ping) = 0;
-
- /**
- * Attempts to update the owner of a lock identified by lockID to lockSessionID.
- * Will only be successful if lock is not held.
- *
- * The other parameters are for diagnostic purposes:
- * - who: unique string for the caller trying to grab the lock.
- * - processId: unique string for the process trying to grab the lock.
- * - time: the time when this is attempted.
- * - why: reason for taking the lock.
- *
- * Returns the result of the operation.
- * Returns LockStateChangeFailed if the lock acquisition cannot be done because lock
- * is already held elsewhere.
- *
- * Common status errors include socket and duplicate key errors.
- */
- virtual StatusWith<LocksType> grabLock(StringData lockID,
+ ServerInfo(); // TODO: SERVER-18007
+ ServerInfo(Date_t time, OID electionId);
+
+ // The local time of the server at the time this was created.
+ Date_t serverTime;
+
+ // The election id of the replica set member at the time this was created.
+ OID electionId;
+ };
+
+ virtual ~DistLockCatalog() = default;
+
+ /**
+ * Returns the ping document of the specified processID.
+ * Common status errors include socket errors.
+ */
+ virtual StatusWith<LockpingsType> getPing(StringData processID) = 0;
+
+ /**
+ * Updates the ping document. Creates a new entry if it does not exists.
+ * Common status errors include socket errors.
+ */
+ virtual Status ping(StringData processID, Date_t ping) = 0;
+
+ /**
+ * Attempts to update the owner of a lock identified by lockID to lockSessionID.
+ * Will only be successful if lock is not held.
+ *
+ * The other parameters are for diagnostic purposes:
+ * - who: unique string for the caller trying to grab the lock.
+ * - processId: unique string for the process trying to grab the lock.
+ * - time: the time when this is attempted.
+ * - why: reason for taking the lock.
+ *
+ * Returns the result of the operation.
+ * Returns LockStateChangeFailed if the lock acquisition cannot be done because lock
+ * is already held elsewhere.
+ *
+ * Common status errors include socket and duplicate key errors.
+ */
+ virtual StatusWith<LocksType> grabLock(StringData lockID,
+ const OID& lockSessionID,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) = 0;
+
+ /**
+ * Attempts to forcefully transfer the ownership of a lock from currentHolderTS
+ * to lockSessionID.
+ *
+ * The other parameters are for diagnostic purposes:
+ * - who: unique string for the caller trying to grab the lock.
+ * - processId: unique string for the process trying to grab the lock.
+ * - time: the time when this is attempted.
+ * - why: reason for taking the lock.
+ *
+ * Returns the result of the operation.
+ * Returns LockStateChangeFailed if the lock acquisition fails.
+ *
+ * Common status errors include socket errors.
+ */
+ virtual StatusWith<LocksType> overtakeLock(StringData lockID,
const OID& lockSessionID,
+ const OID& currentHolderTS,
StringData who,
StringData processId,
Date_t time,
StringData why) = 0;
- /**
- * Attempts to forcefully transfer the ownership of a lock from currentHolderTS
- * to lockSessionID.
- *
- * The other parameters are for diagnostic purposes:
- * - who: unique string for the caller trying to grab the lock.
- * - processId: unique string for the process trying to grab the lock.
- * - time: the time when this is attempted.
- * - why: reason for taking the lock.
- *
- * Returns the result of the operation.
- * Returns LockStateChangeFailed if the lock acquisition fails.
- *
- * Common status errors include socket errors.
- */
- virtual StatusWith<LocksType> overtakeLock(StringData lockID,
- const OID& lockSessionID,
- const OID& currentHolderTS,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) = 0;
-
- /**
- * Attempts to set the state of the lock document with lockSessionID to unlocked.
- * Common status errors include socket errors.
- */
- virtual Status unlock(const OID& lockSessionID) = 0;
-
- /**
- * Get some information from the config server primary.
- * Common status errors include socket errors.
- */
- virtual StatusWith<ServerInfo> getServerInfo() = 0;
-
- /**
- * Returns the lock document.
- * Returns LockNotFound if lock document doesn't exist.
- * Common status errors include socket errors.
- */
- virtual StatusWith<LocksType> getLockByTS(const OID& lockSessionID) = 0;
-
- /**
- * Returns the lock document.
- * Common status errors include socket errors.
- */
- virtual StatusWith<LocksType> getLockByName(StringData name) = 0;
-
- /**
- * Attempts to delete the ping document corresponding to the given processId.
- * Common status errors include socket errors.
- */
- virtual Status stopPing(StringData processId) = 0;
+ /**
+ * Attempts to set the state of the lock document with lockSessionID to unlocked.
+ * Common status errors include socket errors.
+ */
+ virtual Status unlock(const OID& lockSessionID) = 0;
+
+ /**
+ * Get some information from the config server primary.
+ * Common status errors include socket errors.
+ */
+ virtual StatusWith<ServerInfo> getServerInfo() = 0;
- };
+ /**
+ * Returns the lock document.
+ * Returns LockNotFound if lock document doesn't exist.
+ * Common status errors include socket errors.
+ */
+ virtual StatusWith<LocksType> getLockByTS(const OID& lockSessionID) = 0;
+
+ /**
+ * Returns the lock document.
+ * Common status errors include socket errors.
+ */
+ virtual StatusWith<LocksType> getLockByName(StringData name) = 0;
+
+ /**
+ * Attempts to delete the ping document corresponding to the given processId.
+ * Common status errors include socket errors.
+ */
+ virtual Status stopPing(StringData processId) = 0;
+};
}
diff --git a/src/mongo/s/catalog/dist_lock_catalog_impl.cpp b/src/mongo/s/catalog/dist_lock_catalog_impl.cpp
index 57aab2dd2fc..e7a2830e546 100644
--- a/src/mongo/s/catalog/dist_lock_catalog_impl.cpp
+++ b/src/mongo/s/catalog/dist_lock_catalog_impl.cpp
@@ -49,397 +49,371 @@
namespace mongo {
- using std::string;
+using std::string;
namespace {
- const char kCmdResponseWriteConcernField[] = "writeConcernError";
- const char kFindAndModifyResponseResultDocField[] = "value";
- const char kLocalTimeField[] = "localTime";
- const ReadPreferenceSetting kReadPref(ReadPreference::PrimaryOnly, TagSet());
-
- /**
- * Returns the resulting new object from the findAndModify response object.
- * Returns LockStateChangeFailed if value field was null, which indicates that
- * the findAndModify command did not modify any document.
- * This also checks for errors in the response object.
- */
- StatusWith<BSONObj> extractFindAndModifyNewObj(const BSONObj& responseObj) {
- auto cmdStatus = getStatusFromCommandResult(responseObj);
-
- if (!cmdStatus.isOK()) {
- return cmdStatus;
- }
+const char kCmdResponseWriteConcernField[] = "writeConcernError";
+const char kFindAndModifyResponseResultDocField[] = "value";
+const char kLocalTimeField[] = "localTime";
+const ReadPreferenceSetting kReadPref(ReadPreference::PrimaryOnly, TagSet());
- BSONElement wcErrorElem;
- auto wcErrStatus = bsonExtractTypedField(responseObj,
- kCmdResponseWriteConcernField,
- Object,
- &wcErrorElem);
+/**
+ * Returns the resulting new object from the findAndModify response object.
+ * Returns LockStateChangeFailed if value field was null, which indicates that
+ * the findAndModify command did not modify any document.
+ * This also checks for errors in the response object.
+ */
+StatusWith<BSONObj> extractFindAndModifyNewObj(const BSONObj& responseObj) {
+ auto cmdStatus = getStatusFromCommandResult(responseObj);
- if (wcErrStatus.isOK()) {
- BSONObj wcErrObj(wcErrorElem.Obj());
- WCErrorDetail wcError;
+ if (!cmdStatus.isOK()) {
+ return cmdStatus;
+ }
- string wcErrorParseMsg;
- if (!wcError.parseBSON(wcErrObj, &wcErrorParseMsg)) {
- return Status(ErrorCodes::UnsupportedFormat, wcErrorParseMsg);
- }
+ BSONElement wcErrorElem;
+ auto wcErrStatus =
+ bsonExtractTypedField(responseObj, kCmdResponseWriteConcernField, Object, &wcErrorElem);
- return {ErrorCodes::WriteConcernFailed, wcError.getErrMessage()};
- }
+ if (wcErrStatus.isOK()) {
+ BSONObj wcErrObj(wcErrorElem.Obj());
+ WCErrorDetail wcError;
- if (wcErrStatus != ErrorCodes::NoSuchKey) {
- return wcErrStatus;
+ string wcErrorParseMsg;
+ if (!wcError.parseBSON(wcErrObj, &wcErrorParseMsg)) {
+ return Status(ErrorCodes::UnsupportedFormat, wcErrorParseMsg);
}
- if (const auto& newDocElem = responseObj[kFindAndModifyResponseResultDocField]) {
- if (newDocElem.isNull()) {
- return {ErrorCodes::LockStateChangeFailed,
- "findAndModify query predicate didn't match any lock document"};
- }
+ return {ErrorCodes::WriteConcernFailed, wcError.getErrMessage()};
+ }
- if (!newDocElem.isABSONObj()) {
- return {ErrorCodes::UnsupportedFormat,
- str::stream() << "expected an object from the findAndModify response '"
- << kFindAndModifyResponseResultDocField << "'field, got: "
- << newDocElem};
- }
+ if (wcErrStatus != ErrorCodes::NoSuchKey) {
+ return wcErrStatus;
+ }
- return newDocElem.Obj();
+ if (const auto& newDocElem = responseObj[kFindAndModifyResponseResultDocField]) {
+ if (newDocElem.isNull()) {
+ return {ErrorCodes::LockStateChangeFailed,
+ "findAndModify query predicate didn't match any lock document"};
}
- return {ErrorCodes::LockStateChangeFailed,
- str::stream() << "no '" << kFindAndModifyResponseResultDocField
- << "' in findAndModify response"};
- }
-
- /**
- * Extract the electionId from a command response.
- *
- * TODO: this needs to support OP_COMMAND metadata.
- */
- StatusWith<OID> extractElectionId(const BSONObj& responseObj) {
-
- BSONElement gleStatsElem;
- auto gleStatus = bsonExtractTypedField(responseObj,
- "$gleStats",
- Object,
- &gleStatsElem);
-
- if (!gleStatus.isOK()) {
- return {ErrorCodes::UnsupportedFormat, gleStatus.reason()};
+ if (!newDocElem.isABSONObj()) {
+ return {ErrorCodes::UnsupportedFormat,
+ str::stream() << "expected an object from the findAndModify response '"
+ << kFindAndModifyResponseResultDocField
+ << "'field, got: " << newDocElem};
}
- OID electionId;
+ return newDocElem.Obj();
+ }
- auto electionIdStatus = bsonExtractOIDField(gleStatsElem.Obj(),
- "electionId",
- &electionId);
+ return {ErrorCodes::LockStateChangeFailed,
+ str::stream() << "no '" << kFindAndModifyResponseResultDocField
+ << "' in findAndModify response"};
+}
- if (!electionIdStatus.isOK()) {
- return {ErrorCodes::UnsupportedFormat, electionIdStatus.reason()};
- }
+/**
+ * Extract the electionId from a command response.
+ *
+ * TODO: this needs to support OP_COMMAND metadata.
+ */
+StatusWith<OID> extractElectionId(const BSONObj& responseObj) {
+ BSONElement gleStatsElem;
+ auto gleStatus = bsonExtractTypedField(responseObj, "$gleStats", Object, &gleStatsElem);
- return electionId;
+ if (!gleStatus.isOK()) {
+ return {ErrorCodes::UnsupportedFormat, gleStatus.reason()};
}
-} // unnamed namespace
-
- DistLockCatalogImpl::DistLockCatalogImpl(RemoteCommandTargeter* targeter,
- RemoteCommandRunner* executor,
- Milliseconds writeConcernTimeout):
- _cmdRunner(executor),
- _targeter(targeter),
- _writeConcern(WriteConcernOptions(WriteConcernOptions::kMajority,
- WriteConcernOptions::JOURNAL,
- writeConcernTimeout.count())),
- _lockPingNS(LockpingsType::ConfigNS),
- _locksNS(LocksType::ConfigNS) {
+ OID electionId;
+
+ auto electionIdStatus = bsonExtractOIDField(gleStatsElem.Obj(), "electionId", &electionId);
+
+ if (!electionIdStatus.isOK()) {
+ return {ErrorCodes::UnsupportedFormat, electionIdStatus.reason()};
}
- DistLockCatalogImpl::~DistLockCatalogImpl() = default;
+ return electionId;
+}
+
+} // unnamed namespace
+
+DistLockCatalogImpl::DistLockCatalogImpl(RemoteCommandTargeter* targeter,
+ RemoteCommandRunner* executor,
+ Milliseconds writeConcernTimeout)
+ : _cmdRunner(executor),
+ _targeter(targeter),
+ _writeConcern(WriteConcernOptions(WriteConcernOptions::kMajority,
+ WriteConcernOptions::JOURNAL,
+ writeConcernTimeout.count())),
+ _lockPingNS(LockpingsType::ConfigNS),
+ _locksNS(LocksType::ConfigNS) {}
+
+DistLockCatalogImpl::~DistLockCatalogImpl() = default;
- StatusWith<LockpingsType> DistLockCatalogImpl::getPing(StringData processID) {
- invariant(false); // TODO
+StatusWith<LockpingsType> DistLockCatalogImpl::getPing(StringData processID) {
+ invariant(false); // TODO
+}
+
+Status DistLockCatalogImpl::ping(StringData processID, Date_t ping) {
+ auto targetStatus = _targeter->findHost(kReadPref);
+
+ if (!targetStatus.isOK()) {
+ return targetStatus.getStatus();
}
- Status DistLockCatalogImpl::ping(StringData processID, Date_t ping) {
- auto targetStatus = _targeter->findHost(kReadPref);
+ auto request =
+ FindAndModifyRequest::makeUpdate(_lockPingNS,
+ BSON(LockpingsType::process(processID.toString())),
+ BSON("$set" << BSON(LockpingsType::ping(ping))));
+ request.setUpsert(true);
+ request.setWriteConcern(_writeConcern);
- if (!targetStatus.isOK()) {
- return targetStatus.getStatus();
- }
+ auto resultStatus = _cmdRunner->runCommand(
+ RemoteCommandRequest(targetStatus.getValue(), _locksNS.db().toString(), request.toBSON()));
- auto request = FindAndModifyRequest::makeUpdate(_lockPingNS,
- BSON(LockpingsType::process(processID.toString())),
- BSON("$set" << BSON(LockpingsType::ping(ping))));
- request.setUpsert(true);
- request.setWriteConcern(_writeConcern);
+ if (!resultStatus.isOK()) {
+ return resultStatus.getStatus();
+ }
- auto resultStatus = _cmdRunner->runCommand(
- RemoteCommandRequest(targetStatus.getValue(),
- _locksNS.db().toString(),
- request.toBSON()));
+ const RemoteCommandResponse& response = resultStatus.getValue();
+ BSONObj responseObj(response.data);
- if (!resultStatus.isOK()) {
- return resultStatus.getStatus();
- }
+ auto cmdStatus = getStatusFromCommandResult(responseObj);
- const RemoteCommandResponse& response = resultStatus.getValue();
- BSONObj responseObj(response.data);
+ if (!cmdStatus.isOK()) {
+ return cmdStatus;
+ }
- auto cmdStatus = getStatusFromCommandResult(responseObj);
+ auto findAndModifyStatus = extractFindAndModifyNewObj(responseObj);
+ return findAndModifyStatus.getStatus();
+}
- if (!cmdStatus.isOK()) {
- return cmdStatus;
- }
+StatusWith<LocksType> DistLockCatalogImpl::grabLock(StringData lockID,
+ const OID& lockSessionID,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
+ auto targetStatus = _targeter->findHost(kReadPref);
- auto findAndModifyStatus = extractFindAndModifyNewObj(responseObj);
- return findAndModifyStatus.getStatus();
+ if (!targetStatus.isOK()) {
+ return targetStatus.getStatus();
}
- StatusWith<LocksType> DistLockCatalogImpl::grabLock(StringData lockID,
- const OID& lockSessionID,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
- auto targetStatus = _targeter->findHost(kReadPref);
+ BSONObj newLockDetails(BSON(LocksType::lockID(lockSessionID)
+ << LocksType::state(LocksType::LOCKED) << LocksType::who() << who
+ << LocksType::process() << processId << LocksType::when(time)
+ << LocksType::why() << why));
- if (!targetStatus.isOK()) {
- return targetStatus.getStatus();
- }
+ auto request = FindAndModifyRequest::makeUpdate(
+ _locksNS,
+ BSON(LocksType::name() << lockID << LocksType::state(LocksType::UNLOCKED)),
+ BSON("$set" << newLockDetails));
+ request.setUpsert(true);
+ request.setShouldReturnNew(true);
+ request.setWriteConcern(_writeConcern);
- BSONObj newLockDetails(BSON(LocksType::lockID(lockSessionID)
- << LocksType::state(LocksType::LOCKED)
- << LocksType::who() << who
- << LocksType::process() << processId
- << LocksType::when(time)
- << LocksType::why() << why));
-
- auto request = FindAndModifyRequest::makeUpdate(_locksNS,
- BSON(LocksType::name() << lockID << LocksType::state(LocksType::UNLOCKED)),
- BSON("$set" << newLockDetails));
- request.setUpsert(true);
- request.setShouldReturnNew(true);
- request.setWriteConcern(_writeConcern);
-
- auto resultStatus = _cmdRunner->runCommand(
- RemoteCommandRequest(targetStatus.getValue(),
- _locksNS.db().toString(),
- request.toBSON()));
-
- if (!resultStatus.isOK()) {
- return resultStatus.getStatus();
- }
+ auto resultStatus = _cmdRunner->runCommand(
+ RemoteCommandRequest(targetStatus.getValue(), _locksNS.db().toString(), request.toBSON()));
- const RemoteCommandResponse& response = resultStatus.getValue();
- BSONObj responseObj(response.data);
+ if (!resultStatus.isOK()) {
+ return resultStatus.getStatus();
+ }
- auto cmdStatus = getStatusFromCommandResult(responseObj);
+ const RemoteCommandResponse& response = resultStatus.getValue();
+ BSONObj responseObj(response.data);
- if (!cmdStatus.isOK()) {
- return cmdStatus;
- }
+ auto cmdStatus = getStatusFromCommandResult(responseObj);
- auto findAndModifyStatus = extractFindAndModifyNewObj(responseObj);
- if (!findAndModifyStatus.isOK()) {
- return findAndModifyStatus.getStatus();
- }
+ if (!cmdStatus.isOK()) {
+ return cmdStatus;
+ }
- BSONObj newDoc(findAndModifyStatus.getValue());
+ auto findAndModifyStatus = extractFindAndModifyNewObj(responseObj);
+ if (!findAndModifyStatus.isOK()) {
+ return findAndModifyStatus.getStatus();
+ }
- if (newDoc.isEmpty()) {
- return LocksType();
- }
+ BSONObj newDoc(findAndModifyStatus.getValue());
- LocksType lockDoc;
- string errMsg;
- if (!lockDoc.parseBSON(newDoc, &errMsg)) {
- return {ErrorCodes::FailedToParse, errMsg};
- }
+ if (newDoc.isEmpty()) {
+ return LocksType();
+ }
- return lockDoc;
+ LocksType lockDoc;
+ string errMsg;
+ if (!lockDoc.parseBSON(newDoc, &errMsg)) {
+ return {ErrorCodes::FailedToParse, errMsg};
}
- StatusWith<LocksType> DistLockCatalogImpl::overtakeLock(StringData lockID,
- const OID& lockSessionID,
- const OID& currentHolderTS,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
- auto targetStatus = _targeter->findHost(kReadPref);
-
- if (!targetStatus.isOK()) {
- return targetStatus.getStatus();
- }
+ return lockDoc;
+}
- BSONArrayBuilder orQueryBuilder;
- orQueryBuilder.append(BSON(LocksType::name() << lockID
- << LocksType::state(LocksType::UNLOCKED)));
- orQueryBuilder.append(BSON(LocksType::name() << lockID
- << LocksType::lockID(currentHolderTS)));
-
- BSONObj newLockDetails(BSON(LocksType::lockID(lockSessionID)
- << LocksType::state(LocksType::LOCKED)
- << LocksType::who() << who
- << LocksType::process() << processId
- << LocksType::when(time)
- << LocksType::why() << why));
-
- auto request = FindAndModifyRequest::makeUpdate(_locksNS,
- BSON("$or" << orQueryBuilder.arr()),
- BSON("$set" << newLockDetails));
- request.setShouldReturnNew(true);
- request.setWriteConcern(_writeConcern);
-
- auto resultStatus = _cmdRunner->runCommand(
- RemoteCommandRequest(targetStatus.getValue(),
- _locksNS.db().toString(),
- request.toBSON()));
-
- if (!resultStatus.isOK()) {
- return resultStatus.getStatus();
- }
+StatusWith<LocksType> DistLockCatalogImpl::overtakeLock(StringData lockID,
+ const OID& lockSessionID,
+ const OID& currentHolderTS,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
+ auto targetStatus = _targeter->findHost(kReadPref);
- const RemoteCommandResponse& response = resultStatus.getValue();
- BSONObj responseObj(response.data);
+ if (!targetStatus.isOK()) {
+ return targetStatus.getStatus();
+ }
- auto findAndModifyStatus = extractFindAndModifyNewObj(responseObj);
- if (!findAndModifyStatus.isOK()) {
- return findAndModifyStatus.getStatus();
- }
+ BSONArrayBuilder orQueryBuilder;
+ orQueryBuilder.append(
+ BSON(LocksType::name() << lockID << LocksType::state(LocksType::UNLOCKED)));
+ orQueryBuilder.append(BSON(LocksType::name() << lockID << LocksType::lockID(currentHolderTS)));
- BSONObj newDoc(findAndModifyStatus.getValue());
+ BSONObj newLockDetails(BSON(LocksType::lockID(lockSessionID)
+ << LocksType::state(LocksType::LOCKED) << LocksType::who() << who
+ << LocksType::process() << processId << LocksType::when(time)
+ << LocksType::why() << why));
- if (newDoc.isEmpty()) {
- return LocksType();
- }
+ auto request = FindAndModifyRequest::makeUpdate(
+ _locksNS, BSON("$or" << orQueryBuilder.arr()), BSON("$set" << newLockDetails));
+ request.setShouldReturnNew(true);
+ request.setWriteConcern(_writeConcern);
- LocksType lockDoc;
- string errMsg;
- if (!lockDoc.parseBSON(newDoc, &errMsg)) {
- return {ErrorCodes::FailedToParse, errMsg};
- }
+ auto resultStatus = _cmdRunner->runCommand(
+ RemoteCommandRequest(targetStatus.getValue(), _locksNS.db().toString(), request.toBSON()));
- return lockDoc;
+ if (!resultStatus.isOK()) {
+ return resultStatus.getStatus();
}
- Status DistLockCatalogImpl::unlock(const OID& lockSessionID) {
- auto targetStatus = _targeter->findHost(kReadPref);
-
- if (!targetStatus.isOK()) {
- return targetStatus.getStatus();
- }
+ const RemoteCommandResponse& response = resultStatus.getValue();
+ BSONObj responseObj(response.data);
- auto request = FindAndModifyRequest::makeUpdate(_locksNS,
- BSON(LocksType::lockID(lockSessionID)),
- BSON("$set" << BSON(LocksType::state(LocksType::UNLOCKED))));
- request.setWriteConcern(_writeConcern);
+ auto findAndModifyStatus = extractFindAndModifyNewObj(responseObj);
+ if (!findAndModifyStatus.isOK()) {
+ return findAndModifyStatus.getStatus();
+ }
- auto resultStatus = _cmdRunner->runCommand(
- RemoteCommandRequest(targetStatus.getValue(),
- _locksNS.db().toString(),
- request.toBSON()));
+ BSONObj newDoc(findAndModifyStatus.getValue());
- if (!resultStatus.isOK()) {
- return resultStatus.getStatus();
- }
+ if (newDoc.isEmpty()) {
+ return LocksType();
+ }
- const RemoteCommandResponse& response = resultStatus.getValue();
- BSONObj responseObj(response.data);
+ LocksType lockDoc;
+ string errMsg;
+ if (!lockDoc.parseBSON(newDoc, &errMsg)) {
+ return {ErrorCodes::FailedToParse, errMsg};
+ }
- auto findAndModifyStatus = extractFindAndModifyNewObj(responseObj).getStatus();
+ return lockDoc;
+}
- if (findAndModifyStatus == ErrorCodes::LockStateChangeFailed) {
- // Did not modify any document, which implies that the lock already has a
- // a different owner. This is ok since it means that the objective of
- // releasing ownership of the lock has already been accomplished.
- return Status::OK();
- }
+Status DistLockCatalogImpl::unlock(const OID& lockSessionID) {
+ auto targetStatus = _targeter->findHost(kReadPref);
- return findAndModifyStatus;
+ if (!targetStatus.isOK()) {
+ return targetStatus.getStatus();
}
- StatusWith<DistLockCatalog::ServerInfo> DistLockCatalogImpl::getServerInfo() {
- auto targetStatus = _targeter->findHost(kReadPref);
+ auto request = FindAndModifyRequest::makeUpdate(
+ _locksNS,
+ BSON(LocksType::lockID(lockSessionID)),
+ BSON("$set" << BSON(LocksType::state(LocksType::UNLOCKED))));
+ request.setWriteConcern(_writeConcern);
- if (!targetStatus.isOK()) {
- return targetStatus.getStatus();
- }
+ auto resultStatus = _cmdRunner->runCommand(
+ RemoteCommandRequest(targetStatus.getValue(), _locksNS.db().toString(), request.toBSON()));
- auto resultStatus = _cmdRunner->runCommand(RemoteCommandRequest(
- targetStatus.getValue(),
- "admin",
- BSON("serverStatus" << 1)));
+ if (!resultStatus.isOK()) {
+ return resultStatus.getStatus();
+ }
- if (!resultStatus.isOK()) {
- return resultStatus.getStatus();
- }
+ const RemoteCommandResponse& response = resultStatus.getValue();
+ BSONObj responseObj(response.data);
- const RemoteCommandResponse& response = resultStatus.getValue();
- BSONObj responseObj(response.data);
+ auto findAndModifyStatus = extractFindAndModifyNewObj(responseObj).getStatus();
- auto cmdStatus = getStatusFromCommandResult(responseObj);
+ if (findAndModifyStatus == ErrorCodes::LockStateChangeFailed) {
+ // Did not modify any document, which implies that the lock already has a
+ // a different owner. This is ok since it means that the objective of
+ // releasing ownership of the lock has already been accomplished.
+ return Status::OK();
+ }
- if (!cmdStatus.isOK()) {
- return cmdStatus;
- }
+ return findAndModifyStatus;
+}
- BSONElement localTimeElem;
- auto localTimeStatus = bsonExtractTypedField(responseObj,
- kLocalTimeField,
- Date,
- &localTimeElem);
+StatusWith<DistLockCatalog::ServerInfo> DistLockCatalogImpl::getServerInfo() {
+ auto targetStatus = _targeter->findHost(kReadPref);
- if (!localTimeStatus.isOK()) {
- return {ErrorCodes::UnsupportedFormat, localTimeStatus.reason()};
- }
+ if (!targetStatus.isOK()) {
+ return targetStatus.getStatus();
+ }
- auto electionIdStatus = extractElectionId(responseObj);
+ auto resultStatus = _cmdRunner->runCommand(
+ RemoteCommandRequest(targetStatus.getValue(), "admin", BSON("serverStatus" << 1)));
- if (!electionIdStatus.isOK()) {
- return {ErrorCodes::UnsupportedFormat, electionIdStatus.getStatus().reason()};
- }
+ if (!resultStatus.isOK()) {
+ return resultStatus.getStatus();
+ }
+
+ const RemoteCommandResponse& response = resultStatus.getValue();
+ BSONObj responseObj(response.data);
- return DistLockCatalog::ServerInfo(localTimeElem.date(), electionIdStatus.getValue());
+ auto cmdStatus = getStatusFromCommandResult(responseObj);
+
+ if (!cmdStatus.isOK()) {
+ return cmdStatus;
}
- StatusWith<LocksType> DistLockCatalogImpl::getLockByTS(const OID& lockSessionID) {
- invariant(false); // TODO
+ BSONElement localTimeElem;
+ auto localTimeStatus =
+ bsonExtractTypedField(responseObj, kLocalTimeField, Date, &localTimeElem);
+
+ if (!localTimeStatus.isOK()) {
+ return {ErrorCodes::UnsupportedFormat, localTimeStatus.reason()};
}
- StatusWith<LocksType> DistLockCatalogImpl::getLockByName(StringData name) {
- invariant(false); // TODO
+ auto electionIdStatus = extractElectionId(responseObj);
+
+ if (!electionIdStatus.isOK()) {
+ return {ErrorCodes::UnsupportedFormat, electionIdStatus.getStatus().reason()};
}
- Status DistLockCatalogImpl::stopPing(StringData processId) {
- auto targetStatus = _targeter->findHost(kReadPref);
+ return DistLockCatalog::ServerInfo(localTimeElem.date(), electionIdStatus.getValue());
+}
- if (!targetStatus.isOK()) {
- return targetStatus.getStatus();
- }
+StatusWith<LocksType> DistLockCatalogImpl::getLockByTS(const OID& lockSessionID) {
+ invariant(false); // TODO
+}
- auto request = FindAndModifyRequest::makeRemove(_lockPingNS,
- BSON(LockpingsType::process() << processId));
- request.setWriteConcern(_writeConcern);
+StatusWith<LocksType> DistLockCatalogImpl::getLockByName(StringData name) {
+ invariant(false); // TODO
+}
- auto resultStatus = _cmdRunner->runCommand(
- RemoteCommandRequest(targetStatus.getValue(),
- _locksNS.db().toString(),
- request.toBSON()));
+Status DistLockCatalogImpl::stopPing(StringData processId) {
+ auto targetStatus = _targeter->findHost(kReadPref);
- if (!resultStatus.isOK()) {
- return resultStatus.getStatus();
- }
+ if (!targetStatus.isOK()) {
+ return targetStatus.getStatus();
+ }
- const RemoteCommandResponse& response = resultStatus.getValue();
- BSONObj responseObj(response.data);
+ auto request =
+ FindAndModifyRequest::makeRemove(_lockPingNS, BSON(LockpingsType::process() << processId));
+ request.setWriteConcern(_writeConcern);
- auto findAndModifyStatus = extractFindAndModifyNewObj(responseObj);
- return findAndModifyStatus.getStatus();
+ auto resultStatus = _cmdRunner->runCommand(
+ RemoteCommandRequest(targetStatus.getValue(), _locksNS.db().toString(), request.toBSON()));
+
+ if (!resultStatus.isOK()) {
+ return resultStatus.getStatus();
}
-} // namespace mongo
+ const RemoteCommandResponse& response = resultStatus.getValue();
+ BSONObj responseObj(response.data);
+
+ auto findAndModifyStatus = extractFindAndModifyNewObj(responseObj);
+ return findAndModifyStatus.getStatus();
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/dist_lock_catalog_impl.h b/src/mongo/s/catalog/dist_lock_catalog_impl.h
index 4ec349cbfb8..c75d4abbace 100644
--- a/src/mongo/s/catalog/dist_lock_catalog_impl.h
+++ b/src/mongo/s/catalog/dist_lock_catalog_impl.h
@@ -37,53 +37,53 @@
namespace mongo {
- class RemoteCommandRunner;
- class RemoteCommandTargeter;
+class RemoteCommandRunner;
+class RemoteCommandTargeter;
- class DistLockCatalogImpl final : public DistLockCatalog {
- public:
- DistLockCatalogImpl(RemoteCommandTargeter* targeter,
- RemoteCommandRunner* executor,
- Milliseconds writeConcernTimeout);
+class DistLockCatalogImpl final : public DistLockCatalog {
+public:
+ DistLockCatalogImpl(RemoteCommandTargeter* targeter,
+ RemoteCommandRunner* executor,
+ Milliseconds writeConcernTimeout);
- virtual ~DistLockCatalogImpl();
+ virtual ~DistLockCatalogImpl();
- virtual StatusWith<LockpingsType> getPing(StringData processID) override;
+ virtual StatusWith<LockpingsType> getPing(StringData processID) override;
- virtual Status ping(StringData processID, Date_t ping) override;
+ virtual Status ping(StringData processID, Date_t ping) override;
- virtual StatusWith<LocksType> grabLock(StringData lockID,
+ virtual StatusWith<LocksType> grabLock(StringData lockID,
+ const OID& lockSessionID,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) override;
+
+ virtual StatusWith<LocksType> overtakeLock(StringData lockID,
const OID& lockSessionID,
+ const OID& currentHolderTS,
StringData who,
StringData processId,
Date_t time,
StringData why) override;
- virtual StatusWith<LocksType> overtakeLock(StringData lockID,
- const OID& lockSessionID,
- const OID& currentHolderTS,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) override;
-
- virtual Status unlock(const OID& lockSessionID) override;
+ virtual Status unlock(const OID& lockSessionID) override;
- virtual StatusWith<ServerInfo> getServerInfo() override;
+ virtual StatusWith<ServerInfo> getServerInfo() override;
- virtual StatusWith<LocksType> getLockByTS(const OID& lockSessionID) override;
+ virtual StatusWith<LocksType> getLockByTS(const OID& lockSessionID) override;
- virtual StatusWith<LocksType> getLockByName(StringData name) override;
+ virtual StatusWith<LocksType> getLockByName(StringData name) override;
- virtual Status stopPing(StringData processId) override;
+ virtual Status stopPing(StringData processId) override;
- private:
- RemoteCommandRunner* _cmdRunner;
- RemoteCommandTargeter* _targeter;
+private:
+ RemoteCommandRunner* _cmdRunner;
+ RemoteCommandTargeter* _targeter;
- // These are not static to avoid initialization order fiasco.
- const WriteConcernOptions _writeConcern;
- const NamespaceString _lockPingNS;
- const NamespaceString _locksNS;
- };
+ // These are not static to avoid initialization order fiasco.
+ const WriteConcernOptions _writeConcern;
+ const NamespaceString _lockPingNS;
+ const NamespaceString _locksNS;
+};
}
diff --git a/src/mongo/s/catalog/dist_lock_catalog_impl_test.cpp b/src/mongo/s/catalog/dist_lock_catalog_impl_test.cpp
index e10a582e4a1..bdbe5bd9059 100644
--- a/src/mongo/s/catalog/dist_lock_catalog_impl_test.cpp
+++ b/src/mongo/s/catalog/dist_lock_catalog_impl_test.cpp
@@ -44,20 +44,21 @@ f * Copyright (C) 2015 MongoDB Inc.
namespace mongo {
namespace {
- const HostAndPort dummyHost("dummy", 123);
- const Milliseconds kWTimeout(100);
+const HostAndPort dummyHost("dummy", 123);
+const Milliseconds kWTimeout(100);
- const auto noTest = [](const RemoteCommandRequest& request) {};
+const auto noTest = [](const RemoteCommandRequest& request) {};
- TEST(DistLockCatalogImpl, BasicPing) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, BasicPing) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- executor.setNextExpectedCommand([](const RemoteCommandRequest& request) {
+ executor.setNextExpectedCommand(
+ [](const RemoteCommandRequest& request) {
ASSERT_EQUALS(dummyHost, request.target);
ASSERT_EQUALS("config", request.dbname);
@@ -81,92 +82,93 @@ namespace {
_id: "abcd",
ping: { $date: "2014-03-11T09:17:18.098Z" }
}
- })"), Milliseconds(0)));
+ })"),
+ Milliseconds(0)));
- Date_t ping(dateFromISOString("2014-03-11T09:17:18.098Z").getValue());
- auto status = catalog.ping("abcd", ping);
- ASSERT_OK(status);
- }
+ Date_t ping(dateFromISOString("2014-03-11T09:17:18.098Z").getValue());
+ auto status = catalog.ping("abcd", ping);
+ ASSERT_OK(status);
+}
- TEST(DistLockCatalogImpl, PingTargetError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, PingTargetError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue({ErrorCodes::InternalError, "can't target"});
+ targeter.setFindHostReturnValue({ErrorCodes::InternalError, "can't target"});
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- auto status = catalog.ping("abcd", Date_t::now());
- ASSERT_NOT_OK(status);
- }
+ auto status = catalog.ping("abcd", Date_t::now());
+ ASSERT_NOT_OK(status);
+}
- TEST(DistLockCatalogImpl, PingRunnerError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, PingRunnerError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- executor.setNextExpectedCommand(noTest, {ErrorCodes::InternalError, "Bad"});
+ executor.setNextExpectedCommand(noTest, {ErrorCodes::InternalError, "Bad"});
- auto status = catalog.ping("abcd", Date_t::now());
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::InternalError, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.ping("abcd", Date_t::now());
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::InternalError, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, PingCommandError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, PingCommandError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 0,
errmsg: "bad",
code: 9
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.ping("abcd", Date_t::now());
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::FailedToParse, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.ping("abcd", Date_t::now());
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, PingWriteError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, PingWriteError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 0,
code: 11000,
errmsg: "E11000 duplicate key error"
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.ping("abcd", Date_t::now());
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::DuplicateKey, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.ping("abcd", Date_t::now());
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::DuplicateKey, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, PingWriteConcernError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, PingWriteConcernError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 1,
value: null,
writeConcernError: {
@@ -174,23 +176,23 @@ namespace {
errmsg: "waiting for replication timed out"
}
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.ping("abcd", Date_t::now());
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::WriteConcernFailed, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.ping("abcd", Date_t::now());
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::WriteConcernFailed, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, PingUnsupportedWriteConcernResponse) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, PingUnsupportedWriteConcernResponse) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 1,
value: null,
writeConcernError: {
@@ -198,45 +200,47 @@ namespace {
errmsg: "waiting for replication timed out"
}
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.ping("abcd", Date_t::now());
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.ping("abcd", Date_t::now());
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, PingUnsupportedResponseFormat) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, PingUnsupportedResponseFormat) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- executor.setNextExpectedCommand(noTest,
- RemoteCommandResponse(BSON("ok" << 1 << "value" << "NaN"), Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest,
+ RemoteCommandResponse(BSON("ok" << 1 << "value"
+ << "NaN"),
+ Milliseconds(0)));
- auto status = catalog.ping("abcd", Date_t::now());
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
- }
+ auto status = catalog.ping("abcd", Date_t::now());
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
+}
- TEST(DistLockCatalogImpl, GrabLockNoOp) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, GrabLockNoOp) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj responseObj(fromjson("{ ok: 1, value: null }"));
+ BSONObj responseObj(fromjson("{ ok: 1, value: null }"));
- executor.setNextExpectedCommand([](const RemoteCommandRequest& request) {
- ASSERT_EQUALS(dummyHost, request.target);
- ASSERT_EQUALS("config", request.dbname);
+ executor.setNextExpectedCommand([](const RemoteCommandRequest& request) {
+ ASSERT_EQUALS(dummyHost, request.target);
+ ASSERT_EQUALS("config", request.dbname);
- BSONObj expectedCmd(fromjson(R"({
+ BSONObj expectedCmd(fromjson(R"({
findAndModify: "locks",
query: { _id: "test", state: 0 },
update: {
@@ -254,29 +258,26 @@ namespace {
writeConcern: { w: "majority", j: true, wtimeout: 100 }
})"));
- ASSERT_EQUALS(expectedCmd, request.cmdObj);
- },
- RemoteCommandResponse(responseObj, Milliseconds(0)));
-
- OID myID("555f80be366c194b13fb0372");
- Date_t now(dateFromISOString("2015-05-22T19:17:18.098Z").getValue());
- auto resultStatus = catalog.grabLock("test", myID, "me",
- "mongos", now, "because").getStatus();
+ ASSERT_EQUALS(expectedCmd, request.cmdObj);
+ }, RemoteCommandResponse(responseObj, Milliseconds(0)));
- ASSERT_NOT_OK(resultStatus);
- ASSERT_EQUALS(ErrorCodes::LockStateChangeFailed, resultStatus.code());
+ OID myID("555f80be366c194b13fb0372");
+ Date_t now(dateFromISOString("2015-05-22T19:17:18.098Z").getValue());
+ auto resultStatus = catalog.grabLock("test", myID, "me", "mongos", now, "because").getStatus();
- }
+ ASSERT_NOT_OK(resultStatus);
+ ASSERT_EQUALS(ErrorCodes::LockStateChangeFailed, resultStatus.code());
+}
- TEST(DistLockCatalogImpl, GrabLockWithNewDoc) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, GrabLockWithNewDoc) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj responseObj(fromjson(R"({
+ BSONObj responseObj(fromjson(R"({
lastErrorObject: {
updatedExisting: false,
n: 1,
@@ -294,11 +295,11 @@ namespace {
ok: 1
})"));
- executor.setNextExpectedCommand([](const RemoteCommandRequest& request) {
- ASSERT_EQUALS(dummyHost, request.target);
- ASSERT_EQUALS("config", request.dbname);
+ executor.setNextExpectedCommand([](const RemoteCommandRequest& request) {
+ ASSERT_EQUALS(dummyHost, request.target);
+ ASSERT_EQUALS("config", request.dbname);
- BSONObj expectedCmd(fromjson(R"({
+ BSONObj expectedCmd(fromjson(R"({
findAndModify: "locks",
query: { _id: "test", state: 0 },
update: {
@@ -316,103 +317,102 @@ namespace {
writeConcern: { w: "majority", j: true, wtimeout: 100 }
})"));
- ASSERT_EQUALS(expectedCmd, request.cmdObj);
- },
- RemoteCommandResponse(responseObj, Milliseconds(0)));
+ ASSERT_EQUALS(expectedCmd, request.cmdObj);
+ }, RemoteCommandResponse(responseObj, Milliseconds(0)));
- OID myID("555f80be366c194b13fb0372");
- Date_t now(dateFromISOString("2015-05-22T19:17:18.098Z").getValue());
- auto resultStatus = catalog.grabLock("test", myID, "me", "mongos", now, "because");
- ASSERT_OK(resultStatus.getStatus());
+ OID myID("555f80be366c194b13fb0372");
+ Date_t now(dateFromISOString("2015-05-22T19:17:18.098Z").getValue());
+ auto resultStatus = catalog.grabLock("test", myID, "me", "mongos", now, "because");
+ ASSERT_OK(resultStatus.getStatus());
- const auto& lockDoc = resultStatus.getValue();
- ASSERT_TRUE(lockDoc.isValid(nullptr));
- ASSERT_EQUALS("test", lockDoc.getName());
- ASSERT_EQUALS(myID, lockDoc.getLockID());
- ASSERT_EQUALS("me", lockDoc.getWho());
- ASSERT_EQUALS("mongos", lockDoc.getProcess());
- ASSERT_EQUALS("because", lockDoc.getWhy());
- }
+ const auto& lockDoc = resultStatus.getValue();
+ ASSERT_TRUE(lockDoc.isValid(nullptr));
+ ASSERT_EQUALS("test", lockDoc.getName());
+ ASSERT_EQUALS(myID, lockDoc.getLockID());
+ ASSERT_EQUALS("me", lockDoc.getWho());
+ ASSERT_EQUALS("mongos", lockDoc.getProcess());
+ ASSERT_EQUALS("because", lockDoc.getWhy());
+}
- TEST(DistLockCatalogImpl, GrabLockTargetError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, GrabLockTargetError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue({ErrorCodes::InternalError, "can't target"});
+ targeter.setFindHostReturnValue({ErrorCodes::InternalError, "can't target"});
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- auto status = catalog.grabLock("", OID::gen(), "", "", Date_t::now(), "").getStatus();
- ASSERT_NOT_OK(status);
- }
+ auto status = catalog.grabLock("", OID::gen(), "", "", Date_t::now(), "").getStatus();
+ ASSERT_NOT_OK(status);
+}
- TEST(DistLockCatalogImpl, GrabLockRunnerError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, GrabLockRunnerError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- executor.setNextExpectedCommand(noTest, {ErrorCodes::InternalError, "Bad"});
+ executor.setNextExpectedCommand(noTest, {ErrorCodes::InternalError, "Bad"});
- auto status = catalog.grabLock("", OID::gen(), "", "", Date_t::now(), "").getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::InternalError, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.grabLock("", OID::gen(), "", "", Date_t::now(), "").getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::InternalError, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, GrabLockCommandError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, GrabLockCommandError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 0,
errmsg: "bad",
code: 9
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.grabLock("", OID::gen(), "", "", Date_t::now(), "").getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::FailedToParse, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.grabLock("", OID::gen(), "", "", Date_t::now(), "").getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, GrabLockWriteError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, GrabLockWriteError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 0,
code: 11000,
errmsg: "E11000 duplicate key error"
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.grabLock("", OID::gen(), "", "", Date_t::now(), "").getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::DuplicateKey, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.grabLock("", OID::gen(), "", "", Date_t::now(), "").getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::DuplicateKey, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, GrabLockWriteConcernError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, GrabLockWriteConcernError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 1,
value: null,
writeConcernError: {
@@ -420,23 +420,23 @@ namespace {
errmsg: "waiting for replication timed out"
}
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.grabLock("", OID::gen(), "", "", Date_t::now(), "").getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::WriteConcernFailed, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.grabLock("", OID::gen(), "", "", Date_t::now(), "").getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::WriteConcernFailed, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, GrabLockUnsupportedWriteConcernResponse) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, GrabLockUnsupportedWriteConcernResponse) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 1,
value: null,
writeConcernError: {
@@ -444,46 +444,47 @@ namespace {
errmsg: "waiting for replication timed out"
}
})"));
- executor.setNextExpectedCommand(noTest,
- RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.grabLock("", OID::gen(), "", "", Date_t::now(), "").getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.grabLock("", OID::gen(), "", "", Date_t::now(), "").getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, GrabLockUnsupportedResponseFormat) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, GrabLockUnsupportedResponseFormat) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- executor.setNextExpectedCommand(noTest,
- RemoteCommandResponse(BSON("ok" << 1 << "value" << "NaN"), Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest,
+ RemoteCommandResponse(BSON("ok" << 1 << "value"
+ << "NaN"),
+ Milliseconds(0)));
- auto status = catalog.grabLock("", OID::gen(), "", "", Date_t::now(), "").getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
- }
+ auto status = catalog.grabLock("", OID::gen(), "", "", Date_t::now(), "").getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
+}
- TEST(DistLockCatalogImpl, OvertakeLockNoOp) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, OvertakeLockNoOp) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj responseObj(fromjson("{ ok: 1, value: null }"));
+ BSONObj responseObj(fromjson("{ ok: 1, value: null }"));
- executor.setNextExpectedCommand([](const RemoteCommandRequest& request) {
- ASSERT_EQUALS(dummyHost, request.target);
- ASSERT_EQUALS("config", request.dbname);
+ executor.setNextExpectedCommand([](const RemoteCommandRequest& request) {
+ ASSERT_EQUALS(dummyHost, request.target);
+ ASSERT_EQUALS("config", request.dbname);
- BSONObj expectedCmd(fromjson(R"({
+ BSONObj expectedCmd(fromjson(R"({
findAndModify: "locks",
query: {
$or: [
@@ -505,29 +506,29 @@ namespace {
writeConcern: { w: "majority", j: true, wtimeout: 100 }
})"));
- ASSERT_EQUALS(expectedCmd, request.cmdObj);
- },
- RemoteCommandResponse(responseObj, Milliseconds(0)));
+ ASSERT_EQUALS(expectedCmd, request.cmdObj);
+ }, RemoteCommandResponse(responseObj, Milliseconds(0)));
- OID myID("555f80be366c194b13fb0372");
- OID currentOwner("555f99712c99a78c5b083358");
- Date_t now(dateFromISOString("2015-05-22T19:17:18.098Z").getValue());
- auto resultStatus = catalog.overtakeLock("test", myID, currentOwner,
- "me", "mongos", now, "because").getStatus();
+ OID myID("555f80be366c194b13fb0372");
+ OID currentOwner("555f99712c99a78c5b083358");
+ Date_t now(dateFromISOString("2015-05-22T19:17:18.098Z").getValue());
+ auto resultStatus =
+ catalog.overtakeLock("test", myID, currentOwner, "me", "mongos", now, "because")
+ .getStatus();
- ASSERT_NOT_OK(resultStatus);
- ASSERT_EQUALS(ErrorCodes::LockStateChangeFailed, resultStatus.code());
- }
+ ASSERT_NOT_OK(resultStatus);
+ ASSERT_EQUALS(ErrorCodes::LockStateChangeFailed, resultStatus.code());
+}
- TEST(DistLockCatalogImpl, OvertakeLockWithNewDoc) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, OvertakeLockWithNewDoc) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj responseObj(fromjson(R"({
+ BSONObj responseObj(fromjson(R"({
lastErrorObject: {
updatedExisting: false,
n: 1,
@@ -545,11 +546,11 @@ namespace {
ok: 1
})"));
- executor.setNextExpectedCommand([](const RemoteCommandRequest& request) {
- ASSERT_EQUALS(dummyHost, request.target);
- ASSERT_EQUALS("config", request.dbname);
+ executor.setNextExpectedCommand([](const RemoteCommandRequest& request) {
+ ASSERT_EQUALS(dummyHost, request.target);
+ ASSERT_EQUALS("config", request.dbname);
- BSONObj expectedCmd(fromjson(R"({
+ BSONObj expectedCmd(fromjson(R"({
findAndModify: "locks",
query: {
$or: [
@@ -571,109 +572,104 @@ namespace {
writeConcern: { w: "majority", j: true, wtimeout: 100 }
})"));
- ASSERT_EQUALS(expectedCmd, request.cmdObj);
- },
- RemoteCommandResponse(responseObj, Milliseconds(0)));
+ ASSERT_EQUALS(expectedCmd, request.cmdObj);
+ }, RemoteCommandResponse(responseObj, Milliseconds(0)));
- OID myID("555f80be366c194b13fb0372");
- OID currentOwner("555f99712c99a78c5b083358");
- Date_t now(dateFromISOString("2015-05-22T19:17:18.098Z").getValue());
- auto resultStatus = catalog.overtakeLock("test", myID, currentOwner,
- "me", "mongos", now, "because");
- ASSERT_OK(resultStatus.getStatus());
+ OID myID("555f80be366c194b13fb0372");
+ OID currentOwner("555f99712c99a78c5b083358");
+ Date_t now(dateFromISOString("2015-05-22T19:17:18.098Z").getValue());
+ auto resultStatus =
+ catalog.overtakeLock("test", myID, currentOwner, "me", "mongos", now, "because");
+ ASSERT_OK(resultStatus.getStatus());
- const auto& lockDoc = resultStatus.getValue();
- ASSERT_TRUE(lockDoc.isValid(nullptr));
- ASSERT_EQUALS("test", lockDoc.getName());
- ASSERT_EQUALS(myID, lockDoc.getLockID());
- ASSERT_EQUALS("me", lockDoc.getWho());
- ASSERT_EQUALS("mongos", lockDoc.getProcess());
- ASSERT_EQUALS("because", lockDoc.getWhy());
- }
+ const auto& lockDoc = resultStatus.getValue();
+ ASSERT_TRUE(lockDoc.isValid(nullptr));
+ ASSERT_EQUALS("test", lockDoc.getName());
+ ASSERT_EQUALS(myID, lockDoc.getLockID());
+ ASSERT_EQUALS("me", lockDoc.getWho());
+ ASSERT_EQUALS("mongos", lockDoc.getProcess());
+ ASSERT_EQUALS("because", lockDoc.getWhy());
+}
- TEST(DistLockCatalogImpl, OvertakeLockTargetError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, OvertakeLockTargetError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue({ErrorCodes::InternalError, "can't target"});
+ targeter.setFindHostReturnValue({ErrorCodes::InternalError, "can't target"});
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- auto status = catalog.overtakeLock(
- "", OID(), OID(), "", "", Date_t::now(), "").getStatus();
- ASSERT_NOT_OK(status);
- }
+ auto status = catalog.overtakeLock("", OID(), OID(), "", "", Date_t::now(), "").getStatus();
+ ASSERT_NOT_OK(status);
+}
- TEST(DistLockCatalogImpl, OvertakeLockRunnerError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, OvertakeLockRunnerError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- executor.setNextExpectedCommand(noTest, {ErrorCodes::InternalError, "Bad"});
+ executor.setNextExpectedCommand(noTest, {ErrorCodes::InternalError, "Bad"});
- auto status = catalog.overtakeLock(
- "", OID(), OID(), "", "", Date_t::now(), "").getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::InternalError, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.overtakeLock("", OID(), OID(), "", "", Date_t::now(), "").getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::InternalError, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, OvertakeLockCommandError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, OvertakeLockCommandError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 0,
errmsg: "bad",
code: 9
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.overtakeLock(
- "", OID(), OID(), "", "", Date_t::now(), "").getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::FailedToParse, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.overtakeLock("", OID(), OID(), "", "", Date_t::now(), "").getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, OvertakeLockWriteError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, OvertakeLockWriteError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 0,
code: 11000,
errmsg: "E11000 duplicate key error"
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.overtakeLock(
- "", OID(), OID(), "", "", Date_t::now(), "").getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::DuplicateKey, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.overtakeLock("", OID(), OID(), "", "", Date_t::now(), "").getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::DuplicateKey, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, OvertakeLockWriteConcernError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, OvertakeLockWriteConcernError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 1,
value: null,
writeConcernError: {
@@ -681,24 +677,23 @@ namespace {
errmsg: "waiting for replication timed out"
}
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.overtakeLock(
- "", OID(), OID(), "", "", Date_t::now(), "").getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::WriteConcernFailed, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.overtakeLock("", OID(), OID(), "", "", Date_t::now(), "").getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::WriteConcernFailed, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, OvertakeLockUnsupportedWriteConcernResponse) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, OvertakeLockUnsupportedWriteConcernResponse) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 1,
value: null,
writeConcernError: {
@@ -706,42 +701,42 @@ namespace {
errmsg: "waiting for replication timed out"
}
})"));
- executor.setNextExpectedCommand(noTest,
- RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.overtakeLock(
- "", OID(), OID(), "", "", Date_t::now(), "").getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.overtakeLock("", OID(), OID(), "", "", Date_t::now(), "").getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, OvertakeLockUnsupportedResponseFormat) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, OvertakeLockUnsupportedResponseFormat) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- executor.setNextExpectedCommand(noTest,
- RemoteCommandResponse(BSON("ok" << 1 << "value" << "NaN"), Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest,
+ RemoteCommandResponse(BSON("ok" << 1 << "value"
+ << "NaN"),
+ Milliseconds(0)));
- auto status = catalog.overtakeLock(
- "", OID(), OID(), "", "", Date_t::now(), "").getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
- }
+ auto status = catalog.overtakeLock("", OID(), OID(), "", "", Date_t::now(), "").getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
+}
- TEST(DistLockCatalogImpl, BasicUnlock) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, BasicUnlock) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- executor.setNextExpectedCommand([](const RemoteCommandRequest& request) {
+ executor.setNextExpectedCommand(
+ [](const RemoteCommandRequest& request) {
ASSERT_EQUALS(dummyHost, request.target);
ASSERT_EQUALS("config", request.dbname);
@@ -761,21 +756,23 @@ namespace {
ts: ObjectId("555f99712c99a78c5b083358"),
state: 0
}
- })"), Milliseconds(0)));
+ })"),
+ Milliseconds(0)));
- auto status = catalog.unlock(OID("555f99712c99a78c5b083358"));
- ASSERT_OK(status);
- }
+ auto status = catalog.unlock(OID("555f99712c99a78c5b083358"));
+ ASSERT_OK(status);
+}
- TEST(DistLockCatalogImpl, UnlockWithNoNewDoc) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, UnlockWithNoNewDoc) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- executor.setNextExpectedCommand([](const RemoteCommandRequest& request) {
+ executor.setNextExpectedCommand(
+ [](const RemoteCommandRequest& request) {
ASSERT_EQUALS(dummyHost, request.target);
ASSERT_EQUALS("config", request.dbname);
@@ -791,91 +788,92 @@ namespace {
RemoteCommandResponse(fromjson(R"({
ok: 1,
value: null
- })"), Milliseconds(0)));
+ })"),
+ Milliseconds(0)));
- auto status = catalog.unlock(OID("555f99712c99a78c5b083358"));
- ASSERT_OK(status);
- }
+ auto status = catalog.unlock(OID("555f99712c99a78c5b083358"));
+ ASSERT_OK(status);
+}
- TEST(DistLockCatalogImpl, UnlockTargetError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, UnlockTargetError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue({ErrorCodes::InternalError, "can't target"});
+ targeter.setFindHostReturnValue({ErrorCodes::InternalError, "can't target"});
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- auto status = catalog.unlock(OID());
- ASSERT_NOT_OK(status);
- }
+ auto status = catalog.unlock(OID());
+ ASSERT_NOT_OK(status);
+}
- TEST(DistLockCatalogImpl, UnlockRunnerError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, UnlockRunnerError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- executor.setNextExpectedCommand(noTest, {ErrorCodes::InternalError, "Bad"});
+ executor.setNextExpectedCommand(noTest, {ErrorCodes::InternalError, "Bad"});
- auto status = catalog.unlock(OID());
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::InternalError, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.unlock(OID());
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::InternalError, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, UnlockCommandError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, UnlockCommandError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 0,
errmsg: "bad",
code: 9
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.unlock(OID());
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::FailedToParse, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.unlock(OID());
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, UnlockWriteError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, UnlockWriteError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 0,
code: 11000,
errmsg: "E11000 duplicate key error"
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.unlock(OID());
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::DuplicateKey, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.unlock(OID());
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::DuplicateKey, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, UnlockWriteConcernError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, UnlockWriteConcernError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 1,
value: null,
writeConcernError: {
@@ -883,23 +881,23 @@ namespace {
errmsg: "waiting for replication timed out"
}
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.unlock(OID());
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::WriteConcernFailed, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.unlock(OID());
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::WriteConcernFailed, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, UnlockUnsupportedWriteConcernResponse) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, UnlockUnsupportedWriteConcernResponse) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 1,
value: null,
writeConcernError: {
@@ -907,39 +905,41 @@ namespace {
errmsg: "waiting for replication timed out"
}
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.unlock(OID());
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.unlock(OID());
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, UnlockUnsupportedResponseFormat) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, UnlockUnsupportedResponseFormat) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- executor.setNextExpectedCommand(noTest,
- RemoteCommandResponse(BSON("ok" << 1 << "value" << "NaN"), Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest,
+ RemoteCommandResponse(BSON("ok" << 1 << "value"
+ << "NaN"),
+ Milliseconds(0)));
- auto status = catalog.unlock(OID());
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
- }
+ auto status = catalog.unlock(OID());
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
+}
- TEST(DistLockCatalogImpl, BasicGetServerInfo) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, BasicGetServerInfo) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj responseObj(fromjson(R"({
+ BSONObj responseObj(fromjson(R"({
localTime: { $date: "2015-05-26T13:06:27.293Z" },
$gleStats: {
lastOpTime: { $timestamp: { t: 0, i: 0 }},
@@ -948,81 +948,80 @@ namespace {
ok: 1
})"));
- executor.setNextExpectedCommand([](const RemoteCommandRequest& request) {
- ASSERT_EQUALS(dummyHost, request.target);
- ASSERT_EQUALS("admin", request.dbname);
- ASSERT_EQUALS(BSON("serverStatus" << 1), request.cmdObj);
- },
- RemoteCommandResponse(responseObj, Milliseconds(0)));
+ executor.setNextExpectedCommand([](const RemoteCommandRequest& request) {
+ ASSERT_EQUALS(dummyHost, request.target);
+ ASSERT_EQUALS("admin", request.dbname);
+ ASSERT_EQUALS(BSON("serverStatus" << 1), request.cmdObj);
+ }, RemoteCommandResponse(responseObj, Milliseconds(0)));
- Date_t localTime(dateFromISOString("2015-05-26T13:06:27.293Z").getValue());
- OID electionID("555fa85d4d8640862a0fc79b");
- auto resultStatus = catalog.getServerInfo();
- ASSERT_OK(resultStatus.getStatus());
+ Date_t localTime(dateFromISOString("2015-05-26T13:06:27.293Z").getValue());
+ OID electionID("555fa85d4d8640862a0fc79b");
+ auto resultStatus = catalog.getServerInfo();
+ ASSERT_OK(resultStatus.getStatus());
- const auto& serverInfo = resultStatus.getValue();
- ASSERT_EQUALS(electionID, serverInfo.electionId);
- ASSERT_EQUALS(localTime, serverInfo.serverTime);
- }
+ const auto& serverInfo = resultStatus.getValue();
+ ASSERT_EQUALS(electionID, serverInfo.electionId);
+ ASSERT_EQUALS(localTime, serverInfo.serverTime);
+}
- TEST(DistLockCatalogImpl, GetServerTargetError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, GetServerTargetError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue({ErrorCodes::InternalError, "can't target"});
+ targeter.setFindHostReturnValue({ErrorCodes::InternalError, "can't target"});
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- auto status = catalog.getServerInfo().getStatus();
- ASSERT_NOT_OK(status);
- }
+ auto status = catalog.getServerInfo().getStatus();
+ ASSERT_NOT_OK(status);
+}
- TEST(DistLockCatalogImpl, GetServerRunnerError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, GetServerRunnerError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- executor.setNextExpectedCommand(noTest, {ErrorCodes::InternalError, "Bad"});
+ executor.setNextExpectedCommand(noTest, {ErrorCodes::InternalError, "Bad"});
- auto status = catalog.getServerInfo().getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::InternalError, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.getServerInfo().getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::InternalError, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, GetServerCommandError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, GetServerCommandError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 0,
errmsg: "bad",
code: 9
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.getServerInfo().getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::FailedToParse, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.getServerInfo().getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, GetServerBadElectionId) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, GetServerBadElectionId) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
localTime: { $date: "2015-05-26T13:06:27.293Z" },
$gleStats: {
lastOpTime: { $timestamp: { t: 0, i: 0 }},
@@ -1030,23 +1029,23 @@ namespace {
},
ok: 1
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.getServerInfo().getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.getServerInfo().getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, GetServerBadLocalTime) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, GetServerBadLocalTime) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
localTime: "2015-05-26T13:06:27.293Z",
$gleStats: {
lastOpTime: { $timestamp: { t: 0, i: 0 }},
@@ -1054,43 +1053,43 @@ namespace {
},
ok: 1
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.getServerInfo().getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.getServerInfo().getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, GetServerNoGLEStats) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, GetServerNoGLEStats) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
localTime: { $date: "2015-05-26T13:06:27.293Z" },
ok: 1
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.getServerInfo().getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.getServerInfo().getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, GetServerNoElectionId) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, GetServerNoElectionId) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
localTime: { $date: "2015-05-26T13:06:27.293Z" },
$gleStats: {
lastOpTime: { $timestamp: { t: 0, i: 0 }},
@@ -1098,23 +1097,24 @@ namespace {
},
ok: 1
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.getServerInfo().getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.getServerInfo().getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, BasicStopPing) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, BasicStopPing) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- executor.setNextExpectedCommand([](const RemoteCommandRequest& request) {
+ executor.setNextExpectedCommand(
+ [](const RemoteCommandRequest& request) {
ASSERT_EQUALS(dummyHost, request.target);
ASSERT_EQUALS("config", request.dbname);
@@ -1133,91 +1133,92 @@ namespace {
_id: "test",
ping: { $date: "2014-03-11T09:17:18.098Z" }
}
- })"), Milliseconds(0)));
+ })"),
+ Milliseconds(0)));
- auto status = catalog.stopPing("test");
- ASSERT_OK(status);
- }
+ auto status = catalog.stopPing("test");
+ ASSERT_OK(status);
+}
- TEST(DistLockCatalogImpl, StopPingTargetError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, StopPingTargetError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue({ErrorCodes::InternalError, "can't target"});
+ targeter.setFindHostReturnValue({ErrorCodes::InternalError, "can't target"});
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- auto status = catalog.stopPing("");
- ASSERT_NOT_OK(status);
- }
+ auto status = catalog.stopPing("");
+ ASSERT_NOT_OK(status);
+}
- TEST(DistLockCatalogImpl, StopPingRunnerError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, StopPingRunnerError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- executor.setNextExpectedCommand(noTest, {ErrorCodes::InternalError, "Bad"});
+ executor.setNextExpectedCommand(noTest, {ErrorCodes::InternalError, "Bad"});
- auto status = catalog.stopPing("");
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::InternalError, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.stopPing("");
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::InternalError, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, StopPingCommandError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, StopPingCommandError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 0,
errmsg: "bad",
code: 9
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.stopPing("");
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::FailedToParse, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.stopPing("");
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, StopPingWriteError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, StopPingWriteError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 0,
code: 13,
errmsg: "Unauthorized"
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.stopPing("");
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::Unauthorized, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.stopPing("");
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::Unauthorized, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, StopPingWriteConcernError) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, StopPingWriteConcernError) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 1,
value: null,
writeConcernError: {
@@ -1225,23 +1226,23 @@ namespace {
errmsg: "waiting for replication timed out"
}
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.stopPing("");
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::WriteConcernFailed, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.stopPing("");
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::WriteConcernFailed, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, StopPingUnsupportedWriteConcernResponse) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, StopPingUnsupportedWriteConcernResponse) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- BSONObj returnObj(fromjson(R"({
+ BSONObj returnObj(fromjson(R"({
ok: 1,
value: null,
writeConcernError: {
@@ -1249,29 +1250,31 @@ namespace {
errmsg: "waiting for replication timed out"
}
})"));
- executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest, RemoteCommandResponse(returnObj, Milliseconds(0)));
- auto status = catalog.stopPing("");
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
- ASSERT_FALSE(status.reason().empty());
- }
+ auto status = catalog.stopPing("");
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
+ ASSERT_FALSE(status.reason().empty());
+}
- TEST(DistLockCatalogImpl, StopPingUnsupportedResponseFormat) {
- RemoteCommandTargeterMock targeter;
- RemoteCommandRunnerMock executor;
+TEST(DistLockCatalogImpl, StopPingUnsupportedResponseFormat) {
+ RemoteCommandTargeterMock targeter;
+ RemoteCommandRunnerMock executor;
- targeter.setFindHostReturnValue(dummyHost);
+ targeter.setFindHostReturnValue(dummyHost);
- DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
+ DistLockCatalogImpl catalog(&targeter, &executor, kWTimeout);
- executor.setNextExpectedCommand(noTest,
- RemoteCommandResponse(BSON("ok" << 1 << "value" << "NaN"), Milliseconds(0)));
+ executor.setNextExpectedCommand(noTest,
+ RemoteCommandResponse(BSON("ok" << 1 << "value"
+ << "NaN"),
+ Milliseconds(0)));
- auto status = catalog.stopPing("");
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
- }
+ auto status = catalog.stopPing("");
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
+}
-} // unnamed namespace
-} // namespace mongo
+} // unnamed namespace
+} // namespace mongo
diff --git a/src/mongo/s/catalog/dist_lock_catalog_mock.cpp b/src/mongo/s/catalog/dist_lock_catalog_mock.cpp
index 9b8fc3f8f5a..260d4dce6f3 100644
--- a/src/mongo/s/catalog/dist_lock_catalog_mock.cpp
+++ b/src/mongo/s/catalog/dist_lock_catalog_mock.cpp
@@ -40,302 +40,292 @@
namespace mongo {
namespace {
- Status kBadRetValue(ErrorCodes::InternalError, "no return value");
- StatusWith<LocksType> kLocksTypeBadRetValue(kBadRetValue);
- StatusWith<LockpingsType> kLockpingsTypeBadRetValue(kBadRetValue);
- StatusWith<DistLockCatalog::ServerInfo> kServerInfoBadRetValue(kBadRetValue);
+Status kBadRetValue(ErrorCodes::InternalError, "no return value");
+StatusWith<LocksType> kLocksTypeBadRetValue(kBadRetValue);
+StatusWith<LockpingsType> kLockpingsTypeBadRetValue(kBadRetValue);
+StatusWith<DistLockCatalog::ServerInfo> kServerInfoBadRetValue(kBadRetValue);
+
+void noGrabLockFuncSet(StringData lockID,
+ const OID& lockSessionID,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
+ FAIL(str::stream() << "grabLock not expected to be called. "
+ << "lockID: " << lockID << ", who: " << who << ", processId: " << processId
+ << ", why: " << why);
+}
- void noGrabLockFuncSet(StringData lockID,
+void noOvertakeLockFuncSet(StringData lockID,
const OID& lockSessionID,
+ const OID& currentHolderTS,
StringData who,
StringData processId,
Date_t time,
StringData why) {
- FAIL(str::stream() << "grabLock not expected to be called. "
- << "lockID: " << lockID
- << ", who: " << who
- << ", processId: " << processId
- << ", why: " << why);
- }
-
- void noOvertakeLockFuncSet(StringData lockID,
- const OID& lockSessionID,
- const OID& currentHolderTS,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
- FAIL(str::stream() << "overtakeLock not expected to be called. "
- << "lockID: " << lockID
- << ", currentHolderTS: " << currentHolderTS
- << ", who: " << who
- << ", processId: " << processId
- << ", why: " << why);
- }
-
- void noUnLockFuncSet(const OID& lockSessionID) {
- FAIL(str::stream() << "unlock not expected to be called. "
- << "lockSessionID: " << lockSessionID);
- }
+ FAIL(str::stream() << "overtakeLock not expected to be called. "
+ << "lockID: " << lockID << ", currentHolderTS: " << currentHolderTS
+ << ", who: " << who << ", processId: " << processId << ", why: " << why);
+}
- void noPingFuncSet(StringData processID, Date_t ping) {
- // Ping is expected to be called all the time, so default behavior is do nothing.
- }
+void noUnLockFuncSet(const OID& lockSessionID) {
+ FAIL(str::stream() << "unlock not expected to be called. "
+ << "lockSessionID: " << lockSessionID);
+}
- void noStopPingFuncSet(StringData processID) {
- FAIL(str::stream() << "stopPing not expected to be called. "
- << "processID: " << processID);
- }
+void noPingFuncSet(StringData processID, Date_t ping) {
+ // Ping is expected to be called all the time, so default behavior is do nothing.
+}
- void noGetLockByTSSet(const OID& lockSessionID) {
- FAIL(str::stream() << "getLockByTS not expected to be called. "
- << "lockSessionID: " << lockSessionID);
- }
+void noStopPingFuncSet(StringData processID) {
+ FAIL(str::stream() << "stopPing not expected to be called. "
+ << "processID: " << processID);
+}
- void noGetLockByNameSet(StringData name) {
- FAIL(str::stream() << "getLockByName not expected to be called. "
- << "lockName: " << name);
- }
+void noGetLockByTSSet(const OID& lockSessionID) {
+ FAIL(str::stream() << "getLockByTS not expected to be called. "
+ << "lockSessionID: " << lockSessionID);
+}
- void noGetPingSet(StringData processId) {
- FAIL(str::stream() << "getPing not expected to be called. "
- << "lockName: " << processId);
- }
+void noGetLockByNameSet(StringData name) {
+ FAIL(str::stream() << "getLockByName not expected to be called. "
+ << "lockName: " << name);
+}
- void noGetServerInfoSet() {
- FAIL("getServerInfo not expected to be called");
- }
+void noGetPingSet(StringData processId) {
+ FAIL(str::stream() << "getPing not expected to be called. "
+ << "lockName: " << processId);
+}
-} // unnamed namespace
-
- DistLockCatalogMock::DistLockCatalogMock():
- _grabLockChecker(noGrabLockFuncSet),
- _grabLockReturnValue(kLocksTypeBadRetValue),
- _unlockChecker(noUnLockFuncSet),
- _unlockReturnValue(kBadRetValue),
- _pingChecker(noPingFuncSet),
- _pingReturnValue(kBadRetValue),
- _stopPingChecker(noStopPingFuncSet),
- _stopPingReturnValue(kBadRetValue),
- _getLockByTSChecker(noGetLockByTSSet),
- _getLockByTSReturnValue(kLocksTypeBadRetValue),
- _getLockByNameChecker(noGetLockByNameSet),
- _getLockByNameReturnValue(kLocksTypeBadRetValue),
- _overtakeLockChecker(noOvertakeLockFuncSet),
- _overtakeLockReturnValue(kLocksTypeBadRetValue),
- _getPingChecker(noGetPingSet),
- _getPingReturnValue(kLockpingsTypeBadRetValue),
- _getServerInfoChecker(noGetServerInfoSet),
- _getServerInfoReturnValue(kServerInfoBadRetValue) {
- }
+void noGetServerInfoSet() {
+ FAIL("getServerInfo not expected to be called");
+}
- DistLockCatalogMock::~DistLockCatalogMock() {
+} // unnamed namespace
+
+DistLockCatalogMock::DistLockCatalogMock()
+ : _grabLockChecker(noGrabLockFuncSet),
+ _grabLockReturnValue(kLocksTypeBadRetValue),
+ _unlockChecker(noUnLockFuncSet),
+ _unlockReturnValue(kBadRetValue),
+ _pingChecker(noPingFuncSet),
+ _pingReturnValue(kBadRetValue),
+ _stopPingChecker(noStopPingFuncSet),
+ _stopPingReturnValue(kBadRetValue),
+ _getLockByTSChecker(noGetLockByTSSet),
+ _getLockByTSReturnValue(kLocksTypeBadRetValue),
+ _getLockByNameChecker(noGetLockByNameSet),
+ _getLockByNameReturnValue(kLocksTypeBadRetValue),
+ _overtakeLockChecker(noOvertakeLockFuncSet),
+ _overtakeLockReturnValue(kLocksTypeBadRetValue),
+ _getPingChecker(noGetPingSet),
+ _getPingReturnValue(kLockpingsTypeBadRetValue),
+ _getServerInfoChecker(noGetServerInfoSet),
+ _getServerInfoReturnValue(kServerInfoBadRetValue) {}
+
+DistLockCatalogMock::~DistLockCatalogMock() {}
+
+StatusWith<LockpingsType> DistLockCatalogMock::getPing(StringData processID) {
+ auto ret = kLockpingsTypeBadRetValue;
+ GetPingFunc checkerFunc = noGetPingSet;
+
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ ret = _getPingReturnValue;
+ checkerFunc = _getPingChecker;
}
- StatusWith<LockpingsType> DistLockCatalogMock::getPing(StringData processID) {
- auto ret = kLockpingsTypeBadRetValue;
- GetPingFunc checkerFunc = noGetPingSet;
+ checkerFunc(processID);
+ return ret;
+}
- {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- ret = _getPingReturnValue;
- checkerFunc = _getPingChecker;
- }
+Status DistLockCatalogMock::ping(StringData processID, Date_t ping) {
+ auto ret = kBadRetValue;
+ PingFunc checkerFunc = noPingFuncSet;
- checkerFunc(processID);
- return ret;
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ ret = _pingReturnValue;
+ checkerFunc = _pingChecker;
}
- Status DistLockCatalogMock::ping(StringData processID, Date_t ping) {
- auto ret = kBadRetValue;
- PingFunc checkerFunc = noPingFuncSet;
+ checkerFunc(processID, ping);
+ return ret;
+}
- {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- ret = _pingReturnValue;
- checkerFunc = _pingChecker;
- }
+StatusWith<LocksType> DistLockCatalogMock::grabLock(StringData lockID,
+ const OID& lockSessionID,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
+ auto ret = kLocksTypeBadRetValue;
+ GrabLockFunc checkerFunc = noGrabLockFuncSet;
- checkerFunc(processID, ping);
- return ret;
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ ret = _grabLockReturnValue;
+ checkerFunc = _grabLockChecker;
}
- StatusWith<LocksType> DistLockCatalogMock::grabLock(StringData lockID,
+ checkerFunc(lockID, lockSessionID, who, processId, time, why);
+ return ret;
+}
+
+StatusWith<LocksType> DistLockCatalogMock::overtakeLock(StringData lockID,
const OID& lockSessionID,
+ const OID& currentHolderTS,
StringData who,
StringData processId,
Date_t time,
StringData why) {
- auto ret = kLocksTypeBadRetValue;
- GrabLockFunc checkerFunc = noGrabLockFuncSet;
-
- {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- ret = _grabLockReturnValue;
- checkerFunc = _grabLockChecker;
- }
+ auto ret = kLocksTypeBadRetValue;
+ OvertakeLockFunc checkerFunc = noOvertakeLockFuncSet;
- checkerFunc(lockID, lockSessionID, who, processId, time, why);
- return ret;
- }
-
- StatusWith<LocksType> DistLockCatalogMock::overtakeLock(StringData lockID,
- const OID& lockSessionID,
- const OID& currentHolderTS,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
- auto ret = kLocksTypeBadRetValue;
- OvertakeLockFunc checkerFunc = noOvertakeLockFuncSet;
-
- {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- ret = _overtakeLockReturnValue;
- checkerFunc = _overtakeLockChecker;
- }
-
- checkerFunc(lockID, lockSessionID, currentHolderTS, who, processId, time, why);
- return ret;
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ ret = _overtakeLockReturnValue;
+ checkerFunc = _overtakeLockChecker;
}
- Status DistLockCatalogMock::unlock(const OID& lockSessionID) {
- auto ret = kBadRetValue;
- UnlockFunc checkerFunc = noUnLockFuncSet;
+ checkerFunc(lockID, lockSessionID, currentHolderTS, who, processId, time, why);
+ return ret;
+}
- {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- ret = _unlockReturnValue;
- checkerFunc = _unlockChecker;
- }
+Status DistLockCatalogMock::unlock(const OID& lockSessionID) {
+ auto ret = kBadRetValue;
+ UnlockFunc checkerFunc = noUnLockFuncSet;
- checkerFunc(lockSessionID);
- return ret;
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ ret = _unlockReturnValue;
+ checkerFunc = _unlockChecker;
}
- StatusWith<DistLockCatalog::ServerInfo> DistLockCatalogMock::getServerInfo() {
- auto ret = kServerInfoBadRetValue;
- GetServerInfoFunc checkerFunc = noGetServerInfoSet;
+ checkerFunc(lockSessionID);
+ return ret;
+}
- {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- ret = _getServerInfoReturnValue;
- checkerFunc = _getServerInfoChecker;
- }
+StatusWith<DistLockCatalog::ServerInfo> DistLockCatalogMock::getServerInfo() {
+ auto ret = kServerInfoBadRetValue;
+ GetServerInfoFunc checkerFunc = noGetServerInfoSet;
- checkerFunc();
- return ret;
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ ret = _getServerInfoReturnValue;
+ checkerFunc = _getServerInfoChecker;
}
- StatusWith<LocksType> DistLockCatalogMock::getLockByTS(const OID& lockSessionID) {
- auto ret = kLocksTypeBadRetValue;
- GetLockByTSFunc checkerFunc = noGetLockByTSSet;
+ checkerFunc();
+ return ret;
+}
- {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- ret = _getLockByTSReturnValue;
- checkerFunc = _getLockByTSChecker;
- }
+StatusWith<LocksType> DistLockCatalogMock::getLockByTS(const OID& lockSessionID) {
+ auto ret = kLocksTypeBadRetValue;
+ GetLockByTSFunc checkerFunc = noGetLockByTSSet;
- checkerFunc(lockSessionID);
- return ret;
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ ret = _getLockByTSReturnValue;
+ checkerFunc = _getLockByTSChecker;
}
- StatusWith<LocksType> DistLockCatalogMock::getLockByName(StringData name) {
- auto ret = kLocksTypeBadRetValue;
- GetLockByNameFunc checkerFunc = noGetLockByNameSet;
+ checkerFunc(lockSessionID);
+ return ret;
+}
- {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- ret = _getLockByNameReturnValue;
- checkerFunc = _getLockByNameChecker;
- }
+StatusWith<LocksType> DistLockCatalogMock::getLockByName(StringData name) {
+ auto ret = kLocksTypeBadRetValue;
+ GetLockByNameFunc checkerFunc = noGetLockByNameSet;
- checkerFunc(name);
- return ret;
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ ret = _getLockByNameReturnValue;
+ checkerFunc = _getLockByNameChecker;
}
- Status DistLockCatalogMock::stopPing(StringData processId) {
- auto ret = kBadRetValue;
- StopPingFunc checkerFunc = noStopPingFuncSet;
-
- {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- ret = _stopPingReturnValue;
- checkerFunc = _stopPingChecker;
- }
+ checkerFunc(name);
+ return ret;
+}
- checkerFunc(processId);
- return ret;
- }
+Status DistLockCatalogMock::stopPing(StringData processId) {
+ auto ret = kBadRetValue;
+ StopPingFunc checkerFunc = noStopPingFuncSet;
- void DistLockCatalogMock::expectGrabLock(DistLockCatalogMock::GrabLockFunc checkerFunc,
- StatusWith<LocksType> returnThis) {
+ {
stdx::lock_guard<stdx::mutex> lk(_mutex);
- _grabLockChecker = checkerFunc;
- _grabLockReturnValue = returnThis;
+ ret = _stopPingReturnValue;
+ checkerFunc = _stopPingChecker;
}
- void DistLockCatalogMock::expectNoGrabLock() {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- _grabLockChecker = noGrabLockFuncSet;
- _grabLockReturnValue = kLocksTypeBadRetValue;
- }
+ checkerFunc(processId);
+ return ret;
+}
- void DistLockCatalogMock::expectUnLock(DistLockCatalogMock::UnlockFunc checkerFunc,
- Status returnThis) {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- _unlockChecker = checkerFunc;
- _unlockReturnValue = returnThis;
- }
+void DistLockCatalogMock::expectGrabLock(DistLockCatalogMock::GrabLockFunc checkerFunc,
+ StatusWith<LocksType> returnThis) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _grabLockChecker = checkerFunc;
+ _grabLockReturnValue = returnThis;
+}
- void DistLockCatalogMock::expectPing(DistLockCatalogMock::PingFunc checkerFunc,
- Status returnThis) {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- _pingChecker = checkerFunc;
- _pingReturnValue = returnThis;
- }
+void DistLockCatalogMock::expectNoGrabLock() {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _grabLockChecker = noGrabLockFuncSet;
+ _grabLockReturnValue = kLocksTypeBadRetValue;
+}
- void DistLockCatalogMock::expectStopPing(StopPingFunc checkerFunc, Status returnThis) {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- _stopPingChecker = checkerFunc;
- _stopPingReturnValue = returnThis;
- }
+void DistLockCatalogMock::expectUnLock(DistLockCatalogMock::UnlockFunc checkerFunc,
+ Status returnThis) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _unlockChecker = checkerFunc;
+ _unlockReturnValue = returnThis;
+}
- void DistLockCatalogMock::expectGetLockByTS(GetLockByTSFunc checkerFunc,
- StatusWith<LocksType> returnThis) {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- _getLockByTSChecker = checkerFunc;
- _getLockByTSReturnValue = returnThis;
- }
+void DistLockCatalogMock::expectPing(DistLockCatalogMock::PingFunc checkerFunc, Status returnThis) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _pingChecker = checkerFunc;
+ _pingReturnValue = returnThis;
+}
- void DistLockCatalogMock::expectGetLockByName(GetLockByNameFunc checkerFunc,
- StatusWith<LocksType> returnThis) {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- _getLockByNameChecker = checkerFunc;
- _getLockByNameReturnValue = returnThis;
- }
+void DistLockCatalogMock::expectStopPing(StopPingFunc checkerFunc, Status returnThis) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _stopPingChecker = checkerFunc;
+ _stopPingReturnValue = returnThis;
+}
- void DistLockCatalogMock::expectOvertakeLock(OvertakeLockFunc checkerFunc,
- StatusWith<LocksType> returnThis) {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- _overtakeLockChecker = checkerFunc;
- _overtakeLockReturnValue = returnThis;
- }
+void DistLockCatalogMock::expectGetLockByTS(GetLockByTSFunc checkerFunc,
+ StatusWith<LocksType> returnThis) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _getLockByTSChecker = checkerFunc;
+ _getLockByTSReturnValue = returnThis;
+}
- void DistLockCatalogMock::expectGetPing(GetPingFunc checkerFunc,
- StatusWith<LockpingsType> returnThis) {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- _getPingChecker = checkerFunc;
- _getPingReturnValue = returnThis;
- }
+void DistLockCatalogMock::expectGetLockByName(GetLockByNameFunc checkerFunc,
+ StatusWith<LocksType> returnThis) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _getLockByNameChecker = checkerFunc;
+ _getLockByNameReturnValue = returnThis;
+}
- void DistLockCatalogMock::expectGetServerInfo(
- GetServerInfoFunc checkerFunc,
- StatusWith<DistLockCatalog::ServerInfo> returnThis) {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- _getServerInfoChecker = checkerFunc;
- _getServerInfoReturnValue = returnThis;
- }
+void DistLockCatalogMock::expectOvertakeLock(OvertakeLockFunc checkerFunc,
+ StatusWith<LocksType> returnThis) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _overtakeLockChecker = checkerFunc;
+ _overtakeLockReturnValue = returnThis;
+}
+
+void DistLockCatalogMock::expectGetPing(GetPingFunc checkerFunc,
+ StatusWith<LockpingsType> returnThis) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _getPingChecker = checkerFunc;
+ _getPingReturnValue = returnThis;
+}
+void DistLockCatalogMock::expectGetServerInfo(GetServerInfoFunc checkerFunc,
+ StatusWith<DistLockCatalog::ServerInfo> returnThis) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _getServerInfoChecker = checkerFunc;
+ _getServerInfoReturnValue = returnThis;
+}
}
diff --git a/src/mongo/s/catalog/dist_lock_catalog_mock.h b/src/mongo/s/catalog/dist_lock_catalog_mock.h
index 1b8282f4704..6a8ffa01f33 100644
--- a/src/mongo/s/catalog/dist_lock_catalog_mock.h
+++ b/src/mongo/s/catalog/dist_lock_catalog_mock.h
@@ -37,174 +37,174 @@
namespace mongo {
- /**
- * Mock implementation of DistLockCatalog for testing.
- *
- * Example usage:
- *
- * DistLockCatalogMock mock;
- * LocksType badLock;
- * mock.expectGrabLock([](StringData lockID,
- * const OID& lockSessionID,
- * StringData who,
- * StringData processId,
- * Date_t time,
- * StringData why) {
- * ASSERT_EQUALS("test", lockID);
- * }, badLock);
- *
- * mock.grabLock("test", OID(), "me", "x", Date_t::now(), "end");
- *
- * It is also possible to chain the callbacks. For example, if we want to set the test
- * such that grabLock can only be called once, you can do this:
- *
- * DistLockCatalogMock mock;
- * mock.expectGrabLock([&mock](...) {
- * mock.expectNoGrabLock();
- * }, Status::OK());
- */
- class DistLockCatalogMock : public DistLockCatalog {
- public:
- DistLockCatalogMock();
- virtual ~DistLockCatalogMock();
-
- using GrabLockFunc = stdx::function<void (StringData lockID,
- const OID& lockSessionID,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why)>;
- using OvertakeLockFunc = stdx::function<void (StringData lockID,
- const OID& lockSessionID,
- const OID& currentHolderTS,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why)>;
- using UnlockFunc = stdx::function<void (const OID& lockSessionID)>;
- using PingFunc = stdx::function<void (StringData processID, Date_t ping)>;
- using StopPingFunc = stdx::function<void (StringData processID)>;
- using GetPingFunc = StopPingFunc;
- using GetLockByTSFunc = stdx::function<void (const OID& ts)>;
- using GetLockByNameFunc = stdx::function<void (StringData name)>;
- using GetServerInfoFunc = stdx::function<void ()>;
-
- virtual StatusWith<LockpingsType> getPing(StringData processID) override;
-
- virtual Status ping(StringData processID, Date_t ping) override;
-
- virtual StatusWith<LocksType> grabLock(StringData lockID,
+/**
+ * Mock implementation of DistLockCatalog for testing.
+ *
+ * Example usage:
+ *
+ * DistLockCatalogMock mock;
+ * LocksType badLock;
+ * mock.expectGrabLock([](StringData lockID,
+ * const OID& lockSessionID,
+ * StringData who,
+ * StringData processId,
+ * Date_t time,
+ * StringData why) {
+ * ASSERT_EQUALS("test", lockID);
+ * }, badLock);
+ *
+ * mock.grabLock("test", OID(), "me", "x", Date_t::now(), "end");
+ *
+ * It is also possible to chain the callbacks. For example, if we want to set the test
+ * such that grabLock can only be called once, you can do this:
+ *
+ * DistLockCatalogMock mock;
+ * mock.expectGrabLock([&mock](...) {
+ * mock.expectNoGrabLock();
+ * }, Status::OK());
+ */
+class DistLockCatalogMock : public DistLockCatalog {
+public:
+ DistLockCatalogMock();
+ virtual ~DistLockCatalogMock();
+
+ using GrabLockFunc = stdx::function<void(StringData lockID,
+ const OID& lockSessionID,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why)>;
+ using OvertakeLockFunc = stdx::function<void(StringData lockID,
+ const OID& lockSessionID,
+ const OID& currentHolderTS,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why)>;
+ using UnlockFunc = stdx::function<void(const OID& lockSessionID)>;
+ using PingFunc = stdx::function<void(StringData processID, Date_t ping)>;
+ using StopPingFunc = stdx::function<void(StringData processID)>;
+ using GetPingFunc = StopPingFunc;
+ using GetLockByTSFunc = stdx::function<void(const OID& ts)>;
+ using GetLockByNameFunc = stdx::function<void(StringData name)>;
+ using GetServerInfoFunc = stdx::function<void()>;
+
+ virtual StatusWith<LockpingsType> getPing(StringData processID) override;
+
+ virtual Status ping(StringData processID, Date_t ping) override;
+
+ virtual StatusWith<LocksType> grabLock(StringData lockID,
+ const OID& lockSessionID,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) override;
+
+ virtual StatusWith<LocksType> overtakeLock(StringData lockID,
const OID& lockSessionID,
+ const OID& currentHolderTS,
StringData who,
StringData processId,
Date_t time,
StringData why) override;
- virtual StatusWith<LocksType> overtakeLock(StringData lockID,
- const OID& lockSessionID,
- const OID& currentHolderTS,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) override;
-
- virtual Status unlock(const OID& lockSessionID) override;
-
- virtual StatusWith<ServerInfo> getServerInfo() override;
-
- virtual StatusWith<LocksType> getLockByTS(const OID& lockSessionID) override;
-
- virtual StatusWith<LocksType> getLockByName(StringData name) override;
-
- virtual Status stopPing(StringData processId) override;
-
- /**
- * Sets the checker method to use and the return value for grabLock to return every
- * time it is called.
- */
- void expectGrabLock(GrabLockFunc checkerFunc, StatusWith<LocksType> returnThis);
-
- /**
- * Expect grabLock to never be called after this is called.
- */
- void expectNoGrabLock();
-
- /**
- * Sets the checker method to use and the return value for unlock to return every
- * time it is called.
- */
- void expectUnLock(UnlockFunc checkerFunc, Status returnThis);
-
- /**
- * Sets the checker method to use and its return value the every time ping is called.
- */
- void expectPing(PingFunc checkerFunc, Status returnThis);
-
- /**
- * Sets the checker method to use and its return value the every time stopPing is called.
- */
- void expectStopPing(StopPingFunc checkerFunc, Status returnThis);
-
- /**
- * Sets the checker method to use and its return value the every time
- * getLockByTS is called.
- */
- void expectGetLockByTS(GetLockByTSFunc checkerFunc, StatusWith<LocksType> returnThis);
-
- /**
- * Sets the checker method to use and its return value the every time
- * getLockByName is called.
- */
- void expectGetLockByName(GetLockByNameFunc checkerFunc, StatusWith<LocksType> returnThis);
-
- /**
- * Sets the checker method to use and its return value the every time
- * overtakeLock is called.
- */
- void expectOvertakeLock(OvertakeLockFunc checkerFunc, StatusWith<LocksType> returnThis);
-
- /**
- * Sets the checker method to use and its return value the every time
- * getPing is called.
- */
- void expectGetPing(GetPingFunc checkerFunc, StatusWith<LockpingsType> returnThis);
-
- /**
- * Sets the checker method to use and its return value the every time
- * getServerInfo is called.
- */
- void expectGetServerInfo(GetServerInfoFunc checkerFunc,
- StatusWith<DistLockCatalog::ServerInfo> returnThis);
-
- private:
- // Protects all the member variables.
- stdx::mutex _mutex;
-
- GrabLockFunc _grabLockChecker;
- StatusWith<LocksType> _grabLockReturnValue;
-
- UnlockFunc _unlockChecker;
- Status _unlockReturnValue;
-
- PingFunc _pingChecker;
- Status _pingReturnValue;
-
- StopPingFunc _stopPingChecker;
- Status _stopPingReturnValue;
-
- GetLockByTSFunc _getLockByTSChecker;
- StatusWith<LocksType> _getLockByTSReturnValue;
-
- GetLockByNameFunc _getLockByNameChecker;
- StatusWith<LocksType> _getLockByNameReturnValue;
-
- OvertakeLockFunc _overtakeLockChecker;
- StatusWith<LocksType> _overtakeLockReturnValue;
-
- GetPingFunc _getPingChecker;
- StatusWith<LockpingsType> _getPingReturnValue;
-
- GetServerInfoFunc _getServerInfoChecker;
- StatusWith<DistLockCatalog::ServerInfo> _getServerInfoReturnValue;
- };
+ virtual Status unlock(const OID& lockSessionID) override;
+
+ virtual StatusWith<ServerInfo> getServerInfo() override;
+
+ virtual StatusWith<LocksType> getLockByTS(const OID& lockSessionID) override;
+
+ virtual StatusWith<LocksType> getLockByName(StringData name) override;
+
+ virtual Status stopPing(StringData processId) override;
+
+ /**
+ * Sets the checker method to use and the return value for grabLock to return every
+ * time it is called.
+ */
+ void expectGrabLock(GrabLockFunc checkerFunc, StatusWith<LocksType> returnThis);
+
+ /**
+ * Expect grabLock to never be called after this is called.
+ */
+ void expectNoGrabLock();
+
+ /**
+ * Sets the checker method to use and the return value for unlock to return every
+ * time it is called.
+ */
+ void expectUnLock(UnlockFunc checkerFunc, Status returnThis);
+
+ /**
+ * Sets the checker method to use and its return value the every time ping is called.
+ */
+ void expectPing(PingFunc checkerFunc, Status returnThis);
+
+ /**
+ * Sets the checker method to use and its return value the every time stopPing is called.
+ */
+ void expectStopPing(StopPingFunc checkerFunc, Status returnThis);
+
+ /**
+ * Sets the checker method to use and its return value the every time
+ * getLockByTS is called.
+ */
+ void expectGetLockByTS(GetLockByTSFunc checkerFunc, StatusWith<LocksType> returnThis);
+
+ /**
+ * Sets the checker method to use and its return value the every time
+ * getLockByName is called.
+ */
+ void expectGetLockByName(GetLockByNameFunc checkerFunc, StatusWith<LocksType> returnThis);
+
+ /**
+ * Sets the checker method to use and its return value the every time
+ * overtakeLock is called.
+ */
+ void expectOvertakeLock(OvertakeLockFunc checkerFunc, StatusWith<LocksType> returnThis);
+
+ /**
+ * Sets the checker method to use and its return value the every time
+ * getPing is called.
+ */
+ void expectGetPing(GetPingFunc checkerFunc, StatusWith<LockpingsType> returnThis);
+
+ /**
+ * Sets the checker method to use and its return value the every time
+ * getServerInfo is called.
+ */
+ void expectGetServerInfo(GetServerInfoFunc checkerFunc,
+ StatusWith<DistLockCatalog::ServerInfo> returnThis);
+
+private:
+ // Protects all the member variables.
+ stdx::mutex _mutex;
+
+ GrabLockFunc _grabLockChecker;
+ StatusWith<LocksType> _grabLockReturnValue;
+
+ UnlockFunc _unlockChecker;
+ Status _unlockReturnValue;
+
+ PingFunc _pingChecker;
+ Status _pingReturnValue;
+
+ StopPingFunc _stopPingChecker;
+ Status _stopPingReturnValue;
+
+ GetLockByTSFunc _getLockByTSChecker;
+ StatusWith<LocksType> _getLockByTSReturnValue;
+
+ GetLockByNameFunc _getLockByNameChecker;
+ StatusWith<LocksType> _getLockByNameReturnValue;
+
+ OvertakeLockFunc _overtakeLockChecker;
+ StatusWith<LocksType> _overtakeLockReturnValue;
+
+ GetPingFunc _getPingChecker;
+ StatusWith<LockpingsType> _getPingReturnValue;
+
+ GetServerInfoFunc _getServerInfoChecker;
+ StatusWith<DistLockCatalog::ServerInfo> _getServerInfoReturnValue;
+};
}
diff --git a/src/mongo/s/catalog/dist_lock_manager.cpp b/src/mongo/s/catalog/dist_lock_manager.cpp
index e40504e2ff9..834e235b1e9 100644
--- a/src/mongo/s/catalog/dist_lock_manager.cpp
+++ b/src/mongo/s/catalog/dist_lock_manager.cpp
@@ -36,45 +36,41 @@
namespace mongo {
- const stdx::chrono::milliseconds DistLockManager::kDefaultSingleLockAttemptTimeout(0);
- const stdx::chrono::milliseconds DistLockManager::kDefaultLockRetryInterval(1000);
+const stdx::chrono::milliseconds DistLockManager::kDefaultSingleLockAttemptTimeout(0);
+const stdx::chrono::milliseconds DistLockManager::kDefaultLockRetryInterval(1000);
- DistLockManager::ScopedDistLock::ScopedDistLock(): _lockManager(nullptr) {
- }
-
- DistLockManager::ScopedDistLock::ScopedDistLock(DistLockHandle lockHandle,
- DistLockManager* lockManager):
- _lockID(std::move(lockHandle)), _lockManager(lockManager) {
- }
+DistLockManager::ScopedDistLock::ScopedDistLock() : _lockManager(nullptr) {}
- DistLockManager::ScopedDistLock::~ScopedDistLock() {
- if (_lockManager) {
- _lockManager->unlock(_lockID);
- }
- }
+DistLockManager::ScopedDistLock::ScopedDistLock(DistLockHandle lockHandle,
+ DistLockManager* lockManager)
+ : _lockID(std::move(lockHandle)), _lockManager(lockManager) {}
- DistLockManager::ScopedDistLock::ScopedDistLock(ScopedDistLock&& other) {
- *this = std::move(other);
+DistLockManager::ScopedDistLock::~ScopedDistLock() {
+ if (_lockManager) {
+ _lockManager->unlock(_lockID);
}
+}
- DistLockManager::ScopedDistLock&
- DistLockManager::ScopedDistLock::operator=(ScopedDistLock&& other) {
- if (this != &other) {
- _lockID = std::move(other._lockID);
- _lockManager = other._lockManager;
- other._lockManager = nullptr;
- }
+DistLockManager::ScopedDistLock::ScopedDistLock(ScopedDistLock&& other) {
+ *this = std::move(other);
+}
- return *this;
+DistLockManager::ScopedDistLock& DistLockManager::ScopedDistLock::operator=(
+ ScopedDistLock&& other) {
+ if (this != &other) {
+ _lockID = std::move(other._lockID);
+ _lockManager = other._lockManager;
+ other._lockManager = nullptr;
}
- Status DistLockManager::ScopedDistLock::checkStatus() {
- if (!_lockManager) {
- return Status(ErrorCodes::IllegalOperation,
- "no lock manager, lock was not acquired");
- }
+ return *this;
+}
- return _lockManager->checkStatus(_lockID);
+Status DistLockManager::ScopedDistLock::checkStatus() {
+ if (!_lockManager) {
+ return Status(ErrorCodes::IllegalOperation, "no lock manager, lock was not acquired");
}
+ return _lockManager->checkStatus(_lockID);
+}
}
diff --git a/src/mongo/s/catalog/dist_lock_manager.h b/src/mongo/s/catalog/dist_lock_manager.h
index 17fc7ca0ad3..c313447ae11 100644
--- a/src/mongo/s/catalog/dist_lock_manager.h
+++ b/src/mongo/s/catalog/dist_lock_manager.h
@@ -34,94 +34,93 @@
namespace mongo {
- using DistLockHandle = OID;
- class Status;
- template <typename T> class StatusWith;
+using DistLockHandle = OID;
+class Status;
+template <typename T>
+class StatusWith;
+
+/**
+ * Interface for handling distributed locks.
+ *
+ * Usage:
+ *
+ * auto scopedDistLock = mgr->lock(...);
+ *
+ * if (!scopedDistLock.isOK()) {
+ * // Did not get lock. scopedLockStatus destructor will not call unlock.
+ * }
+ *
+ * // To check if lock is still owned:
+ * auto status = scopedDistLock.getValue().checkStatus();
+ *
+ * if (!status.isOK()) {
+ * // Someone took over the lock! Unlock will still be called at destructor, but will
+ * // practically be a no-op since it doesn't own the lock anymore.
+ * }
+ */
+class DistLockManager {
+public:
+ static const stdx::chrono::milliseconds kDefaultSingleLockAttemptTimeout;
+ static const stdx::chrono::milliseconds kDefaultLockRetryInterval;
/**
- * Interface for handling distributed locks.
- *
- * Usage:
- *
- * auto scopedDistLock = mgr->lock(...);
- *
- * if (!scopedDistLock.isOK()) {
- * // Did not get lock. scopedLockStatus destructor will not call unlock.
- * }
- *
- * // To check if lock is still owned:
- * auto status = scopedDistLock.getValue().checkStatus();
- *
- * if (!status.isOK()) {
- * // Someone took over the lock! Unlock will still be called at destructor, but will
- * // practically be a no-op since it doesn't own the lock anymore.
- * }
+ * RAII type for distributed lock. Not meant to be shared across multiple threads.
*/
- class DistLockManager {
+ class ScopedDistLock {
+ MONGO_DISALLOW_COPYING(ScopedDistLock);
+
public:
+ ScopedDistLock(); // TODO: SERVER-18007
+ ScopedDistLock(DistLockHandle lockHandle, DistLockManager* lockManager);
+ ~ScopedDistLock();
- static const stdx::chrono::milliseconds kDefaultSingleLockAttemptTimeout;
- static const stdx::chrono::milliseconds kDefaultLockRetryInterval;
+ ScopedDistLock(ScopedDistLock&& other);
+ ScopedDistLock& operator=(ScopedDistLock&& other);
/**
- * RAII type for distributed lock. Not meant to be shared across multiple threads.
+ * Checks whether the lock is still being held by querying the config server.
*/
- class ScopedDistLock {
- MONGO_DISALLOW_COPYING(ScopedDistLock);
-
- public:
- ScopedDistLock(); // TODO: SERVER-18007
- ScopedDistLock(DistLockHandle lockHandle, DistLockManager* lockManager);
- ~ScopedDistLock();
-
- ScopedDistLock(ScopedDistLock&& other);
- ScopedDistLock& operator=(ScopedDistLock&& other);
-
- /**
- * Checks whether the lock is still being held by querying the config server.
- */
- Status checkStatus();
+ Status checkStatus();
- private:
- DistLockHandle _lockID;
- DistLockManager* _lockManager; // Not owned here.
- };
+ private:
+ DistLockHandle _lockID;
+ DistLockManager* _lockManager; // Not owned here.
+ };
- virtual ~DistLockManager() = default;
+ virtual ~DistLockManager() = default;
- virtual void startUp() = 0;
- virtual void shutDown() = 0;
+ virtual void startUp() = 0;
+ virtual void shutDown() = 0;
- /**
- * Tries multiple times to lock, using the specified lock try interval, until
- * a certain amount of time has passed or when any error that is not LockBusy
- * occurred.
- *
- * waitFor = 0 indicates there should only be one attempt to acquire the lock, and
- * no waiting.
- * waitFor = -1 indicates we should retry indefinitely.
- *
- * Returns OK if the lock was successfully acquired.
- * Returns ErrorCodes::DistributedClockSkewed when a clock skew is detected.
- * Returns ErrorCodes::LockBusy if the lock is being held.
- */
- virtual StatusWith<ScopedDistLock> lock(
- StringData name,
- StringData whyMessage,
- stdx::chrono::milliseconds waitFor = kDefaultSingleLockAttemptTimeout,
- stdx::chrono::milliseconds lockTryInterval = kDefaultLockRetryInterval) = 0;
-
- protected:
+ /**
+ * Tries multiple times to lock, using the specified lock try interval, until
+ * a certain amount of time has passed or when any error that is not LockBusy
+ * occurred.
+ *
+ * waitFor = 0 indicates there should only be one attempt to acquire the lock, and
+ * no waiting.
+ * waitFor = -1 indicates we should retry indefinitely.
+ *
+ * Returns OK if the lock was successfully acquired.
+ * Returns ErrorCodes::DistributedClockSkewed when a clock skew is detected.
+ * Returns ErrorCodes::LockBusy if the lock is being held.
+ */
+ virtual StatusWith<ScopedDistLock> lock(
+ StringData name,
+ StringData whyMessage,
+ stdx::chrono::milliseconds waitFor = kDefaultSingleLockAttemptTimeout,
+ stdx::chrono::milliseconds lockTryInterval = kDefaultLockRetryInterval) = 0;
- /**
- * Unlocks the given lockHandle. Will attempt to retry again later if the config
- * server is not reachable.
- */
- virtual void unlock(const DistLockHandle& lockHandle) = 0;
+protected:
+ /**
+ * Unlocks the given lockHandle. Will attempt to retry again later if the config
+ * server is not reachable.
+ */
+ virtual void unlock(const DistLockHandle& lockHandle) = 0;
- /**
- * Checks if the lockHandle still exists in the config server.
- */
- virtual Status checkStatus(const DistLockHandle& lockHandle) = 0;
- };
+ /**
+ * Checks if the lockHandle still exists in the config server.
+ */
+ virtual Status checkStatus(const DistLockHandle& lockHandle) = 0;
+};
}
diff --git a/src/mongo/s/catalog/dist_lock_manager_mock.cpp b/src/mongo/s/catalog/dist_lock_manager_mock.cpp
index 1231edfea26..2c28aea9a0e 100644
--- a/src/mongo/s/catalog/dist_lock_manager_mock.cpp
+++ b/src/mongo/s/catalog/dist_lock_manager_mock.cpp
@@ -39,82 +39,71 @@
namespace mongo {
- using stdx::chrono::milliseconds;
+using stdx::chrono::milliseconds;
namespace {
- void NoLockFuncSet(StringData name,
- StringData whyMessage,
- milliseconds waitFor,
- milliseconds lockTryInterval) {
- FAIL(str::stream() << "Lock not expected to be called. "
- << "Name: " << name
- << ", whyMessage: " << whyMessage
- << ", waitFor: " << waitFor
- << ", lockTryInterval: " << lockTryInterval);
- }
+void NoLockFuncSet(StringData name,
+ StringData whyMessage,
+ milliseconds waitFor,
+ milliseconds lockTryInterval) {
+ FAIL(str::stream() << "Lock not expected to be called. "
+ << "Name: " << name << ", whyMessage: " << whyMessage
+ << ", waitFor: " << waitFor << ", lockTryInterval: " << lockTryInterval);
+}
-} // namespace
+} // namespace
- DistLockManagerMock::DistLockManagerMock() : _lockReturnStatus{Status::OK()},
- _lockChecker{NoLockFuncSet} {}
+DistLockManagerMock::DistLockManagerMock()
+ : _lockReturnStatus{Status::OK()}, _lockChecker{NoLockFuncSet} {}
- void DistLockManagerMock::startUp() {}
+void DistLockManagerMock::startUp() {}
- void DistLockManagerMock::shutDown() {
- uassert(28659,
- "DistLockManagerMock shut down with outstanding locks present",
- _locks.empty());
- }
+void DistLockManagerMock::shutDown() {
+ uassert(28659, "DistLockManagerMock shut down with outstanding locks present", _locks.empty());
+}
- StatusWith<DistLockManager::ScopedDistLock> DistLockManagerMock::lock(
- StringData name,
- StringData whyMessage,
- milliseconds waitFor,
- milliseconds lockTryInterval) {
-
- _lockChecker(name, whyMessage, waitFor, lockTryInterval);
- _lockChecker = NoLockFuncSet;
-
- if (!_lockReturnStatus.isOK()) {
- return _lockReturnStatus;
- }
-
- if (_locks.end() != std::find_if(
- _locks.begin(),
- _locks.end(),
- [name](LockInfo info)->bool { return info.name == name; })) {
- return Status(ErrorCodes::LockBusy,
- str::stream() << "Lock \"" << name << "\" is already taken");
- }
-
- LockInfo info;
- info.name = name.toString();
- info.lockID = DistLockHandle::gen();
- _locks.push_back(info);
-
- return DistLockManager::ScopedDistLock(info.lockID, this);
- }
+StatusWith<DistLockManager::ScopedDistLock> DistLockManagerMock::lock(
+ StringData name, StringData whyMessage, milliseconds waitFor, milliseconds lockTryInterval) {
+ _lockChecker(name, whyMessage, waitFor, lockTryInterval);
+ _lockChecker = NoLockFuncSet;
- void DistLockManagerMock::unlock(const DistLockHandle& lockHandle) {
- std::vector<LockInfo>::iterator it =
- std::find_if(
- _locks.begin(),
- _locks.end(),
- [&lockHandle](LockInfo info)->bool { return info.lockID == lockHandle; });
- if (it == _locks.end()) {
- return;
- }
- _locks.erase(it);
+ if (!_lockReturnStatus.isOK()) {
+ return _lockReturnStatus;
}
- Status DistLockManagerMock::checkStatus(const DistLockHandle& lockHandle) {
- return Status::OK();
+ if (_locks.end() != std::find_if(_locks.begin(),
+ _locks.end(),
+ [name](LockInfo info) -> bool { return info.name == name; })) {
+ return Status(ErrorCodes::LockBusy,
+ str::stream() << "Lock \"" << name << "\" is already taken");
}
- void DistLockManagerMock::expectLock(LockFunc checker, Status status) {
- _lockReturnStatus = std::move(status);
- _lockChecker = checker;
+ LockInfo info;
+ info.name = name.toString();
+ info.lockID = DistLockHandle::gen();
+ _locks.push_back(info);
+
+ return DistLockManager::ScopedDistLock(info.lockID, this);
+}
+
+void DistLockManagerMock::unlock(const DistLockHandle& lockHandle) {
+ std::vector<LockInfo>::iterator it =
+ std::find_if(_locks.begin(),
+ _locks.end(),
+ [&lockHandle](LockInfo info) -> bool { return info.lockID == lockHandle; });
+ if (it == _locks.end()) {
+ return;
}
+ _locks.erase(it);
+}
+Status DistLockManagerMock::checkStatus(const DistLockHandle& lockHandle) {
+ return Status::OK();
+}
+
+void DistLockManagerMock::expectLock(LockFunc checker, Status status) {
+ _lockReturnStatus = std::move(status);
+ _lockChecker = checker;
+}
}
diff --git a/src/mongo/s/catalog/dist_lock_manager_mock.h b/src/mongo/s/catalog/dist_lock_manager_mock.h
index bc8b1e47004..6b9f9e87801 100644
--- a/src/mongo/s/catalog/dist_lock_manager_mock.h
+++ b/src/mongo/s/catalog/dist_lock_manager_mock.h
@@ -36,44 +36,41 @@
namespace mongo {
- class DistLockManagerMock: public DistLockManager {
- public:
- DistLockManagerMock();
+class DistLockManagerMock : public DistLockManager {
+public:
+ DistLockManagerMock();
- virtual ~DistLockManagerMock() = default;
+ virtual ~DistLockManagerMock() = default;
- virtual void startUp() override;
- virtual void shutDown() override;
+ virtual void startUp() override;
+ virtual void shutDown() override;
- virtual StatusWith<DistLockManager::ScopedDistLock> lock(
- StringData name,
- StringData whyMessage,
- stdx::chrono::milliseconds waitFor,
- stdx::chrono::milliseconds lockTryInterval) override;
+ virtual StatusWith<DistLockManager::ScopedDistLock> lock(
+ StringData name,
+ StringData whyMessage,
+ stdx::chrono::milliseconds waitFor,
+ stdx::chrono::milliseconds lockTryInterval) override;
- using LockFunc = stdx::function<void (StringData name,
- StringData whyMessage,
- stdx::chrono::milliseconds waitFor,
- stdx::chrono::milliseconds lockTryInterval)>;
+ using LockFunc = stdx::function<void(StringData name,
+ StringData whyMessage,
+ stdx::chrono::milliseconds waitFor,
+ stdx::chrono::milliseconds lockTryInterval)>;
- void expectLock(LockFunc checkerFunc, Status lockStatus);
+ void expectLock(LockFunc checkerFunc, Status lockStatus);
- protected:
+protected:
+ virtual void unlock(const DistLockHandle& lockHandle) override;
- virtual void unlock(const DistLockHandle& lockHandle) override;
+ virtual Status checkStatus(const DistLockHandle& lockHandle) override;
- virtual Status checkStatus(const DistLockHandle& lockHandle) override;
-
- private:
-
- struct LockInfo {
- DistLockHandle lockID;
- std::string name;
- };
-
- std::vector<LockInfo> _locks;
- Status _lockReturnStatus;
- LockFunc _lockChecker;
+private:
+ struct LockInfo {
+ DistLockHandle lockID;
+ std::string name;
};
+ std::vector<LockInfo> _locks;
+ Status _lockReturnStatus;
+ LockFunc _lockChecker;
+};
}
diff --git a/src/mongo/s/catalog/dist_lock_ping_info.cpp b/src/mongo/s/catalog/dist_lock_ping_info.cpp
index 070ae1ed77f..a81cc1c749c 100644
--- a/src/mongo/s/catalog/dist_lock_ping_info.cpp
+++ b/src/mongo/s/catalog/dist_lock_ping_info.cpp
@@ -32,18 +32,13 @@
namespace mongo {
- DistLockPingInfo::DistLockPingInfo() = default;
-
- DistLockPingInfo::DistLockPingInfo(StringData idArg,
- Date_t lastPingArg,
- Date_t remoteArg,
- OID tsArg,
- OID electionIdArg) :
- processId(idArg.toString()),
- lastPing(lastPingArg),
- configLocalTime(remoteArg),
- lockSessionId(std::move(tsArg)),
- electionId(std::move(electionIdArg)) {
- }
+DistLockPingInfo::DistLockPingInfo() = default;
+DistLockPingInfo::DistLockPingInfo(
+ StringData idArg, Date_t lastPingArg, Date_t remoteArg, OID tsArg, OID electionIdArg)
+ : processId(idArg.toString()),
+ lastPing(lastPingArg),
+ configLocalTime(remoteArg),
+ lockSessionId(std::move(tsArg)),
+ electionId(std::move(electionIdArg)) {}
}
diff --git a/src/mongo/s/catalog/dist_lock_ping_info.h b/src/mongo/s/catalog/dist_lock_ping_info.h
index 58e7cc0bc64..34f4483bd9c 100644
--- a/src/mongo/s/catalog/dist_lock_ping_info.h
+++ b/src/mongo/s/catalog/dist_lock_ping_info.h
@@ -36,31 +36,31 @@
namespace mongo {
- /**
- * Data structure for storing information about distributed lock pings.
- */
- struct DistLockPingInfo {
- DistLockPingInfo();
- DistLockPingInfo(StringData processId,
- Date_t lastPing,
- Date_t configLocalTime,
- OID lockSessionId,
- OID electionId);
+/**
+ * Data structure for storing information about distributed lock pings.
+ */
+struct DistLockPingInfo {
+ DistLockPingInfo();
+ DistLockPingInfo(StringData processId,
+ Date_t lastPing,
+ Date_t configLocalTime,
+ OID lockSessionId,
+ OID electionId);
- // the process processId of the last known owner of the lock.
- std::string processId;
+ // the process processId of the last known owner of the lock.
+ std::string processId;
- // the ping value from the last owner of the lock.
- Date_t lastPing;
+ // the ping value from the last owner of the lock.
+ Date_t lastPing;
- // the config server local time when this object was updated.
- Date_t configLocalTime;
+ // the config server local time when this object was updated.
+ Date_t configLocalTime;
- // last known owner of the lock.
- OID lockSessionId;
+ // last known owner of the lock.
+ OID lockSessionId;
- // the election id of the config server when this object was updated.
- // Note: unused by legacy dist lock.
- OID electionId;
- };
+ // the election id of the config server when this object was updated.
+ // Note: unused by legacy dist lock.
+ OID electionId;
+};
}
diff --git a/src/mongo/s/catalog/legacy/catalog_manager_legacy.cpp b/src/mongo/s/catalog/legacy/catalog_manager_legacy.cpp
index ab274c579fb..c25618e31e8 100644
--- a/src/mongo/s/catalog/legacy/catalog_manager_legacy.cpp
+++ b/src/mongo/s/catalog/legacy/catalog_manager_legacy.cpp
@@ -80,1657 +80,1570 @@
namespace mongo {
- using std::map;
- using std::pair;
- using std::set;
- using std::string;
- using std::vector;
- using str::stream;
+using std::map;
+using std::pair;
+using std::set;
+using std::string;
+using std::vector;
+using str::stream;
namespace {
- bool validConfigWC(const BSONObj& writeConcern) {
- BSONElement elem(writeConcern["w"]);
- if (elem.eoo()) {
- return true;
- }
+bool validConfigWC(const BSONObj& writeConcern) {
+ BSONElement elem(writeConcern["w"]);
+ if (elem.eoo()) {
+ return true;
+ }
- if (elem.isNumber() && elem.numberInt() <= 1) {
- return true;
- }
+ if (elem.isNumber() && elem.numberInt() <= 1) {
+ return true;
+ }
- if (elem.type() == String && elem.str() == "majority") {
- return true;
- }
+ if (elem.type() == String && elem.str() == "majority") {
+ return true;
+ }
- return false;
+ return false;
+}
+
+void toBatchError(const Status& status, BatchedCommandResponse* response) {
+ response->clear();
+ response->setErrCode(status.code());
+ response->setErrMessage(status.reason());
+ response->setOk(false);
+
+ dassert(response->isValid(NULL));
+}
+
+StatusWith<string> isValidShard(const string& name,
+ const ConnectionString& shardConnectionString,
+ ScopedDbConnection& conn) {
+ if (conn->type() == ConnectionString::SYNC) {
+ return Status(ErrorCodes::BadValue,
+ "can't use sync cluster as a shard; for a replica set, "
+ "you have to use <setname>/<server1>,<server2>,...");
}
- void toBatchError(const Status& status, BatchedCommandResponse* response) {
- response->clear();
- response->setErrCode(status.code());
- response->setErrMessage(status.reason());
- response->setOk(false);
+ BSONObj resIsMongos;
+ // (ok == 0) implies that it is a mongos
+ if (conn->runCommand("admin", BSON("isdbgrid" << 1), resIsMongos)) {
+ return Status(ErrorCodes::BadValue, "can't add a mongos process as a shard");
+ }
- dassert(response->isValid(NULL));
+ BSONObj resIsMaster;
+ if (!conn->runCommand("admin", BSON("isMaster" << 1), resIsMaster)) {
+ return Status(ErrorCodes::OperationFailed,
+ str::stream() << "failed running isMaster: " << resIsMaster);
}
- StatusWith<string> isValidShard(const string& name,
- const ConnectionString& shardConnectionString,
- ScopedDbConnection& conn) {
- if (conn->type() == ConnectionString::SYNC) {
- return Status(ErrorCodes::BadValue,
- "can't use sync cluster as a shard; for a replica set, "
- "you have to use <setname>/<server1>,<server2>,...");
- }
+ // if the shard has only one host, make sure it is not part of a replica set
+ string setName = resIsMaster["setName"].str();
+ string commandSetName = shardConnectionString.getSetName();
+ if (commandSetName.empty() && !setName.empty()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "host is part of set " << setName << "; "
+ << "use replica set url format "
+ << "<setname>/<server1>,<server2>, ...");
+ }
- BSONObj resIsMongos;
- // (ok == 0) implies that it is a mongos
- if (conn->runCommand("admin", BSON("isdbgrid" << 1), resIsMongos)) {
- return Status(ErrorCodes::BadValue,
- "can't add a mongos process as a shard");
- }
+ if (!commandSetName.empty() && setName.empty()) {
+ return Status(ErrorCodes::OperationFailed,
+ str::stream() << "host did not return a set name; "
+ << "is the replica set still initializing? " << resIsMaster);
+ }
- BSONObj resIsMaster;
- if (!conn->runCommand("admin", BSON("isMaster" << 1), resIsMaster)) {
- return Status(ErrorCodes::OperationFailed,
- str::stream() << "failed running isMaster: " << resIsMaster);
- }
+ // if the shard is part of replica set, make sure it is the right one
+ if (!commandSetName.empty() && (commandSetName != setName)) {
+ return Status(ErrorCodes::OperationFailed,
+ str::stream() << "host is part of a different set: " << setName);
+ }
- // if the shard has only one host, make sure it is not part of a replica set
- string setName = resIsMaster["setName"].str();
- string commandSetName = shardConnectionString.getSetName();
- if (commandSetName.empty() && !setName.empty()) {
+ if (setName.empty()) {
+ // check this isn't a --configsvr
+ BSONObj res;
+ bool ok = conn->runCommand("admin", BSON("replSetGetStatus" << 1), res);
+ if (!ok && res["info"].type() == String && res["info"].String() == "configsvr") {
return Status(ErrorCodes::BadValue,
- str::stream() << "host is part of set " << setName << "; "
- << "use replica set url format "
- << "<setname>/<server1>,<server2>, ...");
- }
-
- if (!commandSetName.empty() && setName.empty()) {
- return Status(ErrorCodes::OperationFailed,
- str::stream() << "host did not return a set name; "
- << "is the replica set still initializing? "
- << resIsMaster);
+ "the specified mongod is a --configsvr and "
+ "should thus not be a shard server");
}
+ }
- // if the shard is part of replica set, make sure it is the right one
- if (!commandSetName.empty() && (commandSetName != setName)) {
- return Status(ErrorCodes::OperationFailed,
- str::stream() << "host is part of a different set: " << setName);
+ // if the shard is part of a replica set,
+ // make sure all the hosts mentioned in 'shardConnectionString' are part of
+ // the set. It is fine if not all members of the set are present in 'shardConnectionString'.
+ bool foundAll = true;
+ string offendingHost;
+ if (!commandSetName.empty()) {
+ set<string> hostSet;
+ BSONObjIterator iter(resIsMaster["hosts"].Obj());
+ while (iter.more()) {
+ hostSet.insert(iter.next().String()); // host:port
+ }
+ if (resIsMaster["passives"].isABSONObj()) {
+ BSONObjIterator piter(resIsMaster["passives"].Obj());
+ while (piter.more()) {
+ hostSet.insert(piter.next().String()); // host:port
+ }
}
-
- if (setName.empty()) {
- // check this isn't a --configsvr
- BSONObj res;
- bool ok = conn->runCommand("admin",
- BSON("replSetGetStatus" << 1),
- res);
- if(!ok && res["info"].type() == String && res["info"].String() == "configsvr") {
- return Status(ErrorCodes::BadValue,
- "the specified mongod is a --configsvr and "
- "should thus not be a shard server");
+ if (resIsMaster["arbiters"].isABSONObj()) {
+ BSONObjIterator piter(resIsMaster["arbiters"].Obj());
+ while (piter.more()) {
+ hostSet.insert(piter.next().String()); // host:port
}
}
- // if the shard is part of a replica set,
- // make sure all the hosts mentioned in 'shardConnectionString' are part of
- // the set. It is fine if not all members of the set are present in 'shardConnectionString'.
- bool foundAll = true;
- string offendingHost;
- if (!commandSetName.empty()) {
- set<string> hostSet;
- BSONObjIterator iter(resIsMaster["hosts"].Obj());
- while (iter.more()) {
- hostSet.insert(iter.next().String()); // host:port
- }
- if (resIsMaster["passives"].isABSONObj()) {
- BSONObjIterator piter(resIsMaster["passives"].Obj());
- while (piter.more()) {
- hostSet.insert(piter.next().String()); // host:port
- }
- }
- if (resIsMaster["arbiters"].isABSONObj()) {
- BSONObjIterator piter(resIsMaster["arbiters"].Obj());
- while (piter.more()) {
- hostSet.insert(piter.next().String()); // host:port
- }
+ vector<HostAndPort> hosts = shardConnectionString.getServers();
+ for (size_t i = 0; i < hosts.size(); i++) {
+ if (!hosts[i].hasPort()) {
+ hosts[i] = HostAndPort(hosts[i].host(), hosts[i].port());
}
-
- vector<HostAndPort> hosts = shardConnectionString.getServers();
- for (size_t i = 0; i < hosts.size(); i++) {
- if (!hosts[i].hasPort()) {
- hosts[i] = HostAndPort(hosts[i].host(), hosts[i].port());
- }
- string host = hosts[i].toString(); // host:port
- if (hostSet.find(host) == hostSet.end()) {
- offendingHost = host;
- foundAll = false;
- break;
- }
+ string host = hosts[i].toString(); // host:port
+ if (hostSet.find(host) == hostSet.end()) {
+ offendingHost = host;
+ foundAll = false;
+ break;
}
}
- if (!foundAll) {
- return Status(ErrorCodes::OperationFailed,
- str::stream() << "in seed list " << shardConnectionString.toString()
- << ", host " << offendingHost
- << " does not belong to replica set " << setName);
- }
-
- string shardName(name);
- // shard name defaults to the name of the replica set
- if (name.empty() && !setName.empty()) {
- shardName = setName;
- }
-
- // disallow adding shard replica set with name 'config'
- if (shardName == "config") {
- return Status(ErrorCodes::BadValue,
- "use of shard replica set with name 'config' is not allowed");
- }
+ }
+ if (!foundAll) {
+ return Status(ErrorCodes::OperationFailed,
+ str::stream() << "in seed list " << shardConnectionString.toString()
+ << ", host " << offendingHost
+ << " does not belong to replica set " << setName);
+ }
- return shardName;
+ string shardName(name);
+ // shard name defaults to the name of the replica set
+ if (name.empty() && !setName.empty()) {
+ shardName = setName;
}
- // In order to be accepted as a new shard, that mongod must not have
- // any database name that exists already in any other shards.
- // If that test passes, the new shard's databases are going to be entered as
- // non-sharded db's whose primary is the newly added shard.
- StatusWith<vector<string>> getDBNames(const ConnectionString& shardConnectionString,
- ScopedDbConnection& conn) {
- vector<string> dbNames;
+ // disallow adding shard replica set with name 'config'
+ if (shardName == "config") {
+ return Status(ErrorCodes::BadValue,
+ "use of shard replica set with name 'config' is not allowed");
+ }
- BSONObj resListDB;
- if (!conn->runCommand("admin", BSON("listDatabases" << 1), resListDB)) {
- return Status(ErrorCodes::OperationFailed,
- str::stream() << "failed listing "
- << shardConnectionString.toString()
- << "'s databases:" << resListDB);
- }
+ return shardName;
+}
- BSONObjIterator i(resListDB["databases"].Obj());
- while (i.more()) {
- BSONObj dbEntry = i.next().Obj();
- const string& dbName = dbEntry["name"].String();
- if (!(dbName == "local" || dbName == "admin" || dbName == "config")) {
- dbNames.push_back(dbName);
- }
- }
+// In order to be accepted as a new shard, that mongod must not have
+// any database name that exists already in any other shards.
+// If that test passes, the new shard's databases are going to be entered as
+// non-sharded db's whose primary is the newly added shard.
+StatusWith<vector<string>> getDBNames(const ConnectionString& shardConnectionString,
+ ScopedDbConnection& conn) {
+ vector<string> dbNames;
- return dbNames;
+ BSONObj resListDB;
+ if (!conn->runCommand("admin", BSON("listDatabases" << 1), resListDB)) {
+ return Status(ErrorCodes::OperationFailed,
+ str::stream() << "failed listing " << shardConnectionString.toString()
+ << "'s databases:" << resListDB);
}
- BSONObj buildRemoveLogEntry(const string& shardName, bool isDraining) {
- BSONObjBuilder details;
- details.append("shard", shardName);
- details.append("isDraining", isDraining);
-
- return details.obj();
+ BSONObjIterator i(resListDB["databases"].Obj());
+ while (i.more()) {
+ BSONObj dbEntry = i.next().Obj();
+ const string& dbName = dbEntry["name"].String();
+ if (!(dbName == "local" || dbName == "admin" || dbName == "config")) {
+ dbNames.push_back(dbName);
+ }
}
- // Whether the logChange call should attempt to create the changelog collection
- AtomicInt32 changeLogCollectionCreated(0);
+ return dbNames;
+}
- // Whether the logAction call should attempt to create the actionlog collection
- AtomicInt32 actionLogCollectionCreated(0);
+BSONObj buildRemoveLogEntry(const string& shardName, bool isDraining) {
+ BSONObjBuilder details;
+ details.append("shard", shardName);
+ details.append("isDraining", isDraining);
-} // namespace
+ return details.obj();
+}
+// Whether the logChange call should attempt to create the changelog collection
+AtomicInt32 changeLogCollectionCreated(0);
- CatalogManagerLegacy::CatalogManagerLegacy() = default;
+// Whether the logAction call should attempt to create the actionlog collection
+AtomicInt32 actionLogCollectionCreated(0);
- CatalogManagerLegacy::~CatalogManagerLegacy() = default;
+} // namespace
- Status CatalogManagerLegacy::init(const ConnectionString& configDBCS) {
- // Initialization should not happen more than once
- invariant(!_configServerConnectionString.isValid());
- invariant(_configServers.empty());
- invariant(configDBCS.isValid());
-
- // Extract the hosts in HOST:PORT format
- set<HostAndPort> configHostsAndPortsSet;
- set<string> configHostsOnly;
- std::vector<HostAndPort> configHostAndPorts = configDBCS.getServers();
- for (size_t i = 0; i < configHostAndPorts.size(); i++) {
- // Append the default port, if not specified
- HostAndPort configHost = configHostAndPorts[i];
- if (!configHost.hasPort()) {
- configHost = HostAndPort(configHost.host(), ServerGlobalParams::ConfigServerPort);
- }
-
- // Make sure there are no duplicates
- if (!configHostsAndPortsSet.insert(configHost).second) {
- StringBuilder sb;
- sb << "Host " << configHost.toString()
- << " exists twice in the config servers listing.";
- return Status(ErrorCodes::InvalidOptions, sb.str());
- }
+CatalogManagerLegacy::CatalogManagerLegacy() = default;
- configHostsOnly.insert(configHost.host());
- }
+CatalogManagerLegacy::~CatalogManagerLegacy() = default;
- // Make sure the hosts are reachable
- for (set<string>::const_iterator i = configHostsOnly.begin();
- i != configHostsOnly.end();
- i++) {
+Status CatalogManagerLegacy::init(const ConnectionString& configDBCS) {
+ // Initialization should not happen more than once
+ invariant(!_configServerConnectionString.isValid());
+ invariant(_configServers.empty());
+ invariant(configDBCS.isValid());
- const string host = *i;
+ // Extract the hosts in HOST:PORT format
+ set<HostAndPort> configHostsAndPortsSet;
+ set<string> configHostsOnly;
+ std::vector<HostAndPort> configHostAndPorts = configDBCS.getServers();
+ for (size_t i = 0; i < configHostAndPorts.size(); i++) {
+ // Append the default port, if not specified
+ HostAndPort configHost = configHostAndPorts[i];
+ if (!configHost.hasPort()) {
+ configHost = HostAndPort(configHost.host(), ServerGlobalParams::ConfigServerPort);
+ }
- // If this is a CUSTOM connection string (for testing) don't do DNS resolution
- string errMsg;
- if (ConnectionString::parse(host, errMsg).type() == ConnectionString::CUSTOM) {
- continue;
- }
+ // Make sure there are no duplicates
+ if (!configHostsAndPortsSet.insert(configHost).second) {
+ StringBuilder sb;
+ sb << "Host " << configHost.toString()
+ << " exists twice in the config servers listing.";
- bool ok = false;
+ return Status(ErrorCodes::InvalidOptions, sb.str());
+ }
- for (int x = 10; x > 0; x--) {
- if (!hostbyname(host.c_str()).empty()) {
- ok = true;
- break;
- }
+ configHostsOnly.insert(configHost.host());
+ }
- log() << "can't resolve DNS for [" << host << "] sleeping and trying "
- << x << " more times";
- sleepsecs(10);
- }
+ // Make sure the hosts are reachable
+ for (set<string>::const_iterator i = configHostsOnly.begin(); i != configHostsOnly.end(); i++) {
+ const string host = *i;
- if (!ok) {
- return Status(ErrorCodes::HostNotFound,
- stream() << "unable to resolve DNS for host " << host);
- }
+ // If this is a CUSTOM connection string (for testing) don't do DNS resolution
+ string errMsg;
+ if (ConnectionString::parse(host, errMsg).type() == ConnectionString::CUSTOM) {
+ continue;
}
- LOG(1) << " config string : " << configDBCS.toString();
-
- // Now that the config hosts are verified, initialize the catalog manager. The code below
- // should never fail.
+ bool ok = false;
- _configServerConnectionString = configDBCS;
-
- if (_configServerConnectionString.type() == ConnectionString::MASTER) {
- _configServers.push_back(_configServerConnectionString);
- }
- else if (_configServerConnectionString.type() == ConnectionString::SYNC ||
- (_configServerConnectionString.type() == ConnectionString::SET &&
- _configServerConnectionString.getServers().size() == 1)) {
- // TODO(spencer): Remove second part of the above or statement that allows replset
- // config server strings once we've separated the legacy catalog manager from the
- // CSRS version.
- const vector<HostAndPort> configHPs = _configServerConnectionString.getServers();
- for (vector<HostAndPort>::const_iterator it = configHPs.begin();
- it != configHPs.end();
- ++it) {
-
- _configServers.push_back(ConnectionString(*it));
+ for (int x = 10; x > 0; x--) {
+ if (!hostbyname(host.c_str()).empty()) {
+ ok = true;
+ break;
}
- }
- else {
- // This is only for tests.
- invariant(_configServerConnectionString.type() == ConnectionString::CUSTOM);
- _configServers.push_back(_configServerConnectionString);
- }
- _distLockManager = stdx::make_unique<LegacyDistLockManager>(_configServerConnectionString);
- _distLockManager->startUp();
+ log() << "can't resolve DNS for [" << host << "] sleeping and trying " << x
+ << " more times";
+ sleepsecs(10);
+ }
- {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- _inShutdown = false;
- _consistentFromLastCheck = true;
+ if (!ok) {
+ return Status(ErrorCodes::HostNotFound,
+ stream() << "unable to resolve DNS for host " << host);
}
+ }
- return Status::OK();
+ LOG(1) << " config string : " << configDBCS.toString();
+
+ // Now that the config hosts are verified, initialize the catalog manager. The code below
+ // should never fail.
+
+ _configServerConnectionString = configDBCS;
+
+ if (_configServerConnectionString.type() == ConnectionString::MASTER) {
+ _configServers.push_back(_configServerConnectionString);
+ } else if (_configServerConnectionString.type() == ConnectionString::SYNC ||
+ (_configServerConnectionString.type() == ConnectionString::SET &&
+ _configServerConnectionString.getServers().size() == 1)) {
+ // TODO(spencer): Remove second part of the above or statement that allows replset
+ // config server strings once we've separated the legacy catalog manager from the
+ // CSRS version.
+ const vector<HostAndPort> configHPs = _configServerConnectionString.getServers();
+ for (vector<HostAndPort>::const_iterator it = configHPs.begin(); it != configHPs.end();
+ ++it) {
+ _configServers.push_back(ConnectionString(*it));
+ }
+ } else {
+ // This is only for tests.
+ invariant(_configServerConnectionString.type() == ConnectionString::CUSTOM);
+ _configServers.push_back(_configServerConnectionString);
}
- Status CatalogManagerLegacy::startup(bool upgrade) {
- Status status = _startConfigServerChecker();
- if (!status.isOK()) {
- return status;
- }
+ _distLockManager = stdx::make_unique<LegacyDistLockManager>(_configServerConnectionString);
+ _distLockManager->startUp();
- status = _checkAndUpgradeConfigMetadata(upgrade);
- return status;
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _inShutdown = false;
+ _consistentFromLastCheck = true;
}
- Status CatalogManagerLegacy::_checkAndUpgradeConfigMetadata(bool doUpgrade) {
- VersionType initVersionInfo;
- VersionType versionInfo;
- string errMsg;
+ return Status::OK();
+}
- bool upgraded = checkAndUpgradeConfigVersion(this,
- doUpgrade,
- &initVersionInfo,
- &versionInfo,
- &errMsg);
- if (!upgraded) {
- return Status(ErrorCodes::IncompatibleShardingMetadata,
- str::stream() << "error upgrading config database to v"
- << CURRENT_CONFIG_VERSION << causedBy(errMsg));
- }
-
- return Status::OK();
+Status CatalogManagerLegacy::startup(bool upgrade) {
+ Status status = _startConfigServerChecker();
+ if (!status.isOK()) {
+ return status;
}
- Status CatalogManagerLegacy::_startConfigServerChecker() {
- if (!_checkConfigServersConsistent()) {
- return Status(ErrorCodes::ConfigServersInconsistent,
- "Data inconsistency detected amongst config servers");
- }
+ status = _checkAndUpgradeConfigMetadata(upgrade);
+ return status;
+}
+
+Status CatalogManagerLegacy::_checkAndUpgradeConfigMetadata(bool doUpgrade) {
+ VersionType initVersionInfo;
+ VersionType versionInfo;
+ string errMsg;
+
+ bool upgraded =
+ checkAndUpgradeConfigVersion(this, doUpgrade, &initVersionInfo, &versionInfo, &errMsg);
+ if (!upgraded) {
+ return Status(ErrorCodes::IncompatibleShardingMetadata,
+ str::stream() << "error upgrading config database to v"
+ << CURRENT_CONFIG_VERSION << causedBy(errMsg));
+ }
- stdx::thread t(stdx::bind(&CatalogManagerLegacy::_consistencyChecker, this));
- _consistencyCheckerThread.swap(t);
+ return Status::OK();
+}
- return Status::OK();
+Status CatalogManagerLegacy::_startConfigServerChecker() {
+ if (!_checkConfigServersConsistent()) {
+ return Status(ErrorCodes::ConfigServersInconsistent,
+ "Data inconsistency detected amongst config servers");
}
- ConnectionString CatalogManagerLegacy::connectionString() const {
- return _configServerConnectionString;
- }
+ stdx::thread t(stdx::bind(&CatalogManagerLegacy::_consistencyChecker, this));
+ _consistencyCheckerThread.swap(t);
- void CatalogManagerLegacy::shutDown() {
- LOG(1) << "CatalogManagerLegacy::shutDown() called.";
- {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- _inShutdown = true;
- _consistencyCheckerCV.notify_one();
- }
- _consistencyCheckerThread.join();
+ return Status::OK();
+}
+
+ConnectionString CatalogManagerLegacy::connectionString() const {
+ return _configServerConnectionString;
+}
- invariant(_distLockManager);
- _distLockManager->shutDown();
+void CatalogManagerLegacy::shutDown() {
+ LOG(1) << "CatalogManagerLegacy::shutDown() called.";
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _inShutdown = true;
+ _consistencyCheckerCV.notify_one();
}
+ _consistencyCheckerThread.join();
- Status CatalogManagerLegacy::enableSharding(const std::string& dbName) {
- invariant(nsIsDbOnly(dbName));
+ invariant(_distLockManager);
+ _distLockManager->shutDown();
+}
- DatabaseType db;
+Status CatalogManagerLegacy::enableSharding(const std::string& dbName) {
+ invariant(nsIsDbOnly(dbName));
- // Check for case sensitivity violations
- Status status = _checkDbDoesNotExist(dbName);
- if (status.isOK()) {
- // Database does not exist, create a new entry
- const ShardPtr primary = Shard::pick();
- if (primary) {
- log() << "Placing [" << dbName << "] on: " << primary->toString();
+ DatabaseType db;
- db.setName(dbName);
- db.setPrimary(primary->getId());
- db.setSharded(true);
- }
- else {
- return Status(ErrorCodes::ShardNotFound, "can't find a shard to put new db on");
- }
- }
- else if (status.code() == ErrorCodes::NamespaceExists) {
- // Database exists, so just update it
- StatusWith<DatabaseType> dbStatus = getDatabase(dbName);
- if (!dbStatus.isOK()) {
- return dbStatus.getStatus();
- }
+ // Check for case sensitivity violations
+ Status status = _checkDbDoesNotExist(dbName);
+ if (status.isOK()) {
+ // Database does not exist, create a new entry
+ const ShardPtr primary = Shard::pick();
+ if (primary) {
+ log() << "Placing [" << dbName << "] on: " << primary->toString();
- db = dbStatus.getValue();
+ db.setName(dbName);
+ db.setPrimary(primary->getId());
db.setSharded(true);
+ } else {
+ return Status(ErrorCodes::ShardNotFound, "can't find a shard to put new db on");
}
- else {
- // Some fatal error
- return status;
+ } else if (status.code() == ErrorCodes::NamespaceExists) {
+ // Database exists, so just update it
+ StatusWith<DatabaseType> dbStatus = getDatabase(dbName);
+ if (!dbStatus.isOK()) {
+ return dbStatus.getStatus();
}
- log() << "Enabling sharding for database [" << dbName << "] in config db";
-
- return updateDatabase(dbName, db);
+ db = dbStatus.getValue();
+ db.setSharded(true);
+ } else {
+ // Some fatal error
+ return status;
}
- Status CatalogManagerLegacy::shardCollection(const string& ns,
- const ShardKeyPattern& fieldsAndOrder,
- bool unique,
- vector<BSONObj>* initPoints,
- set<ShardId>* initShardIds) {
+ log() << "Enabling sharding for database [" << dbName << "] in config db";
- StatusWith<DatabaseType> status = getDatabase(nsToDatabase(ns));
- if (!status.isOK()) {
- return status.getStatus();
- }
+ return updateDatabase(dbName, db);
+}
- DatabaseType dbt = status.getValue();
- ShardId dbPrimaryShardId = dbt.getPrimary();
-
- // This is an extra safety check that the collection is not getting sharded concurrently by
- // two different mongos instances. It is not 100%-proof, but it reduces the chance that two
- // invocations of shard collection will step on each other's toes.
- {
- ScopedDbConnection conn(_configServerConnectionString, 30);
- unsigned long long existingChunks = conn->count(ChunkType::ConfigNS,
- BSON(ChunkType::ns(ns)));
- if (existingChunks > 0) {
- conn.done();
- return Status(ErrorCodes::AlreadyInitialized,
- str::stream() << "collection " << ns << " already sharded with "
- << existingChunks << " chunks.");
- }
+Status CatalogManagerLegacy::shardCollection(const string& ns,
+ const ShardKeyPattern& fieldsAndOrder,
+ bool unique,
+ vector<BSONObj>* initPoints,
+ set<ShardId>* initShardIds) {
+ StatusWith<DatabaseType> status = getDatabase(nsToDatabase(ns));
+ if (!status.isOK()) {
+ return status.getStatus();
+ }
+
+ DatabaseType dbt = status.getValue();
+ ShardId dbPrimaryShardId = dbt.getPrimary();
+ // This is an extra safety check that the collection is not getting sharded concurrently by
+ // two different mongos instances. It is not 100%-proof, but it reduces the chance that two
+ // invocations of shard collection will step on each other's toes.
+ {
+ ScopedDbConnection conn(_configServerConnectionString, 30);
+ unsigned long long existingChunks =
+ conn->count(ChunkType::ConfigNS, BSON(ChunkType::ns(ns)));
+ if (existingChunks > 0) {
conn.done();
+ return Status(ErrorCodes::AlreadyInitialized,
+ str::stream() << "collection " << ns << " already sharded with "
+ << existingChunks << " chunks.");
}
- log() << "enable sharding on: " << ns << " with shard key: " << fieldsAndOrder;
+ conn.done();
+ }
- // Record start in changelog
- BSONObjBuilder collectionDetail;
- collectionDetail.append("shardKey", fieldsAndOrder.toBSON());
- collectionDetail.append("collection", ns);
- string dbPrimaryShardStr;
- {
- const auto shard = grid.shardRegistry()->getShard(dbPrimaryShardId);
- dbPrimaryShardStr = shard->toString();
- }
- collectionDetail.append("primary", dbPrimaryShardStr);
-
- BSONArray initialShards;
- if (initShardIds == NULL)
- initialShards = BSONArray();
- else {
- BSONArrayBuilder b;
- for (const ShardId& shardId : *initShardIds) {
- b.append(shardId);
- }
- initialShards = b.arr();
+ log() << "enable sharding on: " << ns << " with shard key: " << fieldsAndOrder;
+
+ // Record start in changelog
+ BSONObjBuilder collectionDetail;
+ collectionDetail.append("shardKey", fieldsAndOrder.toBSON());
+ collectionDetail.append("collection", ns);
+ string dbPrimaryShardStr;
+ {
+ const auto shard = grid.shardRegistry()->getShard(dbPrimaryShardId);
+ dbPrimaryShardStr = shard->toString();
+ }
+ collectionDetail.append("primary", dbPrimaryShardStr);
+
+ BSONArray initialShards;
+ if (initShardIds == NULL)
+ initialShards = BSONArray();
+ else {
+ BSONArrayBuilder b;
+ for (const ShardId& shardId : *initShardIds) {
+ b.append(shardId);
}
+ initialShards = b.arr();
+ }
- collectionDetail.append("initShards", initialShards);
- collectionDetail.append("numChunks", static_cast<int>(initPoints->size() + 1));
-
- logChange(NULL, "shardCollection.start", ns, collectionDetail.obj());
-
- ChunkManagerPtr manager(new ChunkManager(ns, fieldsAndOrder, unique));
- manager->createFirstChunks(dbPrimaryShardId,
- initPoints,
- initShardIds);
- manager->loadExistingRanges(nullptr);
-
- CollectionInfo collInfo;
- collInfo.useChunkManager(manager);
- collInfo.save(ns);
- manager->reload(true);
-
- // Tell the primary mongod to refresh its data
- // TODO: Think the real fix here is for mongos to just
- // assume that all collections are sharded, when we get there
- for (int i = 0;i < 4;i++) {
- if (i == 3) {
- warning() << "too many tries updating initial version of " << ns
- << " on shard primary " << dbPrimaryShardStr
- << ", other mongoses may not see the collection as sharded immediately";
- break;
- }
+ collectionDetail.append("initShards", initialShards);
+ collectionDetail.append("numChunks", static_cast<int>(initPoints->size() + 1));
- try {
- const auto shard = grid.shardRegistry()->getShard(dbPrimaryShardId);
- ShardConnection conn(shard->getConnString(), ns);
- bool isVersionSet = conn.setVersion();
- conn.done();
- if (!isVersionSet) {
- warning() << "could not update initial version of "
- << ns << " on shard primary " << dbPrimaryShardStr;
- } else {
- break;
- }
- }
- catch (const DBException& e) {
- warning() << "could not update initial version of " << ns
- << " on shard primary " << dbPrimaryShardStr
- << causedBy(e);
- }
+ logChange(NULL, "shardCollection.start", ns, collectionDetail.obj());
- sleepsecs(i);
- }
+ ChunkManagerPtr manager(new ChunkManager(ns, fieldsAndOrder, unique));
+ manager->createFirstChunks(dbPrimaryShardId, initPoints, initShardIds);
+ manager->loadExistingRanges(nullptr);
- // Record finish in changelog
- BSONObjBuilder finishDetail;
+ CollectionInfo collInfo;
+ collInfo.useChunkManager(manager);
+ collInfo.save(ns);
+ manager->reload(true);
- finishDetail.append("version", manager->getVersion().toString());
+ // Tell the primary mongod to refresh its data
+ // TODO: Think the real fix here is for mongos to just
+ // assume that all collections are sharded, when we get there
+ for (int i = 0; i < 4; i++) {
+ if (i == 3) {
+ warning() << "too many tries updating initial version of " << ns << " on shard primary "
+ << dbPrimaryShardStr
+ << ", other mongoses may not see the collection as sharded immediately";
+ break;
+ }
- logChange(NULL, "shardCollection", ns, finishDetail.obj());
+ try {
+ const auto shard = grid.shardRegistry()->getShard(dbPrimaryShardId);
+ ShardConnection conn(shard->getConnString(), ns);
+ bool isVersionSet = conn.setVersion();
+ conn.done();
+ if (!isVersionSet) {
+ warning() << "could not update initial version of " << ns << " on shard primary "
+ << dbPrimaryShardStr;
+ } else {
+ break;
+ }
+ } catch (const DBException& e) {
+ warning() << "could not update initial version of " << ns << " on shard primary "
+ << dbPrimaryShardStr << causedBy(e);
+ }
- return Status::OK();
+ sleepsecs(i);
}
- Status CatalogManagerLegacy::createDatabase(const std::string& dbName) {
- invariant(nsIsDbOnly(dbName));
+ // Record finish in changelog
+ BSONObjBuilder finishDetail;
- // The admin and config databases should never be explicitly created. They "just exist",
- // i.e. getDatabase will always return an entry for them.
- invariant(dbName != "admin");
- invariant(dbName != "config");
+ finishDetail.append("version", manager->getVersion().toString());
- // Lock the database globally to prevent conflicts with simultaneous database creation.
- auto scopedDistLock = getDistLockManager()->lock(dbName,
- "createDatabase",
- Seconds{5},
- Milliseconds{500});
- if (!scopedDistLock.isOK()) {
- return scopedDistLock.getStatus();
- }
-
- // Check for case sensitivity violations
- auto status = _checkDbDoesNotExist(dbName);
- if (!status.isOK()) {
- return status;
- }
+ logChange(NULL, "shardCollection", ns, finishDetail.obj());
- // Database does not exist, pick a shard and create a new entry
- const ShardPtr primaryShard = Shard::pick();
- if (!primaryShard) {
- return Status(ErrorCodes::ShardNotFound, "can't find a shard to put new db on");
- }
+ return Status::OK();
+}
- log() << "Placing [" << dbName << "] on: " << primaryShard->toString();
+Status CatalogManagerLegacy::createDatabase(const std::string& dbName) {
+ invariant(nsIsDbOnly(dbName));
- DatabaseType db;
- db.setName(dbName);
- db.setPrimary(primaryShard->getId());
- db.setSharded(false);
+ // The admin and config databases should never be explicitly created. They "just exist",
+ // i.e. getDatabase will always return an entry for them.
+ invariant(dbName != "admin");
+ invariant(dbName != "config");
- BatchedCommandResponse response;
- status = insert(DatabaseType::ConfigNS, db.toBSON(), &response);
- if (status.isOK()) {
- return status;
- }
+ // Lock the database globally to prevent conflicts with simultaneous database creation.
+ auto scopedDistLock =
+ getDistLockManager()->lock(dbName, "createDatabase", Seconds{5}, Milliseconds{500});
+ if (!scopedDistLock.isOK()) {
+ return scopedDistLock.getStatus();
+ }
- if (status.code() == ErrorCodes::DuplicateKey) {
- return Status(ErrorCodes::NamespaceExists, "database " + dbName + " already exists");
- }
+ // Check for case sensitivity violations
+ auto status = _checkDbDoesNotExist(dbName);
+ if (!status.isOK()) {
+ return status;
+ }
- return Status(status.code(), str::stream() << "database metadata write failed for "
- << dbName << ". Error: " << response.toBSON());
+ // Database does not exist, pick a shard and create a new entry
+ const ShardPtr primaryShard = Shard::pick();
+ if (!primaryShard) {
+ return Status(ErrorCodes::ShardNotFound, "can't find a shard to put new db on");
}
- StatusWith<string> CatalogManagerLegacy::addShard(const string& name,
- const ConnectionString& shardConnectionString,
- const long long maxSize) {
+ log() << "Placing [" << dbName << "] on: " << primaryShard->toString();
- string shardName;
- ReplicaSetMonitorPtr rsMonitor;
- vector<string> dbNames;
+ DatabaseType db;
+ db.setName(dbName);
+ db.setPrimary(primaryShard->getId());
+ db.setSharded(false);
- try {
- ScopedDbConnection newShardConn(shardConnectionString);
- newShardConn->getLastError();
-
- StatusWith<string> validShard = isValidShard(name,
- shardConnectionString,
- newShardConn);
- if (!validShard.isOK()) {
- newShardConn.done();
- return validShard.getStatus();
- }
- shardName = validShard.getValue();
+ BatchedCommandResponse response;
+ status = insert(DatabaseType::ConfigNS, db.toBSON(), &response);
+ if (status.isOK()) {
+ return status;
+ }
- StatusWith<vector<string>> shardDBNames = getDBNames(shardConnectionString,
- newShardConn);
- if (!shardDBNames.isOK()) {
- newShardConn.done();
- return shardDBNames.getStatus();
- }
- dbNames = shardDBNames.getValue();
+ if (status.code() == ErrorCodes::DuplicateKey) {
+ return Status(ErrorCodes::NamespaceExists, "database " + dbName + " already exists");
+ }
- if (newShardConn->type() == ConnectionString::SET) {
- rsMonitor = ReplicaSetMonitor::get(shardConnectionString.getSetName());
- }
+ return Status(status.code(),
+ str::stream() << "database metadata write failed for " << dbName
+ << ". Error: " << response.toBSON());
+}
+
+StatusWith<string> CatalogManagerLegacy::addShard(const string& name,
+ const ConnectionString& shardConnectionString,
+ const long long maxSize) {
+ string shardName;
+ ReplicaSetMonitorPtr rsMonitor;
+ vector<string> dbNames;
+ try {
+ ScopedDbConnection newShardConn(shardConnectionString);
+ newShardConn->getLastError();
+
+ StatusWith<string> validShard = isValidShard(name, shardConnectionString, newShardConn);
+ if (!validShard.isOK()) {
newShardConn.done();
+ return validShard.getStatus();
}
- catch (const DBException& e) {
- if (shardConnectionString.type() == ConnectionString::SET) {
- shardConnectionPool.removeHost(shardConnectionString.getSetName());
- ReplicaSetMonitor::remove(shardConnectionString.getSetName());
- }
+ shardName = validShard.getValue();
- return Status(ErrorCodes::OperationFailed,
- str::stream() << "couldn't connect to new shard "
- << e.what());
+ StatusWith<vector<string>> shardDBNames = getDBNames(shardConnectionString, newShardConn);
+ if (!shardDBNames.isOK()) {
+ newShardConn.done();
+ return shardDBNames.getStatus();
}
+ dbNames = shardDBNames.getValue();
- // check that none of the existing shard candidate's db's exist elsewhere
- for (vector<string>::const_iterator it = dbNames.begin(); it != dbNames.end(); ++it) {
- StatusWith<DatabaseType> dbt = getDatabase(*it);
- if (dbt.isOK()) {
- return Status(ErrorCodes::OperationFailed,
- str::stream() << "can't add shard "
- << "'" << shardConnectionString.toString() << "'"
- << " because a local database '" << *it
- << "' exists in another "
- << dbt.getValue().getPrimary());
- }
+ if (newShardConn->type() == ConnectionString::SET) {
+ rsMonitor = ReplicaSetMonitor::get(shardConnectionString.getSetName());
}
- // if a name for a shard wasn't provided, pick one.
- if (shardName.empty()) {
- StatusWith<string> result = _getNewShardName();
- if (!result.isOK()) {
- return Status(ErrorCodes::OperationFailed,
- "error generating new shard name");
- }
- shardName = result.getValue();
+ newShardConn.done();
+ } catch (const DBException& e) {
+ if (shardConnectionString.type() == ConnectionString::SET) {
+ shardConnectionPool.removeHost(shardConnectionString.getSetName());
+ ReplicaSetMonitor::remove(shardConnectionString.getSetName());
}
- // build the ConfigDB shard document
- BSONObjBuilder b;
- b.append(ShardType::name(), shardName);
- b.append(ShardType::host(),
- rsMonitor ? rsMonitor->getServerAddress() : shardConnectionString.toString());
- if (maxSize > 0) {
- b.append(ShardType::maxSizeMB(), maxSize);
- }
- BSONObj shardDoc = b.obj();
+ return Status(ErrorCodes::OperationFailed,
+ str::stream() << "couldn't connect to new shard " << e.what());
+ }
- if (isShardHost(shardConnectionString)) {
- return Status(ErrorCodes::OperationFailed, "host already used");
+ // check that none of the existing shard candidate's db's exist elsewhere
+ for (vector<string>::const_iterator it = dbNames.begin(); it != dbNames.end(); ++it) {
+ StatusWith<DatabaseType> dbt = getDatabase(*it);
+ if (dbt.isOK()) {
+ return Status(ErrorCodes::OperationFailed,
+ str::stream() << "can't add shard "
+ << "'" << shardConnectionString.toString() << "'"
+ << " because a local database '" << *it
+ << "' exists in another " << dbt.getValue().getPrimary());
}
+ }
- log() << "going to add shard: " << shardDoc;
-
- Status result = insert(ShardType::ConfigNS, shardDoc, NULL);
+ // if a name for a shard wasn't provided, pick one.
+ if (shardName.empty()) {
+ StatusWith<string> result = _getNewShardName();
if (!result.isOK()) {
- log() << "error adding shard: " << shardDoc << " err: " << result.reason();
- return result;
+ return Status(ErrorCodes::OperationFailed, "error generating new shard name");
}
+ shardName = result.getValue();
+ }
- Shard::reloadShardInfo();
-
- // add all databases of the new shard
- for (vector<string>::const_iterator it = dbNames.begin(); it != dbNames.end(); ++it) {
- DatabaseType dbt;
- dbt.setName(*it);
- dbt.setPrimary(shardName);
- dbt.setSharded(false);
- Status status = updateDatabase(*it, dbt);
- if (!status.isOK()) {
- log() << "adding shard " << shardConnectionString.toString()
- << " even though could not add database " << *it;
- }
- }
+ // build the ConfigDB shard document
+ BSONObjBuilder b;
+ b.append(ShardType::name(), shardName);
+ b.append(ShardType::host(),
+ rsMonitor ? rsMonitor->getServerAddress() : shardConnectionString.toString());
+ if (maxSize > 0) {
+ b.append(ShardType::maxSizeMB(), maxSize);
+ }
+ BSONObj shardDoc = b.obj();
- // Record in changelog
- BSONObjBuilder shardDetails;
- shardDetails.append("name", shardName);
- shardDetails.append("host", shardConnectionString.toString());
+ if (isShardHost(shardConnectionString)) {
+ return Status(ErrorCodes::OperationFailed, "host already used");
+ }
- logChange(NULL, "addShard", "", shardDetails.obj());
+ log() << "going to add shard: " << shardDoc;
- return shardName;
+ Status result = insert(ShardType::ConfigNS, shardDoc, NULL);
+ if (!result.isOK()) {
+ log() << "error adding shard: " << shardDoc << " err: " << result.reason();
+ return result;
}
- StatusWith<ShardDrainingStatus> CatalogManagerLegacy::removeShard(OperationContext* txn,
- const std::string& name) {
- ScopedDbConnection conn(_configServerConnectionString, 30);
+ Shard::reloadShardInfo();
- if (conn->count(ShardType::ConfigNS,
- BSON(ShardType::name() << NE << name
- << ShardType::draining(true)))) {
- conn.done();
- return Status(ErrorCodes::ConflictingOperationInProgress,
- "Can't have more than one draining shard at a time");
+ // add all databases of the new shard
+ for (vector<string>::const_iterator it = dbNames.begin(); it != dbNames.end(); ++it) {
+ DatabaseType dbt;
+ dbt.setName(*it);
+ dbt.setPrimary(shardName);
+ dbt.setSharded(false);
+ Status status = updateDatabase(*it, dbt);
+ if (!status.isOK()) {
+ log() << "adding shard " << shardConnectionString.toString()
+ << " even though could not add database " << *it;
}
+ }
- if (conn->count(ShardType::ConfigNS,
- BSON(ShardType::name() << NE << name)) == 0) {
- conn.done();
- return Status(ErrorCodes::IllegalOperation,
- "Can't remove last shard");
- }
+ // Record in changelog
+ BSONObjBuilder shardDetails;
+ shardDetails.append("name", shardName);
+ shardDetails.append("host", shardConnectionString.toString());
- BSONObj searchDoc = BSON(ShardType::name() << name);
+ logChange(NULL, "addShard", "", shardDetails.obj());
- // Case 1: start draining chunks
- BSONObj drainingDoc = BSON(ShardType::name() << name << ShardType::draining(true));
- BSONObj shardDoc = conn->findOne(ShardType::ConfigNS, drainingDoc);
- if (shardDoc.isEmpty()) {
- log() << "going to start draining shard: " << name;
- BSONObj newStatus = BSON("$set" << BSON(ShardType::draining(true)));
+ return shardName;
+}
- Status status = update(ShardType::ConfigNS, searchDoc, newStatus, false, false, NULL);
- if (!status.isOK()) {
- log() << "error starting removeShard: " << name
- << "; err: " << status.reason();
- return status;
- }
+StatusWith<ShardDrainingStatus> CatalogManagerLegacy::removeShard(OperationContext* txn,
+ const std::string& name) {
+ ScopedDbConnection conn(_configServerConnectionString, 30);
- BSONObj primaryLocalDoc = BSON(DatabaseType::name("local") <<
- DatabaseType::primary(name));
- log() << "primaryLocalDoc: " << primaryLocalDoc;
- if (conn->count(DatabaseType::ConfigNS, primaryLocalDoc)) {
- log() << "This shard is listed as primary of local db. Removing entry.";
-
- Status status = remove(DatabaseType::ConfigNS,
- BSON(DatabaseType::name("local")),
- 0,
- NULL);
- if (!status.isOK()) {
- log() << "error removing local db: "
- << status.reason();
- return status;
- }
- }
+ if (conn->count(ShardType::ConfigNS,
+ BSON(ShardType::name() << NE << name << ShardType::draining(true)))) {
+ conn.done();
+ return Status(ErrorCodes::ConflictingOperationInProgress,
+ "Can't have more than one draining shard at a time");
+ }
- Shard::reloadShardInfo();
- conn.done();
+ if (conn->count(ShardType::ConfigNS, BSON(ShardType::name() << NE << name)) == 0) {
+ conn.done();
+ return Status(ErrorCodes::IllegalOperation, "Can't remove last shard");
+ }
+
+ BSONObj searchDoc = BSON(ShardType::name() << name);
+
+ // Case 1: start draining chunks
+ BSONObj drainingDoc = BSON(ShardType::name() << name << ShardType::draining(true));
+ BSONObj shardDoc = conn->findOne(ShardType::ConfigNS, drainingDoc);
+ if (shardDoc.isEmpty()) {
+ log() << "going to start draining shard: " << name;
+ BSONObj newStatus = BSON("$set" << BSON(ShardType::draining(true)));
- // Record start in changelog
- logChange(txn, "removeShard.start", "", buildRemoveLogEntry(name, true));
- return ShardDrainingStatus::STARTED;
+ Status status = update(ShardType::ConfigNS, searchDoc, newStatus, false, false, NULL);
+ if (!status.isOK()) {
+ log() << "error starting removeShard: " << name << "; err: " << status.reason();
+ return status;
}
- // Case 2: all chunks drained
- BSONObj shardIDDoc = BSON(ChunkType::shard(shardDoc[ShardType::name()].str()));
- long long chunkCount = conn->count(ChunkType::ConfigNS, shardIDDoc);
- long long dbCount = conn->count(DatabaseType::ConfigNS,
- BSON(DatabaseType::name.ne("local")
- << DatabaseType::primary(name)));
- if (chunkCount == 0 && dbCount == 0) {
- log() << "going to remove shard: " << name;
- audit::logRemoveShard(ClientBasic::getCurrent(), name);
-
- Status status = remove(ShardType::ConfigNS, searchDoc, 0, NULL);
+ BSONObj primaryLocalDoc = BSON(DatabaseType::name("local") << DatabaseType::primary(name));
+ log() << "primaryLocalDoc: " << primaryLocalDoc;
+ if (conn->count(DatabaseType::ConfigNS, primaryLocalDoc)) {
+ log() << "This shard is listed as primary of local db. Removing entry.";
+
+ Status status =
+ remove(DatabaseType::ConfigNS, BSON(DatabaseType::name("local")), 0, NULL);
if (!status.isOK()) {
- log() << "Error concluding removeShard operation on: " << name
- << "; err: " << status.reason();
+ log() << "error removing local db: " << status.reason();
return status;
}
+ }
- grid.shardRegistry()->remove(name);
-
- shardConnectionPool.removeHost(name);
- ReplicaSetMonitor::remove(name);
+ Shard::reloadShardInfo();
+ conn.done();
- Shard::reloadShardInfo();
- conn.done();
+ // Record start in changelog
+ logChange(txn, "removeShard.start", "", buildRemoveLogEntry(name, true));
+ return ShardDrainingStatus::STARTED;
+ }
- // Record finish in changelog
- logChange(txn, "removeShard", "", buildRemoveLogEntry(name, false));
- return ShardDrainingStatus::COMPLETED;
+ // Case 2: all chunks drained
+ BSONObj shardIDDoc = BSON(ChunkType::shard(shardDoc[ShardType::name()].str()));
+ long long chunkCount = conn->count(ChunkType::ConfigNS, shardIDDoc);
+ long long dbCount =
+ conn->count(DatabaseType::ConfigNS,
+ BSON(DatabaseType::name.ne("local") << DatabaseType::primary(name)));
+ if (chunkCount == 0 && dbCount == 0) {
+ log() << "going to remove shard: " << name;
+ audit::logRemoveShard(ClientBasic::getCurrent(), name);
+
+ Status status = remove(ShardType::ConfigNS, searchDoc, 0, NULL);
+ if (!status.isOK()) {
+ log() << "Error concluding removeShard operation on: " << name
+ << "; err: " << status.reason();
+ return status;
}
- // case 3: draining ongoing
- return ShardDrainingStatus::ONGOING;
- }
+ grid.shardRegistry()->remove(name);
- Status CatalogManagerLegacy::updateDatabase(const std::string& dbName, const DatabaseType& db) {
- fassert(28616, db.validate());
+ shardConnectionPool.removeHost(name);
+ ReplicaSetMonitor::remove(name);
- BatchedCommandResponse response;
- Status status = update(DatabaseType::ConfigNS,
- BSON(DatabaseType::name(dbName)),
- db.toBSON(),
- true, // upsert
- false, // multi
- &response);
- if (!status.isOK()) {
- return Status(status.code(),
- str::stream() << "database metadata write failed: "
- << response.toBSON() << "; status: " << status.toString());
- }
+ Shard::reloadShardInfo();
+ conn.done();
- return Status::OK();
+ // Record finish in changelog
+ logChange(txn, "removeShard", "", buildRemoveLogEntry(name, false));
+ return ShardDrainingStatus::COMPLETED;
}
- StatusWith<DatabaseType> CatalogManagerLegacy::getDatabase(const std::string& dbName) {
- invariant(nsIsDbOnly(dbName));
+ // case 3: draining ongoing
+ return ShardDrainingStatus::ONGOING;
+}
+
+Status CatalogManagerLegacy::updateDatabase(const std::string& dbName, const DatabaseType& db) {
+ fassert(28616, db.validate());
+
+ BatchedCommandResponse response;
+ Status status = update(DatabaseType::ConfigNS,
+ BSON(DatabaseType::name(dbName)),
+ db.toBSON(),
+ true, // upsert
+ false, // multi
+ &response);
+ if (!status.isOK()) {
+ return Status(status.code(),
+ str::stream() << "database metadata write failed: " << response.toBSON()
+ << "; status: " << status.toString());
+ }
- // The two databases that are hosted on the config server are config and admin
- if (dbName == "config" || dbName == "admin") {
- DatabaseType dbt;
- dbt.setName(dbName);
- dbt.setSharded(false);
- dbt.setPrimary("config");
+ return Status::OK();
+}
- return dbt;
- }
+StatusWith<DatabaseType> CatalogManagerLegacy::getDatabase(const std::string& dbName) {
+ invariant(nsIsDbOnly(dbName));
- ScopedDbConnection conn(_configServerConnectionString, 30.0);
-
- BSONObj dbObj = conn->findOne(DatabaseType::ConfigNS, BSON(DatabaseType::name(dbName)));
- if (dbObj.isEmpty()) {
- conn.done();
- return Status(ErrorCodes::DatabaseNotFound,
- stream() << "database " << dbName << " not found");
- }
+ // The two databases that are hosted on the config server are config and admin
+ if (dbName == "config" || dbName == "admin") {
+ DatabaseType dbt;
+ dbt.setName(dbName);
+ dbt.setSharded(false);
+ dbt.setPrimary("config");
- conn.done();
- return DatabaseType::fromBSON(dbObj);
+ return dbt;
}
- Status CatalogManagerLegacy::updateCollection(const std::string& collNs,
- const CollectionType& coll) {
- fassert(28634, coll.validate());
+ ScopedDbConnection conn(_configServerConnectionString, 30.0);
- BatchedCommandResponse response;
- Status status = update(CollectionType::ConfigNS,
- BSON(CollectionType::fullNs(collNs)),
- coll.toBSON(),
- true, // upsert
- false, // multi
- &response);
- if (!status.isOK()) {
- return Status(status.code(),
- str::stream() << "collection metadata write failed: "
- << response.toBSON() << "; status: " << status.toString());
- }
+ BSONObj dbObj = conn->findOne(DatabaseType::ConfigNS, BSON(DatabaseType::name(dbName)));
+ if (dbObj.isEmpty()) {
+ conn.done();
+ return Status(ErrorCodes::DatabaseNotFound,
+ stream() << "database " << dbName << " not found");
+ }
- return Status::OK();
+ conn.done();
+ return DatabaseType::fromBSON(dbObj);
+}
+
+Status CatalogManagerLegacy::updateCollection(const std::string& collNs,
+ const CollectionType& coll) {
+ fassert(28634, coll.validate());
+
+ BatchedCommandResponse response;
+ Status status = update(CollectionType::ConfigNS,
+ BSON(CollectionType::fullNs(collNs)),
+ coll.toBSON(),
+ true, // upsert
+ false, // multi
+ &response);
+ if (!status.isOK()) {
+ return Status(status.code(),
+ str::stream() << "collection metadata write failed: " << response.toBSON()
+ << "; status: " << status.toString());
}
- StatusWith<CollectionType> CatalogManagerLegacy::getCollection(const std::string& collNs) {
- ScopedDbConnection conn(_configServerConnectionString, 30.0);
+ return Status::OK();
+}
- BSONObj collObj = conn->findOne(CollectionType::ConfigNS,
- BSON(CollectionType::fullNs(collNs)));
- if (collObj.isEmpty()) {
- conn.done();
- return Status(ErrorCodes::NamespaceNotFound,
- stream() << "collection " << collNs << " not found");
- }
+StatusWith<CollectionType> CatalogManagerLegacy::getCollection(const std::string& collNs) {
+ ScopedDbConnection conn(_configServerConnectionString, 30.0);
+ BSONObj collObj = conn->findOne(CollectionType::ConfigNS, BSON(CollectionType::fullNs(collNs)));
+ if (collObj.isEmpty()) {
conn.done();
- return CollectionType::fromBSON(collObj);
+ return Status(ErrorCodes::NamespaceNotFound,
+ stream() << "collection " << collNs << " not found");
}
- Status CatalogManagerLegacy::getCollections(const std::string* dbName,
- std::vector<CollectionType>* collections) {
- collections->clear();
+ conn.done();
+ return CollectionType::fromBSON(collObj);
+}
- BSONObjBuilder b;
- if (dbName) {
- invariant(!dbName->empty());
- b.appendRegex(CollectionType::fullNs(),
- (string)"^" + pcrecpp::RE::QuoteMeta(*dbName) + "\\.");
- }
+Status CatalogManagerLegacy::getCollections(const std::string* dbName,
+ std::vector<CollectionType>* collections) {
+ collections->clear();
- ScopedDbConnection conn(_configServerConnectionString, 30.0);
+ BSONObjBuilder b;
+ if (dbName) {
+ invariant(!dbName->empty());
+ b.appendRegex(CollectionType::fullNs(),
+ (string) "^" + pcrecpp::RE::QuoteMeta(*dbName) + "\\.");
+ }
- std::unique_ptr<DBClientCursor> cursor(_safeCursor(conn->query(CollectionType::ConfigNS,
- b.obj())));
+ ScopedDbConnection conn(_configServerConnectionString, 30.0);
- while (cursor->more()) {
- const BSONObj collObj = cursor->next();
+ std::unique_ptr<DBClientCursor> cursor(
+ _safeCursor(conn->query(CollectionType::ConfigNS, b.obj())));
- auto status = CollectionType::fromBSON(collObj);
- if (!status.isOK()) {
- conn.done();
- return status.getStatus();
- }
+ while (cursor->more()) {
+ const BSONObj collObj = cursor->next();
- collections->push_back(status.getValue());
+ auto status = CollectionType::fromBSON(collObj);
+ if (!status.isOK()) {
+ conn.done();
+ return status.getStatus();
}
- conn.done();
- return Status::OK();
+ collections->push_back(status.getValue());
}
- Status CatalogManagerLegacy::dropCollection(const std::string& collectionNs) {
- logChange(NULL, "dropCollection.start", collectionNs, BSONObj());
+ conn.done();
+ return Status::OK();
+}
- // Lock the collection globally so that split/migrate cannot run
- auto scopedDistLock = getDistLockManager()->lock(collectionNs, "drop");
- if (!scopedDistLock.isOK()) {
- return scopedDistLock.getStatus();
- }
+Status CatalogManagerLegacy::dropCollection(const std::string& collectionNs) {
+ logChange(NULL, "dropCollection.start", collectionNs, BSONObj());
- LOG(1) << "dropCollection " << collectionNs << " started";
+ // Lock the collection globally so that split/migrate cannot run
+ auto scopedDistLock = getDistLockManager()->lock(collectionNs, "drop");
+ if (!scopedDistLock.isOK()) {
+ return scopedDistLock.getStatus();
+ }
- // This cleans up the collection on all shards
- vector<ShardType> allShards;
- Status status = getAllShards(&allShards);
- if (!status.isOK()) {
- return status;
- }
+ LOG(1) << "dropCollection " << collectionNs << " started";
- LOG(1) << "dropCollection " << collectionNs << " locked";
+ // This cleans up the collection on all shards
+ vector<ShardType> allShards;
+ Status status = getAllShards(&allShards);
+ if (!status.isOK()) {
+ return status;
+ }
- map<string, BSONObj> errors;
+ LOG(1) << "dropCollection " << collectionNs << " locked";
- // Delete data from all mongods
- for (vector<ShardType>::const_iterator i = allShards.begin(); i != allShards.end(); i++) {
- const auto shard = grid.shardRegistry()->getShard(i->getName());
- ScopedDbConnection conn(shard->getConnString());
+ map<string, BSONObj> errors;
- BSONObj info;
- if (!conn->dropCollection(collectionNs, &info)) {
- // Ignore the database not found errors
- if (info["code"].isNumber() &&
- (info["code"].Int() == ErrorCodes::NamespaceNotFound)) {
- conn.done();
- continue;
- }
+ // Delete data from all mongods
+ for (vector<ShardType>::const_iterator i = allShards.begin(); i != allShards.end(); i++) {
+ const auto shard = grid.shardRegistry()->getShard(i->getName());
+ ScopedDbConnection conn(shard->getConnString());
- errors[shard->getConnString().toString()] = info;
+ BSONObj info;
+ if (!conn->dropCollection(collectionNs, &info)) {
+ // Ignore the database not found errors
+ if (info["code"].isNumber() && (info["code"].Int() == ErrorCodes::NamespaceNotFound)) {
+ conn.done();
+ continue;
}
- conn.done();
+ errors[shard->getConnString().toString()] = info;
}
- if (!errors.empty()) {
- StringBuilder sb;
- sb << "Dropping collection failed on the following hosts: ";
-
- for (map<string, BSONObj>::const_iterator it = errors.begin();
- it != errors.end();
- ++it) {
+ conn.done();
+ }
- if (it != errors.begin()) {
- sb << ", ";
- }
+ if (!errors.empty()) {
+ StringBuilder sb;
+ sb << "Dropping collection failed on the following hosts: ";
- sb << it->first << ": " << it->second;
+ for (map<string, BSONObj>::const_iterator it = errors.begin(); it != errors.end(); ++it) {
+ if (it != errors.begin()) {
+ sb << ", ";
}
- return Status(ErrorCodes::OperationFailed, sb.str());
+ sb << it->first << ": " << it->second;
}
- LOG(1) << "dropCollection " << collectionNs << " shard data deleted";
-
- // remove chunk data
- Status result = remove(ChunkType::ConfigNS,
- BSON(ChunkType::ns(collectionNs)),
- 0,
- NULL);
- if (!result.isOK()) {
- return result;
- }
+ return Status(ErrorCodes::OperationFailed, sb.str());
+ }
- LOG(1) << "dropCollection " << collectionNs << " chunk data deleted";
+ LOG(1) << "dropCollection " << collectionNs << " shard data deleted";
- for (vector<ShardType>::const_iterator i = allShards.begin(); i != allShards.end(); i++) {
- const auto shard = grid.shardRegistry()->getShard(i->getName());
- ScopedDbConnection conn(shard->getConnString());
+ // remove chunk data
+ Status result = remove(ChunkType::ConfigNS, BSON(ChunkType::ns(collectionNs)), 0, NULL);
+ if (!result.isOK()) {
+ return result;
+ }
- BSONObj res;
+ LOG(1) << "dropCollection " << collectionNs << " chunk data deleted";
- // this is horrible
- // we need a special command for dropping on the d side
- // this hack works for the moment
+ for (vector<ShardType>::const_iterator i = allShards.begin(); i != allShards.end(); i++) {
+ const auto shard = grid.shardRegistry()->getShard(i->getName());
+ ScopedDbConnection conn(shard->getConnString());
- if (!setShardVersion(conn.conn(),
- collectionNs,
- _configServerConnectionString.toString(),
- ChunkVersion(0, 0, OID()),
- NULL,
- true,
- res)) {
+ BSONObj res;
- return Status(static_cast<ErrorCodes::Error>(8071),
- str::stream() << "cleaning up after drop failed: " << res);
- }
+ // this is horrible
+ // we need a special command for dropping on the d side
+ // this hack works for the moment
- conn->simpleCommand("admin", 0, "unsetSharding");
- conn.done();
+ if (!setShardVersion(conn.conn(),
+ collectionNs,
+ _configServerConnectionString.toString(),
+ ChunkVersion(0, 0, OID()),
+ NULL,
+ true,
+ res)) {
+ return Status(static_cast<ErrorCodes::Error>(8071),
+ str::stream() << "cleaning up after drop failed: " << res);
}
- LOG(1) << "dropCollection " << collectionNs << " completed";
+ conn->simpleCommand("admin", 0, "unsetSharding");
+ conn.done();
+ }
- logChange(NULL, "dropCollection", collectionNs, BSONObj());
+ LOG(1) << "dropCollection " << collectionNs << " completed";
- return Status::OK();
- }
+ logChange(NULL, "dropCollection", collectionNs, BSONObj());
- void CatalogManagerLegacy::logAction(const ActionLogType& actionLog) {
- // Create the action log collection and ensure that it is capped. Wrap in try/catch,
- // because creating an existing collection throws.
- if (actionLogCollectionCreated.load() == 0) {
- try {
- ScopedDbConnection conn(_configServerConnectionString, 30.0);
- conn->createCollection(ActionLogType::ConfigNS, 1024 * 1024 * 2, true);
- conn.done();
+ return Status::OK();
+}
- actionLogCollectionCreated.store(1);
- }
- catch (const DBException& e) {
- // It's ok to ignore this exception
- LOG(1) << "couldn't create actionlog collection: " << e;
- }
- }
+void CatalogManagerLegacy::logAction(const ActionLogType& actionLog) {
+ // Create the action log collection and ensure that it is capped. Wrap in try/catch,
+ // because creating an existing collection throws.
+ if (actionLogCollectionCreated.load() == 0) {
+ try {
+ ScopedDbConnection conn(_configServerConnectionString, 30.0);
+ conn->createCollection(ActionLogType::ConfigNS, 1024 * 1024 * 2, true);
+ conn.done();
- Status result = insert(ActionLogType::ConfigNS, actionLog.toBSON(), NULL);
- if (!result.isOK()) {
- log() << "error encountered while logging action: " << result;
+ actionLogCollectionCreated.store(1);
+ } catch (const DBException& e) {
+ // It's ok to ignore this exception
+ LOG(1) << "couldn't create actionlog collection: " << e;
}
}
- void CatalogManagerLegacy::logChange(OperationContext* opCtx,
- const string& what,
- const string& ns,
- const BSONObj& detail) {
-
- // Create the change log collection and ensure that it is capped. Wrap in try/catch,
- // because creating an existing collection throws.
- if (changeLogCollectionCreated.load() == 0) {
- try {
- ScopedDbConnection conn(_configServerConnectionString, 30.0);
- conn->createCollection(ChangelogType::ConfigNS, 1024 * 1024 * 10, true);
- conn.done();
+ Status result = insert(ActionLogType::ConfigNS, actionLog.toBSON(), NULL);
+ if (!result.isOK()) {
+ log() << "error encountered while logging action: " << result;
+ }
+}
+
+void CatalogManagerLegacy::logChange(OperationContext* opCtx,
+ const string& what,
+ const string& ns,
+ const BSONObj& detail) {
+ // Create the change log collection and ensure that it is capped. Wrap in try/catch,
+ // because creating an existing collection throws.
+ if (changeLogCollectionCreated.load() == 0) {
+ try {
+ ScopedDbConnection conn(_configServerConnectionString, 30.0);
+ conn->createCollection(ChangelogType::ConfigNS, 1024 * 1024 * 10, true);
+ conn.done();
- changeLogCollectionCreated.store(1);
- }
- catch (const UserException& e) {
- // It's ok to ignore this exception
- LOG(1) << "couldn't create changelog collection: " << e;
- }
+ changeLogCollectionCreated.store(1);
+ } catch (const UserException& e) {
+ // It's ok to ignore this exception
+ LOG(1) << "couldn't create changelog collection: " << e;
}
+ }
- // Store this entry's ID so we can use on the exception code path too
- StringBuilder changeIdBuilder;
- changeIdBuilder << getHostNameCached() << "-" << terseCurrentTime()
- << "-" << OID::gen();
+ // Store this entry's ID so we can use on the exception code path too
+ StringBuilder changeIdBuilder;
+ changeIdBuilder << getHostNameCached() << "-" << terseCurrentTime() << "-" << OID::gen();
- const string changeID = changeIdBuilder.str();
+ const string changeID = changeIdBuilder.str();
- Client* client;
- if (opCtx) {
- client = opCtx->getClient();
- }
- else if (haveClient()) {
- client = &cc();
- }
- else {
- client = nullptr;
- }
+ Client* client;
+ if (opCtx) {
+ client = opCtx->getClient();
+ } else if (haveClient()) {
+ client = &cc();
+ } else {
+ client = nullptr;
+ }
- // Send a copy of the message to the local log in case it doesn't manage to reach
- // config.changelog
- BSONObj msg = BSON(ChangelogType::changeID(changeID) <<
- ChangelogType::server(getHostNameCached()) <<
- ChangelogType::clientAddr((client ?
- client->clientAddress(true) : "")) <<
- ChangelogType::time(jsTime()) <<
- ChangelogType::what(what) <<
- ChangelogType::ns(ns) <<
- ChangelogType::details(detail));
+ // Send a copy of the message to the local log in case it doesn't manage to reach
+ // config.changelog
+ BSONObj msg = BSON(ChangelogType::changeID(changeID)
+ << ChangelogType::server(getHostNameCached())
+ << ChangelogType::clientAddr((client ? client->clientAddress(true) : ""))
+ << ChangelogType::time(jsTime()) << ChangelogType::what(what)
+ << ChangelogType::ns(ns) << ChangelogType::details(detail));
- log() << "about to log metadata event: " << msg;
+ log() << "about to log metadata event: " << msg;
- Status result = insert(ChangelogType::ConfigNS, msg, NULL);
- if (!result.isOK()) {
- warning() << "Error encountered while logging config change with ID "
- << changeID << ": " << result;
- }
+ Status result = insert(ChangelogType::ConfigNS, msg, NULL);
+ if (!result.isOK()) {
+ warning() << "Error encountered while logging config change with ID " << changeID << ": "
+ << result;
}
+}
- StatusWith<SettingsType> CatalogManagerLegacy::getGlobalSettings(const string& key) {
- try {
- ScopedDbConnection conn(_configServerConnectionString, 30);
- BSONObj settingsDoc = conn->findOne(SettingsType::ConfigNS,
- BSON(SettingsType::key(key)));
- conn.done();
-
- if (settingsDoc.isEmpty()) {
- return Status(ErrorCodes::NoMatchingDocument,
- str::stream() << "can't find settings document with key: " << key);
- }
+StatusWith<SettingsType> CatalogManagerLegacy::getGlobalSettings(const string& key) {
+ try {
+ ScopedDbConnection conn(_configServerConnectionString, 30);
+ BSONObj settingsDoc = conn->findOne(SettingsType::ConfigNS, BSON(SettingsType::key(key)));
+ conn.done();
- StatusWith<SettingsType> settingsResult = SettingsType::fromBSON(settingsDoc);
- if (!settingsResult.isOK()) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "error while parsing settings document: "
- << settingsDoc
- << " : " << settingsResult.getStatus().toString());
- }
+ if (settingsDoc.isEmpty()) {
+ return Status(ErrorCodes::NoMatchingDocument,
+ str::stream() << "can't find settings document with key: " << key);
+ }
- const SettingsType& settings = settingsResult.getValue();
+ StatusWith<SettingsType> settingsResult = SettingsType::fromBSON(settingsDoc);
+ if (!settingsResult.isOK()) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << "error while parsing settings document: " << settingsDoc
+ << " : " << settingsResult.getStatus().toString());
+ }
- Status validationStatus = settings.validate();
- if (!validationStatus.isOK()) {
- return validationStatus;
- }
+ const SettingsType& settings = settingsResult.getValue();
- return settingsResult;
- }
- catch (const DBException& ex) {
- return Status(ErrorCodes::OperationFailed,
- str::stream() << "unable to successfully obtain "
- << "config.settings document: " << causedBy(ex));
+ Status validationStatus = settings.validate();
+ if (!validationStatus.isOK()) {
+ return validationStatus;
}
- }
- Status CatalogManagerLegacy::getDatabasesForShard(const string& shardName,
- vector<string>* dbs) {
- dbs->clear();
-
- try {
- ScopedDbConnection conn(_configServerConnectionString, 30.0);
- std::unique_ptr<DBClientCursor> cursor(_safeCursor(
- conn->query(DatabaseType::ConfigNS,
- Query(BSON(DatabaseType::primary(shardName))))));
- if (!cursor.get()) {
- conn.done();
- return Status(ErrorCodes::HostUnreachable, "unable to open chunk cursor");
- }
+ return settingsResult;
+ } catch (const DBException& ex) {
+ return Status(ErrorCodes::OperationFailed,
+ str::stream() << "unable to successfully obtain "
+ << "config.settings document: " << causedBy(ex));
+ }
+}
- while (cursor->more()) {
- BSONObj shard = cursor->nextSafe();
- dbs->push_back(shard[DatabaseType::name()].str());
- }
+Status CatalogManagerLegacy::getDatabasesForShard(const string& shardName, vector<string>* dbs) {
+ dbs->clear();
+ try {
+ ScopedDbConnection conn(_configServerConnectionString, 30.0);
+ std::unique_ptr<DBClientCursor> cursor(_safeCursor(
+ conn->query(DatabaseType::ConfigNS, Query(BSON(DatabaseType::primary(shardName))))));
+ if (!cursor.get()) {
conn.done();
+ return Status(ErrorCodes::HostUnreachable, "unable to open chunk cursor");
}
- catch (const DBException& ex) {
- return ex.toStatus();
+
+ while (cursor->more()) {
+ BSONObj shard = cursor->nextSafe();
+ dbs->push_back(shard[DatabaseType::name()].str());
}
- return Status::OK();
+ conn.done();
+ } catch (const DBException& ex) {
+ return ex.toStatus();
}
- Status CatalogManagerLegacy::getChunks(const Query& query,
- int nToReturn,
- vector<ChunkType>* chunks) {
- chunks->clear();
+ return Status::OK();
+}
- try {
- ScopedDbConnection conn(_configServerConnectionString, 30.0);
- std::unique_ptr<DBClientCursor> cursor(_safeCursor(conn->query(ChunkType::ConfigNS,
- query,
- nToReturn)));
- if (!cursor.get()) {
- conn.done();
- return Status(ErrorCodes::HostUnreachable, "unable to open chunk cursor");
- }
+Status CatalogManagerLegacy::getChunks(const Query& query,
+ int nToReturn,
+ vector<ChunkType>* chunks) {
+ chunks->clear();
- while (cursor->more()) {
- BSONObj chunkObj = cursor->nextSafe();
+ try {
+ ScopedDbConnection conn(_configServerConnectionString, 30.0);
+ std::unique_ptr<DBClientCursor> cursor(
+ _safeCursor(conn->query(ChunkType::ConfigNS, query, nToReturn)));
+ if (!cursor.get()) {
+ conn.done();
+ return Status(ErrorCodes::HostUnreachable, "unable to open chunk cursor");
+ }
- StatusWith<ChunkType> chunkRes = ChunkType::fromBSON(chunkObj);
- if (!chunkRes.isOK()) {
- conn.done();
- chunks->clear();
- return {ErrorCodes::FailedToParse,
- stream() << "Failed to parse chunk with id ("
- << chunkObj[ChunkType::name()].toString() << "): "
- << chunkRes.getStatus().reason()};
- }
+ while (cursor->more()) {
+ BSONObj chunkObj = cursor->nextSafe();
- chunks->push_back(chunkRes.getValue());
+ StatusWith<ChunkType> chunkRes = ChunkType::fromBSON(chunkObj);
+ if (!chunkRes.isOK()) {
+ conn.done();
+ chunks->clear();
+ return {ErrorCodes::FailedToParse,
+ stream() << "Failed to parse chunk with id ("
+ << chunkObj[ChunkType::name()].toString()
+ << "): " << chunkRes.getStatus().reason()};
}
- conn.done();
- }
- catch (const DBException& ex) {
- return ex.toStatus();
+ chunks->push_back(chunkRes.getValue());
}
- return Status::OK();
+ conn.done();
+ } catch (const DBException& ex) {
+ return ex.toStatus();
}
- Status CatalogManagerLegacy::getTagsForCollection(const std::string& collectionNs,
- std::vector<TagsType>* tags) {
- tags->clear();
+ return Status::OK();
+}
- try {
- ScopedDbConnection conn(_configServerConnectionString, 30);
- std::unique_ptr<DBClientCursor> cursor(_safeCursor(
- conn->query(TagsType::ConfigNS,
- Query(BSON(TagsType::ns(collectionNs)))
- .sort(TagsType::min()))));
- if (!cursor.get()) {
- conn.done();
- return Status(ErrorCodes::HostUnreachable, "unable to open tags cursor");
- }
+Status CatalogManagerLegacy::getTagsForCollection(const std::string& collectionNs,
+ std::vector<TagsType>* tags) {
+ tags->clear();
- while (cursor->more()) {
- BSONObj tagObj = cursor->nextSafe();
+ try {
+ ScopedDbConnection conn(_configServerConnectionString, 30);
+ std::unique_ptr<DBClientCursor> cursor(_safeCursor(conn->query(
+ TagsType::ConfigNS, Query(BSON(TagsType::ns(collectionNs))).sort(TagsType::min()))));
+ if (!cursor.get()) {
+ conn.done();
+ return Status(ErrorCodes::HostUnreachable, "unable to open tags cursor");
+ }
- StatusWith<TagsType> tagRes = TagsType::fromBSON(tagObj);
- if (!tagRes.isOK()) {
- conn.done();
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "Failed to parse tag BSONObj: "
- << tagRes.getStatus().reason());
- }
+ while (cursor->more()) {
+ BSONObj tagObj = cursor->nextSafe();
- tags->push_back(tagRes.getValue());
+ StatusWith<TagsType> tagRes = TagsType::fromBSON(tagObj);
+ if (!tagRes.isOK()) {
+ conn.done();
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << "Failed to parse tag BSONObj: "
+ << tagRes.getStatus().reason());
}
- conn.done();
- }
- catch (const DBException& ex) {
- return ex.toStatus();
+ tags->push_back(tagRes.getValue());
}
- return Status::OK();
+ conn.done();
+ } catch (const DBException& ex) {
+ return ex.toStatus();
}
- StatusWith<string> CatalogManagerLegacy::getTagForChunk(const std::string& collectionNs,
- const ChunkType& chunk) {
- BSONObj tagDoc;
+ return Status::OK();
+}
- try {
- ScopedDbConnection conn(_configServerConnectionString, 30);
+StatusWith<string> CatalogManagerLegacy::getTagForChunk(const std::string& collectionNs,
+ const ChunkType& chunk) {
+ BSONObj tagDoc;
- Query query(BSON(TagsType::ns(collectionNs) <<
- TagsType::min() << BSON("$lte" << chunk.getMin()) <<
- TagsType::max() << BSON("$gte" << chunk.getMax())));
+ try {
+ ScopedDbConnection conn(_configServerConnectionString, 30);
- tagDoc = conn->findOne(TagsType::ConfigNS, query);
- conn.done();
- }
- catch (const DBException& ex) {
- return ex.toStatus();
- }
+ Query query(BSON(TagsType::ns(collectionNs)
+ << TagsType::min() << BSON("$lte" << chunk.getMin()) << TagsType::max()
+ << BSON("$gte" << chunk.getMax())));
- if (tagDoc.isEmpty()) {
- return std::string("");
- }
+ tagDoc = conn->findOne(TagsType::ConfigNS, query);
+ conn.done();
+ } catch (const DBException& ex) {
+ return ex.toStatus();
+ }
- auto status = TagsType::fromBSON(tagDoc);
- if (status.isOK()) {
- return status.getValue().getTag();
- }
+ if (tagDoc.isEmpty()) {
+ return std::string("");
+ }
- return status.getStatus();
+ auto status = TagsType::fromBSON(tagDoc);
+ if (status.isOK()) {
+ return status.getValue().getTag();
}
- Status CatalogManagerLegacy::getAllShards(vector<ShardType>* shards) {
- ScopedDbConnection conn(_configServerConnectionString, 30.0);
- std::unique_ptr<DBClientCursor> cursor(_safeCursor(conn->query(ShardType::ConfigNS,
- BSONObj())));
- while (cursor->more()) {
- BSONObj shardObj = cursor->nextSafe();
+ return status.getStatus();
+}
- StatusWith<ShardType> shardRes = ShardType::fromBSON(shardObj);
- if (!shardRes.isOK()) {
- shards->clear();
- conn.done();
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "Failed to parse shard with id ("
- << shardObj[ShardType::name()].toString() << "): "
- << shardRes.getStatus().reason());
- }
+Status CatalogManagerLegacy::getAllShards(vector<ShardType>* shards) {
+ ScopedDbConnection conn(_configServerConnectionString, 30.0);
+ std::unique_ptr<DBClientCursor> cursor(
+ _safeCursor(conn->query(ShardType::ConfigNS, BSONObj())));
+ while (cursor->more()) {
+ BSONObj shardObj = cursor->nextSafe();
- shards->push_back(shardRes.getValue());
+ StatusWith<ShardType> shardRes = ShardType::fromBSON(shardObj);
+ if (!shardRes.isOK()) {
+ shards->clear();
+ conn.done();
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << "Failed to parse shard with id ("
+ << shardObj[ShardType::name()].toString()
+ << "): " << shardRes.getStatus().reason());
}
- conn.done();
- return Status::OK();
+ shards->push_back(shardRes.getValue());
}
-
- bool CatalogManagerLegacy::isShardHost(const ConnectionString& connectionString) {
- return _getShardCount(BSON(ShardType::host(connectionString.toString())));
+ conn.done();
+
+ return Status::OK();
+}
+
+bool CatalogManagerLegacy::isShardHost(const ConnectionString& connectionString) {
+ return _getShardCount(BSON(ShardType::host(connectionString.toString())));
+}
+
+bool CatalogManagerLegacy::runUserManagementWriteCommand(const string& commandName,
+ const string& dbname,
+ const BSONObj& cmdObj,
+ BSONObjBuilder* result) {
+ DBClientMultiCommand dispatcher;
+ RawBSONSerializable requestCmdSerial(cmdObj);
+ for (const ConnectionString& configServer : _configServers) {
+ dispatcher.addCommand(configServer, dbname, requestCmdSerial);
}
- bool CatalogManagerLegacy::runUserManagementWriteCommand(const string& commandName,
- const string& dbname,
- const BSONObj& cmdObj,
- BSONObjBuilder* result) {
- DBClientMultiCommand dispatcher;
- RawBSONSerializable requestCmdSerial(cmdObj);
- for (const ConnectionString& configServer : _configServers) {
- dispatcher.addCommand(configServer, dbname, requestCmdSerial);
- }
-
- auto scopedDistLock = getDistLockManager()->lock("authorizationData",
- commandName,
- Seconds{5});
- if (!scopedDistLock.isOK()) {
- return Command::appendCommandStatus(*result, scopedDistLock.getStatus());
- }
-
- dispatcher.sendAll();
+ auto scopedDistLock = getDistLockManager()->lock("authorizationData", commandName, Seconds{5});
+ if (!scopedDistLock.isOK()) {
+ return Command::appendCommandStatus(*result, scopedDistLock.getStatus());
+ }
- BSONObj responseObj;
+ dispatcher.sendAll();
- Status prevStatus{Status::OK()};
- Status currStatus{Status::OK()};
+ BSONObj responseObj;
- BSONObjBuilder responses;
- unsigned failedCount = 0;
- bool sameError = true;
- while (dispatcher.numPending() > 0) {
- ConnectionString host;
- RawBSONSerializable responseCmdSerial;
+ Status prevStatus{Status::OK()};
+ Status currStatus{Status::OK()};
- Status dispatchStatus = dispatcher.recvAny(&host,
- &responseCmdSerial);
+ BSONObjBuilder responses;
+ unsigned failedCount = 0;
+ bool sameError = true;
+ while (dispatcher.numPending() > 0) {
+ ConnectionString host;
+ RawBSONSerializable responseCmdSerial;
- if (!dispatchStatus.isOK()) {
- return Command::appendCommandStatus(*result, dispatchStatus);
- }
+ Status dispatchStatus = dispatcher.recvAny(&host, &responseCmdSerial);
- responseObj = responseCmdSerial.toBSON();
- responses.append(host.toString(), responseObj);
-
- currStatus = Command::getStatusFromCommandResult(responseObj);
- if (!currStatus.isOK()) {
- // same error <=> adjacent error statuses are the same
- if (failedCount > 0 && prevStatus != currStatus) {
- sameError = false;
- }
- failedCount++;
- prevStatus = currStatus;
- }
+ if (!dispatchStatus.isOK()) {
+ return Command::appendCommandStatus(*result, dispatchStatus);
}
- if (failedCount == 0) {
- result->appendElements(responseObj);
- return true;
- }
+ responseObj = responseCmdSerial.toBSON();
+ responses.append(host.toString(), responseObj);
- // if the command succeeds on at least one config server and fails on at least one,
- // manual intervention is required
- if (failedCount < _configServers.size()) {
- Status status(ErrorCodes::ManualInterventionRequired,
- str::stream() << "Config write was not consistent - "
- << "user management command failed on at least one "
- << "config server but passed on at least one other. "
- << "Manual intervention may be required. "
- << "Config responses: " << responses.obj().toString());
- return Command::appendCommandStatus(*result, status);
+ currStatus = Command::getStatusFromCommandResult(responseObj);
+ if (!currStatus.isOK()) {
+ // same error <=> adjacent error statuses are the same
+ if (failedCount > 0 && prevStatus != currStatus) {
+ sameError = false;
+ }
+ failedCount++;
+ prevStatus = currStatus;
}
+ }
- if (sameError) {
- result->appendElements(responseObj);
- return false;
- }
+ if (failedCount == 0) {
+ result->appendElements(responseObj);
+ return true;
+ }
+ // if the command succeeds on at least one config server and fails on at least one,
+ // manual intervention is required
+ if (failedCount < _configServers.size()) {
Status status(ErrorCodes::ManualInterventionRequired,
str::stream() << "Config write was not consistent - "
- << "user management command produced inconsistent results. "
+ << "user management command failed on at least one "
+ << "config server but passed on at least one other. "
<< "Manual intervention may be required. "
<< "Config responses: " << responses.obj().toString());
return Command::appendCommandStatus(*result, status);
}
- bool CatalogManagerLegacy::runUserManagementReadCommand(const string& dbname,
- const BSONObj& cmdObj,
- BSONObjBuilder* result) {
- try {
- // let SyncClusterConnection handle connecting to the first config server
- // that is reachable and returns data
- ScopedDbConnection conn(_configServerConnectionString, 30);
-
- BSONObj cmdResult;
- const bool ok = conn->runCommand(dbname, cmdObj, cmdResult);
- result->appendElements(cmdResult);
- conn.done();
- return ok;
- }
- catch (const DBException& ex) {
- return Command::appendCommandStatus(*result, ex.toStatus());
- }
+ if (sameError) {
+ result->appendElements(responseObj);
+ return false;
}
- Status CatalogManagerLegacy::applyChunkOpsDeprecated(const BSONArray& updateOps,
- const BSONArray& preCondition) {
- BSONObj cmd = BSON("applyOps" << updateOps <<
- "preCondition" << preCondition);
- bool ok;
+ Status status(ErrorCodes::ManualInterventionRequired,
+ str::stream() << "Config write was not consistent - "
+ << "user management command produced inconsistent results. "
+ << "Manual intervention may be required. "
+ << "Config responses: " << responses.obj().toString());
+ return Command::appendCommandStatus(*result, status);
+}
+
+bool CatalogManagerLegacy::runUserManagementReadCommand(const string& dbname,
+ const BSONObj& cmdObj,
+ BSONObjBuilder* result) {
+ try {
+ // let SyncClusterConnection handle connecting to the first config server
+ // that is reachable and returns data
+ ScopedDbConnection conn(_configServerConnectionString, 30);
+
BSONObj cmdResult;
- try {
- ScopedDbConnection conn(_configServerConnectionString, 30);
- ok = conn->runCommand("config", cmd, cmdResult);
- conn.done();
- }
- catch (const DBException& ex) {
- return ex.toStatus();
- }
+ const bool ok = conn->runCommand(dbname, cmdObj, cmdResult);
+ result->appendElements(cmdResult);
+ conn.done();
+ return ok;
+ } catch (const DBException& ex) {
+ return Command::appendCommandStatus(*result, ex.toStatus());
+ }
+}
+
+Status CatalogManagerLegacy::applyChunkOpsDeprecated(const BSONArray& updateOps,
+ const BSONArray& preCondition) {
+ BSONObj cmd = BSON("applyOps" << updateOps << "preCondition" << preCondition);
+ bool ok;
+ BSONObj cmdResult;
+ try {
+ ScopedDbConnection conn(_configServerConnectionString, 30);
+ ok = conn->runCommand("config", cmd, cmdResult);
+ conn.done();
+ } catch (const DBException& ex) {
+ return ex.toStatus();
+ }
- if (!ok) {
- string errMsg(str::stream() << "Unable to save chunk ops. Command: "
- << cmd << ". Result: " << cmdResult);
+ if (!ok) {
+ string errMsg(str::stream() << "Unable to save chunk ops. Command: " << cmd
+ << ". Result: " << cmdResult);
- return Status(ErrorCodes::OperationFailed, errMsg);
- }
+ return Status(ErrorCodes::OperationFailed, errMsg);
+ }
- return Status::OK();
+ return Status::OK();
+}
+
+void CatalogManagerLegacy::writeConfigServerDirect(const BatchedCommandRequest& request,
+ BatchedCommandResponse* response) {
+ // check if config servers are consistent
+ if (!_isConsistentFromLastCheck()) {
+ toBatchError(Status(ErrorCodes::ConfigServersInconsistent,
+ "Data inconsistency detected amongst config servers"),
+ response);
+ return;
}
- void CatalogManagerLegacy::writeConfigServerDirect(const BatchedCommandRequest& request,
- BatchedCommandResponse* response) {
+ // We only support batch sizes of one for config writes
+ if (request.sizeWriteOps() != 1) {
+ toBatchError(Status(ErrorCodes::InvalidOptions,
+ str::stream() << "Writes to config servers must have batch size of 1, "
+ << "found " << request.sizeWriteOps()),
+ response);
- // check if config servers are consistent
- if (!_isConsistentFromLastCheck()) {
- toBatchError(
- Status(ErrorCodes::ConfigServersInconsistent,
- "Data inconsistency detected amongst config servers"),
- response);
- return;
- }
+ return;
+ }
- // We only support batch sizes of one for config writes
- if (request.sizeWriteOps() != 1) {
- toBatchError(
- Status(ErrorCodes::InvalidOptions,
- str::stream() << "Writes to config servers must have batch size of 1, "
- << "found " << request.sizeWriteOps()),
- response);
+ // We only support {w: 0}, {w: 1}, and {w: 'majority'} write concern for config writes
+ if (request.isWriteConcernSet() && !validConfigWC(request.getWriteConcern())) {
+ toBatchError(Status(ErrorCodes::InvalidOptions,
+ str::stream() << "Invalid write concern for write to "
+ << "config servers: " << request.getWriteConcern()),
+ response);
- return;
- }
+ return;
+ }
- // We only support {w: 0}, {w: 1}, and {w: 'majority'} write concern for config writes
- if (request.isWriteConcernSet() && !validConfigWC(request.getWriteConcern())) {
+ DBClientMultiCommand dispatcher;
+ if (_configServers.size() > 1) {
+ // We can't support no-_id upserts to multiple config servers - the _ids will differ
+ if (BatchedCommandRequest::containsNoIDUpsert(request)) {
toBatchError(
Status(ErrorCodes::InvalidOptions,
- str::stream() << "Invalid write concern for write to "
- << "config servers: " << request.getWriteConcern()),
+ str::stream() << "upserts to multiple config servers must include _id"),
response);
-
return;
}
-
- DBClientMultiCommand dispatcher;
- if (_configServers.size() > 1) {
- // We can't support no-_id upserts to multiple config servers - the _ids will differ
- if (BatchedCommandRequest::containsNoIDUpsert(request)) {
- toBatchError(
- Status(ErrorCodes::InvalidOptions,
- str::stream() << "upserts to multiple config servers must include _id"),
- response);
- return;
- }
- }
-
- ConfigCoordinator exec(&dispatcher, _configServerConnectionString);
- exec.executeBatch(request, response);
}
- Status CatalogManagerLegacy::_checkDbDoesNotExist(const std::string& dbName) const {
- ScopedDbConnection conn(_configServerConnectionString, 30);
+ ConfigCoordinator exec(&dispatcher, _configServerConnectionString);
+ exec.executeBatch(request, response);
+}
- BSONObjBuilder b;
- b.appendRegex(DatabaseType::name(),
- (string)"^" + pcrecpp::RE::QuoteMeta(dbName) + "$", "i");
+Status CatalogManagerLegacy::_checkDbDoesNotExist(const std::string& dbName) const {
+ ScopedDbConnection conn(_configServerConnectionString, 30);
- BSONObj dbObj = conn->findOne(DatabaseType::ConfigNS, b.obj());
- conn.done();
+ BSONObjBuilder b;
+ b.appendRegex(DatabaseType::name(), (string) "^" + pcrecpp::RE::QuoteMeta(dbName) + "$", "i");
- // If our name is exactly the same as the name we want, try loading
- // the database again.
- if (!dbObj.isEmpty() && dbObj[DatabaseType::name()].String() == dbName) {
- return Status(ErrorCodes::NamespaceExists,
- str::stream() << "database " << dbName << " already exists");
- }
+ BSONObj dbObj = conn->findOne(DatabaseType::ConfigNS, b.obj());
+ conn.done();
- if (!dbObj.isEmpty()) {
- return Status(ErrorCodes::DatabaseDifferCase,
- str::stream() << "can't have 2 databases that just differ on case "
- << " have: " << dbObj[DatabaseType::name()].String()
- << " want to add: " << dbName);
- }
+ // If our name is exactly the same as the name we want, try loading
+ // the database again.
+ if (!dbObj.isEmpty() && dbObj[DatabaseType::name()].String() == dbName) {
+ return Status(ErrorCodes::NamespaceExists,
+ str::stream() << "database " << dbName << " already exists");
+ }
- return Status::OK();
+ if (!dbObj.isEmpty()) {
+ return Status(ErrorCodes::DatabaseDifferCase,
+ str::stream() << "can't have 2 databases that just differ on case "
+ << " have: " << dbObj[DatabaseType::name()].String()
+ << " want to add: " << dbName);
}
- StatusWith<string> CatalogManagerLegacy::_getNewShardName() const {
- BSONObj o;
- {
- ScopedDbConnection conn(_configServerConnectionString, 30);
- o = conn->findOne(ShardType::ConfigNS,
- Query(fromjson("{" + ShardType::name() + ": /^shard/}"))
- .sort(BSON(ShardType::name() << -1 )));
- conn.done();
- }
+ return Status::OK();
+}
- int count = 0;
- if (!o.isEmpty()) {
- string last = o[ShardType::name()].String();
- std::istringstream is(last.substr(5));
- is >> count;
- count++;
- }
+StatusWith<string> CatalogManagerLegacy::_getNewShardName() const {
+ BSONObj o;
+ {
+ ScopedDbConnection conn(_configServerConnectionString, 30);
+ o = conn->findOne(ShardType::ConfigNS,
+ Query(fromjson("{" + ShardType::name() + ": /^shard/}"))
+ .sort(BSON(ShardType::name() << -1)));
+ conn.done();
+ }
- // TODO fix so that we can have more than 10000 automatically generated shard names
- if (count < 9999) {
- std::stringstream ss;
- ss << "shard" << std::setfill('0') << std::setw(4) << count;
- return ss.str();
- }
+ int count = 0;
+ if (!o.isEmpty()) {
+ string last = o[ShardType::name()].String();
+ std::istringstream is(last.substr(5));
+ is >> count;
+ count++;
+ }
- return Status(ErrorCodes::OperationFailed,
- "unable to generate new shard name");
+ // TODO fix so that we can have more than 10000 automatically generated shard names
+ if (count < 9999) {
+ std::stringstream ss;
+ ss << "shard" << std::setfill('0') << std::setw(4) << count;
+ return ss.str();
}
- size_t CatalogManagerLegacy::_getShardCount(const BSONObj& query) const {
- ScopedDbConnection conn(_configServerConnectionString, 30.0);
- long long shardCount = conn->count(ShardType::ConfigNS, query);
- conn.done();
+ return Status(ErrorCodes::OperationFailed, "unable to generate new shard name");
+}
- return shardCount;
- }
+size_t CatalogManagerLegacy::_getShardCount(const BSONObj& query) const {
+ ScopedDbConnection conn(_configServerConnectionString, 30.0);
+ long long shardCount = conn->count(ShardType::ConfigNS, query);
+ conn.done();
- DistLockManager* CatalogManagerLegacy::getDistLockManager() {
- invariant(_distLockManager);
- return _distLockManager.get();
- }
+ return shardCount;
+}
- bool CatalogManagerLegacy::_checkConfigServersConsistent(const unsigned tries) const {
- if (tries <= 0)
- return false;
+DistLockManager* CatalogManagerLegacy::getDistLockManager() {
+ invariant(_distLockManager);
+ return _distLockManager.get();
+}
- unsigned firstGood = 0;
- int up = 0;
- vector<BSONObj> res;
+bool CatalogManagerLegacy::_checkConfigServersConsistent(const unsigned tries) const {
+ if (tries <= 0)
+ return false;
- // The last error we saw on a config server
- string errMsg;
+ unsigned firstGood = 0;
+ int up = 0;
+ vector<BSONObj> res;
- for (unsigned i = 0; i < _configServers.size(); i++) {
- BSONObj result;
- std::unique_ptr<ScopedDbConnection> conn;
-
- try {
- conn.reset(new ScopedDbConnection(_configServers[i], 30.0));
-
- if (!conn->get()->runCommand("config",
- BSON("dbhash" << 1 <<
- "collections" << BSON_ARRAY("chunks" <<
- "databases" <<
- "collections" <<
- "shards" <<
- "version")),
- result)) {
-
- errMsg = result["errmsg"].eoo() ? "" : result["errmsg"].String();
- if (!result["assertion"].eoo()) errMsg = result["assertion"].String();
-
- warning() << "couldn't check dbhash on config server " << _configServers[i]
- << causedBy(result.toString());
-
- result = BSONObj();
- }
- else {
- result = result.getOwned();
- if (up == 0)
- firstGood = i;
- up++;
- }
- conn->done();
- }
- catch (const DBException& e) {
- if (conn) {
- conn->kill();
- }
+ // The last error we saw on a config server
+ string errMsg;
- // We need to catch DBExceptions b/c sometimes we throw them
- // instead of socket exceptions when findN fails
+ for (unsigned i = 0; i < _configServers.size(); i++) {
+ BSONObj result;
+ std::unique_ptr<ScopedDbConnection> conn;
- errMsg = e.toString();
- warning() << " couldn't check dbhash on config server "
- << _configServers[i] << causedBy(e);
+ try {
+ conn.reset(new ScopedDbConnection(_configServers[i], 30.0));
+
+ if (!conn->get()->runCommand(
+ "config",
+ BSON("dbhash" << 1 << "collections" << BSON_ARRAY("chunks"
+ << "databases"
+ << "collections"
+ << "shards"
+ << "version")),
+ result)) {
+ errMsg = result["errmsg"].eoo() ? "" : result["errmsg"].String();
+ if (!result["assertion"].eoo())
+ errMsg = result["assertion"].String();
+
+ warning() << "couldn't check dbhash on config server " << _configServers[i]
+ << causedBy(result.toString());
+
+ result = BSONObj();
+ } else {
+ result = result.getOwned();
+ if (up == 0)
+ firstGood = i;
+ up++;
+ }
+ conn->done();
+ } catch (const DBException& e) {
+ if (conn) {
+ conn->kill();
}
- res.push_back(result);
- }
- if (_configServers.size() == 1)
- return true;
+ // We need to catch DBExceptions b/c sometimes we throw them
+ // instead of socket exceptions when findN fails
- if (up == 0) {
- // Use a ptr to error so if empty we won't add causedby
- error() << "no config servers successfully contacted" << causedBy(&errMsg);
- return false;
- }
- else if (up == 1) {
- warning() << "only 1 config server reachable, continuing";
- return true;
+ errMsg = e.toString();
+ warning() << " couldn't check dbhash on config server " << _configServers[i]
+ << causedBy(e);
}
+ res.push_back(result);
+ }
- BSONObj base = res[firstGood];
- for (unsigned i = firstGood+1; i < res.size(); i++) {
- if (res[i].isEmpty())
- continue;
+ if (_configServers.size() == 1)
+ return true;
- string chunksHash1 = base.getFieldDotted("collections.chunks");
- string chunksHash2 = res[i].getFieldDotted("collections.chunks");
+ if (up == 0) {
+ // Use a ptr to error so if empty we won't add causedby
+ error() << "no config servers successfully contacted" << causedBy(&errMsg);
+ return false;
+ } else if (up == 1) {
+ warning() << "only 1 config server reachable, continuing";
+ return true;
+ }
- string databaseHash1 = base.getFieldDotted("collections.databases");
- string databaseHash2 = res[i].getFieldDotted("collections.databases");
+ BSONObj base = res[firstGood];
+ for (unsigned i = firstGood + 1; i < res.size(); i++) {
+ if (res[i].isEmpty())
+ continue;
- string collectionsHash1 = base.getFieldDotted("collections.collections");
- string collectionsHash2 = res[i].getFieldDotted("collections.collections");
+ string chunksHash1 = base.getFieldDotted("collections.chunks");
+ string chunksHash2 = res[i].getFieldDotted("collections.chunks");
- string shardHash1 = base.getFieldDotted("collections.shards");
- string shardHash2 = res[i].getFieldDotted("collections.shards");
+ string databaseHash1 = base.getFieldDotted("collections.databases");
+ string databaseHash2 = res[i].getFieldDotted("collections.databases");
- string versionHash1 = base.getFieldDotted("collections.version") ;
- string versionHash2 = res[i].getFieldDotted("collections.version");
+ string collectionsHash1 = base.getFieldDotted("collections.collections");
+ string collectionsHash2 = res[i].getFieldDotted("collections.collections");
- if (chunksHash1 == chunksHash2 &&
- databaseHash1 == databaseHash2 &&
- collectionsHash1 == collectionsHash2 &&
- shardHash1 == shardHash2 &&
- versionHash1 == versionHash2) {
- continue;
- }
+ string shardHash1 = base.getFieldDotted("collections.shards");
+ string shardHash2 = res[i].getFieldDotted("collections.shards");
- warning() << "config servers " << _configServers[firstGood].toString()
- << " and " << _configServers[i].toString() << " differ";
- if (tries <= 1) {
- error() << ": " << base["collections"].Obj()
- << " vs " << res[i]["collections"].Obj();
- return false;
- }
+ string versionHash1 = base.getFieldDotted("collections.version");
+ string versionHash2 = res[i].getFieldDotted("collections.version");
- return _checkConfigServersConsistent(tries - 1);
+ if (chunksHash1 == chunksHash2 && databaseHash1 == databaseHash2 &&
+ collectionsHash1 == collectionsHash2 && shardHash1 == shardHash2 &&
+ versionHash1 == versionHash2) {
+ continue;
}
- return true;
+ warning() << "config servers " << _configServers[firstGood].toString() << " and "
+ << _configServers[i].toString() << " differ";
+ if (tries <= 1) {
+ error() << ": " << base["collections"].Obj() << " vs " << res[i]["collections"].Obj();
+ return false;
+ }
+
+ return _checkConfigServersConsistent(tries - 1);
}
- void CatalogManagerLegacy::_consistencyChecker() {
- stdx::unique_lock<stdx::mutex> lk(_mutex);
- while (!_inShutdown) {
- lk.unlock();
- const bool isConsistent = _checkConfigServersConsistent();
+ return true;
+}
- lk.lock();
- _consistentFromLastCheck = isConsistent;
- if (_inShutdown) break;
- _consistencyCheckerCV.wait_for(lk, Seconds(60));
- }
- LOG(1) << "Consistency checker thread shutting down";
- }
+void CatalogManagerLegacy::_consistencyChecker() {
+ stdx::unique_lock<stdx::mutex> lk(_mutex);
+ while (!_inShutdown) {
+ lk.unlock();
+ const bool isConsistent = _checkConfigServersConsistent();
- bool CatalogManagerLegacy::_isConsistentFromLastCheck() {
- stdx::unique_lock<stdx::mutex> lk(_mutex);
- return _consistentFromLastCheck;
+ lk.lock();
+ _consistentFromLastCheck = isConsistent;
+ if (_inShutdown)
+ break;
+ _consistencyCheckerCV.wait_for(lk, Seconds(60));
}
+ LOG(1) << "Consistency checker thread shutting down";
+}
+
+bool CatalogManagerLegacy::_isConsistentFromLastCheck() {
+ stdx::unique_lock<stdx::mutex> lk(_mutex);
+ return _consistentFromLastCheck;
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/catalog/legacy/catalog_manager_legacy.h b/src/mongo/s/catalog/legacy/catalog_manager_legacy.h
index aff0ce6d185..5a762a1aebc 100644
--- a/src/mongo/s/catalog/legacy/catalog_manager_legacy.h
+++ b/src/mongo/s/catalog/legacy/catalog_manager_legacy.h
@@ -39,173 +39,170 @@
namespace mongo {
+/**
+ * Implements the catalog manager using the legacy 3-config server protocol.
+ */
+class CatalogManagerLegacy final : public CatalogManager {
+public:
+ CatalogManagerLegacy();
+ ~CatalogManagerLegacy();
+
/**
- * Implements the catalog manager using the legacy 3-config server protocol.
+ * Initializes the catalog manager with the hosts, which will be used as a configuration
+ * server. Can only be called once for the lifetime.
*/
- class CatalogManagerLegacy final : public CatalogManager {
- public:
- CatalogManagerLegacy();
- ~CatalogManagerLegacy();
+ Status init(const ConnectionString& configCS);
- /**
- * Initializes the catalog manager with the hosts, which will be used as a configuration
- * server. Can only be called once for the lifetime.
- */
- Status init(const ConnectionString& configCS);
+ Status startup(bool upgrade) override;
- Status startup(bool upgrade) override;
+ ConnectionString connectionString() const override;
- ConnectionString connectionString() const override;
+ void shutDown() override;
- void shutDown() override;
+ Status enableSharding(const std::string& dbName) override;
- Status enableSharding(const std::string& dbName) override;
+ Status shardCollection(const std::string& ns,
+ const ShardKeyPattern& fieldsAndOrder,
+ bool unique,
+ std::vector<BSONObj>* initPoints,
+ std::set<ShardId>* initShardIds) override;
- Status shardCollection(const std::string& ns,
- const ShardKeyPattern& fieldsAndOrder,
- bool unique,
- std::vector<BSONObj>* initPoints,
- std::set<ShardId>* initShardIds) override;
+ StatusWith<std::string> addShard(const std::string& name,
+ const ConnectionString& shardConnectionString,
+ const long long maxSize) override;
- StatusWith<std::string> addShard(const std::string& name,
- const ConnectionString& shardConnectionString,
- const long long maxSize) override;
+ StatusWith<ShardDrainingStatus> removeShard(OperationContext* txn,
+ const std::string& name) override;
- StatusWith<ShardDrainingStatus> removeShard(OperationContext* txn,
- const std::string& name) override;
+ Status createDatabase(const std::string& dbName) override;
- Status createDatabase(const std::string& dbName) override;
+ Status updateDatabase(const std::string& dbName, const DatabaseType& db) override;
- Status updateDatabase(const std::string& dbName, const DatabaseType& db) override;
+ StatusWith<DatabaseType> getDatabase(const std::string& dbName) override;
- StatusWith<DatabaseType> getDatabase(const std::string& dbName) override;
+ Status updateCollection(const std::string& collNs, const CollectionType& coll) override;
- Status updateCollection(const std::string& collNs, const CollectionType& coll) override;
+ StatusWith<CollectionType> getCollection(const std::string& collNs) override;
- StatusWith<CollectionType> getCollection(const std::string& collNs) override;
+ Status getCollections(const std::string* dbName, std::vector<CollectionType>* collections);
- Status getCollections(const std::string* dbName, std::vector<CollectionType>* collections);
+ Status dropCollection(const std::string& collectionNs);
- Status dropCollection(const std::string& collectionNs);
+ Status getDatabasesForShard(const std::string& shardName,
+ std::vector<std::string>* dbs) override;
- Status getDatabasesForShard(const std::string& shardName,
- std::vector<std::string>* dbs) override;
+ Status getChunks(const Query& query, int nToReturn, std::vector<ChunkType>* chunks) override;
- Status getChunks(const Query& query,
- int nToReturn,
- std::vector<ChunkType>* chunks) override;
+ Status getTagsForCollection(const std::string& collectionNs,
+ std::vector<TagsType>* tags) override;
- Status getTagsForCollection(const std::string& collectionNs,
- std::vector<TagsType>* tags) override;
+ StatusWith<std::string> getTagForChunk(const std::string& collectionNs,
+ const ChunkType& chunk) override;
- StatusWith<std::string> getTagForChunk(const std::string& collectionNs,
- const ChunkType& chunk) override;
+ Status getAllShards(std::vector<ShardType>* shards) override;
- Status getAllShards(std::vector<ShardType>* shards) override;
+ bool isShardHost(const ConnectionString& shardConnectionString) override;
- bool isShardHost(const ConnectionString& shardConnectionString) override;
-
- /**
- * Grabs a distributed lock and runs the command on all config servers.
- */
- bool runUserManagementWriteCommand(const std::string& commandName,
- const std::string& dbname,
- const BSONObj& cmdObj,
- BSONObjBuilder* result) override;
+ /**
+ * Grabs a distributed lock and runs the command on all config servers.
+ */
+ bool runUserManagementWriteCommand(const std::string& commandName,
+ const std::string& dbname,
+ const BSONObj& cmdObj,
+ BSONObjBuilder* result) override;
- bool runUserManagementReadCommand(const std::string& dbname,
- const BSONObj& cmdObj,
- BSONObjBuilder* result) override;
+ bool runUserManagementReadCommand(const std::string& dbname,
+ const BSONObj& cmdObj,
+ BSONObjBuilder* result) override;
- Status applyChunkOpsDeprecated(const BSONArray& updateOps,
- const BSONArray& preCondition) override;
+ Status applyChunkOpsDeprecated(const BSONArray& updateOps,
+ const BSONArray& preCondition) override;
- void logAction(const ActionLogType& actionLog);
+ void logAction(const ActionLogType& actionLog);
- void logChange(OperationContext* txn,
- const std::string& what,
- const std::string& ns,
- const BSONObj& detail) override;
+ void logChange(OperationContext* txn,
+ const std::string& what,
+ const std::string& ns,
+ const BSONObj& detail) override;
- StatusWith<SettingsType> getGlobalSettings(const std::string& key) override;
+ StatusWith<SettingsType> getGlobalSettings(const std::string& key) override;
- void writeConfigServerDirect(const BatchedCommandRequest& request,
- BatchedCommandResponse* response) override;
+ void writeConfigServerDirect(const BatchedCommandRequest& request,
+ BatchedCommandResponse* response) override;
- DistLockManager* getDistLockManager() override;
+ DistLockManager* getDistLockManager() override;
- private:
+private:
+ /**
+ * Updates the config server's metadata to the current version.
+ */
+ Status _checkAndUpgradeConfigMetadata(bool doUpgrade);
- /**
- * Updates the config server's metadata to the current version.
- */
- Status _checkAndUpgradeConfigMetadata(bool doUpgrade);
+ /**
+ * Starts the thread that periodically checks data consistency amongst the config servers.
+ * Note: this is not thread safe and can only be called once for the lifetime.
+ */
+ Status _startConfigServerChecker();
- /**
- * Starts the thread that periodically checks data consistency amongst the config servers.
- * Note: this is not thread safe and can only be called once for the lifetime.
- */
- Status _startConfigServerChecker();
+ /**
+ * Direct network check to see if a particular database does not already exist with the
+ * same name or different case.
+ */
+ Status _checkDbDoesNotExist(const std::string& dbName) const;
- /**
- * Direct network check to see if a particular database does not already exist with the
- * same name or different case.
- */
- Status _checkDbDoesNotExist(const std::string& dbName) const;
+ /**
+ * Generates a new shard name "shard<xxxx>"
+ * where <xxxx> is an autoincrementing value and <xxxx> < 10000
+ */
+ StatusWith<std::string> _getNewShardName() const;
- /**
- * Generates a new shard name "shard<xxxx>"
- * where <xxxx> is an autoincrementing value and <xxxx> < 10000
- */
- StatusWith<std::string> _getNewShardName() const;
+ /**
+ * Returns the number of shards recognized by the config servers
+ * in this sharded cluster.
+ * Optional: use query parameter to filter shard count.
+ */
+ size_t _getShardCount(const BSONObj& query = {}) const;
- /**
- * Returns the number of shards recognized by the config servers
- * in this sharded cluster.
- * Optional: use query parameter to filter shard count.
- */
- size_t _getShardCount(const BSONObj& query = {}) const;
+ /**
+ * Returns true if all config servers have the same state.
+ * If inconsistency detected on first attempt, checks at most 3 more times.
+ */
+ bool _checkConfigServersConsistent(const unsigned tries = 4) const;
- /**
- * Returns true if all config servers have the same state.
- * If inconsistency detected on first attempt, checks at most 3 more times.
- */
- bool _checkConfigServersConsistent(const unsigned tries = 4) const;
+ /**
+ * Checks data consistency amongst config servers every 60 seconds.
+ */
+ void _consistencyChecker();
- /**
- * Checks data consistency amongst config servers every 60 seconds.
- */
- void _consistencyChecker();
+ /**
+ * Returns true if the config servers have the same contents since the last
+ * check was performed.
+ */
+ bool _isConsistentFromLastCheck();
- /**
- * Returns true if the config servers have the same contents since the last
- * check was performed.
- */
- bool _isConsistentFromLastCheck();
+ // Parsed config server hosts, as specified on the command line.
+ ConnectionString _configServerConnectionString;
+ std::vector<ConnectionString> _configServers;
- // Parsed config server hosts, as specified on the command line.
- ConnectionString _configServerConnectionString;
- std::vector<ConnectionString> _configServers;
+ // Distribted lock manager singleton.
+ std::unique_ptr<DistLockManager> _distLockManager;
- // Distribted lock manager singleton.
- std::unique_ptr<DistLockManager> _distLockManager;
+ // protects _inShutdown, _consistentFromLastCheck; used by _consistencyCheckerCV
+ stdx::mutex _mutex;
- // protects _inShutdown, _consistentFromLastCheck; used by _consistencyCheckerCV
- stdx::mutex _mutex;
+ // True if CatalogManagerLegacy::shutDown has been called. False, otherwise.
+ bool _inShutdown = false;
- // True if CatalogManagerLegacy::shutDown has been called. False, otherwise.
- bool _inShutdown = false;
+ // used by consistency checker thread to check if config
+ // servers are consistent
+ bool _consistentFromLastCheck = false;
- // used by consistency checker thread to check if config
- // servers are consistent
- bool _consistentFromLastCheck = false;
+ // Thread that runs dbHash on config servers for checking data consistency.
+ stdx::thread _consistencyCheckerThread;
- // Thread that runs dbHash on config servers for checking data consistency.
- stdx::thread _consistencyCheckerThread;
+ // condition variable used by the consistency checker thread to wait
+ // for <= 60s, on every iteration, until shutDown is called
+ stdx::condition_variable _consistencyCheckerCV;
+};
- // condition variable used by the consistency checker thread to wait
- // for <= 60s, on every iteration, until shutDown is called
- stdx::condition_variable _consistencyCheckerCV;
- };
-
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/catalog/legacy/cluster_client_internal.cpp b/src/mongo/s/catalog/legacy/cluster_client_internal.cpp
index 5af263b5e56..c8b60c9c9dc 100644
--- a/src/mongo/s/catalog/legacy/cluster_client_internal.cpp
+++ b/src/mongo/s/catalog/legacy/cluster_client_internal.cpp
@@ -44,188 +44,175 @@
namespace mongo {
- using std::endl;
- using std::string;
- using std::unique_ptr;
- using std::vector;
- using mongoutils::str::stream;
-
- Status checkClusterMongoVersions(CatalogManager* catalogManager,
- const string& minMongoVersion) {
-
- unique_ptr<ScopedDbConnection> connPtr;
-
- //
- // Find mongos pings in config server
- //
-
- try {
- connPtr.reset(new ScopedDbConnection(catalogManager->connectionString(), 30));
- ScopedDbConnection& conn = *connPtr;
- unique_ptr<DBClientCursor> cursor(_safeCursor(conn->query(MongosType::ConfigNS,
- Query())));
-
- while (cursor->more()) {
-
- BSONObj pingDoc = cursor->next();
-
- MongosType ping;
- string errMsg;
- // NOTE: We don't care if the ping is invalid, legacy stuff will be
- if (!ping.parseBSON(pingDoc, &errMsg)) {
- warning() << "could not parse ping document: " << pingDoc << causedBy(errMsg)
- << endl;
- continue;
- }
+using std::endl;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using mongoutils::str::stream;
+
+Status checkClusterMongoVersions(CatalogManager* catalogManager, const string& minMongoVersion) {
+ unique_ptr<ScopedDbConnection> connPtr;
+
+ //
+ // Find mongos pings in config server
+ //
+
+ try {
+ connPtr.reset(new ScopedDbConnection(catalogManager->connectionString(), 30));
+ ScopedDbConnection& conn = *connPtr;
+ unique_ptr<DBClientCursor> cursor(_safeCursor(conn->query(MongosType::ConfigNS, Query())));
+
+ while (cursor->more()) {
+ BSONObj pingDoc = cursor->next();
+
+ MongosType ping;
+ string errMsg;
+ // NOTE: We don't care if the ping is invalid, legacy stuff will be
+ if (!ping.parseBSON(pingDoc, &errMsg)) {
+ warning() << "could not parse ping document: " << pingDoc << causedBy(errMsg)
+ << endl;
+ continue;
+ }
- string mongoVersion = "2.0";
- // Hack to determine older mongos versions from ping format
- if (ping.isWaitingSet()) mongoVersion = "2.2";
- if (ping.isMongoVersionSet() && ping.getMongoVersion() != "") {
- mongoVersion = ping.getMongoVersion();
- }
+ string mongoVersion = "2.0";
+ // Hack to determine older mongos versions from ping format
+ if (ping.isWaitingSet())
+ mongoVersion = "2.2";
+ if (ping.isMongoVersionSet() && ping.getMongoVersion() != "") {
+ mongoVersion = ping.getMongoVersion();
+ }
- Date_t lastPing = ping.getPing();
+ Date_t lastPing = ping.getPing();
- Minutes quietIntervalMins{0};
- Date_t currentJsTime = jsTime();
- if (currentJsTime >= lastPing) {
- quietIntervalMins = duration_cast<Minutes>(currentJsTime - lastPing);
- }
+ Minutes quietIntervalMins{0};
+ Date_t currentJsTime = jsTime();
+ if (currentJsTime >= lastPing) {
+ quietIntervalMins = duration_cast<Minutes>(currentJsTime - lastPing);
+ }
- // We assume that anything that hasn't pinged in 5 minutes is probably down
- if (quietIntervalMins >= Minutes{5}) {
- log() << "stale mongos detected " << quietIntervalMins.count()
- << " minutes ago, network location is " << pingDoc["_id"].String()
- << ", not checking version";
- }
- else {
- if (versionCmp(mongoVersion, minMongoVersion) < 0) {
- return Status(ErrorCodes::RemoteValidationError,
- stream() << "version " << mongoVersion
- << " detected on mongos at "
- << ping.getName()
- << ", but version >= " << minMongoVersion
- << " required; you must wait 5 minutes "
- << "after shutting down a pre-" << minMongoVersion
- << " mongos");
- }
+ // We assume that anything that hasn't pinged in 5 minutes is probably down
+ if (quietIntervalMins >= Minutes{5}) {
+ log() << "stale mongos detected " << quietIntervalMins.count()
+ << " minutes ago, network location is " << pingDoc["_id"].String()
+ << ", not checking version";
+ } else {
+ if (versionCmp(mongoVersion, minMongoVersion) < 0) {
+ return Status(
+ ErrorCodes::RemoteValidationError,
+ stream() << "version " << mongoVersion << " detected on mongos at "
+ << ping.getName() << ", but version >= " << minMongoVersion
+ << " required; you must wait 5 minutes "
+ << "after shutting down a pre-" << minMongoVersion << " mongos");
}
}
}
- catch (const DBException& e) {
- return e.toStatus("could not read mongos pings collection");
- }
+ } catch (const DBException& e) {
+ return e.toStatus("could not read mongos pings collection");
+ }
- connPtr->done();
+ connPtr->done();
- //
- // Load shards from config server
- //
+ //
+ // Load shards from config server
+ //
- vector<HostAndPort> servers;
+ vector<HostAndPort> servers;
- try {
- vector<ShardType> shards;
- Status status = catalogManager->getAllShards(&shards);
+ try {
+ vector<ShardType> shards;
+ Status status = catalogManager->getAllShards(&shards);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ for (const ShardType& shard : shards) {
+ Status status = shard.validate();
if (!status.isOK()) {
- return status;
+ return Status(ErrorCodes::UnsupportedFormat,
+ stream() << "shard " << shard.toBSON()
+ << " failed validation: " << causedBy(status));
}
- for (const ShardType& shard : shards) {
- Status status = shard.validate();
- if (!status.isOK()) {
- return Status(ErrorCodes::UnsupportedFormat,
- stream() << "shard " << shard.toBSON()
- << " failed validation: " << causedBy(status));
- }
-
- const auto shardConnStatus = ConnectionString::parse(shard.getHost());
- if (!shardConnStatus.isOK()) {
- return Status(ErrorCodes::UnsupportedFormat,
- stream() << "invalid shard host " << shard.getHost()
- << " read from the config server"
- << shardConnStatus.getStatus().toString());
- }
-
- vector<HostAndPort> shardServers = shardConnStatus.getValue().getServers();
- servers.insert(servers.end(), shardServers.begin(), shardServers.end());
+ const auto shardConnStatus = ConnectionString::parse(shard.getHost());
+ if (!shardConnStatus.isOK()) {
+ return Status(ErrorCodes::UnsupportedFormat,
+ stream() << "invalid shard host " << shard.getHost()
+ << " read from the config server"
+ << shardConnStatus.getStatus().toString());
}
- }
- catch (const DBException& e) {
- return e.toStatus("could not read shards collection");
- }
-
- // Add config servers to list of servers to check version against
- vector<HostAndPort> configServers = catalogManager->connectionString().getServers();
- servers.insert(servers.end(), configServers.begin(), configServers.end());
- //
- // We've now got all the shard info from the config server, start contacting the shards
- // and config servers and verifying their versions.
- //
+ vector<HostAndPort> shardServers = shardConnStatus.getValue().getServers();
+ servers.insert(servers.end(), shardServers.begin(), shardServers.end());
+ }
+ } catch (const DBException& e) {
+ return e.toStatus("could not read shards collection");
+ }
- for (vector<HostAndPort>::iterator serverIt = servers.begin();
- serverIt != servers.end(); ++serverIt) {
+ // Add config servers to list of servers to check version against
+ vector<HostAndPort> configServers = catalogManager->connectionString().getServers();
+ servers.insert(servers.end(), configServers.begin(), configServers.end());
- // Note: This will *always* be a single-host connection
- ConnectionString serverLoc(*serverIt);
- dassert(serverLoc.type() == ConnectionString::MASTER ||
- serverLoc.type() == ConnectionString::CUSTOM); // for dbtests
+ //
+ // We've now got all the shard info from the config server, start contacting the shards
+ // and config servers and verifying their versions.
+ //
- log() << "checking that version of host " << serverLoc << " is compatible with "
- << minMongoVersion << endl;
+ for (vector<HostAndPort>::iterator serverIt = servers.begin(); serverIt != servers.end();
+ ++serverIt) {
+ // Note: This will *always* be a single-host connection
+ ConnectionString serverLoc(*serverIt);
+ dassert(serverLoc.type() == ConnectionString::MASTER ||
+ serverLoc.type() == ConnectionString::CUSTOM); // for dbtests
- unique_ptr<ScopedDbConnection> serverConnPtr;
+ log() << "checking that version of host " << serverLoc << " is compatible with "
+ << minMongoVersion << endl;
- bool resultOk;
- BSONObj buildInfo;
+ unique_ptr<ScopedDbConnection> serverConnPtr;
- try {
- serverConnPtr.reset(new ScopedDbConnection(serverLoc, 30));
- ScopedDbConnection& serverConn = *serverConnPtr;
+ bool resultOk;
+ BSONObj buildInfo;
- resultOk = serverConn->runCommand("admin",
- BSON("buildInfo" << 1),
- buildInfo);
- }
- catch (const DBException& e) {
- warning() << "could not run buildInfo command on " << serverLoc.toString() << " "
- << causedBy(e) << ". Please ensure that this server is up and at a "
- "version >= "
- << minMongoVersion;
- continue;
- }
+ try {
+ serverConnPtr.reset(new ScopedDbConnection(serverLoc, 30));
+ ScopedDbConnection& serverConn = *serverConnPtr;
+
+ resultOk = serverConn->runCommand("admin", BSON("buildInfo" << 1), buildInfo);
+ } catch (const DBException& e) {
+ warning() << "could not run buildInfo command on " << serverLoc.toString() << " "
+ << causedBy(e) << ". Please ensure that this server is up and at a "
+ "version >= " << minMongoVersion;
+ continue;
+ }
- // TODO: Make running commands saner such that we can consolidate error handling
- if (!resultOk) {
- return Status(ErrorCodes::UnknownError,
- stream() << DBClientConnection::getLastErrorString(buildInfo)
- << causedBy(buildInfo.toString()));
- }
+ // TODO: Make running commands saner such that we can consolidate error handling
+ if (!resultOk) {
+ return Status(ErrorCodes::UnknownError,
+ stream() << DBClientConnection::getLastErrorString(buildInfo)
+ << causedBy(buildInfo.toString()));
+ }
- serverConnPtr->done();
+ serverConnPtr->done();
- verify(buildInfo["version"].type() == String);
- string mongoVersion = buildInfo["version"].String();
+ verify(buildInfo["version"].type() == String);
+ string mongoVersion = buildInfo["version"].String();
- if (versionCmp(mongoVersion, minMongoVersion) < 0) {
- return Status(ErrorCodes::RemoteValidationError,
- stream() << "version " << mongoVersion << " detected on mongo "
- "server at " << serverLoc.toString() <<
- ", but version >= " << minMongoVersion << " required");
- }
+ if (versionCmp(mongoVersion, minMongoVersion) < 0) {
+ return Status(ErrorCodes::RemoteValidationError,
+ stream() << "version " << mongoVersion << " detected on mongo "
+ "server at "
+ << serverLoc.toString() << ", but version >= " << minMongoVersion
+ << " required");
}
-
- return Status::OK();
}
- // Helper function for safe cursors
- DBClientCursor* _safeCursor(unique_ptr<DBClientCursor> cursor) {
- // TODO: Make error handling more consistent, it's annoying that cursors error out by
- // throwing exceptions *and* being empty
- uassert(16625, str::stream() << "cursor not found, transport error", cursor.get());
- return cursor.release();
- }
+ return Status::OK();
+}
+// Helper function for safe cursors
+DBClientCursor* _safeCursor(unique_ptr<DBClientCursor> cursor) {
+ // TODO: Make error handling more consistent, it's annoying that cursors error out by
+ // throwing exceptions *and* being empty
+ uassert(16625, str::stream() << "cursor not found, transport error", cursor.get());
+ return cursor.release();
+}
}
diff --git a/src/mongo/s/catalog/legacy/cluster_client_internal.h b/src/mongo/s/catalog/legacy/cluster_client_internal.h
index 8a140e171cd..ae60e519fbd 100644
--- a/src/mongo/s/catalog/legacy/cluster_client_internal.h
+++ b/src/mongo/s/catalog/legacy/cluster_client_internal.h
@@ -34,27 +34,26 @@
namespace mongo {
- class CatalogManager;
- class DBClientCursor;
- class Status;
-
- /**
- * Tries to check the versions of all active hosts in a cluster. Not 100% accurate, but pretty
- * effective if hosts are reachable.
- *
- * Returns OK if hosts are compatible as far as we know, RemoteValidationError if hosts are not
- * compatible, and an error Status if anything else goes wrong.
- */
- Status checkClusterMongoVersions(CatalogManager* catalogManager,
- const std::string& minMongoVersion);
-
- //
- // Needed to normalize exception behavior of connections and cursors
- // TODO: Remove when we refactor the client connection interface to something more consistent.
- //
-
- // Helper function which throws for invalid cursor initialization.
- // Note: cursor ownership will be passed to this function.
- DBClientCursor* _safeCursor(std::unique_ptr<DBClientCursor> cursor);
+class CatalogManager;
+class DBClientCursor;
+class Status;
+/**
+ * Tries to check the versions of all active hosts in a cluster. Not 100% accurate, but pretty
+ * effective if hosts are reachable.
+ *
+ * Returns OK if hosts are compatible as far as we know, RemoteValidationError if hosts are not
+ * compatible, and an error Status if anything else goes wrong.
+ */
+Status checkClusterMongoVersions(CatalogManager* catalogManager,
+ const std::string& minMongoVersion);
+
+//
+// Needed to normalize exception behavior of connections and cursors
+// TODO: Remove when we refactor the client connection interface to something more consistent.
+//
+
+// Helper function which throws for invalid cursor initialization.
+// Note: cursor ownership will be passed to this function.
+DBClientCursor* _safeCursor(std::unique_ptr<DBClientCursor> cursor);
}
diff --git a/src/mongo/s/catalog/legacy/config_coordinator.cpp b/src/mongo/s/catalog/legacy/config_coordinator.cpp
index e7f33095be9..f4d02c5433a 100644
--- a/src/mongo/s/catalog/legacy/config_coordinator.cpp
+++ b/src/mongo/s/catalog/legacy/config_coordinator.cpp
@@ -43,431 +43,424 @@
namespace mongo {
- using std::string;
- using std::vector;
+using std::string;
+using std::vector;
namespace {
- /**
- * A BSON serializable object representing a setShardVersion command request.
- */
- class SSVRequest : public BSONSerializable {
- MONGO_DISALLOW_COPYING(SSVRequest);
- public:
- SSVRequest(const std::string& configDBString) : _configDBString(configDBString) {
-
- }
-
- bool isValid(std::string* errMsg) const {
- return true;
- }
+/**
+ * A BSON serializable object representing a setShardVersion command request.
+ */
+class SSVRequest : public BSONSerializable {
+ MONGO_DISALLOW_COPYING(SSVRequest);
- /** Returns the BSON representation of the entry. */
- BSONObj toBSON() const {
- BSONObjBuilder builder;
- builder.append("setShardVersion", ""); // empty ns for init
- builder.append("configdb", _configDBString);
- builder.append("init", true);
- builder.append("authoritative", true);
- return builder.obj();
- }
+public:
+ SSVRequest(const std::string& configDBString) : _configDBString(configDBString) {}
- bool parseBSON(const BSONObj& source, std::string* errMsg) {
- // Not implemented
- invariant(false);
- return false;
- }
+ bool isValid(std::string* errMsg) const {
+ return true;
+ }
- void clear() {
- // Not implemented
- invariant(false);
- }
+ /** Returns the BSON representation of the entry. */
+ BSONObj toBSON() const {
+ BSONObjBuilder builder;
+ builder.append("setShardVersion", ""); // empty ns for init
+ builder.append("configdb", _configDBString);
+ builder.append("init", true);
+ builder.append("authoritative", true);
+ return builder.obj();
+ }
- string toString() const {
- return toBSON().toString();
- }
+ bool parseBSON(const BSONObj& source, std::string* errMsg) {
+ // Not implemented
+ invariant(false);
+ return false;
+ }
- private:
- const std::string _configDBString;
- };
+ void clear() {
+ // Not implemented
+ invariant(false);
+ }
- /**
- * A BSON serializable object representing a setShardVersion command response.
- */
- class SSVResponse : public BSONSerializable {
- MONGO_DISALLOW_COPYING(SSVResponse);
- public:
- static const BSONField<int> ok;
- static const BSONField<int> errCode;
- static const BSONField<string> errMessage;
+ string toString() const {
+ return toBSON().toString();
+ }
+private:
+ const std::string _configDBString;
+};
- SSVResponse() {
- clear();
- }
+/**
+ * A BSON serializable object representing a setShardVersion command response.
+ */
+class SSVResponse : public BSONSerializable {
+ MONGO_DISALLOW_COPYING(SSVResponse);
- bool isValid(std::string* errMsg) const {
- return _isOkSet;
- }
+public:
+ static const BSONField<int> ok;
+ static const BSONField<int> errCode;
+ static const BSONField<string> errMessage;
- BSONObj toBSON() const {
- BSONObjBuilder builder;
- if (_isOkSet) builder << ok(_ok);
- if (_isErrCodeSet) builder << errCode(_errCode);
- if (_isErrMessageSet) builder << errMessage(_errMessage);
+ SSVResponse() {
+ clear();
+ }
- return builder.obj();
- }
+ bool isValid(std::string* errMsg) const {
+ return _isOkSet;
+ }
- bool parseBSON(const BSONObj& source, std::string* errMsg) {
- FieldParser::FieldState result;
+ BSONObj toBSON() const {
+ BSONObjBuilder builder;
- result = FieldParser::extractNumber(source, ok, &_ok, errMsg);
- if (result == FieldParser::FIELD_INVALID) {
- return false;
- }
- _isOkSet = result != FieldParser::FIELD_NONE;
+ if (_isOkSet)
+ builder << ok(_ok);
+ if (_isErrCodeSet)
+ builder << errCode(_errCode);
+ if (_isErrMessageSet)
+ builder << errMessage(_errMessage);
- result = FieldParser::extract(source, errCode, &_errCode, errMsg);
- if (result == FieldParser::FIELD_INVALID) {
- return false;
- }
- _isErrCodeSet = result != FieldParser::FIELD_NONE;
+ return builder.obj();
+ }
- result = FieldParser::extract(source, errMessage, &_errMessage, errMsg);
- if (result == FieldParser::FIELD_INVALID) {
- return false;
- }
- _isErrMessageSet = result != FieldParser::FIELD_NONE;
+ bool parseBSON(const BSONObj& source, std::string* errMsg) {
+ FieldParser::FieldState result;
- return true;
+ result = FieldParser::extractNumber(source, ok, &_ok, errMsg);
+ if (result == FieldParser::FIELD_INVALID) {
+ return false;
}
+ _isOkSet = result != FieldParser::FIELD_NONE;
- void clear() {
- _ok = false;
- _isOkSet = false;
-
- _errCode = 0;
- _isErrCodeSet = false;
-
- _errMessage = "";
- _isErrMessageSet = false;
+ result = FieldParser::extract(source, errCode, &_errCode, errMsg);
+ if (result == FieldParser::FIELD_INVALID) {
+ return false;
}
+ _isErrCodeSet = result != FieldParser::FIELD_NONE;
- string toString() const {
- return toBSON().toString();
+ result = FieldParser::extract(source, errMessage, &_errMessage, errMsg);
+ if (result == FieldParser::FIELD_INVALID) {
+ return false;
}
+ _isErrMessageSet = result != FieldParser::FIELD_NONE;
- int getOk() {
- dassert(_isOkSet);
- return _ok;
- }
+ return true;
+ }
- void setOk(int ok) {
- _ok = ok;
- _isOkSet = true;
- }
+ void clear() {
+ _ok = false;
+ _isOkSet = false;
- int getErrCode() {
- if (_isErrCodeSet) {
- return _errCode;
- }
- else {
- return errCode.getDefault();
- }
- }
+ _errCode = 0;
+ _isErrCodeSet = false;
- void setErrCode(int errCode) {
- _errCode = errCode;
- _isErrCodeSet = true;
- }
+ _errMessage = "";
+ _isErrMessageSet = false;
+ }
- bool isErrCodeSet() const {
- return _isErrCodeSet;
- }
+ string toString() const {
+ return toBSON().toString();
+ }
- const string& getErrMessage() {
- dassert(_isErrMessageSet);
- return _errMessage;
- }
+ int getOk() {
+ dassert(_isOkSet);
+ return _ok;
+ }
- void setErrMessage(StringData errMsg) {
- _errMessage = errMsg.toString();
- _isErrMessageSet = true;
+ void setOk(int ok) {
+ _ok = ok;
+ _isOkSet = true;
+ }
+
+ int getErrCode() {
+ if (_isErrCodeSet) {
+ return _errCode;
+ } else {
+ return errCode.getDefault();
}
+ }
- private:
- int _ok;
- bool _isOkSet;
+ void setErrCode(int errCode) {
+ _errCode = errCode;
+ _isErrCodeSet = true;
+ }
- int _errCode;
- bool _isErrCodeSet;
+ bool isErrCodeSet() const {
+ return _isErrCodeSet;
+ }
- string _errMessage;
- bool _isErrMessageSet;
- };
+ const string& getErrMessage() {
+ dassert(_isErrMessageSet);
+ return _errMessage;
+ }
- const BSONField<int> SSVResponse::ok("ok");
- const BSONField<int> SSVResponse::errCode("code");
- const BSONField<string> SSVResponse::errMessage("errmsg");
+ void setErrMessage(StringData errMsg) {
+ _errMessage = errMsg.toString();
+ _isErrMessageSet = true;
+ }
+private:
+ int _ok;
+ bool _isOkSet;
- struct ConfigResponse {
- ConnectionString configHost;
- BatchedCommandResponse response;
- };
+ int _errCode;
+ bool _isErrCodeSet;
- void buildErrorFrom(const Status& status, BatchedCommandResponse* response) {
- response->setOk(false);
- response->setErrCode(static_cast<int>(status.code()));
- response->setErrMessage(status.reason());
+ string _errMessage;
+ bool _isErrMessageSet;
+};
- dassert(response->isValid(NULL));
- }
+const BSONField<int> SSVResponse::ok("ok");
+const BSONField<int> SSVResponse::errCode("code");
+const BSONField<string> SSVResponse::errMessage("errmsg");
- bool areResponsesEqual(const BatchedCommandResponse& responseA,
- const BatchedCommandResponse& responseB) {
- // Note: This needs to also take into account comparing responses from legacy writes
- // and write commands.
+struct ConfigResponse {
+ ConnectionString configHost;
+ BatchedCommandResponse response;
+};
- // TODO: Better reporting of why not equal
- if (responseA.getOk() != responseB.getOk()) {
- return false;
- }
+void buildErrorFrom(const Status& status, BatchedCommandResponse* response) {
+ response->setOk(false);
+ response->setErrCode(static_cast<int>(status.code()));
+ response->setErrMessage(status.reason());
- if (responseA.getN() != responseB.getN()) {
- return false;
- }
+ dassert(response->isValid(NULL));
+}
- if (responseA.isUpsertDetailsSet()) {
- // TODO:
- }
+bool areResponsesEqual(const BatchedCommandResponse& responseA,
+ const BatchedCommandResponse& responseB) {
+ // Note: This needs to also take into account comparing responses from legacy writes
+ // and write commands.
- if (responseA.getOk()) {
- return true;
- }
+ // TODO: Better reporting of why not equal
+ if (responseA.getOk() != responseB.getOk()) {
+ return false;
+ }
+
+ if (responseA.getN() != responseB.getN()) {
+ return false;
+ }
- // TODO: Compare errors here
+ if (responseA.isUpsertDetailsSet()) {
+ // TODO:
+ }
+ if (responseA.getOk()) {
return true;
}
- bool areAllResponsesEqual(const vector<ConfigResponse*>& responses) {
- BatchedCommandResponse* lastResponse = NULL;
+ // TODO: Compare errors here
- for (vector<ConfigResponse*>::const_iterator it = responses.begin();
- it != responses.end();
- ++it) {
+ return true;
+}
- BatchedCommandResponse* response = &(*it)->response;
+bool areAllResponsesEqual(const vector<ConfigResponse*>& responses) {
+ BatchedCommandResponse* lastResponse = NULL;
- if (lastResponse != NULL) {
- if (!areResponsesEqual(*lastResponse, *response)) {
- return false;
- }
- }
+ for (vector<ConfigResponse*>::const_iterator it = responses.begin(); it != responses.end();
+ ++it) {
+ BatchedCommandResponse* response = &(*it)->response;
- lastResponse = response;
+ if (lastResponse != NULL) {
+ if (!areResponsesEqual(*lastResponse, *response)) {
+ return false;
+ }
}
- return true;
+ lastResponse = response;
}
- void combineResponses(const vector<ConfigResponse*>& responses,
- BatchedCommandResponse* clientResponse) {
-
- if (areAllResponsesEqual(responses)) {
- responses.front()->response.cloneTo(clientResponse);
- return;
- }
-
- BSONObjBuilder builder;
- for (vector<ConfigResponse*>::const_iterator it = responses.begin();
- it != responses.end();
- ++it) {
+ return true;
+}
- builder.append((*it)->configHost.toString(), (*it)->response.toBSON());
- }
+void combineResponses(const vector<ConfigResponse*>& responses,
+ BatchedCommandResponse* clientResponse) {
+ if (areAllResponsesEqual(responses)) {
+ responses.front()->response.cloneTo(clientResponse);
+ return;
+ }
- clientResponse->setOk(false);
- clientResponse->setErrCode(ErrorCodes::ManualInterventionRequired);
- clientResponse->setErrMessage("config write was not consistent, "
- "manual intervention may be required. "
- "config responses: " + builder.obj().toString());
+ BSONObjBuilder builder;
+ for (vector<ConfigResponse*>::const_iterator it = responses.begin(); it != responses.end();
+ ++it) {
+ builder.append((*it)->configHost.toString(), (*it)->response.toBSON());
}
-} // namespace
+ clientResponse->setOk(false);
+ clientResponse->setErrCode(ErrorCodes::ManualInterventionRequired);
+ clientResponse->setErrMessage(
+ "config write was not consistent, "
+ "manual intervention may be required. "
+ "config responses: " +
+ builder.obj().toString());
+}
+} // namespace
- ConfigCoordinator::ConfigCoordinator(MultiCommandDispatch* dispatcher,
- const ConnectionString& configServerConnectionString)
- : _dispatcher(dispatcher),
- _configServerConnectionString(configServerConnectionString) {
- }
+ConfigCoordinator::ConfigCoordinator(MultiCommandDispatch* dispatcher,
+ const ConnectionString& configServerConnectionString)
+ : _dispatcher(dispatcher), _configServerConnectionString(configServerConnectionString) {}
- bool ConfigCoordinator::_checkConfigString(BatchedCommandResponse* clientResponse) {
- //
- // Send side
- //
+bool ConfigCoordinator::_checkConfigString(BatchedCommandResponse* clientResponse) {
+ //
+ // Send side
+ //
- for (const HostAndPort& server : _configServerConnectionString.getServers()) {
- SSVRequest ssvRequest(_configServerConnectionString.toString());
- _dispatcher->addCommand(ConnectionString(server), "admin", ssvRequest);
- }
+ for (const HostAndPort& server : _configServerConnectionString.getServers()) {
+ SSVRequest ssvRequest(_configServerConnectionString.toString());
+ _dispatcher->addCommand(ConnectionString(server), "admin", ssvRequest);
+ }
- _dispatcher->sendAll();
+ _dispatcher->sendAll();
- //
- // Recv side
- //
+ //
+ // Recv side
+ //
- bool ssvError = false;
- while (_dispatcher->numPending() > 0) {
- ConnectionString configHost;
- SSVResponse response;
+ bool ssvError = false;
+ while (_dispatcher->numPending() > 0) {
+ ConnectionString configHost;
+ SSVResponse response;
- // We've got to recv everything, no matter what - even if some failed.
- Status dispatchStatus = _dispatcher->recvAny(&configHost, &response);
+ // We've got to recv everything, no matter what - even if some failed.
+ Status dispatchStatus = _dispatcher->recvAny(&configHost, &response);
- if (ssvError) {
- // record only the first failure.
- continue;
- }
+ if (ssvError) {
+ // record only the first failure.
+ continue;
+ }
- if (!dispatchStatus.isOK()) {
- ssvError = true;
- clientResponse->setOk(false);
- clientResponse->setErrCode(static_cast<int>(dispatchStatus.code()));
- clientResponse->setErrMessage(dispatchStatus.reason());
- }
- else if (!response.getOk()) {
- ssvError = true;
- clientResponse->setOk(false);
- clientResponse->setErrMessage(response.getErrMessage());
+ if (!dispatchStatus.isOK()) {
+ ssvError = true;
+ clientResponse->setOk(false);
+ clientResponse->setErrCode(static_cast<int>(dispatchStatus.code()));
+ clientResponse->setErrMessage(dispatchStatus.reason());
+ } else if (!response.getOk()) {
+ ssvError = true;
+ clientResponse->setOk(false);
+ clientResponse->setErrMessage(response.getErrMessage());
- if (response.isErrCodeSet()) {
- clientResponse->setErrCode(response.getErrCode());
- }
+ if (response.isErrCodeSet()) {
+ clientResponse->setErrCode(response.getErrCode());
}
}
-
- return !ssvError;
}
- /**
- * The core config write functionality.
- *
- * Config writes run in two passes - the first is a quick check to ensure the config servers
- * are all reachable, the second runs the actual write.
- *
- * TODO: Upgrade and move this logic to the config servers, a state machine implementation
- * is probably the next step.
- */
- void ConfigCoordinator::executeBatch(const BatchedCommandRequest& clientRequest,
- BatchedCommandResponse* clientResponse) {
-
- const NamespaceString nss(clientRequest.getNS());
-
- // Should never use it for anything other than DBs residing on the config server
- dassert(nss.db() == "config" || nss.db() == "admin");
- dassert(clientRequest.sizeWriteOps() == 1u);
-
- // This is an opportunistic check that all config servers look healthy by calling
- // getLastError on each one of them. If there was some form of write/journaling error, get
- // last error would fail.
- {
- for (const HostAndPort& server : _configServerConnectionString.getServers()) {
- _dispatcher->addCommand(ConnectionString(server),
- "admin",
- RawBSONSerializable(BSON("getLastError" << true <<
- "fsync" << true)));
- }
+ return !ssvError;
+}
+
+/**
+ * The core config write functionality.
+ *
+ * Config writes run in two passes - the first is a quick check to ensure the config servers
+ * are all reachable, the second runs the actual write.
+ *
+ * TODO: Upgrade and move this logic to the config servers, a state machine implementation
+ * is probably the next step.
+ */
+void ConfigCoordinator::executeBatch(const BatchedCommandRequest& clientRequest,
+ BatchedCommandResponse* clientResponse) {
+ const NamespaceString nss(clientRequest.getNS());
+
+ // Should never use it for anything other than DBs residing on the config server
+ dassert(nss.db() == "config" || nss.db() == "admin");
+ dassert(clientRequest.sizeWriteOps() == 1u);
+
+ // This is an opportunistic check that all config servers look healthy by calling
+ // getLastError on each one of them. If there was some form of write/journaling error, get
+ // last error would fail.
+ {
+ for (const HostAndPort& server : _configServerConnectionString.getServers()) {
+ _dispatcher->addCommand(
+ ConnectionString(server),
+ "admin",
+ RawBSONSerializable(BSON("getLastError" << true << "fsync" << true)));
+ }
- _dispatcher->sendAll();
+ _dispatcher->sendAll();
- bool error = false;
- while (_dispatcher->numPending()) {
- ConnectionString host;
- RawBSONSerializable response;
+ bool error = false;
+ while (_dispatcher->numPending()) {
+ ConnectionString host;
+ RawBSONSerializable response;
- Status status = _dispatcher->recvAny(&host, &response);
- if (status.isOK()) {
- BSONObj obj = response.toBSON();
+ Status status = _dispatcher->recvAny(&host, &response);
+ if (status.isOK()) {
+ BSONObj obj = response.toBSON();
- LOG(3) << "Response " << obj.toString();
+ LOG(3) << "Response " << obj.toString();
- // If the ok field is anything other than 1, count it as error
- if (!obj["ok"].trueValue()) {
- error = true;
- log() << "Config server check for host " << host
- << " returned error: " << response;
- }
- }
- else {
+ // If the ok field is anything other than 1, count it as error
+ if (!obj["ok"].trueValue()) {
error = true;
log() << "Config server check for host " << host
- << " failed with status: " << status;
+ << " returned error: " << response;
}
- }
-
- // All responses should have been gathered by this point
- if (error) {
- clientResponse->setOk(false);
- clientResponse->setErrCode(ErrorCodes::RemoteValidationError);
- clientResponse->setErrMessage("Could not verify that config servers were active"
- " and reachable before write");
- return;
+ } else {
+ error = true;
+ log() << "Config server check for host " << host
+ << " failed with status: " << status;
}
}
- if (!_checkConfigString(clientResponse)) {
+ // All responses should have been gathered by this point
+ if (error) {
+ clientResponse->setOk(false);
+ clientResponse->setErrCode(ErrorCodes::RemoteValidationError);
+ clientResponse->setErrMessage(
+ "Could not verify that config servers were active"
+ " and reachable before write");
return;
}
+ }
- //
- // Do the actual writes
- //
+ if (!_checkConfigString(clientResponse)) {
+ return;
+ }
- BatchedCommandRequest configRequest( clientRequest.getBatchType() );
- clientRequest.cloneTo( &configRequest );
- configRequest.setNS( nss.coll() );
+ //
+ // Do the actual writes
+ //
- OwnedPointerVector<ConfigResponse> responsesOwned;
- vector<ConfigResponse*>& responses = responsesOwned.mutableVector();
+ BatchedCommandRequest configRequest(clientRequest.getBatchType());
+ clientRequest.cloneTo(&configRequest);
+ configRequest.setNS(nss.coll());
- //
- // Send the actual config writes
- //
+ OwnedPointerVector<ConfigResponse> responsesOwned;
+ vector<ConfigResponse*>& responses = responsesOwned.mutableVector();
- // Get as many batches as we can at once
- for (const HostAndPort& server : _configServerConnectionString.getServers()) {
- _dispatcher->addCommand(ConnectionString(server), nss.db(), configRequest);
- }
+ //
+ // Send the actual config writes
+ //
- // Send them all out
- _dispatcher->sendAll();
+ // Get as many batches as we can at once
+ for (const HostAndPort& server : _configServerConnectionString.getServers()) {
+ _dispatcher->addCommand(ConnectionString(server), nss.db(), configRequest);
+ }
- //
- // Recv side
- //
+ // Send them all out
+ _dispatcher->sendAll();
- while (_dispatcher->numPending() > 0) {
- // Get the response
- responses.push_back(new ConfigResponse());
+ //
+ // Recv side
+ //
- ConfigResponse& configResponse = *responses.back();
- Status dispatchStatus = _dispatcher->recvAny(&configResponse.configHost,
- &configResponse.response);
+ while (_dispatcher->numPending() > 0) {
+ // Get the response
+ responses.push_back(new ConfigResponse());
- if (!dispatchStatus.isOK()) {
- buildErrorFrom(dispatchStatus, &configResponse.response);
- }
- }
+ ConfigResponse& configResponse = *responses.back();
+ Status dispatchStatus =
+ _dispatcher->recvAny(&configResponse.configHost, &configResponse.response);
- combineResponses(responses, clientResponse);
+ if (!dispatchStatus.isOK()) {
+ buildErrorFrom(dispatchStatus, &configResponse.response);
+ }
}
-} // namespace mongo
+ combineResponses(responses, clientResponse);
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/legacy/config_coordinator.h b/src/mongo/s/catalog/legacy/config_coordinator.h
index 3cd0301f23f..b1a3e18ca88 100644
--- a/src/mongo/s/catalog/legacy/config_coordinator.h
+++ b/src/mongo/s/catalog/legacy/config_coordinator.h
@@ -36,27 +36,26 @@
namespace mongo {
- class MultiCommandDispatch;
+class MultiCommandDispatch;
- class ConfigCoordinator {
- public:
- ConfigCoordinator(MultiCommandDispatch* dispatcher,
- const ConnectionString& configServerConnectionString);
+class ConfigCoordinator {
+public:
+ ConfigCoordinator(MultiCommandDispatch* dispatcher,
+ const ConnectionString& configServerConnectionString);
- void executeBatch(const BatchedCommandRequest& request, BatchedCommandResponse* response);
+ void executeBatch(const BatchedCommandRequest& request, BatchedCommandResponse* response);
- private:
- /**
- * Initialize configDB string in config server or if already initialized,
- * check that it matches. Returns false if an error occured.
- */
- bool _checkConfigString(BatchedCommandResponse* clientResponse);
+private:
+ /**
+ * Initialize configDB string in config server or if already initialized,
+ * check that it matches. Returns false if an error occured.
+ */
+ bool _checkConfigString(BatchedCommandResponse* clientResponse);
- // Not owned here
- MultiCommandDispatch* const _dispatcher;
-
- const ConnectionString _configServerConnectionString;
- };
+ // Not owned here
+ MultiCommandDispatch* const _dispatcher;
+ const ConnectionString _configServerConnectionString;
+};
}
diff --git a/src/mongo/s/catalog/legacy/config_upgrade.cpp b/src/mongo/s/catalog/legacy/config_upgrade.cpp
index 2ed119d914f..3592d109e2b 100644
--- a/src/mongo/s/catalog/legacy/config_upgrade.cpp
+++ b/src/mongo/s/catalog/legacy/config_upgrade.cpp
@@ -52,519 +52,492 @@
namespace mongo {
- using std::unique_ptr;
- using std::make_pair;
- using std::map;
- using std::string;
- using std::vector;
- using str::stream;
-
- // Implemented in the respective steps' .cpp file
- bool doUpgradeV0ToV7(CatalogManager* catalogManager,
- const VersionType& lastVersionInfo,
- std::string* errMsg);
-
- bool doUpgradeV6ToV7(CatalogManager* catalogManager,
- const VersionType& lastVersionInfo,
- std::string* errMsg);
+using std::unique_ptr;
+using std::make_pair;
+using std::map;
+using std::string;
+using std::vector;
+using str::stream;
+
+// Implemented in the respective steps' .cpp file
+bool doUpgradeV0ToV7(CatalogManager* catalogManager,
+ const VersionType& lastVersionInfo,
+ std::string* errMsg);
+
+bool doUpgradeV6ToV7(CatalogManager* catalogManager,
+ const VersionType& lastVersionInfo,
+ std::string* errMsg);
namespace {
- struct VersionRange {
- VersionRange(int _minCompatibleVersion, int _currentVersion)
- : minCompatibleVersion(_minCompatibleVersion),
- currentVersion(_currentVersion) {
+struct VersionRange {
+ VersionRange(int _minCompatibleVersion, int _currentVersion)
+ : minCompatibleVersion(_minCompatibleVersion), currentVersion(_currentVersion) {}
- }
-
- bool operator==(const VersionRange& other) const {
- return (other.minCompatibleVersion == minCompatibleVersion)
- && (other.currentVersion == currentVersion);
- }
-
- bool operator!=(const VersionRange& other) const {
- return !(*this == other);
- }
-
- int minCompatibleVersion;
- int currentVersion;
- };
-
- enum VersionStatus {
- // No way to upgrade the test version to be compatible with current version
- VersionStatus_Incompatible,
+ bool operator==(const VersionRange& other) const {
+ return (other.minCompatibleVersion == minCompatibleVersion) &&
+ (other.currentVersion == currentVersion);
+ }
- // Current version is compatible with test version
- VersionStatus_Compatible,
+ bool operator!=(const VersionRange& other) const {
+ return !(*this == other);
+ }
- // Test version must be upgraded to be compatible with current version
- VersionStatus_NeedUpgrade
- };
+ int minCompatibleVersion;
+ int currentVersion;
+};
- /**
- * Encapsulates the information needed to register a config upgrade.
- */
- struct UpgradeStep {
- typedef stdx::function<bool(CatalogManager*, const VersionType&, string*)> UpgradeCallback;
+enum VersionStatus {
+ // No way to upgrade the test version to be compatible with current version
+ VersionStatus_Incompatible,
- UpgradeStep(int _fromVersion,
- const VersionRange& _toVersionRange,
- UpgradeCallback _upgradeCallback)
- : fromVersion(_fromVersion),
- toVersionRange(_toVersionRange),
- upgradeCallback(_upgradeCallback) {
+ // Current version is compatible with test version
+ VersionStatus_Compatible,
- }
+ // Test version must be upgraded to be compatible with current version
+ VersionStatus_NeedUpgrade
+};
- // The config version we're upgrading from
- int fromVersion;
+/**
+ * Encapsulates the information needed to register a config upgrade.
+ */
+struct UpgradeStep {
+ typedef stdx::function<bool(CatalogManager*, const VersionType&, string*)> UpgradeCallback;
- // The config version we're upgrading to and the min compatible config version (min, to)
- VersionRange toVersionRange;
+ UpgradeStep(int _fromVersion,
+ const VersionRange& _toVersionRange,
+ UpgradeCallback _upgradeCallback)
+ : fromVersion(_fromVersion),
+ toVersionRange(_toVersionRange),
+ upgradeCallback(_upgradeCallback) {}
- // The upgrade callback which performs the actual upgrade
- UpgradeCallback upgradeCallback;
- };
+ // The config version we're upgrading from
+ int fromVersion;
- typedef map<int, UpgradeStep> ConfigUpgradeRegistry;
+ // The config version we're upgrading to and the min compatible config version (min, to)
+ VersionRange toVersionRange;
- /**
- * Does a sanity-check validation of the registry ensuring three things:
- * 1. All upgrade paths lead to the same minCompatible/currentVersion
- * 2. Our constants match this final version pair
- * 3. There is a zero-version upgrade path
- */
- void validateRegistry(const ConfigUpgradeRegistry& registry) {
- VersionRange maxCompatibleConfigVersionRange(-1, -1);
- bool hasZeroVersionUpgrade = false;
+ // The upgrade callback which performs the actual upgrade
+ UpgradeCallback upgradeCallback;
+};
- for (const auto& upgradeStep : registry) {
- const UpgradeStep& upgrade = upgradeStep.second;
+typedef map<int, UpgradeStep> ConfigUpgradeRegistry;
- if (upgrade.fromVersion == 0) {
- hasZeroVersionUpgrade = true;
- }
+/**
+ * Does a sanity-check validation of the registry ensuring three things:
+ * 1. All upgrade paths lead to the same minCompatible/currentVersion
+ * 2. Our constants match this final version pair
+ * 3. There is a zero-version upgrade path
+ */
+void validateRegistry(const ConfigUpgradeRegistry& registry) {
+ VersionRange maxCompatibleConfigVersionRange(-1, -1);
+ bool hasZeroVersionUpgrade = false;
- if (maxCompatibleConfigVersionRange.currentVersion
- < upgrade.toVersionRange.currentVersion) {
+ for (const auto& upgradeStep : registry) {
+ const UpgradeStep& upgrade = upgradeStep.second;
- maxCompatibleConfigVersionRange = upgrade.toVersionRange;
- }
- else if (maxCompatibleConfigVersionRange.currentVersion
- == upgrade.toVersionRange.currentVersion) {
+ if (upgrade.fromVersion == 0) {
+ hasZeroVersionUpgrade = true;
+ }
- // Make sure all max upgrade paths end up with same version and compatibility
- fassert(16621, maxCompatibleConfigVersionRange == upgrade.toVersionRange);
- }
+ if (maxCompatibleConfigVersionRange.currentVersion <
+ upgrade.toVersionRange.currentVersion) {
+ maxCompatibleConfigVersionRange = upgrade.toVersionRange;
+ } else if (maxCompatibleConfigVersionRange.currentVersion ==
+ upgrade.toVersionRange.currentVersion) {
+ // Make sure all max upgrade paths end up with same version and compatibility
+ fassert(16621, maxCompatibleConfigVersionRange == upgrade.toVersionRange);
}
+ }
- // Make sure we have a zero-version upgrade
- fassert(16622, hasZeroVersionUpgrade);
+ // Make sure we have a zero-version upgrade
+ fassert(16622, hasZeroVersionUpgrade);
- // Make sure our max registered range is the same as our constants
- fassert(16623,
- maxCompatibleConfigVersionRange
- == VersionRange(MIN_COMPATIBLE_CONFIG_VERSION, CURRENT_CONFIG_VERSION));
- }
+ // Make sure our max registered range is the same as our constants
+ fassert(16623,
+ maxCompatibleConfigVersionRange ==
+ VersionRange(MIN_COMPATIBLE_CONFIG_VERSION, CURRENT_CONFIG_VERSION));
+}
- /**
- * Creates a registry of config upgrades used by the code below.
- *
- * MODIFY THIS CODE HERE TO CREATE A NEW UPGRADE PATH FROM X to Y
- * YOU MUST ALSO MODIFY THE VERSION DECLARATIONS IN config_upgrade.h
- *
- * Caveats:
- * - All upgrade paths must eventually lead to the exact same version range of
- * min and max compatible versions.
- * - This resulting version range must be equal to:
- * make_pair(MIN_COMPATIBLE_CONFIG_VERSION, CURRENT_CONFIG_VERSION)
- * - There must always be an upgrade path from the empty version (0) to the latest
- * config version.
- *
- * If any of the above is false, we fassert and fail to start.
- */
- ConfigUpgradeRegistry createRegistry() {
- ConfigUpgradeRegistry registry;
-
- // v0 to v7
- UpgradeStep v0ToV7(0, VersionRange(6, 7), doUpgradeV0ToV7);
- registry.insert(make_pair(v0ToV7.fromVersion, v0ToV7));
-
- // v6 to v7
- UpgradeStep v6ToV7(6, VersionRange(6, 7), doUpgradeV6ToV7);
- registry.insert(make_pair(v6ToV7.fromVersion, v6ToV7));
-
- validateRegistry(registry);
-
- return registry;
- }
+/**
+ * Creates a registry of config upgrades used by the code below.
+ *
+ * MODIFY THIS CODE HERE TO CREATE A NEW UPGRADE PATH FROM X to Y
+ * YOU MUST ALSO MODIFY THE VERSION DECLARATIONS IN config_upgrade.h
+ *
+ * Caveats:
+ * - All upgrade paths must eventually lead to the exact same version range of
+ * min and max compatible versions.
+ * - This resulting version range must be equal to:
+ * make_pair(MIN_COMPATIBLE_CONFIG_VERSION, CURRENT_CONFIG_VERSION)
+ * - There must always be an upgrade path from the empty version (0) to the latest
+ * config version.
+ *
+ * If any of the above is false, we fassert and fail to start.
+ */
+ConfigUpgradeRegistry createRegistry() {
+ ConfigUpgradeRegistry registry;
- /**
- * Checks whether or not a particular cluster version is compatible with our current
- * version and mongodb version. The version is compatible if it falls between the
- * MIN_COMPATIBLE_CONFIG_VERSION and CURRENT_CONFIG_VERSION and is not explicitly excluded.
- *
- * @return a VersionStatus enum indicating compatibility
- */
- VersionStatus isConfigVersionCompatible(const VersionType& versionInfo, string* whyNot) {
- string dummy;
- if (!whyNot) {
- whyNot = &dummy;
- }
+ // v0 to v7
+ UpgradeStep v0ToV7(0, VersionRange(6, 7), doUpgradeV0ToV7);
+ registry.insert(make_pair(v0ToV7.fromVersion, v0ToV7));
- // Check if we're empty
- if (versionInfo.getCurrentVersion() == UpgradeHistory_EmptyVersion) {
- return VersionStatus_NeedUpgrade;
- }
+ // v6 to v7
+ UpgradeStep v6ToV7(6, VersionRange(6, 7), doUpgradeV6ToV7);
+ registry.insert(make_pair(v6ToV7.fromVersion, v6ToV7));
- // Check that we aren't too old
- if (CURRENT_CONFIG_VERSION < versionInfo.getMinCompatibleVersion()) {
+ validateRegistry(registry);
- *whyNot = stream() << "the config version " << CURRENT_CONFIG_VERSION
- << " of our process is too old "
- << "for the detected config version "
- << versionInfo.getMinCompatibleVersion();
+ return registry;
+}
- return VersionStatus_Incompatible;
- }
+/**
+ * Checks whether or not a particular cluster version is compatible with our current
+ * version and mongodb version. The version is compatible if it falls between the
+ * MIN_COMPATIBLE_CONFIG_VERSION and CURRENT_CONFIG_VERSION and is not explicitly excluded.
+ *
+ * @return a VersionStatus enum indicating compatibility
+ */
+VersionStatus isConfigVersionCompatible(const VersionType& versionInfo, string* whyNot) {
+ string dummy;
+ if (!whyNot) {
+ whyNot = &dummy;
+ }
- // Check that the mongo version of this process hasn't been excluded from the cluster
- vector<MongoVersionRange> excludedRanges;
- if (versionInfo.isExcludingMongoVersionsSet() &&
- !MongoVersionRange::parseBSONArray(versionInfo.getExcludingMongoVersions(),
- &excludedRanges,
- whyNot))
- {
+ // Check if we're empty
+ if (versionInfo.getCurrentVersion() == UpgradeHistory_EmptyVersion) {
+ return VersionStatus_NeedUpgrade;
+ }
- *whyNot = stream() << "could not understand excluded version ranges"
- << causedBy(whyNot);
+ // Check that we aren't too old
+ if (CURRENT_CONFIG_VERSION < versionInfo.getMinCompatibleVersion()) {
+ *whyNot = stream() << "the config version " << CURRENT_CONFIG_VERSION
+ << " of our process is too old "
+ << "for the detected config version "
+ << versionInfo.getMinCompatibleVersion();
- return VersionStatus_Incompatible;
- }
+ return VersionStatus_Incompatible;
+ }
- // versionString is the global version of this process
- if (isInMongoVersionRanges(versionString, excludedRanges)) {
+ // Check that the mongo version of this process hasn't been excluded from the cluster
+ vector<MongoVersionRange> excludedRanges;
+ if (versionInfo.isExcludingMongoVersionsSet() &&
+ !MongoVersionRange::parseBSONArray(
+ versionInfo.getExcludingMongoVersions(), &excludedRanges, whyNot)) {
+ *whyNot = stream() << "could not understand excluded version ranges" << causedBy(whyNot);
- // Cast needed here for MSVC compiler issue
- *whyNot = stream() << "not compatible with current config version, version "
- << reinterpret_cast<const char*>(versionString)
- << "has been excluded.";
+ return VersionStatus_Incompatible;
+ }
- return VersionStatus_Incompatible;
- }
+ // versionString is the global version of this process
+ if (isInMongoVersionRanges(versionString, excludedRanges)) {
+ // Cast needed here for MSVC compiler issue
+ *whyNot = stream() << "not compatible with current config version, version "
+ << reinterpret_cast<const char*>(versionString) << "has been excluded.";
- // Check if we need to upgrade
- if (versionInfo.getCurrentVersion() >= CURRENT_CONFIG_VERSION) {
- return VersionStatus_Compatible;
- }
+ return VersionStatus_Incompatible;
+ }
- return VersionStatus_NeedUpgrade;
+ // Check if we need to upgrade
+ if (versionInfo.getCurrentVersion() >= CURRENT_CONFIG_VERSION) {
+ return VersionStatus_Compatible;
}
- // Checks that all config servers are online
- bool _checkConfigServersAlive(const ConnectionString& configLoc, string* errMsg) {
- bool resultOk;
- BSONObj result;
- try {
- ScopedDbConnection conn(configLoc, 30);
- if (conn->type() == ConnectionString::SYNC) {
- // TODO: Dynamic cast is bad, we need a better way of managing this op
- // via the heirarchy (or not)
- SyncClusterConnection* scc = dynamic_cast<SyncClusterConnection*>(conn.get());
- fassert(16729, scc != NULL);
- return scc->prepare(*errMsg);
- }
- else {
- resultOk = conn->runCommand("admin", BSON( "fsync" << 1 ), result);
- }
- conn.done();
- }
- catch (const DBException& e) {
- *errMsg = e.toString();
- return false;
+ return VersionStatus_NeedUpgrade;
+}
+
+// Checks that all config servers are online
+bool _checkConfigServersAlive(const ConnectionString& configLoc, string* errMsg) {
+ bool resultOk;
+ BSONObj result;
+ try {
+ ScopedDbConnection conn(configLoc, 30);
+ if (conn->type() == ConnectionString::SYNC) {
+ // TODO: Dynamic cast is bad, we need a better way of managing this op
+ // via the heirarchy (or not)
+ SyncClusterConnection* scc = dynamic_cast<SyncClusterConnection*>(conn.get());
+ fassert(16729, scc != NULL);
+ return scc->prepare(*errMsg);
+ } else {
+ resultOk = conn->runCommand("admin", BSON("fsync" << 1), result);
}
-
- if (!resultOk) {
- *errMsg = DBClientWithCommands::getLastErrorString(result);
- return false;
- }
-
- return true;
+ conn.done();
+ } catch (const DBException& e) {
+ *errMsg = e.toString();
+ return false;
}
- // Dispatches upgrades based on version to the upgrades registered in the upgrade registry
- bool _nextUpgrade(CatalogManager* catalogManager,
- const ConfigUpgradeRegistry& registry,
- const VersionType& lastVersionInfo,
- VersionType* upgradedVersionInfo,
- string* errMsg) {
+ if (!resultOk) {
+ *errMsg = DBClientWithCommands::getLastErrorString(result);
+ return false;
+ }
- int fromVersion = lastVersionInfo.getCurrentVersion();
+ return true;
+}
- ConfigUpgradeRegistry::const_iterator foundIt = registry.find(fromVersion);
+// Dispatches upgrades based on version to the upgrades registered in the upgrade registry
+bool _nextUpgrade(CatalogManager* catalogManager,
+ const ConfigUpgradeRegistry& registry,
+ const VersionType& lastVersionInfo,
+ VersionType* upgradedVersionInfo,
+ string* errMsg) {
+ int fromVersion = lastVersionInfo.getCurrentVersion();
- if (foundIt == registry.end()) {
+ ConfigUpgradeRegistry::const_iterator foundIt = registry.find(fromVersion);
- *errMsg = stream() << "newer version " << CURRENT_CONFIG_VERSION
- << " of mongo config metadata is required, " << "current version is "
- << fromVersion << ", "
- << "don't know how to upgrade from this version";
+ if (foundIt == registry.end()) {
+ *errMsg = stream() << "newer version " << CURRENT_CONFIG_VERSION
+ << " of mongo config metadata is required, "
+ << "current version is " << fromVersion << ", "
+ << "don't know how to upgrade from this version";
- return false;
- }
+ return false;
+ }
- const UpgradeStep& upgrade = foundIt->second;
- int toVersion = upgrade.toVersionRange.currentVersion;
+ const UpgradeStep& upgrade = foundIt->second;
+ int toVersion = upgrade.toVersionRange.currentVersion;
- log() << "starting next upgrade step from v" << fromVersion << " to v" << toVersion;
+ log() << "starting next upgrade step from v" << fromVersion << " to v" << toVersion;
- // Log begin to config.changelog
- catalogManager->logChange(NULL,
- "starting upgrade of config database",
- VersionType::ConfigNS,
- BSON("from" << fromVersion << "to" << toVersion));
+ // Log begin to config.changelog
+ catalogManager->logChange(NULL,
+ "starting upgrade of config database",
+ VersionType::ConfigNS,
+ BSON("from" << fromVersion << "to" << toVersion));
- if (!upgrade.upgradeCallback(catalogManager, lastVersionInfo, errMsg)) {
- *errMsg = stream() << "error upgrading config database from v"
- << fromVersion << " to v" << toVersion << causedBy(errMsg);
- return false;
- }
+ if (!upgrade.upgradeCallback(catalogManager, lastVersionInfo, errMsg)) {
+ *errMsg = stream() << "error upgrading config database from v" << fromVersion << " to v"
+ << toVersion << causedBy(errMsg);
+ return false;
+ }
- // Get the config version we've upgraded to and make sure it's sane
- Status verifyConfigStatus = getConfigVersion(catalogManager, upgradedVersionInfo);
+ // Get the config version we've upgraded to and make sure it's sane
+ Status verifyConfigStatus = getConfigVersion(catalogManager, upgradedVersionInfo);
- if (!verifyConfigStatus.isOK()) {
- *errMsg = stream() << "failed to validate v" << fromVersion << " config version upgrade"
- << causedBy(verifyConfigStatus);
+ if (!verifyConfigStatus.isOK()) {
+ *errMsg = stream() << "failed to validate v" << fromVersion << " config version upgrade"
+ << causedBy(verifyConfigStatus);
- return false;
- }
-
- catalogManager->logChange(NULL,
- "finished upgrade of config database",
- VersionType::ConfigNS,
- BSON("from" << fromVersion << "to" << toVersion));
- return true;
+ return false;
}
-} // namespace
+ catalogManager->logChange(NULL,
+ "finished upgrade of config database",
+ VersionType::ConfigNS,
+ BSON("from" << fromVersion << "to" << toVersion));
+ return true;
+}
+} // namespace
- /**
- * Returns the config version of the cluster pointed at by the connection string.
- *
- * @return OK if version found successfully, error status if something bad happened.
- */
- Status getConfigVersion(CatalogManager* catalogManager, VersionType* versionInfo) {
- try {
- versionInfo->clear();
- ScopedDbConnection conn(catalogManager->connectionString(), 30);
-
- unique_ptr<DBClientCursor> cursor(_safeCursor(conn->query("config.version",
- BSONObj())));
+/**
+ * Returns the config version of the cluster pointed at by the connection string.
+ *
+ * @return OK if version found successfully, error status if something bad happened.
+ */
+Status getConfigVersion(CatalogManager* catalogManager, VersionType* versionInfo) {
+ try {
+ versionInfo->clear();
- bool hasConfigData = conn->count(ShardType::ConfigNS)
- || conn->count(DatabaseType::ConfigNS)
- || conn->count(CollectionType::ConfigNS);
+ ScopedDbConnection conn(catalogManager->connectionString(), 30);
- if (!cursor->more()) {
+ unique_ptr<DBClientCursor> cursor(_safeCursor(conn->query("config.version", BSONObj())));
- // Version is 1 if we have data, 0 if we're completely empty
- if (hasConfigData) {
- versionInfo->setMinCompatibleVersion(UpgradeHistory_UnreportedVersion);
- versionInfo->setCurrentVersion(UpgradeHistory_UnreportedVersion);
- }
- else {
- versionInfo->setMinCompatibleVersion(UpgradeHistory_EmptyVersion);
- versionInfo->setCurrentVersion(UpgradeHistory_EmptyVersion);
- }
+ bool hasConfigData = conn->count(ShardType::ConfigNS) ||
+ conn->count(DatabaseType::ConfigNS) || conn->count(CollectionType::ConfigNS);
- conn.done();
- return Status::OK();
+ if (!cursor->more()) {
+ // Version is 1 if we have data, 0 if we're completely empty
+ if (hasConfigData) {
+ versionInfo->setMinCompatibleVersion(UpgradeHistory_UnreportedVersion);
+ versionInfo->setCurrentVersion(UpgradeHistory_UnreportedVersion);
+ } else {
+ versionInfo->setMinCompatibleVersion(UpgradeHistory_EmptyVersion);
+ versionInfo->setCurrentVersion(UpgradeHistory_EmptyVersion);
}
- BSONObj versionDoc = cursor->next();
- string errMsg;
+ conn.done();
+ return Status::OK();
+ }
- if (!versionInfo->parseBSON(versionDoc, &errMsg) || !versionInfo->isValid(&errMsg)) {
- conn.done();
+ BSONObj versionDoc = cursor->next();
+ string errMsg;
- return Status(ErrorCodes::UnsupportedFormat,
- stream() << "invalid config version document " << versionDoc
- << causedBy(errMsg));
- }
+ if (!versionInfo->parseBSON(versionDoc, &errMsg) || !versionInfo->isValid(&errMsg)) {
+ conn.done();
- if (cursor->more()) {
- conn.done();
+ return Status(ErrorCodes::UnsupportedFormat,
+ stream() << "invalid config version document " << versionDoc
+ << causedBy(errMsg));
+ }
- return Status(ErrorCodes::RemoteValidationError,
- stream() << "should only have 1 document "
- << "in config.version collection");
- }
+ if (cursor->more()) {
conn.done();
- }
- catch (const DBException& e) {
- return e.toStatus();
- }
- return Status::OK();
+ return Status(ErrorCodes::RemoteValidationError,
+ stream() << "should only have 1 document "
+ << "in config.version collection");
+ }
+ conn.done();
+ } catch (const DBException& e) {
+ return e.toStatus();
}
- bool checkAndUpgradeConfigVersion(CatalogManager* catalogManager,
- bool upgrade,
- VersionType* initialVersionInfo,
- VersionType* versionInfo,
- string* errMsg) {
+ return Status::OK();
+}
+
+bool checkAndUpgradeConfigVersion(CatalogManager* catalogManager,
+ bool upgrade,
+ VersionType* initialVersionInfo,
+ VersionType* versionInfo,
+ string* errMsg) {
+ string dummy;
+ if (!errMsg) {
+ errMsg = &dummy;
+ }
- string dummy;
- if (!errMsg) {
- errMsg = &dummy;
- }
+ Status getConfigStatus = getConfigVersion(catalogManager, versionInfo);
+ if (!getConfigStatus.isOK()) {
+ *errMsg = stream() << "could not load config version for upgrade"
+ << causedBy(getConfigStatus);
+ return false;
+ }
- Status getConfigStatus = getConfigVersion(catalogManager, versionInfo);
- if (!getConfigStatus.isOK()) {
- *errMsg = stream() << "could not load config version for upgrade"
- << causedBy(getConfigStatus);
- return false;
- }
+ versionInfo->cloneTo(initialVersionInfo);
- versionInfo->cloneTo(initialVersionInfo);
+ VersionStatus comp = isConfigVersionCompatible(*versionInfo, errMsg);
- VersionStatus comp = isConfigVersionCompatible(*versionInfo, errMsg);
+ if (comp == VersionStatus_Incompatible)
+ return false;
+ if (comp == VersionStatus_Compatible)
+ return true;
- if (comp == VersionStatus_Incompatible) return false;
- if (comp == VersionStatus_Compatible) return true;
+ invariant(comp == VersionStatus_NeedUpgrade);
- invariant(comp == VersionStatus_NeedUpgrade);
+ //
+ // Our current config version is now greater than the current version, so we should upgrade
+ // if possible.
+ //
- //
- // Our current config version is now greater than the current version, so we should upgrade
- // if possible.
- //
+ // The first empty version is technically an upgrade, but has special semantics
+ bool isEmptyVersion = versionInfo->getCurrentVersion() == UpgradeHistory_EmptyVersion;
- // The first empty version is technically an upgrade, but has special semantics
- bool isEmptyVersion = versionInfo->getCurrentVersion() == UpgradeHistory_EmptyVersion;
+ // First check for the upgrade flag (but no flag is needed if we're upgrading from empty)
+ if (!isEmptyVersion && !upgrade) {
+ *errMsg = stream() << "newer version " << CURRENT_CONFIG_VERSION
+ << " of mongo config metadata is required, "
+ << "current version is " << versionInfo->getCurrentVersion() << ", "
+ << "need to run mongos with --upgrade";
- // First check for the upgrade flag (but no flag is needed if we're upgrading from empty)
- if (!isEmptyVersion && !upgrade) {
- *errMsg = stream() << "newer version " << CURRENT_CONFIG_VERSION
- << " of mongo config metadata is required, " << "current version is "
- << versionInfo->getCurrentVersion() << ", "
- << "need to run mongos with --upgrade";
+ return false;
+ }
- return false;
+ // Contact the config servers to make sure all are online - otherwise we wait a long time
+ // for locks.
+ if (!_checkConfigServersAlive(catalogManager->connectionString(), errMsg)) {
+ if (isEmptyVersion) {
+ *errMsg = stream() << "all config servers must be reachable for initial"
+ << " config database creation" << causedBy(errMsg);
+ } else {
+ *errMsg = stream() << "all config servers must be reachable for config upgrade"
+ << causedBy(errMsg);
}
- // Contact the config servers to make sure all are online - otherwise we wait a long time
- // for locks.
- if (!_checkConfigServersAlive(catalogManager->connectionString(), errMsg)) {
+ return false;
+ }
- if (isEmptyVersion) {
- *errMsg = stream() << "all config servers must be reachable for initial"
- << " config database creation" << causedBy(errMsg);
- }
- else {
- *errMsg = stream() << "all config servers must be reachable for config upgrade"
+ // Check whether or not the balancer is online, if it is online we will not upgrade
+ // (but we will initialize the config server)
+ if (!isEmptyVersion) {
+ auto balSettingsResult = catalogManager->getGlobalSettings(SettingsType::BalancerDocKey);
+ if (balSettingsResult.isOK()) {
+ SettingsType balSettings = balSettingsResult.getValue();
+ if (!balSettings.getBalancerStopped()) {
+ *errMsg = stream() << "balancer must be stopped for config upgrade"
<< causedBy(errMsg);
}
-
- return false;
}
+ }
- // Check whether or not the balancer is online, if it is online we will not upgrade
- // (but we will initialize the config server)
- if (!isEmptyVersion) {
- auto balSettingsResult =
- catalogManager->getGlobalSettings(SettingsType::BalancerDocKey);
- if (balSettingsResult.isOK()) {
- SettingsType balSettings = balSettingsResult.getValue();
- if (!balSettings.getBalancerStopped()) {
- *errMsg = stream() << "balancer must be stopped for config upgrade"
- << causedBy(errMsg);
- }
- }
- }
+ //
+ // Acquire a lock for the upgrade process.
+ //
+ // We want to ensure that only a single mongo process is upgrading the config server at a
+ // time.
+ //
+
+ string whyMessage(stream() << "upgrading config database to new format v"
+ << CURRENT_CONFIG_VERSION);
+ auto lockTimeout = stdx::chrono::milliseconds(20 * 60 * 1000);
+ auto scopedDistLock =
+ catalogManager->getDistLockManager()->lock("configUpgrade", whyMessage, lockTimeout);
+ if (!scopedDistLock.isOK()) {
+ *errMsg = scopedDistLock.getStatus().toString();
+ return false;
+ }
- //
- // Acquire a lock for the upgrade process.
- //
- // We want to ensure that only a single mongo process is upgrading the config server at a
- // time.
- //
+ //
+ // Double-check compatibility inside the upgrade lock
+ // Another process may have won the lock earlier and done the upgrade for us, check
+ // if this is the case.
+ //
+
+ getConfigStatus = getConfigVersion(catalogManager, versionInfo);
+ if (!getConfigStatus.isOK()) {
+ *errMsg = stream() << "could not reload config version for upgrade"
+ << causedBy(getConfigStatus);
+ return false;
+ }
- string whyMessage(stream() << "upgrading config database to new format v"
- << CURRENT_CONFIG_VERSION);
- auto lockTimeout = stdx::chrono::milliseconds(20 * 60 * 1000);
- auto scopedDistLock = catalogManager->getDistLockManager()->lock("configUpgrade",
- whyMessage,
- lockTimeout);
- if (!scopedDistLock.isOK()) {
- *errMsg = scopedDistLock.getStatus().toString();
- return false;
- }
+ versionInfo->cloneTo(initialVersionInfo);
- //
- // Double-check compatibility inside the upgrade lock
- // Another process may have won the lock earlier and done the upgrade for us, check
- // if this is the case.
- //
+ comp = isConfigVersionCompatible(*versionInfo, errMsg);
- getConfigStatus = getConfigVersion(catalogManager, versionInfo);
- if (!getConfigStatus.isOK()) {
- *errMsg = stream() << "could not reload config version for upgrade"
- << causedBy(getConfigStatus);
- return false;
- }
+ if (comp == VersionStatus_Incompatible)
+ return false;
+ if (comp == VersionStatus_Compatible)
+ return true;
- versionInfo->cloneTo(initialVersionInfo);
+ invariant(comp == VersionStatus_NeedUpgrade);
- comp = isConfigVersionCompatible(*versionInfo, errMsg);
+ //
+ // Run through the upgrade steps necessary to bring our config version to the current
+ // version
+ //
- if (comp == VersionStatus_Incompatible) return false;
- if (comp == VersionStatus_Compatible) return true;
+ log() << "starting upgrade of config server from v" << versionInfo->getCurrentVersion()
+ << " to v" << CURRENT_CONFIG_VERSION;
- invariant(comp == VersionStatus_NeedUpgrade);
+ ConfigUpgradeRegistry registry(createRegistry());
+
+ while (versionInfo->getCurrentVersion() < CURRENT_CONFIG_VERSION) {
+ int fromVersion = versionInfo->getCurrentVersion();
//
- // Run through the upgrade steps necessary to bring our config version to the current
- // version
+ // Run the next upgrade process and replace versionInfo with the result of the
+ // upgrade.
//
- log() << "starting upgrade of config server from v" << versionInfo->getCurrentVersion()
- << " to v" << CURRENT_CONFIG_VERSION;
-
- ConfigUpgradeRegistry registry(createRegistry());
-
- while (versionInfo->getCurrentVersion() < CURRENT_CONFIG_VERSION) {
- int fromVersion = versionInfo->getCurrentVersion();
-
- //
- // Run the next upgrade process and replace versionInfo with the result of the
- // upgrade.
- //
-
- if (!_nextUpgrade(catalogManager, registry, *versionInfo, versionInfo, errMsg)) {
- return false;
- }
-
- // Ensure we're making progress here
- if (versionInfo->getCurrentVersion() <= fromVersion) {
+ if (!_nextUpgrade(catalogManager, registry, *versionInfo, versionInfo, errMsg)) {
+ return false;
+ }
- *errMsg = stream() << "bad v" << fromVersion << " config version upgrade, "
- << "version did not increment and is now "
- << versionInfo->getCurrentVersion();
+ // Ensure we're making progress here
+ if (versionInfo->getCurrentVersion() <= fromVersion) {
+ *errMsg = stream() << "bad v" << fromVersion << " config version upgrade, "
+ << "version did not increment and is now "
+ << versionInfo->getCurrentVersion();
- return false;
- }
+ return false;
}
+ }
- invariant(versionInfo->getCurrentVersion() == CURRENT_CONFIG_VERSION);
+ invariant(versionInfo->getCurrentVersion() == CURRENT_CONFIG_VERSION);
- log() << "upgrade of config server to v" << versionInfo->getCurrentVersion()
- << " successful";
+ log() << "upgrade of config server to v" << versionInfo->getCurrentVersion() << " successful";
- return true;
- }
+ return true;
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/catalog/legacy/config_upgrade.h b/src/mongo/s/catalog/legacy/config_upgrade.h
index cd37a9cb634..7caf9d5a177 100644
--- a/src/mongo/s/catalog/legacy/config_upgrade.h
+++ b/src/mongo/s/catalog/legacy/config_upgrade.h
@@ -32,118 +32,118 @@
namespace mongo {
- class CatalogManager;
- class Status;
- class VersionType;
+class CatalogManager;
+class Status;
+class VersionType;
+
+/**
+ * UPGRADE HISTORY
+ *
+ * The enum below documents the version changes to *both* the config server data layout
+ * and the versioning protocol between clients (i.e. the set of calls between mongos and
+ * mongod).
+ *
+ * Friendly notice:
+ *
+ * EVERY CHANGE EITHER IN CONFIG LAYOUT AND IN S/D PROTOCOL MUST BE RECORDED HERE BY AN INCREASE
+ * IN THE VERSION AND BY TAKING THE FOLLOWING STEPS. (IF YOU DON'T UNDERSTAND THESE STEPS, YOU
+ * SHOULD PROBABLY NOT BE UPGRADING THE VERSIONS BY YOURSELF.)
+ *
+ * + A new entry in the UpgradeHistory enum is created
+ * + The CURRENT_CONFIG_VERSION below is incremented to that version
+ * + There should be a determination if the MIN_COMPATIBLE_CONFIG_VERSION should be increased or
+ * not. This means determining if, by introducing the changes to layout and/or protocol, the
+ * new mongos/d can co-exist in a cluster with the old ones.
+ * + If layout changes are involved, there should be a corresponding layout upgrade routine. See
+ * for instance config_upgrade_vX_to_vY.cpp.
+ * + Again, if a layout change occurs, the base upgrade method, config_upgrade_v0_to_vX.cpp must
+ * be upgraded. This means that all new clusters will start at the newest versions.
+ *
+ */
+enum UpgradeHistory {
/**
- * UPGRADE HISTORY
- *
- * The enum below documents the version changes to *both* the config server data layout
- * and the versioning protocol between clients (i.e. the set of calls between mongos and
- * mongod).
- *
- * Friendly notice:
+ * The empty version, reported when there is no config server data
+ */
+ UpgradeHistory_EmptyVersion = 0,
+
+ /**
+ * The unreported version older mongoses used before config.version collection existed
*
- * EVERY CHANGE EITHER IN CONFIG LAYOUT AND IN S/D PROTOCOL MUST BE RECORDED HERE BY AN INCREASE
- * IN THE VERSION AND BY TAKING THE FOLLOWING STEPS. (IF YOU DON'T UNDERSTAND THESE STEPS, YOU
- * SHOULD PROBABLY NOT BE UPGRADING THE VERSIONS BY YOURSELF.)
+ * If there is a config.shards/databases/collections collection but no config.version
+ * collection, version 1 is assumed
+ */
+ UpgradeHistory_UnreportedVersion = 1,
+
+ /**
+ * NOTE: We skip version 2 here since it is very old and we shouldn't see it in the wild.
*
- * + A new entry in the UpgradeHistory enum is created
- * + The CURRENT_CONFIG_VERSION below is incremented to that version
- * + There should be a determination if the MIN_COMPATIBLE_CONFIG_VERSION should be increased or
- * not. This means determining if, by introducing the changes to layout and/or protocol, the
- * new mongos/d can co-exist in a cluster with the old ones.
- * + If layout changes are involved, there should be a corresponding layout upgrade routine. See
- * for instance config_upgrade_vX_to_vY.cpp.
- * + Again, if a layout change occurs, the base upgrade method, config_upgrade_v0_to_vX.cpp must
- * be upgraded. This means that all new clusters will start at the newest versions.
+ * Do not skip upgrade versions in the future.
+ */
+
+ /**
+ * Base version used by pre-2.4 mongoses with no collection epochs.
+ */
+ UpgradeHistory_NoEpochVersion = 3,
+
+ /**
+ * Version upgrade which added collection epochs to all sharded collections and
+ * chunks.
*
+ * Also:
+ * + Version document in config.version now of the form:
+ * { minVersion : X, currentVersion : Y, clusterId : OID(...) }
+ * + Mongos pings include a "mongoVersion" field indicating the mongos version
+ * + Mongos pings include a "configVersion" field indicating the current config version
+ * + Mongos explicitly ignores any collection with a "primary" field
*/
- enum UpgradeHistory {
-
- /**
- * The empty version, reported when there is no config server data
- */
- UpgradeHistory_EmptyVersion = 0,
-
- /**
- * The unreported version older mongoses used before config.version collection existed
- *
- * If there is a config.shards/databases/collections collection but no config.version
- * collection, version 1 is assumed
- */
- UpgradeHistory_UnreportedVersion = 1,
-
- /**
- * NOTE: We skip version 2 here since it is very old and we shouldn't see it in the wild.
- *
- * Do not skip upgrade versions in the future.
- */
-
- /**
- * Base version used by pre-2.4 mongoses with no collection epochs.
- */
- UpgradeHistory_NoEpochVersion = 3,
-
- /**
- * Version upgrade which added collection epochs to all sharded collections and
- * chunks.
- *
- * Also:
- * + Version document in config.version now of the form:
- * { minVersion : X, currentVersion : Y, clusterId : OID(...) }
- * + Mongos pings include a "mongoVersion" field indicating the mongos version
- * + Mongos pings include a "configVersion" field indicating the current config version
- * + Mongos explicitly ignores any collection with a "primary" field
- */
- UpgradeHistory_MandatoryEpochVersion = 4,
-
- /**
- * Version upgrade with the following changes:
- *
- * + Dropping a collection from mongos now waits for the chunks to be removed from the
- * config server before contacting each shard. Because of this, mongos should be
- * upgraded first before mongod or never drop collections during upgrade.
- */
- UpgradeHistory_DummyBumpPre2_6 = 5,
-
- /**
- * Version upgrade with the following changes:
- *
- * + "_secondaryThrottle" field for config.settings now accepts write concern
- * specifications.
- * + config.locks { ts: 1 } index is no longer unique.
- */
- UpgradeHistory_DummyBumpPre2_8 = 6, // Note: 2.8 is also known as 3.0.
-
- UpgradeHistory_DummyBumpPre3_0 = 7,
- };
-
- // Earliest version we're compatible with
- const int MIN_COMPATIBLE_CONFIG_VERSION = UpgradeHistory_DummyBumpPre2_8;
-
- // Latest version we know how to communicate with
- const int CURRENT_CONFIG_VERSION = UpgradeHistory_DummyBumpPre3_0;
+ UpgradeHistory_MandatoryEpochVersion = 4,
/**
- * Returns the config version of the cluster pointed at by the connection string.
+ * Version upgrade with the following changes:
*
- * @return OK if version found successfully, error status if something bad happened.
+ * + Dropping a collection from mongos now waits for the chunks to be removed from the
+ * config server before contacting each shard. Because of this, mongos should be
+ * upgraded first before mongod or never drop collections during upgrade.
*/
- Status getConfigVersion(CatalogManager* catalogManager, VersionType* versionInfo);
+ UpgradeHistory_DummyBumpPre2_6 = 5,
/**
- * Checks the config version and ensures it's the latest version, otherwise tries to update.
+ * Version upgrade with the following changes:
*
- * @return true if the config version is now compatible.
- * @return initial and finalVersionInfo indicating the start and end versions of the upgrade.
- * These are the same if no upgrade occurred.
+ * + "_secondaryThrottle" field for config.settings now accepts write concern
+ * specifications.
+ * + config.locks { ts: 1 } index is no longer unique.
*/
- bool checkAndUpgradeConfigVersion(CatalogManager* catalogManager,
- bool upgrade,
- VersionType* initialVersionInfo,
- VersionType* finalVersionInfo,
- std::string* errMsg);
+ UpgradeHistory_DummyBumpPre2_8 = 6, // Note: 2.8 is also known as 3.0.
+
+ UpgradeHistory_DummyBumpPre3_0 = 7,
+};
+
+// Earliest version we're compatible with
+const int MIN_COMPATIBLE_CONFIG_VERSION = UpgradeHistory_DummyBumpPre2_8;
+
+// Latest version we know how to communicate with
+const int CURRENT_CONFIG_VERSION = UpgradeHistory_DummyBumpPre3_0;
+
+/**
+ * Returns the config version of the cluster pointed at by the connection string.
+ *
+ * @return OK if version found successfully, error status if something bad happened.
+ */
+Status getConfigVersion(CatalogManager* catalogManager, VersionType* versionInfo);
+
+/**
+ * Checks the config version and ensures it's the latest version, otherwise tries to update.
+ *
+ * @return true if the config version is now compatible.
+ * @return initial and finalVersionInfo indicating the start and end versions of the upgrade.
+ * These are the same if no upgrade occurred.
+ */
+bool checkAndUpgradeConfigVersion(CatalogManager* catalogManager,
+ bool upgrade,
+ VersionType* initialVersionInfo,
+ VersionType* finalVersionInfo,
+ std::string* errMsg);
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/catalog/legacy/config_upgrade_helpers.cpp b/src/mongo/s/catalog/legacy/config_upgrade_helpers.cpp
index dfde57d6774..41bc88eeb41 100644
--- a/src/mongo/s/catalog/legacy/config_upgrade_helpers.cpp
+++ b/src/mongo/s/catalog/legacy/config_upgrade_helpers.cpp
@@ -45,81 +45,76 @@
namespace mongo {
- using std::endl;
- using std::string;
- using std::unique_ptr;
+using std::endl;
+using std::string;
+using std::unique_ptr;
- using mongoutils::str::stream;
+using mongoutils::str::stream;
- // Custom field used in upgrade state to determine if/where we failed on last upgrade
- const BSONField<bool> inCriticalSectionField("inCriticalSection", false);
+// Custom field used in upgrade state to determine if/where we failed on last upgrade
+const BSONField<bool> inCriticalSectionField("inCriticalSection", false);
- Status preUpgradeCheck(CatalogManager* catalogManager,
- const VersionType& lastVersionInfo,
- string minMongosVersion) {
-
- if (lastVersionInfo.isUpgradeIdSet() && lastVersionInfo.getUpgradeId().isSet()) {
- //
- // Another upgrade failed, so cleanup may be necessary
- //
-
- BSONObj lastUpgradeState = lastVersionInfo.getUpgradeState();
+Status preUpgradeCheck(CatalogManager* catalogManager,
+ const VersionType& lastVersionInfo,
+ string minMongosVersion) {
+ if (lastVersionInfo.isUpgradeIdSet() && lastVersionInfo.getUpgradeId().isSet()) {
+ //
+ // Another upgrade failed, so cleanup may be necessary
+ //
- bool inCriticalSection;
- string errMsg;
- if (!FieldParser::extract(lastUpgradeState,
- inCriticalSectionField,
- &inCriticalSection,
- &errMsg)) {
- return Status(ErrorCodes::FailedToParse, causedBy(errMsg));
- }
+ BSONObj lastUpgradeState = lastVersionInfo.getUpgradeState();
- if (inCriticalSection) {
- // Note: custom message must be supplied by caller
- return Status(ErrorCodes::ManualInterventionRequired, "");
- }
+ bool inCriticalSection;
+ string errMsg;
+ if (!FieldParser::extract(
+ lastUpgradeState, inCriticalSectionField, &inCriticalSection, &errMsg)) {
+ return Status(ErrorCodes::FailedToParse, causedBy(errMsg));
}
- //
- // Check the versions of other mongo processes in the cluster before upgrade.
- // We can't upgrade if there are active pre-v2.4 processes in the cluster
- //
- return checkClusterMongoVersions(catalogManager, string(minMongosVersion));
- }
-
- Status commitConfigUpgrade(CatalogManager* catalogManager,
- int currentVersion,
- int minCompatibleVersion,
- int newVersion) {
-
- // Note: DO NOT CLEAR the config version unless bumping the minCompatibleVersion,
- // we want to save the excludes that were set.
-
- BSONObjBuilder setObj;
- setObj << VersionType::minCompatibleVersion(minCompatibleVersion);
- setObj << VersionType::currentVersion(newVersion);
-
- BSONObjBuilder unsetObj;
- unsetObj.append(VersionType::upgradeId(), 1);
- unsetObj.append(VersionType::upgradeState(), 1);
- unsetObj.append("version", 1); // remove deprecated field, no longer supported >= v3.0.
-
- Status result = catalogManager->update(
- VersionType::ConfigNS,
- BSON("_id" << 1 << VersionType::currentVersion(currentVersion)),
- BSON("$set" << setObj.done() << "$unset" << unsetObj.done()),
- false,
- false,
- NULL);
- if (!result.isOK()) {
- return Status(result.code(),
- str::stream() << "could not write new version info "
- << " and exit critical upgrade section: "
- << result.reason());
+ if (inCriticalSection) {
+ // Note: custom message must be supplied by caller
+ return Status(ErrorCodes::ManualInterventionRequired, "");
}
+ }
- return result;
+ //
+ // Check the versions of other mongo processes in the cluster before upgrade.
+ // We can't upgrade if there are active pre-v2.4 processes in the cluster
+ //
+ return checkClusterMongoVersions(catalogManager, string(minMongosVersion));
+}
+
+Status commitConfigUpgrade(CatalogManager* catalogManager,
+ int currentVersion,
+ int minCompatibleVersion,
+ int newVersion) {
+ // Note: DO NOT CLEAR the config version unless bumping the minCompatibleVersion,
+ // we want to save the excludes that were set.
+
+ BSONObjBuilder setObj;
+ setObj << VersionType::minCompatibleVersion(minCompatibleVersion);
+ setObj << VersionType::currentVersion(newVersion);
+
+ BSONObjBuilder unsetObj;
+ unsetObj.append(VersionType::upgradeId(), 1);
+ unsetObj.append(VersionType::upgradeState(), 1);
+ unsetObj.append("version", 1); // remove deprecated field, no longer supported >= v3.0.
+
+ Status result =
+ catalogManager->update(VersionType::ConfigNS,
+ BSON("_id" << 1 << VersionType::currentVersion(currentVersion)),
+ BSON("$set" << setObj.done() << "$unset" << unsetObj.done()),
+ false,
+ false,
+ NULL);
+ if (!result.isOK()) {
+ return Status(result.code(),
+ str::stream() << "could not write new version info "
+ << " and exit critical upgrade section: " << result.reason());
}
-} // namespace mongo
+ return result;
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/legacy/config_upgrade_helpers.h b/src/mongo/s/catalog/legacy/config_upgrade_helpers.h
index 10d03676ea8..7411737e262 100644
--- a/src/mongo/s/catalog/legacy/config_upgrade_helpers.h
+++ b/src/mongo/s/catalog/legacy/config_upgrade_helpers.h
@@ -39,32 +39,32 @@
namespace mongo {
- class CatalogManager;
- class ConnectionString;
- class OID;
- class Status;
- class VersionType;
+class CatalogManager;
+class ConnectionString;
+class OID;
+class Status;
+class VersionType;
- /**
- * Checks whether an unsuccessful upgrade was performed last time and also checks whether
- * the mongos in the current cluster have the mimimum version required. Returns not ok if
- * the check failed and the upgrade should not proceed.
- *
- * Note: There is also a special case for ManualInterventionRequired error where the
- * message will be empty.
- */
- Status preUpgradeCheck(CatalogManager* catalogManager,
- const VersionType& lastVersionInfo,
- std::string minMongosVersion);
+/**
+ * Checks whether an unsuccessful upgrade was performed last time and also checks whether
+ * the mongos in the current cluster have the mimimum version required. Returns not ok if
+ * the check failed and the upgrade should not proceed.
+ *
+ * Note: There is also a special case for ManualInterventionRequired error where the
+ * message will be empty.
+ */
+Status preUpgradeCheck(CatalogManager* catalogManager,
+ const VersionType& lastVersionInfo,
+ std::string minMongosVersion);
- /**
- * Informs the config server that the upgrade task was completed by bumping the version.
- * This also clears all upgrade state effectively leaving the critical section if the
- * upgrade process did enter it.
- */
- Status commitConfigUpgrade(CatalogManager* catalogManager,
- int currentVersion,
- int minCompatibleVersion,
- int newVersion);
+/**
+ * Informs the config server that the upgrade task was completed by bumping the version.
+ * This also clears all upgrade state effectively leaving the critical section if the
+ * upgrade process did enter it.
+ */
+Status commitConfigUpgrade(CatalogManager* catalogManager,
+ int currentVersion,
+ int minCompatibleVersion,
+ int newVersion);
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/catalog/legacy/config_upgrade_v0_to_v7.cpp b/src/mongo/s/catalog/legacy/config_upgrade_v0_to_v7.cpp
index 787d0114e73..f086fabc521 100644
--- a/src/mongo/s/catalog/legacy/config_upgrade_v0_to_v7.cpp
+++ b/src/mongo/s/catalog/legacy/config_upgrade_v0_to_v7.cpp
@@ -39,59 +39,57 @@
namespace mongo {
- using std::string;
- using mongo::str::stream;
-
-
- /**
- * Upgrade v0 to v7 described here
- *
- * This upgrade takes the config server from empty to an initial version.
- */
- bool doUpgradeV0ToV7(CatalogManager* catalogManager,
- const VersionType& lastVersionInfo,
- string* errMsg) {
-
- string dummy;
- if (!errMsg) errMsg = &dummy;
-
- verify(lastVersionInfo.getCurrentVersion() == UpgradeHistory_EmptyVersion);
-
- //
- // Even though the initial config write is a single-document update, that single document
- // is on multiple config servers and requests can interleave. The upgrade lock prevents
- // this.
- //
-
- log() << "writing initial config version at v" << CURRENT_CONFIG_VERSION;
-
- OID newClusterId = OID::gen();
-
- VersionType versionInfo;
-
- // Upgrade to new version
- versionInfo.setMinCompatibleVersion(MIN_COMPATIBLE_CONFIG_VERSION);
- versionInfo.setCurrentVersion(CURRENT_CONFIG_VERSION);
- versionInfo.setClusterId(newClusterId);
-
- verify(versionInfo.isValid(NULL));
-
- // If the cluster has not previously been initialized, we need to set the version before
- // using so subsequent mongoses use the config data the same way. This requires all three
- // config servers online initially.
- Status result = catalogManager->update(VersionType::ConfigNS,
- BSON("_id" << 1),
- versionInfo.toBSON(),
- true, // upsert
- false, // multi
- NULL);
- if (!result.isOK()) {
- *errMsg = stream() << "error writing initial config version: "
- << result.reason();
- return false;
- }
-
- return true;
+using std::string;
+using mongo::str::stream;
+
+
+/**
+ * Upgrade v0 to v7 described here
+ *
+ * This upgrade takes the config server from empty to an initial version.
+ */
+bool doUpgradeV0ToV7(CatalogManager* catalogManager,
+ const VersionType& lastVersionInfo,
+ string* errMsg) {
+ string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
+
+ verify(lastVersionInfo.getCurrentVersion() == UpgradeHistory_EmptyVersion);
+
+ //
+ // Even though the initial config write is a single-document update, that single document
+ // is on multiple config servers and requests can interleave. The upgrade lock prevents
+ // this.
+ //
+
+ log() << "writing initial config version at v" << CURRENT_CONFIG_VERSION;
+
+ OID newClusterId = OID::gen();
+
+ VersionType versionInfo;
+
+ // Upgrade to new version
+ versionInfo.setMinCompatibleVersion(MIN_COMPATIBLE_CONFIG_VERSION);
+ versionInfo.setCurrentVersion(CURRENT_CONFIG_VERSION);
+ versionInfo.setClusterId(newClusterId);
+
+ verify(versionInfo.isValid(NULL));
+
+ // If the cluster has not previously been initialized, we need to set the version before
+ // using so subsequent mongoses use the config data the same way. This requires all three
+ // config servers online initially.
+ Status result = catalogManager->update(VersionType::ConfigNS,
+ BSON("_id" << 1),
+ versionInfo.toBSON(),
+ true, // upsert
+ false, // multi
+ NULL);
+ if (!result.isOK()) {
+ *errMsg = stream() << "error writing initial config version: " << result.reason();
+ return false;
}
+ return true;
+}
}
diff --git a/src/mongo/s/catalog/legacy/config_upgrade_v6_to_v7.cpp b/src/mongo/s/catalog/legacy/config_upgrade_v6_to_v7.cpp
index 6f841b0dc87..430d2eedb6e 100644
--- a/src/mongo/s/catalog/legacy/config_upgrade_v6_to_v7.cpp
+++ b/src/mongo/s/catalog/legacy/config_upgrade_v6_to_v7.cpp
@@ -40,74 +40,73 @@
namespace mongo {
- using std::list;
- using std::string;
- using std::vector;
-
- static const char* minMongoProcessVersion = "3.0";
-
- static const char* cannotCleanupMessage =
- "\n\n"
- "******\n"
- "Cannot upgrade config database from v6 to v7 because a previous upgrade\n"
- "failed in the critical section. Manual intervention is required to re-sync\n"
- "the config servers.\n"
- "******\n";
-
- /**
- * Upgrades v6 to v7.
- */
- bool doUpgradeV6ToV7(CatalogManager* catalogManager,
- const VersionType& lastVersionInfo,
- string* errMsg) {
-
- string dummy;
- if (!errMsg) errMsg = &dummy;
-
- invariant(lastVersionInfo.getCurrentVersion() == UpgradeHistory_DummyBumpPre2_8);
-
- Status result = preUpgradeCheck(catalogManager, lastVersionInfo, minMongoProcessVersion);
- if (!result.isOK()) {
- if (result.code() == ErrorCodes::ManualInterventionRequired) {
- *errMsg = cannotCleanupMessage;
- }
- else {
- *errMsg = result.toString();
- }
-
- return false;
- }
+using std::list;
+using std::string;
+using std::vector;
+
+static const char* minMongoProcessVersion = "3.0";
+
+static const char* cannotCleanupMessage =
+ "\n\n"
+ "******\n"
+ "Cannot upgrade config database from v6 to v7 because a previous upgrade\n"
+ "failed in the critical section. Manual intervention is required to re-sync\n"
+ "the config servers.\n"
+ "******\n";
- // This is not needed because we are not actually going to make any modifications
- // on the other collections in the config server for this particular upgrade.
- // startConfigUpgrade(configLoc.toString(),
- // lastVersionInfo.getCurrentVersion(),
- // OID::gen());
-
- // If we actually need to modify something in the config servers these need to follow
- // after calling startConfigUpgrade(...):
- //
- // 1. Acquire necessary locks.
- // 2. Make a backup of the collections we are about to modify.
- // 3. Perform the upgrade process on the backup collection.
- // 4. Verify that no changes were made to the collections since the backup was performed.
- // 5. Call enterConfigUpgradeCriticalSection(configLoc.toString(),
- // lastVersionInfo.getCurrentVersion()).
- // 6. Rename the backup collection to the name of the original collection with
- // dropTarget set to true.
-
- // We're only after the version bump in commitConfigUpgrade here since we never
- // get into the critical section.
- Status commitStatus = commitConfigUpgrade(catalogManager,
- lastVersionInfo.getCurrentVersion(),
- MIN_COMPATIBLE_CONFIG_VERSION,
- CURRENT_CONFIG_VERSION);
-
- if (!commitStatus.isOK()) {
- *errMsg = commitStatus.toString();
- return false;
+/**
+ * Upgrades v6 to v7.
+ */
+bool doUpgradeV6ToV7(CatalogManager* catalogManager,
+ const VersionType& lastVersionInfo,
+ string* errMsg) {
+ string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
+
+ invariant(lastVersionInfo.getCurrentVersion() == UpgradeHistory_DummyBumpPre2_8);
+
+ Status result = preUpgradeCheck(catalogManager, lastVersionInfo, minMongoProcessVersion);
+ if (!result.isOK()) {
+ if (result.code() == ErrorCodes::ManualInterventionRequired) {
+ *errMsg = cannotCleanupMessage;
+ } else {
+ *errMsg = result.toString();
}
- return true;
+ return false;
}
+
+ // This is not needed because we are not actually going to make any modifications
+ // on the other collections in the config server for this particular upgrade.
+ // startConfigUpgrade(configLoc.toString(),
+ // lastVersionInfo.getCurrentVersion(),
+ // OID::gen());
+
+ // If we actually need to modify something in the config servers these need to follow
+ // after calling startConfigUpgrade(...):
+ //
+ // 1. Acquire necessary locks.
+ // 2. Make a backup of the collections we are about to modify.
+ // 3. Perform the upgrade process on the backup collection.
+ // 4. Verify that no changes were made to the collections since the backup was performed.
+ // 5. Call enterConfigUpgradeCriticalSection(configLoc.toString(),
+ // lastVersionInfo.getCurrentVersion()).
+ // 6. Rename the backup collection to the name of the original collection with
+ // dropTarget set to true.
+
+ // We're only after the version bump in commitConfigUpgrade here since we never
+ // get into the critical section.
+ Status commitStatus = commitConfigUpgrade(catalogManager,
+ lastVersionInfo.getCurrentVersion(),
+ MIN_COMPATIBLE_CONFIG_VERSION,
+ CURRENT_CONFIG_VERSION);
+
+ if (!commitStatus.isOK()) {
+ *errMsg = commitStatus.toString();
+ return false;
+ }
+
+ return true;
+}
}
diff --git a/src/mongo/s/catalog/legacy/distlock.cpp b/src/mongo/s/catalog/legacy/distlock.cpp
index 22c0a4e6128..b0d30b28967 100644
--- a/src/mongo/s/catalog/legacy/distlock.cpp
+++ b/src/mongo/s/catalog/legacy/distlock.cpp
@@ -43,796 +43,760 @@
namespace mongo {
- using std::endl;
- using std::list;
- using std::set;
- using std::string;
- using std::stringstream;
- using std::unique_ptr;
- using std::vector;
-
- LabeledLevel DistributedLock::logLvl( 1 );
- DistributedLock::LastPings DistributedLock::lastPings;
-
- ThreadLocalValue<string> distLockIds("");
-
- /* ==================
- * Module initialization
- */
-
- static SimpleMutex _cachedProcessMutex;
- static string* _cachedProcessString = NULL;
-
- static void initModule() {
- stdx::lock_guard<SimpleMutex> lk(_cachedProcessMutex);
- if (_cachedProcessString) {
- // someone got the lock before us
- return;
- }
-
- // cache process string
- stringstream ss;
- ss << getHostName() << ":" << serverGlobalParams.port << ":" << time(0) << ":" << rand();
- _cachedProcessString = new string( ss.str() );
- }
-
- /* =================== */
+using std::endl;
+using std::list;
+using std::set;
+using std::string;
+using std::stringstream;
+using std::unique_ptr;
+using std::vector;
- string getDistLockProcess() {
- if (!_cachedProcessString)
- initModule();
- verify( _cachedProcessString );
- return *_cachedProcessString;
- }
+LabeledLevel DistributedLock::logLvl(1);
+DistributedLock::LastPings DistributedLock::lastPings;
- string getDistLockId() {
- string s = distLockIds.get();
- if ( s.empty() ) {
- stringstream ss;
- ss << getDistLockProcess() << ":" << getThreadName() << ":" << rand();
- s = ss.str();
- distLockIds.set( s );
- }
- return s;
- }
+ThreadLocalValue<string> distLockIds("");
- LockException::LockException(StringData msg, int code): LockException(msg, code, OID()) {
- }
+/* ==================
+ * Module initialization
+ */
- LockException::LockException(StringData msg, int code, DistLockHandle lockID):
- DBException(msg.toString(), code),
- _mustUnlockID(lockID) {
- }
+static SimpleMutex _cachedProcessMutex;
+static string* _cachedProcessString = NULL;
- DistLockHandle LockException::getMustUnlockID() const {
- return _mustUnlockID;
+static void initModule() {
+ stdx::lock_guard<SimpleMutex> lk(_cachedProcessMutex);
+ if (_cachedProcessString) {
+ // someone got the lock before us
+ return;
}
- /**
- * Create a new distributed lock, potentially with a custom sleep and takeover time. If a custom sleep time is
- * specified (time between pings)
- */
- DistributedLock::DistributedLock( const ConnectionString& conn , const string& name , unsigned long long lockTimeout, bool asProcess )
- : _conn(conn), _name(name),
- _processId( asProcess ? getDistLockId() : getDistLockProcess() ),
- _lockTimeout( lockTimeout == 0 ? LOCK_TIMEOUT : lockTimeout ),
- _maxClockSkew( _lockTimeout / LOCK_SKEW_FACTOR ), _maxNetSkew( _maxClockSkew ),
- _lockPing( _maxClockSkew )
- {
- LOG( logLvl ) << "created new distributed lock for " << name << " on " << conn
- << " ( lock timeout : " << _lockTimeout
- << ", ping interval : " << _lockPing << ", process : " << asProcess << " )" << endl;
-
-
- }
+ // cache process string
+ stringstream ss;
+ ss << getHostName() << ":" << serverGlobalParams.port << ":" << time(0) << ":" << rand();
+ _cachedProcessString = new string(ss.str());
+}
- DistLockPingInfo DistributedLock::LastPings::getLastPing(const ConnectionString& conn,
- const string& lockName) {
- stdx::lock_guard<stdx::mutex> lock(_mutex);
- return _lastPings[std::make_pair(conn.toString(), lockName)];
- }
+/* =================== */
- void DistributedLock::LastPings::setLastPing(const ConnectionString& conn,
- const string& lockName,
- const DistLockPingInfo& pd) {
- stdx::lock_guard<stdx::mutex> lock(_mutex);
- _lastPings[std::make_pair(conn.toString(), lockName)] = pd;
- }
+string getDistLockProcess() {
+ if (!_cachedProcessString)
+ initModule();
+ verify(_cachedProcessString);
+ return *_cachedProcessString;
+}
- Date_t DistributedLock::getRemoteTime() const {
- return DistributedLock::remoteTime(_conn, _maxNetSkew);
+string getDistLockId() {
+ string s = distLockIds.get();
+ if (s.empty()) {
+ stringstream ss;
+ ss << getDistLockProcess() << ":" << getThreadName() << ":" << rand();
+ s = ss.str();
+ distLockIds.set(s);
}
+ return s;
+}
- bool DistributedLock::isRemoteTimeSkewed() const {
- return !DistributedLock::checkSkew(_conn,
- NUM_LOCK_SKEW_CHECKS,
- _maxClockSkew,
- _maxNetSkew);
- }
+LockException::LockException(StringData msg, int code) : LockException(msg, code, OID()) {}
- const ConnectionString& DistributedLock::getRemoteConnection() const {
- return _conn;
- }
+LockException::LockException(StringData msg, int code, DistLockHandle lockID)
+ : DBException(msg.toString(), code), _mustUnlockID(lockID) {}
- const string& DistributedLock::getProcessId() const {
- return _processId;
- }
-
- /**
- * Returns the remote time as reported by the cluster or server. The maximum difference between the reported time
- * and the actual time on the remote server (at the completion of the function) is the maxNetSkew
- */
- Date_t DistributedLock::remoteTime( const ConnectionString& cluster, unsigned long long maxNetSkew ) {
+DistLockHandle LockException::getMustUnlockID() const {
+ return _mustUnlockID;
+}
- ConnectionString server( *cluster.getServers().begin() );
+/**
+ * Create a new distributed lock, potentially with a custom sleep and takeover time. If a custom sleep time is
+ * specified (time between pings)
+ */
+DistributedLock::DistributedLock(const ConnectionString& conn,
+ const string& name,
+ unsigned long long lockTimeout,
+ bool asProcess)
+ : _conn(conn),
+ _name(name),
+ _processId(asProcess ? getDistLockId() : getDistLockProcess()),
+ _lockTimeout(lockTimeout == 0 ? LOCK_TIMEOUT : lockTimeout),
+ _maxClockSkew(_lockTimeout / LOCK_SKEW_FACTOR),
+ _maxNetSkew(_maxClockSkew),
+ _lockPing(_maxClockSkew) {
+ LOG(logLvl) << "created new distributed lock for " << name << " on " << conn
+ << " ( lock timeout : " << _lockTimeout << ", ping interval : " << _lockPing
+ << ", process : " << asProcess << " )" << endl;
+}
- // Get result and delay if successful, errMsg if not
- bool success = false;
- BSONObj result;
- string errMsg;
- Milliseconds delay{0};
+DistLockPingInfo DistributedLock::LastPings::getLastPing(const ConnectionString& conn,
+ const string& lockName) {
+ stdx::lock_guard<stdx::mutex> lock(_mutex);
+ return _lastPings[std::make_pair(conn.toString(), lockName)];
+}
- unique_ptr<ScopedDbConnection> connPtr;
- try {
- connPtr.reset( new ScopedDbConnection( server.toString() ) );
- ScopedDbConnection& conn = *connPtr;
+void DistributedLock::LastPings::setLastPing(const ConnectionString& conn,
+ const string& lockName,
+ const DistLockPingInfo& pd) {
+ stdx::lock_guard<stdx::mutex> lock(_mutex);
+ _lastPings[std::make_pair(conn.toString(), lockName)] = pd;
+}
- Date_t then = jsTime();
- success = conn->runCommand( string( "admin" ), BSON( "serverStatus" << 1 ), result );
- delay = jsTime() - then;
+Date_t DistributedLock::getRemoteTime() const {
+ return DistributedLock::remoteTime(_conn, _maxNetSkew);
+}
- if ( !success ) errMsg = result.toString();
- conn.done();
- }
- catch ( const DBException& ex ) {
+bool DistributedLock::isRemoteTimeSkewed() const {
+ return !DistributedLock::checkSkew(_conn, NUM_LOCK_SKEW_CHECKS, _maxClockSkew, _maxNetSkew);
+}
- if ( connPtr && connPtr->get()->isFailed() ) {
- // Return to the pool so the pool knows about the failure
- connPtr->done();
- }
+const ConnectionString& DistributedLock::getRemoteConnection() const {
+ return _conn;
+}
- success = false;
- errMsg = ex.toString();
- }
-
- if( !success ) {
- throw TimeNotFoundException( str::stream() << "could not get status from server "
- << server.toString() << " in cluster "
- << cluster.toString() << " to check time"
- << causedBy( errMsg ),
- 13647 );
- }
+const string& DistributedLock::getProcessId() const {
+ return _processId;
+}
- // Make sure that our delay is not more than 2x our maximum network skew, since this is the max our remote
- // time value can be off by if we assume a response in the middle of the delay.
- if (delay > Milliseconds(maxNetSkew * 2)) {
- throw TimeNotFoundException( str::stream()
- << "server " << server.toString() << " in cluster "
- << cluster.toString()
- << " did not respond within max network delay of "
- << maxNetSkew << "ms",
- 13648 );
+/**
+ * Returns the remote time as reported by the cluster or server. The maximum difference between the reported time
+ * and the actual time on the remote server (at the completion of the function) is the maxNetSkew
+ */
+Date_t DistributedLock::remoteTime(const ConnectionString& cluster, unsigned long long maxNetSkew) {
+ ConnectionString server(*cluster.getServers().begin());
+
+ // Get result and delay if successful, errMsg if not
+ bool success = false;
+ BSONObj result;
+ string errMsg;
+ Milliseconds delay{0};
+
+ unique_ptr<ScopedDbConnection> connPtr;
+ try {
+ connPtr.reset(new ScopedDbConnection(server.toString()));
+ ScopedDbConnection& conn = *connPtr;
+
+ Date_t then = jsTime();
+ success = conn->runCommand(string("admin"), BSON("serverStatus" << 1), result);
+ delay = jsTime() - then;
+
+ if (!success)
+ errMsg = result.toString();
+ conn.done();
+ } catch (const DBException& ex) {
+ if (connPtr && connPtr->get()->isFailed()) {
+ // Return to the pool so the pool knows about the failure
+ connPtr->done();
}
- return result["localTime"].Date() - (delay / 2);
+ success = false;
+ errMsg = ex.toString();
}
- bool DistributedLock::checkSkew( const ConnectionString& cluster, unsigned skewChecks, unsigned long long maxClockSkew, unsigned long long maxNetSkew ) {
-
- vector<HostAndPort> servers = cluster.getServers();
+ if (!success) {
+ throw TimeNotFoundException(str::stream() << "could not get status from server "
+ << server.toString() << " in cluster "
+ << cluster.toString() << " to check time"
+ << causedBy(errMsg),
+ 13647);
+ }
- if(servers.size() < 1) return true;
+ // Make sure that our delay is not more than 2x our maximum network skew, since this is the max our remote
+ // time value can be off by if we assume a response in the middle of the delay.
+ if (delay > Milliseconds(maxNetSkew * 2)) {
+ throw TimeNotFoundException(
+ str::stream() << "server " << server.toString() << " in cluster " << cluster.toString()
+ << " did not respond within max network delay of " << maxNetSkew << "ms",
+ 13648);
+ }
- vector<long long> avgSkews;
+ return result["localTime"].Date() - (delay / 2);
+}
- for(unsigned i = 0; i < skewChecks; i++) {
+bool DistributedLock::checkSkew(const ConnectionString& cluster,
+ unsigned skewChecks,
+ unsigned long long maxClockSkew,
+ unsigned long long maxNetSkew) {
+ vector<HostAndPort> servers = cluster.getServers();
- // Find the average skew for each server
- unsigned s = 0;
- for(vector<HostAndPort>::iterator si = servers.begin(); si != servers.end(); ++si,s++) {
+ if (servers.size() < 1)
+ return true;
- if(i == 0) avgSkews.push_back(0);
+ vector<long long> avgSkews;
- // Could check if this is self, but shouldn't matter since local network connection should be fast.
- ConnectionString server( *si );
+ for (unsigned i = 0; i < skewChecks; i++) {
+ // Find the average skew for each server
+ unsigned s = 0;
+ for (vector<HostAndPort>::iterator si = servers.begin(); si != servers.end(); ++si, s++) {
+ if (i == 0)
+ avgSkews.push_back(0);
- vector<long long> skew;
+ // Could check if this is self, but shouldn't matter since local network connection should be fast.
+ ConnectionString server(*si);
- BSONObj result;
+ vector<long long> skew;
- Date_t remote = remoteTime( server, maxNetSkew );
- Date_t local = jsTime();
+ BSONObj result;
- // Remote time can be delayed by at most MAX_NET_SKEW
+ Date_t remote = remoteTime(server, maxNetSkew);
+ Date_t local = jsTime();
- // Skew is how much time we'd have to add to local to get to remote
- avgSkews[s] += (remote - local).count();
+ // Remote time can be delayed by at most MAX_NET_SKEW
- LOG( logLvl + 1 ) << "skew from remote server " << server << " found: "
- << (remote - local).count();
+ // Skew is how much time we'd have to add to local to get to remote
+ avgSkews[s] += (remote - local).count();
- }
+ LOG(logLvl + 1) << "skew from remote server " << server
+ << " found: " << (remote - local).count();
}
+ }
- // Analyze skews
-
- long long serverMaxSkew = 0;
- long long serverMinSkew = 0;
+ // Analyze skews
- for(unsigned s = 0; s < avgSkews.size(); s++) {
+ long long serverMaxSkew = 0;
+ long long serverMinSkew = 0;
- long long avgSkew = (avgSkews[s] /= skewChecks);
+ for (unsigned s = 0; s < avgSkews.size(); s++) {
+ long long avgSkew = (avgSkews[s] /= skewChecks);
- // Keep track of max and min skews
- if(s == 0) {
+ // Keep track of max and min skews
+ if (s == 0) {
+ serverMaxSkew = avgSkew;
+ serverMinSkew = avgSkew;
+ } else {
+ if (avgSkew > serverMaxSkew)
serverMaxSkew = avgSkew;
+ if (avgSkew < serverMinSkew)
serverMinSkew = avgSkew;
- }
- else {
- if(avgSkew > serverMaxSkew)
- serverMaxSkew = avgSkew;
- if(avgSkew < serverMinSkew)
- serverMinSkew = avgSkew;
- }
-
}
+ }
- long long totalSkew = serverMaxSkew - serverMinSkew;
-
- // Make sure our max skew is not more than our pre-set limit
- if(totalSkew > (long long) maxClockSkew) {
- LOG( logLvl + 1 ) << "total clock skew of " << totalSkew << "ms for servers " << cluster << " is out of " << maxClockSkew << "ms bounds." << endl;
- return false;
- }
+ long long totalSkew = serverMaxSkew - serverMinSkew;
- LOG( logLvl + 1 ) << "total clock skew of " << totalSkew << "ms for servers " << cluster << " is in " << maxClockSkew << "ms bounds." << endl;
- return true;
+ // Make sure our max skew is not more than our pre-set limit
+ if (totalSkew > (long long)maxClockSkew) {
+ LOG(logLvl + 1) << "total clock skew of " << totalSkew << "ms for servers " << cluster
+ << " is out of " << maxClockSkew << "ms bounds." << endl;
+ return false;
}
- Status DistributedLock::checkStatus(double timeout) {
-
- BSONObj lockObj;
- try {
- ScopedDbConnection conn(_conn.toString(), timeout );
- lockObj = conn->findOne( LocksType::ConfigNS,
- BSON( LocksType::name(_name) ) ).getOwned();
- conn.done();
- }
- catch ( DBException& e ) {
- return e.toStatus();
- }
+ LOG(logLvl + 1) << "total clock skew of " << totalSkew << "ms for servers " << cluster
+ << " is in " << maxClockSkew << "ms bounds." << endl;
+ return true;
+}
- if ( lockObj.isEmpty() ) {
- return Status(ErrorCodes::LockFailed,
- str::stream() << "no lock for " << _name << " exists in the locks collection");
- }
+Status DistributedLock::checkStatus(double timeout) {
+ BSONObj lockObj;
+ try {
+ ScopedDbConnection conn(_conn.toString(), timeout);
+ lockObj = conn->findOne(LocksType::ConfigNS, BSON(LocksType::name(_name))).getOwned();
+ conn.done();
+ } catch (DBException& e) {
+ return e.toStatus();
+ }
- if ( lockObj[LocksType::state()].numberInt() < 2 ) {
- return Status(ErrorCodes::LockFailed,
- str::stream() << "lock " << _name << " current state is not held ("
- << lockObj[LocksType::state()].numberInt() << ")");
- }
+ if (lockObj.isEmpty()) {
+ return Status(ErrorCodes::LockFailed,
+ str::stream() << "no lock for " << _name
+ << " exists in the locks collection");
+ }
- if ( lockObj[LocksType::process()].String() != _processId ) {
- return Status(ErrorCodes::LockFailed,
- str::stream() << "lock " << _name << " is currently being held by "
- << "another process ("
- << lockObj[LocksType::process()].String() << ")");
- }
+ if (lockObj[LocksType::state()].numberInt() < 2) {
+ return Status(ErrorCodes::LockFailed,
+ str::stream() << "lock " << _name << " current state is not held ("
+ << lockObj[LocksType::state()].numberInt() << ")");
+ }
- return Status::OK();
+ if (lockObj[LocksType::process()].String() != _processId) {
+ return Status(ErrorCodes::LockFailed,
+ str::stream() << "lock " << _name << " is currently being held by "
+ << "another process (" << lockObj[LocksType::process()].String()
+ << ")");
}
- static void logErrMsgOrWarn(StringData messagePrefix,
- StringData lockName,
- StringData errMsg,
- StringData altErrMsg) {
+ return Status::OK();
+}
- if (errMsg.empty()) {
- LOG(DistributedLock::logLvl - 1) << messagePrefix << " '" << lockName << "' " <<
- altErrMsg << std::endl;
- }
- else {
- warning() << messagePrefix << " '" << lockName << "' " << causedBy(errMsg.toString());
- }
+static void logErrMsgOrWarn(StringData messagePrefix,
+ StringData lockName,
+ StringData errMsg,
+ StringData altErrMsg) {
+ if (errMsg.empty()) {
+ LOG(DistributedLock::logLvl - 1) << messagePrefix << " '" << lockName << "' " << altErrMsg
+ << std::endl;
+ } else {
+ warning() << messagePrefix << " '" << lockName << "' " << causedBy(errMsg.toString());
}
+}
- // Semantics of this method are basically that if the lock cannot be acquired, returns false,
- // can be retried. If the lock should not be tried again (some unexpected error),
- // a LockException is thrown.
- bool DistributedLock::lock_try(const string& why, BSONObj* other, double timeout) {
- // This should always be true, if not, we are using the lock incorrectly.
- verify( _name != "" );
+// Semantics of this method are basically that if the lock cannot be acquired, returns false,
+// can be retried. If the lock should not be tried again (some unexpected error),
+// a LockException is thrown.
+bool DistributedLock::lock_try(const string& why, BSONObj* other, double timeout) {
+ // This should always be true, if not, we are using the lock incorrectly.
+ verify(_name != "");
- LOG( logLvl ) << "trying to acquire new distributed lock for " << _name << " on " << _conn
- << " ( lock timeout : " << _lockTimeout
- << ", ping interval : " << _lockPing << ", process : " << _processId << " )"
- << endl;
+ LOG(logLvl) << "trying to acquire new distributed lock for " << _name << " on " << _conn
+ << " ( lock timeout : " << _lockTimeout << ", ping interval : " << _lockPing
+ << ", process : " << _processId << " )" << endl;
- // write to dummy if 'other' is null
- BSONObj dummyOther;
- if ( other == NULL )
- other = &dummyOther;
+ // write to dummy if 'other' is null
+ BSONObj dummyOther;
+ if (other == NULL)
+ other = &dummyOther;
- ScopedDbConnection conn(_conn.toString(), timeout );
+ ScopedDbConnection conn(_conn.toString(), timeout);
- BSONObjBuilder queryBuilder;
- queryBuilder.append( LocksType::name() , _name );
- queryBuilder.append( LocksType::state() , 0 );
+ BSONObjBuilder queryBuilder;
+ queryBuilder.append(LocksType::name(), _name);
+ queryBuilder.append(LocksType::state(), 0);
- {
- // make sure its there so we can use simple update logic below
- BSONObj o = conn->findOne( LocksType::ConfigNS , BSON( LocksType::name(_name) ) ).getOwned();
+ {
+ // make sure its there so we can use simple update logic below
+ BSONObj o = conn->findOne(LocksType::ConfigNS, BSON(LocksType::name(_name))).getOwned();
- // Case 1: No locks
- if ( o.isEmpty() ) {
- try {
- LOG( logLvl ) << "inserting initial doc in " << LocksType::ConfigNS << " for lock " << _name << endl;
- conn->insert( LocksType::ConfigNS,
- BSON( LocksType::name(_name)
- << LocksType::state(0)
- << LocksType::who("")
- << LocksType::lockID(OID()) ));
- }
- catch ( UserException& e ) {
- warning() << "could not insert initial doc for distributed lock " << _name << causedBy( e ) << endl;
- }
+ // Case 1: No locks
+ if (o.isEmpty()) {
+ try {
+ LOG(logLvl) << "inserting initial doc in " << LocksType::ConfigNS << " for lock "
+ << _name << endl;
+ conn->insert(LocksType::ConfigNS,
+ BSON(LocksType::name(_name) << LocksType::state(0)
+ << LocksType::who("")
+ << LocksType::lockID(OID())));
+ } catch (UserException& e) {
+ warning() << "could not insert initial doc for distributed lock " << _name
+ << causedBy(e) << endl;
}
+ }
- // Case 2: A set lock that we might be able to force
- else if ( o[LocksType::state()].numberInt() > 0 ) {
-
- string lockName = o[LocksType::name()].String() + string("/") + o[LocksType::process()].String();
-
- BSONObj lastPing = conn->findOne( LockpingsType::ConfigNS, o[LocksType::process()].wrap( LockpingsType::process() ) );
- if ( lastPing.isEmpty() ) {
- LOG( logLvl ) << "empty ping found for process in lock '" << lockName << "'" << endl;
- // TODO: Using 0 as a "no time found" value Will fail if dates roll over, but then, so will a lot.
- lastPing = BSON( LockpingsType::process(o[LocksType::process()].String()) <<
- LockpingsType::ping(Date_t()) );
- }
-
- unsigned long long elapsed = 0;
- unsigned long long takeover = _lockTimeout;
- DistLockPingInfo lastPingEntry = getLastPing();
+ // Case 2: A set lock that we might be able to force
+ else if (o[LocksType::state()].numberInt() > 0) {
+ string lockName =
+ o[LocksType::name()].String() + string("/") + o[LocksType::process()].String();
+
+ BSONObj lastPing = conn->findOne(
+ LockpingsType::ConfigNS, o[LocksType::process()].wrap(LockpingsType::process()));
+ if (lastPing.isEmpty()) {
+ LOG(logLvl) << "empty ping found for process in lock '" << lockName << "'" << endl;
+ // TODO: Using 0 as a "no time found" value Will fail if dates roll over, but then, so will a lot.
+ lastPing = BSON(LockpingsType::process(o[LocksType::process()].String())
+ << LockpingsType::ping(Date_t()));
+ }
- LOG(logLvl) << "checking last ping for lock '" << lockName
- << "' against process " << lastPingEntry.processId
- << " and ping " << lastPingEntry.lastPing;
+ unsigned long long elapsed = 0;
+ unsigned long long takeover = _lockTimeout;
+ DistLockPingInfo lastPingEntry = getLastPing();
- try {
+ LOG(logLvl) << "checking last ping for lock '" << lockName << "' against process "
+ << lastPingEntry.processId << " and ping " << lastPingEntry.lastPing;
- Date_t remote = remoteTime( _conn );
-
- auto pingDocProcessId = lastPing[LockpingsType::process()].String();
- auto pingDocPingValue = lastPing[LockpingsType::ping()].Date();
-
- // Timeout the elapsed time using comparisons of remote clock
- // For non-finalized locks, timeout 15 minutes since last seen (ts)
- // For finalized locks, timeout 15 minutes since last ping
- bool recPingChange =
- o[LocksType::state()].numberInt() == 2 &&
- (lastPingEntry.processId != pingDocProcessId ||
- lastPingEntry.lastPing != pingDocPingValue);
- bool recTSChange = lastPingEntry.lockSessionId != o[LocksType::lockID()].OID();
-
- if (recPingChange || recTSChange) {
- // If the ping has changed since we last checked, mark the current date and time
- setLastPing(DistLockPingInfo(pingDocProcessId,
- pingDocPingValue,
- remote,
- o[LocksType::lockID()].OID(),
- OID()));
- }
- else {
-
- // GOTCHA! Due to network issues, it is possible that the current time
- // is less than the remote time. We *have* to check this here, otherwise
- // we overflow and our lock breaks.
- if (lastPingEntry.configLocalTime >= remote)
- elapsed = 0;
- else
- elapsed = (remote - lastPingEntry.configLocalTime).count();
- }
+ try {
+ Date_t remote = remoteTime(_conn);
+
+ auto pingDocProcessId = lastPing[LockpingsType::process()].String();
+ auto pingDocPingValue = lastPing[LockpingsType::ping()].Date();
+
+ // Timeout the elapsed time using comparisons of remote clock
+ // For non-finalized locks, timeout 15 minutes since last seen (ts)
+ // For finalized locks, timeout 15 minutes since last ping
+ bool recPingChange = o[LocksType::state()].numberInt() == 2 &&
+ (lastPingEntry.processId != pingDocProcessId ||
+ lastPingEntry.lastPing != pingDocPingValue);
+ bool recTSChange = lastPingEntry.lockSessionId != o[LocksType::lockID()].OID();
+
+ if (recPingChange || recTSChange) {
+ // If the ping has changed since we last checked, mark the current date and time
+ setLastPing(DistLockPingInfo(pingDocProcessId,
+ pingDocPingValue,
+ remote,
+ o[LocksType::lockID()].OID(),
+ OID()));
+ } else {
+ // GOTCHA! Due to network issues, it is possible that the current time
+ // is less than the remote time. We *have* to check this here, otherwise
+ // we overflow and our lock breaks.
+ if (lastPingEntry.configLocalTime >= remote)
+ elapsed = 0;
+ else
+ elapsed = (remote - lastPingEntry.configLocalTime).count();
}
- catch( LockException& e ) {
-
- // Remote server cannot be found / is not responsive
- warning() << "Could not get remote time from " << _conn << causedBy( e );
- // If our config server is having issues, forget all the pings until we can see it again
- resetLastPing();
+ } catch (LockException& e) {
+ // Remote server cannot be found / is not responsive
+ warning() << "Could not get remote time from " << _conn << causedBy(e);
+ // If our config server is having issues, forget all the pings until we can see it again
+ resetLastPing();
+ }
- }
+ if (elapsed <= takeover) {
+ LOG(1) << "could not force lock '" << lockName << "' because elapsed time "
+ << elapsed << " <= takeover time " << takeover;
+ *other = o;
+ other->getOwned();
+ conn.done();
+ return false;
+ }
- if (elapsed <= takeover) {
- LOG(1) << "could not force lock '" << lockName
- << "' because elapsed time " << elapsed
- << " <= takeover time " << takeover;
- *other = o; other->getOwned(); conn.done();
- return false;
- }
+ LOG(0) << "forcing lock '" << lockName << "' because elapsed time " << elapsed
+ << " > takeover time " << takeover;
- LOG(0) << "forcing lock '" << lockName
- << "' because elapsed time " << elapsed
- << " > takeover time " << takeover;
-
- if( elapsed > takeover ) {
-
- // Lock may forced, reset our timer if succeeds or fails
- // Ensures that another timeout must happen if something borks up here, and resets our pristine
- // ping state if acquired.
- resetLastPing();
-
- try {
-
- // Check the clock skew again. If we check this before we get a lock
- // and after the lock times out, we can be pretty sure the time is
- // increasing at the same rate on all servers and therefore our
- // timeout is accurate
- if (isRemoteTimeSkewed()) {
- string msg(str::stream() << "remote time in cluster "
- << _conn.toString()
- << " is now skewed, cannot force lock.");
- throw LockException(msg, ErrorCodes::DistributedClockSkewed);
- }
-
- // Make sure we break the lock with the correct "ts" (OID) value, otherwise
- // we can overwrite a new lock inserted in the meantime.
- conn->update( LocksType::ConfigNS,
- BSON( LocksType::name(_name) <<
- LocksType::state(o[LocksType::state()].numberInt()) <<
- LocksType::lockID(o[LocksType::lockID()].OID()) ),
- BSON( "$set" << BSON( LocksType::state(0) ) ) );
-
- BSONObj err = conn->getLastErrorDetailed();
- string errMsg = DBClientWithCommands::getLastErrorString(err);
-
- // TODO: Clean up all the extra code to exit this method, probably with a refactor
- if ( !errMsg.empty() || !err["n"].type() || err["n"].numberInt() < 1 ) {
- logErrMsgOrWarn("Could not force lock", lockName, errMsg, "(another force won");
- *other = o; other->getOwned(); conn.done();
- return false;
- }
+ if (elapsed > takeover) {
+ // Lock may forced, reset our timer if succeeds or fails
+ // Ensures that another timeout must happen if something borks up here, and resets our pristine
+ // ping state if acquired.
+ resetLastPing();
+ try {
+ // Check the clock skew again. If we check this before we get a lock
+ // and after the lock times out, we can be pretty sure the time is
+ // increasing at the same rate on all servers and therefore our
+ // timeout is accurate
+ if (isRemoteTimeSkewed()) {
+ string msg(str::stream() << "remote time in cluster " << _conn.toString()
+ << " is now skewed, cannot force lock.");
+ throw LockException(msg, ErrorCodes::DistributedClockSkewed);
}
- catch( UpdateNotTheSame& ) {
- // Ok to continue since we know we forced at least one lock document, and all lock docs
- // are required for a lock to be held.
- warning() << "lock forcing " << lockName << " inconsistent" << endl;
- }
- catch (const LockException& ) {
- // Let the exception go up and don't repackage the exception.
- throw;
- }
- catch( std::exception& e ) {
+
+ // Make sure we break the lock with the correct "ts" (OID) value, otherwise
+ // we can overwrite a new lock inserted in the meantime.
+ conn->update(LocksType::ConfigNS,
+ BSON(LocksType::name(_name)
+ << LocksType::state(o[LocksType::state()].numberInt())
+ << LocksType::lockID(o[LocksType::lockID()].OID())),
+ BSON("$set" << BSON(LocksType::state(0))));
+
+ BSONObj err = conn->getLastErrorDetailed();
+ string errMsg = DBClientWithCommands::getLastErrorString(err);
+
+ // TODO: Clean up all the extra code to exit this method, probably with a refactor
+ if (!errMsg.empty() || !err["n"].type() || err["n"].numberInt() < 1) {
+ logErrMsgOrWarn(
+ "Could not force lock", lockName, errMsg, "(another force won");
+ *other = o;
+ other->getOwned();
conn.done();
- string msg(str::stream() << "exception forcing distributed lock "
- << lockName << causedBy(e));
- throw LockException(msg, 13660);
+ return false;
}
+ } catch (UpdateNotTheSame&) {
+ // Ok to continue since we know we forced at least one lock document, and all lock docs
+ // are required for a lock to be held.
+ warning() << "lock forcing " << lockName << " inconsistent" << endl;
+ } catch (const LockException&) {
+ // Let the exception go up and don't repackage the exception.
+ throw;
+ } catch (std::exception& e) {
+ conn.done();
+ string msg(str::stream() << "exception forcing distributed lock " << lockName
+ << causedBy(e));
+ throw LockException(msg, 13660);
}
- else {
- // Not strictly necessary, but helpful for small timeouts where thread
- // scheduling is significant. This ensures that two attempts are still
- // required for a force if not acquired, and resets our state if we
- // are acquired.
- resetLastPing();
-
- // Test that the lock is held by trying to update the finalized state of the lock to the same state
- // if it does not update or does not update on all servers, we can't re-enter.
- try {
-
- // Test the lock with the correct "ts" (OID) value
- conn->update( LocksType::ConfigNS,
- BSON( LocksType::name(_name) <<
- LocksType::state(2) <<
- LocksType::lockID(o[LocksType::lockID()].OID()) ),
- BSON( "$set" << BSON( LocksType::state(2) ) ) );
-
- BSONObj err = conn->getLastErrorDetailed();
- string errMsg = DBClientWithCommands::getLastErrorString(err);
-
- // TODO: Clean up all the extra code to exit this method, probably with a refactor
- if ( ! errMsg.empty() || ! err["n"].type() || err["n"].numberInt() < 1 ) {
- logErrMsgOrWarn("Could not re-enter lock", lockName, errMsg, "(not sure lock is held");
- *other = o; other->getOwned(); conn.done();
- return false;
- }
- }
- catch( UpdateNotTheSame& ) {
- // NOT ok to continue since our lock isn't held by all servers, so isn't valid.
- warning() << "inconsistent state re-entering lock, lock " << lockName << " not held" << endl;
- *other = o; other->getOwned(); conn.done();
- return false;
- }
- catch( std::exception& e ) {
+ } else {
+ // Not strictly necessary, but helpful for small timeouts where thread
+ // scheduling is significant. This ensures that two attempts are still
+ // required for a force if not acquired, and resets our state if we
+ // are acquired.
+ resetLastPing();
+
+ // Test that the lock is held by trying to update the finalized state of the lock to the same state
+ // if it does not update or does not update on all servers, we can't re-enter.
+ try {
+ // Test the lock with the correct "ts" (OID) value
+ conn->update(LocksType::ConfigNS,
+ BSON(LocksType::name(_name)
+ << LocksType::state(2)
+ << LocksType::lockID(o[LocksType::lockID()].OID())),
+ BSON("$set" << BSON(LocksType::state(2))));
+
+ BSONObj err = conn->getLastErrorDetailed();
+ string errMsg = DBClientWithCommands::getLastErrorString(err);
+
+ // TODO: Clean up all the extra code to exit this method, probably with a refactor
+ if (!errMsg.empty() || !err["n"].type() || err["n"].numberInt() < 1) {
+ logErrMsgOrWarn(
+ "Could not re-enter lock", lockName, errMsg, "(not sure lock is held");
+ *other = o;
+ other->getOwned();
conn.done();
- string msg(str::stream() << "exception re-entering distributed lock "
- << lockName << causedBy(e));
- throw LockException(msg, 13660);
+ return false;
}
- LOG( logLvl - 1 ) << "re-entered distributed lock '" << lockName << "'" << endl;
- *other = o.getOwned();
+ } catch (UpdateNotTheSame&) {
+ // NOT ok to continue since our lock isn't held by all servers, so isn't valid.
+ warning() << "inconsistent state re-entering lock, lock " << lockName
+ << " not held" << endl;
+ *other = o;
+ other->getOwned();
conn.done();
- return true;
-
+ return false;
+ } catch (std::exception& e) {
+ conn.done();
+ string msg(str::stream() << "exception re-entering distributed lock "
+ << lockName << causedBy(e));
+ throw LockException(msg, 13660);
}
- LOG( logLvl - 1 ) << "lock '" << lockName << "' successfully forced" << endl;
-
- // We don't need the ts value in the query, since we will only ever replace locks with state=0.
- }
- // Case 3: We have an expired lock
- else if ( o[LocksType::lockID()].type() ) {
- queryBuilder.append( o[LocksType::lockID()] );
+ LOG(logLvl - 1) << "re-entered distributed lock '" << lockName << "'" << endl;
+ *other = o.getOwned();
+ conn.done();
+ return true;
}
- }
- // Always reset our ping if we're trying to get a lock, since getting a lock implies the lock state is open
- // and no locks need to be forced. If anything goes wrong, we don't want to remember an old lock.
- resetLastPing();
+ LOG(logLvl - 1) << "lock '" << lockName << "' successfully forced" << endl;
- bool gotLock = false;
- BSONObj currLock;
+ // We don't need the ts value in the query, since we will only ever replace locks with state=0.
+ }
+ // Case 3: We have an expired lock
+ else if (o[LocksType::lockID()].type()) {
+ queryBuilder.append(o[LocksType::lockID()]);
+ }
+ }
- BSONObj lockDetails = BSON( LocksType::state(1)
- << LocksType::who(getDistLockId())
- << LocksType::process(_processId)
- << LocksType::when(jsTime())
- << LocksType::why(why)
- << LocksType::lockID(OID::gen()) );
- BSONObj whatIWant = BSON( "$set" << lockDetails );
+ // Always reset our ping if we're trying to get a lock, since getting a lock implies the lock state is open
+ // and no locks need to be forced. If anything goes wrong, we don't want to remember an old lock.
+ resetLastPing();
- BSONObj query = queryBuilder.obj();
+ bool gotLock = false;
+ BSONObj currLock;
- string lockName = _name + string("/") + _processId;
+ BSONObj lockDetails =
+ BSON(LocksType::state(1) << LocksType::who(getDistLockId())
+ << LocksType::process(_processId) << LocksType::when(jsTime())
+ << LocksType::why(why) << LocksType::lockID(OID::gen()));
+ BSONObj whatIWant = BSON("$set" << lockDetails);
- try {
+ BSONObj query = queryBuilder.obj();
- // Main codepath to acquire lock
+ string lockName = _name + string("/") + _processId;
- LOG( logLvl ) << "about to acquire distributed lock '" << lockName << "'";
+ try {
+ // Main codepath to acquire lock
- LOG( logLvl + 1 ) << "trying to acquire lock " << query.toString( false, true )
- << " with details " << lockDetails.toString( false, true ) << endl;
+ LOG(logLvl) << "about to acquire distributed lock '" << lockName << "'";
- conn->update( LocksType::ConfigNS , query , whatIWant );
+ LOG(logLvl + 1) << "trying to acquire lock " << query.toString(false, true)
+ << " with details " << lockDetails.toString(false, true) << endl;
- BSONObj err = conn->getLastErrorDetailed();
- string errMsg = DBClientWithCommands::getLastErrorString(err);
+ conn->update(LocksType::ConfigNS, query, whatIWant);
- currLock = conn->findOne( LocksType::ConfigNS , BSON( LocksType::name(_name) ) );
+ BSONObj err = conn->getLastErrorDetailed();
+ string errMsg = DBClientWithCommands::getLastErrorString(err);
- if ( !errMsg.empty() || !err["n"].type() || err["n"].numberInt() < 1 ) {
- logErrMsgOrWarn("could not acquire lock", lockName, errMsg, "(another update won)");
- *other = currLock;
- other->getOwned();
- gotLock = false;
- }
- else {
- gotLock = true;
- }
+ currLock = conn->findOne(LocksType::ConfigNS, BSON(LocksType::name(_name)));
+ if (!errMsg.empty() || !err["n"].type() || err["n"].numberInt() < 1) {
+ logErrMsgOrWarn("could not acquire lock", lockName, errMsg, "(another update won)");
+ *other = currLock;
+ other->getOwned();
+ gotLock = false;
+ } else {
+ gotLock = true;
}
- catch ( UpdateNotTheSame& up ) {
-
- // this means our update got through on some, but not others
- warning() << "distributed lock '" << lockName << " did not propagate properly." << causedBy( up ) << endl;
-
- // Overall protection derives from:
- // All unlocking updates use the ts value when setting state to 0
- // This ensures that during locking, we can override all smaller ts locks with
- // our own safe ts value and not be unlocked afterward.
- for ( unsigned i = 0; i < up.size(); i++ ) {
- ScopedDbConnection indDB(up[i].first);
- BSONObj indUpdate;
+ } catch (UpdateNotTheSame& up) {
+ // this means our update got through on some, but not others
+ warning() << "distributed lock '" << lockName << " did not propagate properly."
+ << causedBy(up) << endl;
- try {
+ // Overall protection derives from:
+ // All unlocking updates use the ts value when setting state to 0
+ // This ensures that during locking, we can override all smaller ts locks with
+ // our own safe ts value and not be unlocked afterward.
+ for (unsigned i = 0; i < up.size(); i++) {
+ ScopedDbConnection indDB(up[i].first);
+ BSONObj indUpdate;
- indUpdate = indDB->findOne( LocksType::ConfigNS , BSON( LocksType::name(_name) ) );
-
- // If we override this lock in any way, grab and protect it.
- // We assume/ensure that if a process does not have all lock documents, it is no longer
- // holding the lock.
- // Note - finalized locks may compete too, but we know they've won already if competing
- // in this round. Cleanup of crashes during finalizing may take a few tries.
- if( indUpdate[LocksType::lockID()] < lockDetails[LocksType::lockID()] || indUpdate[LocksType::state()].numberInt() == 0 ) {
-
- BSONObj grabQuery = BSON( LocksType::name(_name)
- << LocksType::lockID(indUpdate[LocksType::lockID()].OID()) );
-
- // Change ts so we won't be forced, state so we won't be relocked
- BSONObj grabChanges = BSON( LocksType::lockID(lockDetails[LocksType::lockID()].OID())
- << LocksType::state(1) );
-
- // Either our update will succeed, and we'll grab the lock, or it will fail b/c some other
- // process grabbed the lock (which will change the ts), but the lock will be set until forcing
- indDB->update( LocksType::ConfigNS, grabQuery, BSON( "$set" << grabChanges ) );
-
- indUpdate = indDB->findOne( LocksType::ConfigNS, BSON( LocksType::name(_name) ) );
-
- // The tournament was interfered and it is not safe to proceed further.
- // One case this could happen is when the LockPinger processes old
- // entries from addUnlockOID. See SERVER-10688 for more detailed
- // description of race.
- if ( indUpdate[LocksType::state()].numberInt() <= 0 ) {
- LOG( logLvl - 1 ) << "lock tournament interrupted, "
- << "so no lock was taken; "
- << "new state of lock: " << indUpdate << endl;
-
- // We now break and set our currLock lockID value to zero, so that
- // we know that we did not acquire the lock below. Later code will
- // cleanup failed entries.
- currLock = BSON(LocksType::lockID(OID()));
- indDB.done();
- break;
- }
+ try {
+ indUpdate = indDB->findOne(LocksType::ConfigNS, BSON(LocksType::name(_name)));
+
+ // If we override this lock in any way, grab and protect it.
+ // We assume/ensure that if a process does not have all lock documents, it is no longer
+ // holding the lock.
+ // Note - finalized locks may compete too, but we know they've won already if competing
+ // in this round. Cleanup of crashes during finalizing may take a few tries.
+ if (indUpdate[LocksType::lockID()] < lockDetails[LocksType::lockID()] ||
+ indUpdate[LocksType::state()].numberInt() == 0) {
+ BSONObj grabQuery =
+ BSON(LocksType::name(_name)
+ << LocksType::lockID(indUpdate[LocksType::lockID()].OID()));
+
+ // Change ts so we won't be forced, state so we won't be relocked
+ BSONObj grabChanges =
+ BSON(LocksType::lockID(lockDetails[LocksType::lockID()].OID())
+ << LocksType::state(1));
+
+ // Either our update will succeed, and we'll grab the lock, or it will fail b/c some other
+ // process grabbed the lock (which will change the ts), but the lock will be set until forcing
+ indDB->update(LocksType::ConfigNS, grabQuery, BSON("$set" << grabChanges));
+
+ indUpdate = indDB->findOne(LocksType::ConfigNS, BSON(LocksType::name(_name)));
+
+ // The tournament was interfered and it is not safe to proceed further.
+ // One case this could happen is when the LockPinger processes old
+ // entries from addUnlockOID. See SERVER-10688 for more detailed
+ // description of race.
+ if (indUpdate[LocksType::state()].numberInt() <= 0) {
+ LOG(logLvl - 1) << "lock tournament interrupted, "
+ << "so no lock was taken; "
+ << "new state of lock: " << indUpdate << endl;
+
+ // We now break and set our currLock lockID value to zero, so that
+ // we know that we did not acquire the lock below. Later code will
+ // cleanup failed entries.
+ currLock = BSON(LocksType::lockID(OID()));
+ indDB.done();
+ break;
}
- // else our lock is the same, in which case we're safe, or it's a bigger lock,
- // in which case we won't need to protect anything since we won't have the lock.
-
- }
- catch( std::exception& e ) {
- conn.done();
- string msg(str::stream() << "distributed lock " << lockName
- << " had errors communicating with individual server "
- << up[1].first << causedBy(e));
- throw LockException(msg, 13661);
- }
-
- verify( !indUpdate.isEmpty() );
-
- // Find max TS value
- if ( currLock.isEmpty() || currLock[LocksType::lockID()] < indUpdate[LocksType::lockID()] ) {
- currLock = indUpdate.getOwned();
}
+ // else our lock is the same, in which case we're safe, or it's a bigger lock,
+ // in which case we won't need to protect anything since we won't have the lock.
- indDB.done();
-
+ } catch (std::exception& e) {
+ conn.done();
+ string msg(str::stream() << "distributed lock " << lockName
+ << " had errors communicating with individual server "
+ << up[1].first << causedBy(e));
+ throw LockException(msg, 13661);
}
- // Locks on all servers are now set and safe until forcing
+ verify(!indUpdate.isEmpty());
- if ( currLock[LocksType::lockID()] == lockDetails[LocksType::lockID()] ) {
- LOG( logLvl - 1 ) << "lock update won, completing lock propagation for '" << lockName << "'" << endl;
- gotLock = true;
+ // Find max TS value
+ if (currLock.isEmpty() ||
+ currLock[LocksType::lockID()] < indUpdate[LocksType::lockID()]) {
+ currLock = indUpdate.getOwned();
}
- else {
- LOG( logLvl - 1 ) << "lock update lost, lock '" << lockName << "' not propagated." << endl;
- gotLock = false;
- }
- }
- catch( std::exception& e ) {
- conn.done();
- string msg(str::stream() << "exception creating distributed lock "
- << lockName << causedBy(e));
- throw LockException(msg, 13663 );
- }
- // Complete lock propagation
- if( gotLock ) {
+ indDB.done();
+ }
- // This is now safe, since we know that no new locks will be placed on top of the ones we've checked for at
- // least 15 minutes. Sets the state = 2, so that future clients can determine that the lock is truly set.
- // The invariant for rollbacks is that we will never force locks with state = 2 and active pings, since that
- // indicates the lock is active, but this means the process creating/destroying them must explicitly poll
- // when something goes wrong.
- try {
+ // Locks on all servers are now set and safe until forcing
- BSONObjBuilder finalLockDetails;
- BSONObjIterator bi( lockDetails );
- while( bi.more() ) {
- BSONElement el = bi.next();
- if( (string) ( el.fieldName() ) == LocksType::state() )
- finalLockDetails.append( LocksType::state(), 2 );
- else finalLockDetails.append( el );
- }
+ if (currLock[LocksType::lockID()] == lockDetails[LocksType::lockID()]) {
+ LOG(logLvl - 1) << "lock update won, completing lock propagation for '" << lockName
+ << "'" << endl;
+ gotLock = true;
+ } else {
+ LOG(logLvl - 1) << "lock update lost, lock '" << lockName << "' not propagated."
+ << endl;
+ gotLock = false;
+ }
+ } catch (std::exception& e) {
+ conn.done();
+ string msg(str::stream() << "exception creating distributed lock " << lockName
+ << causedBy(e));
+ throw LockException(msg, 13663);
+ }
- conn->update( LocksType::ConfigNS , BSON( LocksType::name(_name) ) , BSON( "$set" << finalLockDetails.obj() ) );
+ // Complete lock propagation
+ if (gotLock) {
+ // This is now safe, since we know that no new locks will be placed on top of the ones we've checked for at
+ // least 15 minutes. Sets the state = 2, so that future clients can determine that the lock is truly set.
+ // The invariant for rollbacks is that we will never force locks with state = 2 and active pings, since that
+ // indicates the lock is active, but this means the process creating/destroying them must explicitly poll
+ // when something goes wrong.
+ try {
+ BSONObjBuilder finalLockDetails;
+ BSONObjIterator bi(lockDetails);
+ while (bi.more()) {
+ BSONElement el = bi.next();
+ if ((string)(el.fieldName()) == LocksType::state())
+ finalLockDetails.append(LocksType::state(), 2);
+ else
+ finalLockDetails.append(el);
+ }
- BSONObj err = conn->getLastErrorDetailed();
- string errMsg = DBClientWithCommands::getLastErrorString(err);
+ conn->update(LocksType::ConfigNS,
+ BSON(LocksType::name(_name)),
+ BSON("$set" << finalLockDetails.obj()));
- currLock = conn->findOne( LocksType::ConfigNS , BSON( LocksType::name(_name) ) );
+ BSONObj err = conn->getLastErrorDetailed();
+ string errMsg = DBClientWithCommands::getLastErrorString(err);
- if ( !errMsg.empty() || !err["n"].type() || err["n"].numberInt() < 1 ) {
- warning() << "could not finalize winning lock " << lockName
- << ( !errMsg.empty() ? causedBy( errMsg ) : " (did not update lock) " ) << endl;
- gotLock = false;
- }
- else {
- // SUCCESS!
- gotLock = true;
- }
+ currLock = conn->findOne(LocksType::ConfigNS, BSON(LocksType::name(_name)));
- }
- catch( std::exception& e ) {
- conn.done();
- string msg(str::stream() << "exception finalizing winning lock" << causedBy(e));
- // Inform caller about the potential orphan lock.
- throw LockException(msg,
- 13662,
- lockDetails[LocksType::lockID()].OID());
+ if (!errMsg.empty() || !err["n"].type() || err["n"].numberInt() < 1) {
+ warning() << "could not finalize winning lock " << lockName
+ << (!errMsg.empty() ? causedBy(errMsg) : " (did not update lock) ")
+ << endl;
+ gotLock = false;
+ } else {
+ // SUCCESS!
+ gotLock = true;
}
+ } catch (std::exception& e) {
+ conn.done();
+ string msg(str::stream() << "exception finalizing winning lock" << causedBy(e));
+ // Inform caller about the potential orphan lock.
+ throw LockException(msg, 13662, lockDetails[LocksType::lockID()].OID());
}
-
- *other = currLock;
- other->getOwned();
-
- // Log our lock results
- if(gotLock)
- LOG( logLvl - 1 ) << "distributed lock '" << lockName <<
- "' acquired, ts : " << currLock[LocksType::lockID()].OID() << endl;
- else
- LOG( logLvl - 1 ) << "distributed lock '" << lockName << "' was not acquired." << endl;
-
- conn.done();
-
- return gotLock;
}
- // This function *must not* throw exceptions, since it can be used in destructors - failure
- // results in queuing and trying again later.
- bool DistributedLock::unlock(const DistLockHandle& lockID) {
+ *other = currLock;
+ other->getOwned();
- verify(_name != "");
- string lockName = _name + string("/") + _processId;
+ // Log our lock results
+ if (gotLock)
+ LOG(logLvl - 1) << "distributed lock '" << lockName
+ << "' acquired, ts : " << currLock[LocksType::lockID()].OID() << endl;
+ else
+ LOG(logLvl - 1) << "distributed lock '" << lockName << "' was not acquired." << endl;
- const int maxAttempts = 3;
- int attempted = 0;
+ conn.done();
- while ( ++attempted <= maxAttempts ) {
+ return gotLock;
+}
- // Awkward, but necessary since the constructor itself throws exceptions
- unique_ptr<ScopedDbConnection> connPtr;
+// This function *must not* throw exceptions, since it can be used in destructors - failure
+// results in queuing and trying again later.
+bool DistributedLock::unlock(const DistLockHandle& lockID) {
+ verify(_name != "");
+ string lockName = _name + string("/") + _processId;
- try {
+ const int maxAttempts = 3;
+ int attempted = 0;
- connPtr.reset( new ScopedDbConnection( _conn.toString() ) );
- ScopedDbConnection& conn = *connPtr;
+ while (++attempted <= maxAttempts) {
+ // Awkward, but necessary since the constructor itself throws exceptions
+ unique_ptr<ScopedDbConnection> connPtr;
- // Use ts when updating lock, so that new locks can be sure they won't get trampled.
- conn->update(LocksType::ConfigNS,
- BSON(LocksType::name(_name)
- << LocksType::lockID(lockID)),
- BSON("$set" << BSON(LocksType::state(0))));
+ try {
+ connPtr.reset(new ScopedDbConnection(_conn.toString()));
+ ScopedDbConnection& conn = *connPtr;
- // Check that the lock was actually unlocked... if not, try again
- BSONObj err = conn->getLastErrorDetailed();
- string errMsg = DBClientWithCommands::getLastErrorString(err);
+ // Use ts when updating lock, so that new locks can be sure they won't get trampled.
+ conn->update(LocksType::ConfigNS,
+ BSON(LocksType::name(_name) << LocksType::lockID(lockID)),
+ BSON("$set" << BSON(LocksType::state(0))));
- if ( !errMsg.empty() || !err["n"].type() || err["n"].numberInt() < 1 ){
- warning() << "distributed lock unlock update failed, retrying "
- << ( errMsg.empty() ? causedBy( "( update not registered )" ) : causedBy( errMsg ) ) << endl;
- conn.done();
- continue;
- }
+ // Check that the lock was actually unlocked... if not, try again
+ BSONObj err = conn->getLastErrorDetailed();
+ string errMsg = DBClientWithCommands::getLastErrorString(err);
- LOG(0) << "distributed lock '" << lockName << "' unlocked. ";
+ if (!errMsg.empty() || !err["n"].type() || err["n"].numberInt() < 1) {
+ warning() << "distributed lock unlock update failed, retrying "
+ << (errMsg.empty() ? causedBy("( update not registered )")
+ : causedBy(errMsg)) << endl;
conn.done();
- return true;
- }
- catch (const UpdateNotTheSame&) {
- LOG(0) << "distributed lock '" << lockName << "' unlocked (messily). ";
- // This isn't a connection problem, so don't throw away the conn
- connPtr->done();
- return true;
+ continue;
}
- catch ( std::exception& e) {
- warning() << "distributed lock '" << lockName << "' failed unlock attempt."
- << causedBy( e ) << endl;
- // TODO: If our lock timeout is small, sleeping this long may be unsafe.
- if( attempted != maxAttempts) sleepsecs(1 << attempted);
- }
+ LOG(0) << "distributed lock '" << lockName << "' unlocked. ";
+ conn.done();
+ return true;
+ } catch (const UpdateNotTheSame&) {
+ LOG(0) << "distributed lock '" << lockName << "' unlocked (messily). ";
+ // This isn't a connection problem, so don't throw away the conn
+ connPtr->done();
+ return true;
+ } catch (std::exception& e) {
+ warning() << "distributed lock '" << lockName << "' failed unlock attempt."
+ << causedBy(e) << endl;
+
+ // TODO: If our lock timeout is small, sleeping this long may be unsafe.
+ if (attempted != maxAttempts)
+ sleepsecs(1 << attempted);
}
-
- return false;
}
+ return false;
+}
}
diff --git a/src/mongo/s/catalog/legacy/distlock.h b/src/mongo/s/catalog/legacy/distlock.h
index b569080147e..b267be42a04 100644
--- a/src/mongo/s/catalog/legacy/distlock.h
+++ b/src/mongo/s/catalog/legacy/distlock.h
@@ -37,199 +37,198 @@
namespace mongo {
- namespace {
-
- enum TimeConstants {
- LOCK_TIMEOUT = 15 * 60 * 1000,
- LOCK_SKEW_FACTOR = 30,
- LOCK_PING = LOCK_TIMEOUT / LOCK_SKEW_FACTOR,
- MAX_LOCK_NET_SKEW = LOCK_TIMEOUT / LOCK_SKEW_FACTOR,
- MAX_LOCK_CLOCK_SKEW = LOCK_TIMEOUT / LOCK_SKEW_FACTOR,
- NUM_LOCK_SKEW_CHECKS = 3,
- };
-
- // The maximum clock skew we need to handle between config servers is
- // 2 * MAX_LOCK_NET_SKEW + MAX_LOCK_CLOCK_SKEW.
-
- // Net effect of *this* clock being slow is effectively a multiplier on the max net skew
- // and a linear increase or decrease of the max clock skew.
- }
+namespace {
+
+enum TimeConstants {
+ LOCK_TIMEOUT = 15 * 60 * 1000,
+ LOCK_SKEW_FACTOR = 30,
+ LOCK_PING = LOCK_TIMEOUT / LOCK_SKEW_FACTOR,
+ MAX_LOCK_NET_SKEW = LOCK_TIMEOUT / LOCK_SKEW_FACTOR,
+ MAX_LOCK_CLOCK_SKEW = LOCK_TIMEOUT / LOCK_SKEW_FACTOR,
+ NUM_LOCK_SKEW_CHECKS = 3,
+};
+
+// The maximum clock skew we need to handle between config servers is
+// 2 * MAX_LOCK_NET_SKEW + MAX_LOCK_CLOCK_SKEW.
+
+// Net effect of *this* clock being slow is effectively a multiplier on the max net skew
+// and a linear increase or decrease of the max clock skew.
+}
+
+/**
+ * Exception class to encapsulate exceptions while managing distributed locks
+ */
+class LockException : public DBException {
+public:
+ LockException(StringData msg, int code);
/**
- * Exception class to encapsulate exceptions while managing distributed locks
+ * Use this to signal that a lock with lockID needs to be unlocked. For example, in cases
+ * where the final lock acquisition was not propagated properly to all config servers.
*/
- class LockException : public DBException {
- public:
- LockException(StringData msg, int code);
+ LockException(StringData msg, int code, DistLockHandle lockID);
- /**
- * Use this to signal that a lock with lockID needs to be unlocked. For example, in cases
- * where the final lock acquisition was not propagated properly to all config servers.
- */
- LockException(StringData msg, int code, DistLockHandle lockID);
+ /**
+ * Returns the OID of the lock that needs to be unlocked.
+ */
+ DistLockHandle getMustUnlockID() const;
- /**
- * Returns the OID of the lock that needs to be unlocked.
- */
- DistLockHandle getMustUnlockID() const;
+ virtual ~LockException() = default;
- virtual ~LockException() = default;
+private:
+ // The identifier of a lock that needs to be unlocked.
+ DistLockHandle _mustUnlockID;
+};
- private:
- // The identifier of a lock that needs to be unlocked.
- DistLockHandle _mustUnlockID;
- };
+/**
+ * Indicates an error in retrieving time values from remote servers.
+ */
+class TimeNotFoundException : public LockException {
+public:
+ TimeNotFoundException(const char* msg, int code) : LockException(msg, code) {}
+ TimeNotFoundException(const std::string& msg, int code) : LockException(msg, code) {}
+ virtual ~TimeNotFoundException() = default;
+};
+
+/**
+ * The distributed lock is a configdb backed way of synchronizing system-wide tasks. A task
+ * must be identified by a unique name across the system (e.g., "balancer"). A lock is taken
+ * by writing a document in the configdb's locks collection with that name.
+ *
+ * To be maintained, each taken lock needs to be revalidated ("pinged") within a
+ * pre-established amount of time. This class does this maintenance automatically once a
+ * DistributedLock object was constructed. The ping procedure records the local time to
+ * the ping document, but that time is untrusted and is only used as a point of reference
+ * of whether the ping was refreshed or not. Ultimately, the clock a configdb is the source
+ * of truth when determining whether a ping is still fresh or not. This is achieved by
+ * (1) remembering the ping document time along with config server time when unable to
+ * take a lock, and (2) ensuring all config servers report similar times and have similar
+ * time rates (the difference in times must start and stay small).
+ *
+ * Lock states include:
+ * 0: unlocked
+ * 1: about to be locked
+ * 2: locked
+ *
+ * Valid state transitions:
+ * 0 -> 1
+ * 1 -> 2
+ * 2 -> 0
+ *
+ * Note that at any point in time, a lock can be force unlocked if the ping for the lock
+ * becomes too stale.
+ */
+class DistributedLock {
+public:
+ static logger::LabeledLevel logLvl;
- /**
- * Indicates an error in retrieving time values from remote servers.
- */
- class TimeNotFoundException : public LockException {
+ class LastPings {
public:
- TimeNotFoundException( const char * msg , int code ) : LockException( msg, code ) {}
- TimeNotFoundException( const std::string& msg, int code ) : LockException( msg, code ) {}
- virtual ~TimeNotFoundException() = default;
+ DistLockPingInfo getLastPing(const ConnectionString& conn, const std::string& lockName);
+ void setLastPing(const ConnectionString& conn,
+ const std::string& lockName,
+ const DistLockPingInfo& pd);
+
+ stdx::mutex _mutex;
+ std::map<std::pair<std::string, std::string>, DistLockPingInfo> _lastPings;
};
+ static LastPings lastPings;
+
/**
- * The distributed lock is a configdb backed way of synchronizing system-wide tasks. A task
- * must be identified by a unique name across the system (e.g., "balancer"). A lock is taken
- * by writing a document in the configdb's locks collection with that name.
+ * The constructor does not connect to the configdb yet and constructing does not mean the lock was acquired.
+ * Construction does trigger a lock "pinging" mechanism, though.
*
- * To be maintained, each taken lock needs to be revalidated ("pinged") within a
- * pre-established amount of time. This class does this maintenance automatically once a
- * DistributedLock object was constructed. The ping procedure records the local time to
- * the ping document, but that time is untrusted and is only used as a point of reference
- * of whether the ping was refreshed or not. Ultimately, the clock a configdb is the source
- * of truth when determining whether a ping is still fresh or not. This is achieved by
- * (1) remembering the ping document time along with config server time when unable to
- * take a lock, and (2) ensuring all config servers report similar times and have similar
- * time rates (the difference in times must start and stay small).
+ * @param conn address of config(s) server(s)
+ * @param name identifier for the lock
+ * @param lockTimeout how long can the log go "unpinged" before a new attempt to lock steals it (in minutes).
+ * @param lockPing how long to wait between lock pings
+ * @param legacy use legacy logic
*
- * Lock states include:
- * 0: unlocked
- * 1: about to be locked
- * 2: locked
- *
- * Valid state transitions:
- * 0 -> 1
- * 1 -> 2
- * 2 -> 0
+ */
+ DistributedLock(const ConnectionString& conn,
+ const std::string& name,
+ unsigned long long lockTimeout = 0,
+ bool asProcess = false);
+ ~DistributedLock(){};
+
+ /**
+ * Attempts to acquire 'this' lock, checking if it could or should be stolen from the previous holder. Please
+ * consider using the dist_lock_try construct to acquire this lock in an exception safe way.
*
- * Note that at any point in time, a lock can be force unlocked if the ping for the lock
- * becomes too stale.
+ * @param why human readable description of why the lock is being taken (used to log)
+ * @param other configdb's lock document that is currently holding the lock, if lock is taken, or our own lock
+ * details if not
+ * @return true if it managed to grab the lock
*/
- class DistributedLock {
- public:
+ bool lock_try(const std::string& why, BSONObj* other = 0, double timeout = 0.0);
- static logger::LabeledLevel logLvl;
-
- class LastPings {
- public:
- DistLockPingInfo getLastPing(const ConnectionString& conn,
- const std::string& lockName);
- void setLastPing(const ConnectionString& conn,
- const std::string& lockName,
- const DistLockPingInfo& pd);
-
- stdx::mutex _mutex;
- std::map< std::pair<std::string, std::string>, DistLockPingInfo > _lastPings;
- };
-
- static LastPings lastPings;
-
- /**
- * The constructor does not connect to the configdb yet and constructing does not mean the lock was acquired.
- * Construction does trigger a lock "pinging" mechanism, though.
- *
- * @param conn address of config(s) server(s)
- * @param name identifier for the lock
- * @param lockTimeout how long can the log go "unpinged" before a new attempt to lock steals it (in minutes).
- * @param lockPing how long to wait between lock pings
- * @param legacy use legacy logic
- *
- */
- DistributedLock( const ConnectionString& conn , const std::string& name , unsigned long long lockTimeout = 0, bool asProcess = false );
- ~DistributedLock(){};
-
- /**
- * Attempts to acquire 'this' lock, checking if it could or should be stolen from the previous holder. Please
- * consider using the dist_lock_try construct to acquire this lock in an exception safe way.
- *
- * @param why human readable description of why the lock is being taken (used to log)
- * @param other configdb's lock document that is currently holding the lock, if lock is taken, or our own lock
- * details if not
- * @return true if it managed to grab the lock
- */
- bool lock_try(const std::string& why, BSONObj* other = 0, double timeout = 0.0);
-
- /**
- * Returns OK if this lock is held (but does not guarantee that this owns it) and
- * it was possible to confirm that, within 'timeout' seconds, if provided, with the
- * config servers.
- */
- Status checkStatus(double timeout);
-
- /**
- * Releases a previously taken lock. Returns true on success.
- */
- bool unlock(const OID& lockID);
-
- Date_t getRemoteTime() const;
-
- bool isRemoteTimeSkewed() const;
-
- const std::string& getProcessId() const;
-
- const ConnectionString& getRemoteConnection() const;
-
- /**
- * Checks the skew among a cluster of servers and returns true if the min and max clock
- * times among the servers are within maxClockSkew.
- */
- static bool checkSkew( const ConnectionString& cluster,
- unsigned skewChecks = NUM_LOCK_SKEW_CHECKS,
- unsigned long long maxClockSkew = MAX_LOCK_CLOCK_SKEW,
- unsigned long long maxNetSkew = MAX_LOCK_NET_SKEW );
-
- /**
- * Get the remote time from a server or cluster
- */
- static Date_t remoteTime( const ConnectionString& cluster, unsigned long long maxNetSkew = MAX_LOCK_NET_SKEW );
-
- /**
- * Namespace for lock pings
- */
- static const std::string lockPingNS;
-
- /**
- * Namespace for locks
- */
- static const std::string locksNS;
-
- const ConnectionString _conn;
- const std::string _name;
- const std::string _processId;
-
- // Timeout for lock, usually LOCK_TIMEOUT
- const unsigned long long _lockTimeout;
- const unsigned long long _maxClockSkew;
- const unsigned long long _maxNetSkew;
- const unsigned long long _lockPing;
-
- private:
-
- void resetLastPing() {
- lastPings.setLastPing(_conn, _name, DistLockPingInfo());
- }
-
- void setLastPing(const DistLockPingInfo& pd) {
- lastPings.setLastPing(_conn, _name, pd);
- }
-
- DistLockPingInfo getLastPing() {
- return lastPings.getLastPing(_conn, _name);
- }
- };
+ /**
+ * Returns OK if this lock is held (but does not guarantee that this owns it) and
+ * it was possible to confirm that, within 'timeout' seconds, if provided, with the
+ * config servers.
+ */
+ Status checkStatus(double timeout);
-}
+ /**
+ * Releases a previously taken lock. Returns true on success.
+ */
+ bool unlock(const OID& lockID);
+
+ Date_t getRemoteTime() const;
+
+ bool isRemoteTimeSkewed() const;
+
+ const std::string& getProcessId() const;
+
+ const ConnectionString& getRemoteConnection() const;
+
+ /**
+ * Checks the skew among a cluster of servers and returns true if the min and max clock
+ * times among the servers are within maxClockSkew.
+ */
+ static bool checkSkew(const ConnectionString& cluster,
+ unsigned skewChecks = NUM_LOCK_SKEW_CHECKS,
+ unsigned long long maxClockSkew = MAX_LOCK_CLOCK_SKEW,
+ unsigned long long maxNetSkew = MAX_LOCK_NET_SKEW);
+
+ /**
+ * Get the remote time from a server or cluster
+ */
+ static Date_t remoteTime(const ConnectionString& cluster,
+ unsigned long long maxNetSkew = MAX_LOCK_NET_SKEW);
+
+ /**
+ * Namespace for lock pings
+ */
+ static const std::string lockPingNS;
+
+ /**
+ * Namespace for locks
+ */
+ static const std::string locksNS;
+
+ const ConnectionString _conn;
+ const std::string _name;
+ const std::string _processId;
+
+ // Timeout for lock, usually LOCK_TIMEOUT
+ const unsigned long long _lockTimeout;
+ const unsigned long long _maxClockSkew;
+ const unsigned long long _maxNetSkew;
+ const unsigned long long _lockPing;
+private:
+ void resetLastPing() {
+ lastPings.setLastPing(_conn, _name, DistLockPingInfo());
+ }
+
+ void setLastPing(const DistLockPingInfo& pd) {
+ lastPings.setLastPing(_conn, _name, pd);
+ }
+
+ DistLockPingInfo getLastPing() {
+ return lastPings.getLastPing(_conn, _name);
+ }
+};
+}
diff --git a/src/mongo/s/catalog/legacy/legacy_dist_lock_manager.cpp b/src/mongo/s/catalog/legacy/legacy_dist_lock_manager.cpp
index 18cc7d537cf..e375c792f28 100644
--- a/src/mongo/s/catalog/legacy/legacy_dist_lock_manager.cpp
+++ b/src/mongo/s/catalog/legacy/legacy_dist_lock_manager.cpp
@@ -39,187 +39,176 @@
namespace mongo {
- using std::string;
- using std::unique_ptr;
- using stdx::chrono::milliseconds;
+using std::string;
+using std::unique_ptr;
+using stdx::chrono::milliseconds;
namespace {
- const stdx::chrono::seconds kDefaultSocketTimeout(30);
- const milliseconds kDefaultPingInterval(30 * 1000);
-} // unnamed namespace
-
- LegacyDistLockManager::LegacyDistLockManager(ConnectionString configServer):
- _configServer(std::move(configServer)),
- _isStopped(false),
- _pingerEnabled(true) {
- }
+const stdx::chrono::seconds kDefaultSocketTimeout(30);
+const milliseconds kDefaultPingInterval(30 * 1000);
+} // unnamed namespace
- void LegacyDistLockManager::startUp() {
- stdx::lock_guard<stdx::mutex> sl(_mutex);
- invariant(!_pinger);
- _pinger = stdx::make_unique<LegacyDistLockPinger>();
- }
+LegacyDistLockManager::LegacyDistLockManager(ConnectionString configServer)
+ : _configServer(std::move(configServer)), _isStopped(false), _pingerEnabled(true) {}
- void LegacyDistLockManager::shutDown() {
- stdx::unique_lock<stdx::mutex> sl(_mutex);
- _isStopped = true;
+void LegacyDistLockManager::startUp() {
+ stdx::lock_guard<stdx::mutex> sl(_mutex);
+ invariant(!_pinger);
+ _pinger = stdx::make_unique<LegacyDistLockPinger>();
+}
- while (!_lockMap.empty()) {
- _noLocksCV.wait(sl);
- }
+void LegacyDistLockManager::shutDown() {
+ stdx::unique_lock<stdx::mutex> sl(_mutex);
+ _isStopped = true;
- if (_pinger) {
- _pinger->shutdown();
- }
+ while (!_lockMap.empty()) {
+ _noLocksCV.wait(sl);
}
- StatusWith<DistLockManager::ScopedDistLock> LegacyDistLockManager::lock(
- StringData name,
- StringData whyMessage,
- milliseconds waitFor,
- milliseconds lockTryInterval) {
-
- auto distLock = stdx::make_unique<DistributedLock>(_configServer, name.toString());
+ if (_pinger) {
+ _pinger->shutdown();
+ }
+}
- {
- stdx::lock_guard<stdx::mutex> sl(_mutex);
+StatusWith<DistLockManager::ScopedDistLock> LegacyDistLockManager::lock(
+ StringData name, StringData whyMessage, milliseconds waitFor, milliseconds lockTryInterval) {
+ auto distLock = stdx::make_unique<DistributedLock>(_configServer, name.toString());
- if (_isStopped) {
- return Status(ErrorCodes::LockBusy, "legacy distlock manager is stopped");
- }
+ {
+ stdx::lock_guard<stdx::mutex> sl(_mutex);
- if (_pingerEnabled) {
- auto pingStatus = _pinger->startPing(*(distLock.get()), kDefaultPingInterval);
- if (!pingStatus.isOK()) {
- return pingStatus;
- }
- }
+ if (_isStopped) {
+ return Status(ErrorCodes::LockBusy, "legacy distlock manager is stopped");
}
- auto lastStatus = Status(ErrorCodes::LockBusy,
- str::stream() << "timed out waiting for " << name);
-
- Timer timer;
- Timer msgTimer;
- while (waitFor <= milliseconds::zero() || milliseconds(timer.millis()) < waitFor) {
- bool acquired = false;
- BSONObj lockDoc;
- try {
- acquired = distLock->lock_try(whyMessage.toString(),
- &lockDoc,
- kDefaultSocketTimeout.count());
-
- if (!acquired) {
- lastStatus = Status(ErrorCodes::LockBusy,
- str::stream() << "Lock for " << whyMessage
- << " is taken.");
- }
+ if (_pingerEnabled) {
+ auto pingStatus = _pinger->startPing(*(distLock.get()), kDefaultPingInterval);
+ if (!pingStatus.isOK()) {
+ return pingStatus;
}
- catch (const LockException& lockExcep) {
- OID needUnlockID(lockExcep.getMustUnlockID());
- if (needUnlockID.isSet()) {
- _pinger->addUnlockOID(needUnlockID);
- }
+ }
+ }
- lastStatus = lockExcep.toStatus();
+ auto lastStatus =
+ Status(ErrorCodes::LockBusy, str::stream() << "timed out waiting for " << name);
+
+ Timer timer;
+ Timer msgTimer;
+ while (waitFor <= milliseconds::zero() || milliseconds(timer.millis()) < waitFor) {
+ bool acquired = false;
+ BSONObj lockDoc;
+ try {
+ acquired =
+ distLock->lock_try(whyMessage.toString(), &lockDoc, kDefaultSocketTimeout.count());
+
+ if (!acquired) {
+ lastStatus = Status(ErrorCodes::LockBusy,
+ str::stream() << "Lock for " << whyMessage << " is taken.");
}
- catch (...) {
- lastStatus = exceptionToStatus();
+ } catch (const LockException& lockExcep) {
+ OID needUnlockID(lockExcep.getMustUnlockID());
+ if (needUnlockID.isSet()) {
+ _pinger->addUnlockOID(needUnlockID);
}
- if (acquired) {
- verify(!lockDoc.isEmpty());
+ lastStatus = lockExcep.toStatus();
+ } catch (...) {
+ lastStatus = exceptionToStatus();
+ }
- LocksType lock;
- string errMsg;
- if (!lock.parseBSON(lockDoc, &errMsg)) {
- return StatusWith<ScopedDistLock>(
- ErrorCodes::UnsupportedFormat,
- str::stream() << "error while parsing lock document: " << errMsg);
- }
+ if (acquired) {
+ verify(!lockDoc.isEmpty());
- dassert(lock.isLockIDSet());
+ LocksType lock;
+ string errMsg;
+ if (!lock.parseBSON(lockDoc, &errMsg)) {
+ return StatusWith<ScopedDistLock>(
+ ErrorCodes::UnsupportedFormat,
+ str::stream() << "error while parsing lock document: " << errMsg);
+ }
- {
- stdx::lock_guard<stdx::mutex> sl(_mutex);
- _lockMap.insert(std::make_pair(lock.getLockID(), std::move(distLock)));
- }
+ dassert(lock.isLockIDSet());
- return ScopedDistLock(lock.getLockID(), this);
+ {
+ stdx::lock_guard<stdx::mutex> sl(_mutex);
+ _lockMap.insert(std::make_pair(lock.getLockID(), std::move(distLock)));
}
- if (waitFor == milliseconds::zero()) break;
+ return ScopedDistLock(lock.getLockID(), this);
+ }
- if (lastStatus != ErrorCodes::LockBusy) {
- return lastStatus;
- }
+ if (waitFor == milliseconds::zero())
+ break;
- // Periodically message for debugging reasons
- if (msgTimer.seconds() > 10) {
- log() << "waited " << timer.seconds() << "s for distributed lock " << name
- << " for " << whyMessage << ": " << lastStatus.toString();
+ if (lastStatus != ErrorCodes::LockBusy) {
+ return lastStatus;
+ }
- msgTimer.reset();
- }
+ // Periodically message for debugging reasons
+ if (msgTimer.seconds() > 10) {
+ log() << "waited " << timer.seconds() << "s for distributed lock " << name << " for "
+ << whyMessage << ": " << lastStatus.toString();
- milliseconds timeRemaining =
- std::max(milliseconds::zero(), waitFor - milliseconds(timer.millis()));
- sleepFor(std::min(lockTryInterval, timeRemaining));
+ msgTimer.reset();
}
- return lastStatus;
+ milliseconds timeRemaining =
+ std::max(milliseconds::zero(), waitFor - milliseconds(timer.millis()));
+ sleepFor(std::min(lockTryInterval, timeRemaining));
}
- void LegacyDistLockManager::unlock(const DistLockHandle& lockHandle) BOOST_NOEXCEPT {
- unique_ptr<DistributedLock> distLock;
+ return lastStatus;
+}
- {
- stdx::lock_guard<stdx::mutex> sl(_mutex);
- auto iter = _lockMap.find(lockHandle);
- invariant(iter != _lockMap.end());
+void LegacyDistLockManager::unlock(const DistLockHandle& lockHandle) BOOST_NOEXCEPT {
+ unique_ptr<DistributedLock> distLock;
- distLock = std::move(iter->second);
- _lockMap.erase(iter);
- }
+ {
+ stdx::lock_guard<stdx::mutex> sl(_mutex);
+ auto iter = _lockMap.find(lockHandle);
+ invariant(iter != _lockMap.end());
- if (!distLock->unlock(lockHandle)) {
- _pinger->addUnlockOID(lockHandle);
- }
+ distLock = std::move(iter->second);
+ _lockMap.erase(iter);
+ }
- {
- stdx::lock_guard<stdx::mutex> sl(_mutex);
- if (_lockMap.empty()) {
- _noLocksCV.notify_all();
- }
- }
+ if (!distLock->unlock(lockHandle)) {
+ _pinger->addUnlockOID(lockHandle);
}
- Status LegacyDistLockManager::checkStatus(const DistLockHandle& lockHandle) {
- // Note: this should not happen when locks are managed through ScopedDistLock.
- if (_pinger->willUnlockOID(lockHandle)) {
- return Status(ErrorCodes::LockFailed,
- str::stream() << "lock " << lockHandle << " is not held and "
- << "is currently being scheduled for lazy unlock");
+ {
+ stdx::lock_guard<stdx::mutex> sl(_mutex);
+ if (_lockMap.empty()) {
+ _noLocksCV.notify_all();
}
+ }
+}
- DistributedLock* distLock = nullptr;
+Status LegacyDistLockManager::checkStatus(const DistLockHandle& lockHandle) {
+ // Note: this should not happen when locks are managed through ScopedDistLock.
+ if (_pinger->willUnlockOID(lockHandle)) {
+ return Status(ErrorCodes::LockFailed,
+ str::stream() << "lock " << lockHandle << " is not held and "
+ << "is currently being scheduled for lazy unlock");
+ }
- {
- // Assumption: lockHandles are never shared across threads.
- stdx::lock_guard<stdx::mutex> sl(_mutex);
- auto iter = _lockMap.find(lockHandle);
- invariant(iter != _lockMap.end());
+ DistributedLock* distLock = nullptr;
- distLock = iter->second.get();
- }
+ {
+ // Assumption: lockHandles are never shared across threads.
+ stdx::lock_guard<stdx::mutex> sl(_mutex);
+ auto iter = _lockMap.find(lockHandle);
+ invariant(iter != _lockMap.end());
- return distLock->checkStatus(kDefaultSocketTimeout.count());
+ distLock = iter->second.get();
}
- void LegacyDistLockManager::enablePinger(bool enable) {
- stdx::lock_guard<stdx::mutex> sl(_mutex);
- _pingerEnabled = enable;
- }
+ return distLock->checkStatus(kDefaultSocketTimeout.count());
+}
+void LegacyDistLockManager::enablePinger(bool enable) {
+ stdx::lock_guard<stdx::mutex> sl(_mutex);
+ _pingerEnabled = enable;
+}
}
diff --git a/src/mongo/s/catalog/legacy/legacy_dist_lock_manager.h b/src/mongo/s/catalog/legacy/legacy_dist_lock_manager.h
index 50a6088c333..238989a3cdb 100644
--- a/src/mongo/s/catalog/legacy/legacy_dist_lock_manager.h
+++ b/src/mongo/s/catalog/legacy/legacy_dist_lock_manager.h
@@ -41,46 +41,43 @@
namespace mongo {
- class DistributedLock;
+class DistributedLock;
- class LegacyDistLockManager: public DistLockManager {
- public:
- explicit LegacyDistLockManager(ConnectionString configServer);
+class LegacyDistLockManager : public DistLockManager {
+public:
+ explicit LegacyDistLockManager(ConnectionString configServer);
- virtual ~LegacyDistLockManager() = default;
+ virtual ~LegacyDistLockManager() = default;
- virtual void startUp() override;
- virtual void shutDown() override;
+ virtual void startUp() override;
+ virtual void shutDown() override;
- virtual StatusWith<DistLockManager::ScopedDistLock> lock(
- StringData name,
- StringData whyMessage,
- stdx::chrono::milliseconds waitFor,
- stdx::chrono::milliseconds lockTryInterval) override;
+ virtual StatusWith<DistLockManager::ScopedDistLock> lock(
+ StringData name,
+ StringData whyMessage,
+ stdx::chrono::milliseconds waitFor,
+ stdx::chrono::milliseconds lockTryInterval) override;
- // For testing only.
- void enablePinger(bool enable);
+ // For testing only.
+ void enablePinger(bool enable);
- protected:
+protected:
+ virtual void unlock(const DistLockHandle& lockHandle) BOOST_NOEXCEPT override;
- virtual void unlock(const DistLockHandle& lockHandle) BOOST_NOEXCEPT override;
+ virtual Status checkStatus(const DistLockHandle& lockHandle) override;
- virtual Status checkStatus(const DistLockHandle& lockHandle) override;
+private:
+ const ConnectionString _configServer;
- private:
+ stdx::mutex _mutex;
+ stdx::condition_variable _noLocksCV;
+ std::map<DistLockHandle, std::unique_ptr<DistributedLock>> _lockMap;
- const ConnectionString _configServer;
+ std::unique_ptr<LegacyDistLockPinger> _pinger;
- stdx::mutex _mutex;
- stdx::condition_variable _noLocksCV;
- std::map<DistLockHandle, std::unique_ptr<DistributedLock>> _lockMap;
-
- std::unique_ptr<LegacyDistLockPinger> _pinger;
-
- bool _isStopped;
-
- // For testing only.
- bool _pingerEnabled;
- };
+ bool _isStopped;
+ // For testing only.
+ bool _pingerEnabled;
+};
}
diff --git a/src/mongo/s/catalog/legacy/legacy_dist_lock_pinger.cpp b/src/mongo/s/catalog/legacy/legacy_dist_lock_pinger.cpp
index 5e5f03b0864..ef5299fd558 100644
--- a/src/mongo/s/catalog/legacy/legacy_dist_lock_pinger.cpp
+++ b/src/mongo/s/catalog/legacy/legacy_dist_lock_pinger.cpp
@@ -42,318 +42,304 @@
namespace mongo {
- using std::string;
+using std::string;
namespace {
- string pingThreadId(const ConnectionString& conn, const string& processId) {
- return conn.toString() + "/" + processId;
- }
+string pingThreadId(const ConnectionString& conn, const string& processId) {
+ return conn.toString() + "/" + processId;
+}
}
- void LegacyDistLockPinger::_distLockPingThread(ConnectionString addr,
- const string& process,
- Milliseconds sleepTime) {
- setThreadName("LockPinger");
-
- string pingId = pingThreadId(addr, process);
-
- LOG(0) << "creating distributed lock ping thread for " << addr
- << " and process " << process << " (sleeping for " << sleepTime.count() << "ms)";
-
- static int loops = 0;
- Date_t lastPingTime = jsTime();
- while (!shouldStopPinging(addr, process)) {
+void LegacyDistLockPinger::_distLockPingThread(ConnectionString addr,
+ const string& process,
+ Milliseconds sleepTime) {
+ setThreadName("LockPinger");
- LOG(3) << "distributed lock pinger '" << pingId << "' about to ping.";
+ string pingId = pingThreadId(addr, process);
- Date_t pingTime;
+ LOG(0) << "creating distributed lock ping thread for " << addr << " and process " << process
+ << " (sleeping for " << sleepTime.count() << "ms)";
- try {
- ScopedDbConnection conn(addr.toString(), 30.0);
+ static int loops = 0;
+ Date_t lastPingTime = jsTime();
+ while (!shouldStopPinging(addr, process)) {
+ LOG(3) << "distributed lock pinger '" << pingId << "' about to ping.";
- pingTime = jsTime();
- const auto elapsed = pingTime - lastPingTime;
- if (elapsed > 10 * sleepTime) {
- warning() << "Lock pinger for addr: " << addr
- << ", proc: " << process
- << " was inactive for " << elapsed;
- }
+ Date_t pingTime;
- lastPingTime = pingTime;
+ try {
+ ScopedDbConnection conn(addr.toString(), 30.0);
- // Refresh the entry corresponding to this process in the lockpings collection.
- conn->update(LockpingsType::ConfigNS,
- BSON(LockpingsType::process(process)),
- BSON("$set" << BSON(LockpingsType::ping(pingTime))),
- true);
+ pingTime = jsTime();
+ const auto elapsed = pingTime - lastPingTime;
+ if (elapsed > 10 * sleepTime) {
+ warning() << "Lock pinger for addr: " << addr << ", proc: " << process
+ << " was inactive for " << elapsed;
+ }
- string err = conn->getLastError();
- if (!err.empty()) {
- warning() << "pinging failed for distributed lock pinger '" << pingId << "'."
- << causedBy(err);
- conn.done();
+ lastPingTime = pingTime;
- if (!shouldStopPinging(addr, process)) {
- waitTillNextPingTime(sleepTime);
- }
- continue;
- }
+ // Refresh the entry corresponding to this process in the lockpings collection.
+ conn->update(LockpingsType::ConfigNS,
+ BSON(LockpingsType::process(process)),
+ BSON("$set" << BSON(LockpingsType::ping(pingTime))),
+ true);
- // Remove really old entries from the lockpings collection if they're not
- // holding a lock. This may happen if an instance of a process was taken down
- // and no new instance came up to replace it for a quite a while.
- // NOTE this is NOT the same as the standard take-over mechanism, which forces
- // the lock entry.
- BSONObj fieldsToReturn = BSON(LocksType::state() << 1
- << LocksType::process() << 1);
- auto activeLocks =
- conn->query(LocksType::ConfigNS,
- BSON(LocksType::state() << NE << LocksType::UNLOCKED));
-
- uassert(16060,
- str::stream() << "cannot query locks collection on config server "
- << conn.getHost(),
- activeLocks.get());
-
- std::set<string> pids;
- while (activeLocks->more()) {
- BSONObj lock = activeLocks->nextSafe();
-
- if (!lock[LocksType::process()].eoo()) {
- pids.insert(lock[LocksType::process()].str());
- }
- else {
- warning() << "found incorrect lock document during lock ping cleanup: "
- << lock.toString();
- }
- }
+ string err = conn->getLastError();
+ if (!err.empty()) {
+ warning() << "pinging failed for distributed lock pinger '" << pingId << "'."
+ << causedBy(err);
+ conn.done();
- // This can potentially delete ping entries that are actually active (if the clock
- // of another pinger is too skewed). This is still fine as the lock logic only
- // checks if there is a change in the ping document and the document going away
- // is a valid change.
- Date_t fourDays = pingTime - stdx::chrono::hours{4 * 24};
- conn->remove(LockpingsType::ConfigNS,
- BSON(LockpingsType::process() << NIN << pids
- << LockpingsType::ping() << LT << fourDays));
- err = conn->getLastError();
-
- if (!err.empty()) {
- warning() << "ping cleanup for distributed lock pinger '" << pingId
- << " failed." << causedBy(err);
- conn.done();
-
- if (!shouldStopPinging(addr, process)) {
- waitTillNextPingTime(sleepTime);
- }
- continue;
+ if (!shouldStopPinging(addr, process)) {
+ waitTillNextPingTime(sleepTime);
}
+ continue;
+ }
- LOG(1 - (loops % 10 == 0 ? 1 : 0)) << "cluster " << addr
- << " pinged successfully at " << pingTime
- << " by distributed lock pinger '" << pingId
- << "', sleeping for " << sleepTime.count() << "ms";
-
- // Remove old locks, if possible
- // Make sure no one else is adding to this list at the same time
- stdx::lock_guard<stdx::mutex> lk(_mutex);
-
- int numOldLocks = _unlockList.size();
- if (numOldLocks > 0) {
- LOG(0) << "trying to delete " << _unlockList.size()
- << " old lock entries for process " << process;
+ // Remove really old entries from the lockpings collection if they're not
+ // holding a lock. This may happen if an instance of a process was taken down
+ // and no new instance came up to replace it for a quite a while.
+ // NOTE this is NOT the same as the standard take-over mechanism, which forces
+ // the lock entry.
+ BSONObj fieldsToReturn = BSON(LocksType::state() << 1 << LocksType::process() << 1);
+ auto activeLocks = conn->query(LocksType::ConfigNS,
+ BSON(LocksType::state() << NE << LocksType::UNLOCKED));
+
+ uassert(16060,
+ str::stream() << "cannot query locks collection on config server "
+ << conn.getHost(),
+ activeLocks.get());
+
+ std::set<string> pids;
+ while (activeLocks->more()) {
+ BSONObj lock = activeLocks->nextSafe();
+
+ if (!lock[LocksType::process()].eoo()) {
+ pids.insert(lock[LocksType::process()].str());
+ } else {
+ warning() << "found incorrect lock document during lock ping cleanup: "
+ << lock.toString();
}
+ }
- bool removed = false;
- for (auto iter = _unlockList.begin(); iter != _unlockList.end();
- iter = (removed ? _unlockList.erase(iter) : ++iter)) {
- removed = false;
- try {
- // Got DistLockHandle from lock, so we don't need to specify _id again
- conn->update(LocksType::ConfigNS,
- BSON(LocksType::lockID(*iter)),
- BSON("$set" << BSON( LocksType::state(LocksType::UNLOCKED))));
-
- // Either the update went through or it didn't,
- // either way we're done trying to unlock.
- LOG(0) << "handled late remove of old distributed lock with ts " << *iter;
- removed = true;
- }
- catch (UpdateNotTheSame&) {
- LOG(0) << "partially removed old distributed lock with ts " << *iter;
- removed = true;
- }
- catch (std::exception& e) {
- warning() << "could not remove old distributed lock with ts " << *iter
- << causedBy(e);
- }
+ // This can potentially delete ping entries that are actually active (if the clock
+ // of another pinger is too skewed). This is still fine as the lock logic only
+ // checks if there is a change in the ping document and the document going away
+ // is a valid change.
+ Date_t fourDays = pingTime - stdx::chrono::hours{4 * 24};
+ conn->remove(LockpingsType::ConfigNS,
+ BSON(LockpingsType::process() << NIN << pids << LockpingsType::ping() << LT
+ << fourDays));
+ err = conn->getLastError();
+
+ if (!err.empty()) {
+ warning() << "ping cleanup for distributed lock pinger '" << pingId << " failed."
+ << causedBy(err);
+ conn.done();
+ if (!shouldStopPinging(addr, process)) {
+ waitTillNextPingTime(sleepTime);
}
+ continue;
+ }
- if (numOldLocks > 0 && _unlockList.size() > 0) {
- LOG(0) << "not all old lock entries could be removed for process " << process;
- }
+ LOG(1 - (loops % 10 == 0 ? 1 : 0)) << "cluster " << addr << " pinged successfully at "
+ << pingTime << " by distributed lock pinger '"
+ << pingId << "', sleeping for " << sleepTime.count()
+ << "ms";
- conn.done();
+ // Remove old locks, if possible
+ // Make sure no one else is adding to this list at the same time
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ int numOldLocks = _unlockList.size();
+ if (numOldLocks > 0) {
+ LOG(0) << "trying to delete " << _unlockList.size()
+ << " old lock entries for process " << process;
}
- catch (std::exception& e) {
- warning() << "distributed lock pinger '" << pingId
- << "' detected an exception while pinging." << causedBy(e);
+
+ bool removed = false;
+ for (auto iter = _unlockList.begin(); iter != _unlockList.end();
+ iter = (removed ? _unlockList.erase(iter) : ++iter)) {
+ removed = false;
+ try {
+ // Got DistLockHandle from lock, so we don't need to specify _id again
+ conn->update(LocksType::ConfigNS,
+ BSON(LocksType::lockID(*iter)),
+ BSON("$set" << BSON(LocksType::state(LocksType::UNLOCKED))));
+
+ // Either the update went through or it didn't,
+ // either way we're done trying to unlock.
+ LOG(0) << "handled late remove of old distributed lock with ts " << *iter;
+ removed = true;
+ } catch (UpdateNotTheSame&) {
+ LOG(0) << "partially removed old distributed lock with ts " << *iter;
+ removed = true;
+ } catch (std::exception& e) {
+ warning() << "could not remove old distributed lock with ts " << *iter
+ << causedBy(e);
+ }
}
- if (!shouldStopPinging(addr, process)) {
- waitTillNextPingTime(sleepTime);
+ if (numOldLocks > 0 && _unlockList.size() > 0) {
+ LOG(0) << "not all old lock entries could be removed for process " << process;
}
- }
- warning() << "removing distributed lock ping thread '" << pingId << "'";
+ conn.done();
- if (shouldStopPinging(addr, process)) {
- acknowledgeStopPing(addr, process);
+ } catch (std::exception& e) {
+ warning() << "distributed lock pinger '" << pingId
+ << "' detected an exception while pinging." << causedBy(e);
}
- }
- void LegacyDistLockPinger::distLockPingThread(ConnectionString addr,
- long long clockSkew,
- const std::string& processId,
- stdx::chrono::milliseconds sleepTime) {
- try {
- jsTimeVirtualThreadSkew(clockSkew);
- _distLockPingThread(addr, processId, sleepTime);
- }
- catch (std::exception& e) {
- error() << "unexpected error while running distributed lock pinger for " << addr
- << ", process " << processId << causedBy(e);
- }
- catch (...) {
- error() << "unknown error while running distributed lock pinger for " << addr
- << ", process " << processId;
+ if (!shouldStopPinging(addr, process)) {
+ waitTillNextPingTime(sleepTime);
}
}
- Status LegacyDistLockPinger::startPing(const DistributedLock& lock,
- stdx::chrono::milliseconds sleepTime) {
- const ConnectionString& conn = lock.getRemoteConnection();
- const string& processId = lock.getProcessId();
- string pingID = pingThreadId(conn, processId);
+ warning() << "removing distributed lock ping thread '" << pingId << "'";
- {
- // Make sure we don't start multiple threads for a process id.
- stdx::lock_guard<stdx::mutex> lk(_mutex);
+ if (shouldStopPinging(addr, process)) {
+ acknowledgeStopPing(addr, process);
+ }
+}
- if (_inShutdown) {
- return Status(ErrorCodes::ShutdownInProgress,
- "shutting down, will not start ping");
- }
+void LegacyDistLockPinger::distLockPingThread(ConnectionString addr,
+ long long clockSkew,
+ const std::string& processId,
+ stdx::chrono::milliseconds sleepTime) {
+ try {
+ jsTimeVirtualThreadSkew(clockSkew);
+ _distLockPingThread(addr, processId, sleepTime);
+ } catch (std::exception& e) {
+ error() << "unexpected error while running distributed lock pinger for " << addr
+ << ", process " << processId << causedBy(e);
+ } catch (...) {
+ error() << "unknown error while running distributed lock pinger for " << addr
+ << ", process " << processId;
+ }
+}
- // Ignore if we already have a pinging thread for this process.
- if (_seen.count(pingID) > 0) {
- return Status::OK();
- }
+Status LegacyDistLockPinger::startPing(const DistributedLock& lock,
+ stdx::chrono::milliseconds sleepTime) {
+ const ConnectionString& conn = lock.getRemoteConnection();
+ const string& processId = lock.getProcessId();
+ string pingID = pingThreadId(conn, processId);
- // Check the config server clock skew.
- if (lock.isRemoteTimeSkewed()) {
- return Status(ErrorCodes::DistributedClockSkewed,
- str::stream() << "clock skew of the cluster " << conn.toString()
- << " is too far out of bounds "
- << "to allow distributed locking.");
- }
+ {
+ // Make sure we don't start multiple threads for a process id.
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+
+ if (_inShutdown) {
+ return Status(ErrorCodes::ShutdownInProgress, "shutting down, will not start ping");
}
- {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- stdx::thread thread(stdx::bind(&LegacyDistLockPinger::distLockPingThread,
- this,
- conn,
- getJSTimeVirtualThreadSkew(),
- processId,
- sleepTime));
- _pingThreads.insert(std::make_pair(pingID, std::move(thread)));
-
- _seen.insert(pingID);
+ // Ignore if we already have a pinging thread for this process.
+ if (_seen.count(pingID) > 0) {
+ return Status::OK();
}
- return Status::OK();
+ // Check the config server clock skew.
+ if (lock.isRemoteTimeSkewed()) {
+ return Status(ErrorCodes::DistributedClockSkewed,
+ str::stream() << "clock skew of the cluster " << conn.toString()
+ << " is too far out of bounds "
+ << "to allow distributed locking.");
+ }
}
- void LegacyDistLockPinger::addUnlockOID(const DistLockHandle& lockID) {
- // Modifying the lock from some other thread
+ {
stdx::lock_guard<stdx::mutex> lk(_mutex);
- _unlockList.push_back(lockID);
+ stdx::thread thread(stdx::bind(&LegacyDistLockPinger::distLockPingThread,
+ this,
+ conn,
+ getJSTimeVirtualThreadSkew(),
+ processId,
+ sleepTime));
+ _pingThreads.insert(std::make_pair(pingID, std::move(thread)));
+
+ _seen.insert(pingID);
}
- bool LegacyDistLockPinger::willUnlockOID(const DistLockHandle& lockID) {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- return find(_unlockList.begin(), _unlockList.end(), lockID) != _unlockList.end();
- }
+ return Status::OK();
+}
- void LegacyDistLockPinger::stopPing(const ConnectionString& conn, const string& processId) {
- {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
+void LegacyDistLockPinger::addUnlockOID(const DistLockHandle& lockID) {
+ // Modifying the lock from some other thread
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _unlockList.push_back(lockID);
+}
- string pingId = pingThreadId(conn, processId);
+bool LegacyDistLockPinger::willUnlockOID(const DistLockHandle& lockID) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ return find(_unlockList.begin(), _unlockList.end(), lockID) != _unlockList.end();
+}
- verify(_seen.count(pingId) > 0);
- _kill.insert(pingId);
- _pingStoppedCV.notify_all();
- }
- }
+void LegacyDistLockPinger::stopPing(const ConnectionString& conn, const string& processId) {
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
- void LegacyDistLockPinger::shutdown() {
- {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- _inShutdown = true;
- _pingStoppedCV.notify_all();
- }
+ string pingId = pingThreadId(conn, processId);
- // Don't grab _mutex, otherwise will deadlock trying to join. Safe to read
- // _pingThreads since it cannot be modified once _shutdown is true.
- for (auto& idToThread : _pingThreads) {
- if (idToThread.second.joinable()) {
- idToThread.second.join();
- }
- }
+ verify(_seen.count(pingId) > 0);
+ _kill.insert(pingId);
+ _pingStoppedCV.notify_all();
}
+}
- bool LegacyDistLockPinger::shouldStopPinging(const ConnectionString& conn,
- const string& processId) {
- if (inShutdown()) {
- return true;
- }
-
+void LegacyDistLockPinger::shutdown() {
+ {
stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _inShutdown = true;
+ _pingStoppedCV.notify_all();
+ }
- if (_inShutdown) {
- return true;
+ // Don't grab _mutex, otherwise will deadlock trying to join. Safe to read
+ // _pingThreads since it cannot be modified once _shutdown is true.
+ for (auto& idToThread : _pingThreads) {
+ if (idToThread.second.joinable()) {
+ idToThread.second.join();
}
+ }
+}
- return _kill.count(pingThreadId(conn, processId)) > 0;
+bool LegacyDistLockPinger::shouldStopPinging(const ConnectionString& conn,
+ const string& processId) {
+ if (inShutdown()) {
+ return true;
}
- void LegacyDistLockPinger::acknowledgeStopPing(const ConnectionString& addr,
- const string& processId) {
- {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
- string pingId = pingThreadId(addr, processId);
+ if (_inShutdown) {
+ return true;
+ }
- _kill.erase(pingId);
- _seen.erase(pingId);
- }
+ return _kill.count(pingThreadId(conn, processId)) > 0;
+}
- try {
- ScopedDbConnection conn(addr.toString(), 30.0);
- conn->remove(LockpingsType::ConfigNS, BSON(LockpingsType::process(processId)));
- }
- catch (const DBException& ex) {
- warning() << "Error encountered while stopping ping on " << processId
- << causedBy(ex);
- }
+void LegacyDistLockPinger::acknowledgeStopPing(const ConnectionString& addr,
+ const string& processId) {
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+
+ string pingId = pingThreadId(addr, processId);
+
+ _kill.erase(pingId);
+ _seen.erase(pingId);
}
- void LegacyDistLockPinger::waitTillNextPingTime(stdx::chrono::milliseconds duration) {
- stdx::unique_lock<stdx::mutex> lk(_mutex);
- _pingStoppedCV.wait_for(lk, duration);
+ try {
+ ScopedDbConnection conn(addr.toString(), 30.0);
+ conn->remove(LockpingsType::ConfigNS, BSON(LockpingsType::process(processId)));
+ } catch (const DBException& ex) {
+ warning() << "Error encountered while stopping ping on " << processId << causedBy(ex);
}
}
+
+void LegacyDistLockPinger::waitTillNextPingTime(stdx::chrono::milliseconds duration) {
+ stdx::unique_lock<stdx::mutex> lk(_mutex);
+ _pingStoppedCV.wait_for(lk, duration);
+}
+}
diff --git a/src/mongo/s/catalog/legacy/legacy_dist_lock_pinger.h b/src/mongo/s/catalog/legacy/legacy_dist_lock_pinger.h
index 777cb39314b..41874a59d13 100644
--- a/src/mongo/s/catalog/legacy/legacy_dist_lock_pinger.h
+++ b/src/mongo/s/catalog/legacy/legacy_dist_lock_pinger.h
@@ -43,98 +43,97 @@
namespace mongo {
- class DistributedLock;
-
- class LegacyDistLockPinger {
- public:
- LegacyDistLockPinger() = default;
-
- /**
- * Starts pinging the process id for the given lock.
- */
- Status startPing(const DistributedLock& lock, Milliseconds sleepTime);
-
- /**
- * Adds a distributed lock that has the given id to the unlock list. The unlock list
- * contains the list of locks that this pinger will repeatedly attempt to unlock until
- * it succeeds.
- */
- void addUnlockOID(const DistLockHandle& lockID);
-
- /**
- * Returns true if the given lock id is currently in the unlock queue.
- */
- bool willUnlockOID(const DistLockHandle& lockID);
-
- /**
- * For testing only: non-blocking call to stop pinging the given process id.
- */
- void stopPing(const ConnectionString& conn, const std::string& processId);
-
- /**
- * Kills all ping threads and wait for them to cleanup.
- */
- void shutdown();
-
- private:
- /**
- * Helper method for calling _distLockPingThread.
- */
- void distLockPingThread(ConnectionString addr,
- long long clockSkew,
- const std::string& processId,
- Milliseconds sleepTime);
-
- /**
- * Function for repeatedly pinging the process id. Also attempts to unlock all the
- * locks in the unlock list.
- */
- void _distLockPingThread(ConnectionString addr,
- const std::string& process,
- Milliseconds sleepTime);
-
- /**
- * Returns true if a request has been made to stop pinging the give process id.
- */
- bool shouldStopPinging(const ConnectionString& conn, const std::string& processId);
-
- /**
- * Acknowledge the stop ping request and performs the necessary cleanup.
- */
- void acknowledgeStopPing(const ConnectionString& conn, const std::string& processId);
-
- /**
- * Blocks until duration has elapsed or if the ping thread is interrupted.
- */
- void waitTillNextPingTime(Milliseconds duration);
-
- //
- // All member variables are labeled with one of the following codes indicating the
- // synchronization rules for accessing them.
- //
- // (M) Must hold _mutex for access.
-
- stdx::mutex _mutex;
-
- // Triggered everytime a pinger thread is stopped.
- stdx::condition_variable _pingStoppedCV; // (M)
-
- // pingID -> thread
- // This can contain multiple elements in tests, but in tne normal case, this will
- // contain only a single element.
- // Note: can be safely read when _inShutdown is true.
- std::map<std::string, stdx::thread> _pingThreads; // (M*)
-
- // Contains the list of process id to stopPing.
- std::set<std::string> _kill; // (M)
-
- // Contains all of the process id to ping.
- std::set<std::string> _seen; // (M)
-
- // Contains all lock ids to keeping on retrying to unlock until success.
- std::list<DistLockHandle> _unlockList; // (M)
-
- bool _inShutdown = false; // (M)
- };
-
+class DistributedLock;
+
+class LegacyDistLockPinger {
+public:
+ LegacyDistLockPinger() = default;
+
+ /**
+ * Starts pinging the process id for the given lock.
+ */
+ Status startPing(const DistributedLock& lock, Milliseconds sleepTime);
+
+ /**
+ * Adds a distributed lock that has the given id to the unlock list. The unlock list
+ * contains the list of locks that this pinger will repeatedly attempt to unlock until
+ * it succeeds.
+ */
+ void addUnlockOID(const DistLockHandle& lockID);
+
+ /**
+ * Returns true if the given lock id is currently in the unlock queue.
+ */
+ bool willUnlockOID(const DistLockHandle& lockID);
+
+ /**
+ * For testing only: non-blocking call to stop pinging the given process id.
+ */
+ void stopPing(const ConnectionString& conn, const std::string& processId);
+
+ /**
+ * Kills all ping threads and wait for them to cleanup.
+ */
+ void shutdown();
+
+private:
+ /**
+ * Helper method for calling _distLockPingThread.
+ */
+ void distLockPingThread(ConnectionString addr,
+ long long clockSkew,
+ const std::string& processId,
+ Milliseconds sleepTime);
+
+ /**
+ * Function for repeatedly pinging the process id. Also attempts to unlock all the
+ * locks in the unlock list.
+ */
+ void _distLockPingThread(ConnectionString addr,
+ const std::string& process,
+ Milliseconds sleepTime);
+
+ /**
+ * Returns true if a request has been made to stop pinging the give process id.
+ */
+ bool shouldStopPinging(const ConnectionString& conn, const std::string& processId);
+
+ /**
+ * Acknowledge the stop ping request and performs the necessary cleanup.
+ */
+ void acknowledgeStopPing(const ConnectionString& conn, const std::string& processId);
+
+ /**
+ * Blocks until duration has elapsed or if the ping thread is interrupted.
+ */
+ void waitTillNextPingTime(Milliseconds duration);
+
+ //
+ // All member variables are labeled with one of the following codes indicating the
+ // synchronization rules for accessing them.
+ //
+ // (M) Must hold _mutex for access.
+
+ stdx::mutex _mutex;
+
+ // Triggered everytime a pinger thread is stopped.
+ stdx::condition_variable _pingStoppedCV; // (M)
+
+ // pingID -> thread
+ // This can contain multiple elements in tests, but in tne normal case, this will
+ // contain only a single element.
+ // Note: can be safely read when _inShutdown is true.
+ std::map<std::string, stdx::thread> _pingThreads; // (M*)
+
+ // Contains the list of process id to stopPing.
+ std::set<std::string> _kill; // (M)
+
+ // Contains all of the process id to ping.
+ std::set<std::string> _seen; // (M)
+
+ // Contains all lock ids to keeping on retrying to unlock until success.
+ std::list<DistLockHandle> _unlockList; // (M)
+
+ bool _inShutdown = false; // (M)
+};
}
diff --git a/src/mongo/s/catalog/replset/catalog_manager_replica_set.cpp b/src/mongo/s/catalog/replset/catalog_manager_replica_set.cpp
index 9cc35e05bcd..15c7913feb6 100644
--- a/src/mongo/s/catalog/replset/catalog_manager_replica_set.cpp
+++ b/src/mongo/s/catalog/replset/catalog_manager_replica_set.cpp
@@ -63,465 +63,449 @@
namespace mongo {
- using std::set;
- using std::string;
- using std::unique_ptr;
- using std::vector;
- using str::stream;
+using std::set;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using str::stream;
namespace {
- const Status notYetImplemented(ErrorCodes::InternalError, "Not yet implemented"); // todo remove
+const Status notYetImplemented(ErrorCodes::InternalError, "Not yet implemented"); // todo remove
- // Until read committed is supported always write to the primary with majoirty write and read
- // from the secondary. That way we ensure that reads will see a consistent data.
- const ReadPreferenceSetting kConfigWriteSelector(ReadPreference::PrimaryOnly, TagSet{});
- const ReadPreferenceSetting kConfigReadSelector(ReadPreference::SecondaryOnly, TagSet{});
+// Until read committed is supported always write to the primary with majoirty write and read
+// from the secondary. That way we ensure that reads will see a consistent data.
+const ReadPreferenceSetting kConfigWriteSelector(ReadPreference::PrimaryOnly, TagSet{});
+const ReadPreferenceSetting kConfigReadSelector(ReadPreference::SecondaryOnly, TagSet{});
- const int kNotMasterNumRetries = 3;
- const Milliseconds kNotMasterRetryInterval{500};
+const int kNotMasterNumRetries = 3;
+const Milliseconds kNotMasterRetryInterval{500};
- void _toBatchError(const Status& status, BatchedCommandResponse* response) {
- response->clear();
- response->setErrCode(status.code());
- response->setErrMessage(status.reason());
- response->setOk(false);
- }
-
-} // namespace
+void _toBatchError(const Status& status, BatchedCommandResponse* response) {
+ response->clear();
+ response->setErrCode(status.code());
+ response->setErrMessage(status.reason());
+ response->setOk(false);
+}
- CatalogManagerReplicaSet::CatalogManagerReplicaSet() = default;
+} // namespace
- CatalogManagerReplicaSet::~CatalogManagerReplicaSet() = default;
+CatalogManagerReplicaSet::CatalogManagerReplicaSet() = default;
- Status CatalogManagerReplicaSet::init(const ConnectionString& configCS,
- std::unique_ptr<DistLockManager> distLockManager) {
+CatalogManagerReplicaSet::~CatalogManagerReplicaSet() = default;
- invariant(configCS.type() == ConnectionString::SET);
+Status CatalogManagerReplicaSet::init(const ConnectionString& configCS,
+ std::unique_ptr<DistLockManager> distLockManager) {
+ invariant(configCS.type() == ConnectionString::SET);
- _configServerConnectionString = configCS;
- _distLockManager = std::move(distLockManager);
+ _configServerConnectionString = configCS;
+ _distLockManager = std::move(distLockManager);
- return Status::OK();
- }
-
- Status CatalogManagerReplicaSet::startup(bool upgrade) {
- return Status::OK();
- }
+ return Status::OK();
+}
- ConnectionString CatalogManagerReplicaSet::connectionString() const {
- return _configServerConnectionString;
- }
+Status CatalogManagerReplicaSet::startup(bool upgrade) {
+ return Status::OK();
+}
- void CatalogManagerReplicaSet::shutDown() {
- LOG(1) << "CatalogManagerReplicaSet::shutDown() called.";
- {
- std::lock_guard<std::mutex> lk(_mutex);
- _inShutdown = true;
- }
+ConnectionString CatalogManagerReplicaSet::connectionString() const {
+ return _configServerConnectionString;
+}
- invariant(_distLockManager);
- _distLockManager->shutDown();
+void CatalogManagerReplicaSet::shutDown() {
+ LOG(1) << "CatalogManagerReplicaSet::shutDown() called.";
+ {
+ std::lock_guard<std::mutex> lk(_mutex);
+ _inShutdown = true;
}
- Status CatalogManagerReplicaSet::enableSharding(const std::string& dbName) {
- return notYetImplemented;
+ invariant(_distLockManager);
+ _distLockManager->shutDown();
+}
+
+Status CatalogManagerReplicaSet::enableSharding(const std::string& dbName) {
+ return notYetImplemented;
+}
+
+Status CatalogManagerReplicaSet::shardCollection(const string& ns,
+ const ShardKeyPattern& fieldsAndOrder,
+ bool unique,
+ vector<BSONObj>* initPoints,
+ set<ShardId>* initShardsIds) {
+ return notYetImplemented;
+}
+
+Status CatalogManagerReplicaSet::createDatabase(const std::string& dbName) {
+ return notYetImplemented;
+}
+
+StatusWith<string> CatalogManagerReplicaSet::addShard(const string& name,
+ const ConnectionString& shardConnectionString,
+ const long long maxSize) {
+ return notYetImplemented;
+}
+
+StatusWith<ShardDrainingStatus> CatalogManagerReplicaSet::removeShard(OperationContext* txn,
+ const std::string& name) {
+ return notYetImplemented;
+}
+
+Status CatalogManagerReplicaSet::updateDatabase(const std::string& dbName, const DatabaseType& db) {
+ fassert(28684, db.validate());
+
+ return notYetImplemented;
+}
+
+StatusWith<DatabaseType> CatalogManagerReplicaSet::getDatabase(const std::string& dbName) {
+ invariant(nsIsDbOnly(dbName));
+
+ // The two databases that are hosted on the config server are config and admin
+ if (dbName == "config" || dbName == "admin") {
+ DatabaseType dbt;
+ dbt.setName(dbName);
+ dbt.setSharded(false);
+ dbt.setPrimary("config");
+
+ return dbt;
}
- Status CatalogManagerReplicaSet::shardCollection(const string& ns,
- const ShardKeyPattern& fieldsAndOrder,
- bool unique,
- vector<BSONObj>* initPoints,
- set<ShardId>* initShardsIds) {
- return notYetImplemented;
+ const auto configShard = grid.shardRegistry()->getShard("config");
+ const auto readHost = configShard->getTargeter()->findHost(kConfigReadSelector);
+ if (!readHost.isOK()) {
+ return readHost.getStatus();
}
- Status CatalogManagerReplicaSet::createDatabase(const std::string& dbName) {
- return notYetImplemented;
- }
+ auto findStatus = grid.shardRegistry()->exhaustiveFind(readHost.getValue(),
+ NamespaceString(DatabaseType::ConfigNS),
+ BSON(DatabaseType::name(dbName)),
+ 1);
- StatusWith<string> CatalogManagerReplicaSet::addShard(
- const string& name,
- const ConnectionString& shardConnectionString,
- const long long maxSize) {
- return notYetImplemented;
+ if (!findStatus.isOK()) {
+ return findStatus.getStatus();
}
- StatusWith<ShardDrainingStatus> CatalogManagerReplicaSet::removeShard(OperationContext* txn,
- const std::string& name) {
- return notYetImplemented;
+ const auto& docs = findStatus.getValue();
+ if (docs.empty()) {
+ return {ErrorCodes::NamespaceNotFound, stream() << "database " << dbName << " not found"};
}
- Status CatalogManagerReplicaSet::updateDatabase(const std::string& dbName,
- const DatabaseType& db) {
- fassert(28684, db.validate());
-
- return notYetImplemented;
+ invariant(docs.size() == 1);
+
+ return DatabaseType::fromBSON(docs.front());
+}
+
+Status CatalogManagerReplicaSet::updateCollection(const std::string& collNs,
+ const CollectionType& coll) {
+ fassert(28683, coll.validate());
+
+ BatchedCommandResponse response;
+ Status status = update(CollectionType::ConfigNS,
+ BSON(CollectionType::fullNs(collNs)),
+ coll.toBSON(),
+ true, // upsert
+ false, // multi
+ &response);
+ if (!status.isOK()) {
+ return Status(status.code(),
+ str::stream() << "collection metadata write failed: " << response.toBSON()
+ << "; status: " << status.toString());
}
- StatusWith<DatabaseType> CatalogManagerReplicaSet::getDatabase(const std::string& dbName) {
- invariant(nsIsDbOnly(dbName));
-
- // The two databases that are hosted on the config server are config and admin
- if (dbName == "config" || dbName == "admin") {
- DatabaseType dbt;
- dbt.setName(dbName);
- dbt.setSharded(false);
- dbt.setPrimary("config");
+ return Status::OK();
+}
- return dbt;
- }
-
- const auto configShard = grid.shardRegistry()->getShard("config");
- const auto readHost = configShard->getTargeter()->findHost(kConfigReadSelector);
- if (!readHost.isOK()) {
- return readHost.getStatus();
- }
-
- auto findStatus = grid.shardRegistry()->exhaustiveFind(
- readHost.getValue(),
- NamespaceString(DatabaseType::ConfigNS),
- BSON(DatabaseType::name(dbName)),
- 1);
-
- if (!findStatus.isOK()) {
- return findStatus.getStatus();
- }
+StatusWith<CollectionType> CatalogManagerReplicaSet::getCollection(const std::string& collNs) {
+ auto configShard = grid.shardRegistry()->getShard("config");
- const auto& docs = findStatus.getValue();
- if (docs.empty()) {
- return {ErrorCodes::NamespaceNotFound,
- stream() << "database " << dbName << " not found"};
- }
+ auto readHostStatus = configShard->getTargeter()->findHost(kConfigReadSelector);
+ if (!readHostStatus.isOK()) {
+ return readHostStatus.getStatus();
+ }
- invariant(docs.size() == 1);
+ auto statusFind =
+ grid.shardRegistry()->exhaustiveFind(readHostStatus.getValue(),
+ NamespaceString(CollectionType::ConfigNS),
+ BSON(CollectionType::fullNs(collNs)),
+ 1);
- return DatabaseType::fromBSON(docs.front());
+ if (!statusFind.isOK()) {
+ return statusFind.getStatus();
}
- Status CatalogManagerReplicaSet::updateCollection(const std::string& collNs,
- const CollectionType& coll) {
- fassert(28683, coll.validate());
-
- BatchedCommandResponse response;
- Status status = update(CollectionType::ConfigNS,
- BSON(CollectionType::fullNs(collNs)),
- coll.toBSON(),
- true, // upsert
- false, // multi
- &response);
- if (!status.isOK()) {
- return Status(status.code(),
- str::stream() << "collection metadata write failed: "
- << response.toBSON() << "; status: " << status.toString());
- }
-
- return Status::OK();
+ const auto& retVal = statusFind.getValue();
+ if (retVal.empty()) {
+ return Status(ErrorCodes::NamespaceNotFound,
+ stream() << "collection " << collNs << " not found");
}
- StatusWith<CollectionType> CatalogManagerReplicaSet::getCollection(const std::string& collNs) {
- auto configShard = grid.shardRegistry()->getShard("config");
+ invariant(retVal.size() == 1);
- auto readHostStatus = configShard->getTargeter()->findHost(kConfigReadSelector);
- if (!readHostStatus.isOK()) {
- return readHostStatus.getStatus();
- }
+ return CollectionType::fromBSON(retVal.front());
+}
- auto statusFind = grid.shardRegistry()->exhaustiveFind(
- readHostStatus.getValue(),
- NamespaceString(CollectionType::ConfigNS),
- BSON(CollectionType::fullNs(collNs)),
- 1);
+Status CatalogManagerReplicaSet::getCollections(const std::string* dbName,
+ std::vector<CollectionType>* collections) {
+ return notYetImplemented;
+}
- if (!statusFind.isOK()) {
- return statusFind.getStatus();
- }
+Status CatalogManagerReplicaSet::dropCollection(const std::string& collectionNs) {
+ return notYetImplemented;
+}
- const auto& retVal = statusFind.getValue();
- if (retVal.empty()) {
- return Status(ErrorCodes::NamespaceNotFound,
- stream() << "collection " << collNs << " not found");
- }
+void CatalogManagerReplicaSet::logAction(const ActionLogType& actionLog) {}
- invariant(retVal.size() == 1);
+void CatalogManagerReplicaSet::logChange(OperationContext* opCtx,
+ const string& what,
+ const string& ns,
+ const BSONObj& detail) {}
- return CollectionType::fromBSON(retVal.front());
+StatusWith<SettingsType> CatalogManagerReplicaSet::getGlobalSettings(const string& key) {
+ const auto configShard = grid.shardRegistry()->getShard("config");
+ const auto readHost = configShard->getTargeter()->findHost(kConfigReadSelector);
+ if (!readHost.isOK()) {
+ return readHost.getStatus();
}
- Status CatalogManagerReplicaSet::getCollections(const std::string* dbName,
- std::vector<CollectionType>* collections) {
- return notYetImplemented;
- }
+ auto findStatus = grid.shardRegistry()->exhaustiveFind(readHost.getValue(),
+ NamespaceString(SettingsType::ConfigNS),
+ BSON(SettingsType::key(key)),
+ 1);
- Status CatalogManagerReplicaSet::dropCollection(const std::string& collectionNs) {
- return notYetImplemented;
+ if (!findStatus.isOK()) {
+ return findStatus.getStatus();
}
- void CatalogManagerReplicaSet::logAction(const ActionLogType& actionLog) {
-
+ const auto& docs = findStatus.getValue();
+ if (docs.empty()) {
+ return {ErrorCodes::NoMatchingDocument,
+ str::stream() << "can't find settings document with key: " << key};
}
- void CatalogManagerReplicaSet::logChange(OperationContext* opCtx,
- const string& what,
- const string& ns,
- const BSONObj& detail) {
+ BSONObj settingsDoc = docs.front();
+ StatusWith<SettingsType> settingsResult = SettingsType::fromBSON(settingsDoc);
+ if (!settingsResult.isOK()) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << "error while parsing settings document: " << settingsDoc << " : "
+ << settingsResult.getStatus().toString()};
}
- StatusWith<SettingsType> CatalogManagerReplicaSet::getGlobalSettings(const string& key) {
- const auto configShard = grid.shardRegistry()->getShard("config");
- const auto readHost = configShard->getTargeter()->findHost(kConfigReadSelector);
- if (!readHost.isOK()) {
- return readHost.getStatus();
- }
-
- auto findStatus = grid.shardRegistry()->exhaustiveFind(
- readHost.getValue(),
- NamespaceString(SettingsType::ConfigNS),
- BSON(SettingsType::key(key)),
- 1);
-
- if (!findStatus.isOK()) {
- return findStatus.getStatus();
- }
-
- const auto& docs = findStatus.getValue();
- if (docs.empty()) {
- return {ErrorCodes::NoMatchingDocument,
- str::stream() << "can't find settings document with key: " << key};
- }
-
- BSONObj settingsDoc = docs.front();
- StatusWith<SettingsType> settingsResult = SettingsType::fromBSON(settingsDoc);
- if (!settingsResult.isOK()) {
- return {ErrorCodes::FailedToParse,
- str::stream() << "error while parsing settings document: " << settingsDoc
- << " : " << settingsResult.getStatus().toString()};
- }
-
- const SettingsType& settings = settingsResult.getValue();
+ const SettingsType& settings = settingsResult.getValue();
- Status validationStatus = settings.validate();
- if (!validationStatus.isOK()) {
- return validationStatus;
- }
-
- return settingsResult;
+ Status validationStatus = settings.validate();
+ if (!validationStatus.isOK()) {
+ return validationStatus;
}
- Status CatalogManagerReplicaSet::getDatabasesForShard(const string& shardName,
- vector<string>* dbs) {
- return notYetImplemented;
+ return settingsResult;
+}
+
+Status CatalogManagerReplicaSet::getDatabasesForShard(const string& shardName,
+ vector<string>* dbs) {
+ return notYetImplemented;
+}
+
+Status CatalogManagerReplicaSet::getChunks(const Query& query,
+ int nToReturn,
+ vector<ChunkType>* chunks) {
+ auto configShard = grid.shardRegistry()->getShard("config");
+ auto readHostStatus = configShard->getTargeter()->findHost(kConfigReadSelector);
+ if (!readHostStatus.isOK()) {
+ return readHostStatus.getStatus();
}
- Status CatalogManagerReplicaSet::getChunks(const Query& query,
- int nToReturn,
- vector<ChunkType>* chunks) {
+ auto findStatus = grid.shardRegistry()->exhaustiveFind(readHostStatus.getValue(),
+ NamespaceString(ChunkType::ConfigNS),
+ query.obj,
+ boost::none); // no limit
+ if (!findStatus.isOK()) {
+ return findStatus.getStatus();
+ }
- auto configShard = grid.shardRegistry()->getShard("config");
- auto readHostStatus = configShard->getTargeter()->findHost(kConfigReadSelector);
- if (!readHostStatus.isOK()) {
- return readHostStatus.getStatus();
+ for (const BSONObj& obj : findStatus.getValue()) {
+ auto chunkRes = ChunkType::fromBSON(obj);
+ if (!chunkRes.isOK()) {
+ chunks->clear();
+ return {ErrorCodes::FailedToParse,
+ stream() << "Failed to parse chunk with id ("
+ << obj[ChunkType::name()].toString()
+ << "): " << chunkRes.getStatus().reason()};
}
- auto findStatus = grid.shardRegistry()->exhaustiveFind(readHostStatus.getValue(),
- NamespaceString(ChunkType::ConfigNS),
- query.obj,
- boost::none); // no limit
- if (!findStatus.isOK()) {
- return findStatus.getStatus();
- }
+ chunks->push_back(chunkRes.getValue());
+ }
- for (const BSONObj& obj : findStatus.getValue()) {
- auto chunkRes = ChunkType::fromBSON(obj);
- if (!chunkRes.isOK()) {
- chunks->clear();
- return {ErrorCodes::FailedToParse,
- stream() << "Failed to parse chunk with id ("
- << obj[ChunkType::name()].toString() << "): "
- << chunkRes.getStatus().reason()};
- }
+ return Status::OK();
+}
- chunks->push_back(chunkRes.getValue());
- }
+Status CatalogManagerReplicaSet::getTagsForCollection(const std::string& collectionNs,
+ std::vector<TagsType>* tags) {
+ return notYetImplemented;
+}
- return Status::OK();
- }
+StatusWith<string> CatalogManagerReplicaSet::getTagForChunk(const std::string& collectionNs,
+ const ChunkType& chunk) {
+ return notYetImplemented;
+}
- Status CatalogManagerReplicaSet::getTagsForCollection(const std::string& collectionNs,
- std::vector<TagsType>* tags) {
- return notYetImplemented;
+Status CatalogManagerReplicaSet::getAllShards(vector<ShardType>* shards) {
+ const auto configShard = grid.shardRegistry()->getShard("config");
+ const auto readHost = configShard->getTargeter()->findHost(kConfigReadSelector);
+ if (!readHost.isOK()) {
+ return readHost.getStatus();
}
- StatusWith<string> CatalogManagerReplicaSet::getTagForChunk(const std::string& collectionNs,
- const ChunkType& chunk) {
- return notYetImplemented;
+ auto findStatus = grid.shardRegistry()->exhaustiveFind(readHost.getValue(),
+ NamespaceString(ShardType::ConfigNS),
+ BSONObj(), // no query filter
+ boost::none); // no limit
+ if (!findStatus.isOK()) {
+ return findStatus.getStatus();
}
- Status CatalogManagerReplicaSet::getAllShards(vector<ShardType>* shards) {
- const auto configShard = grid.shardRegistry()->getShard("config");
- const auto readHost = configShard->getTargeter()->findHost(kConfigReadSelector);
- if (!readHost.isOK()) {
- return readHost.getStatus();
- }
-
- auto findStatus = grid.shardRegistry()->exhaustiveFind(readHost.getValue(),
- NamespaceString(ShardType::ConfigNS),
- BSONObj(), // no query filter
- boost::none); // no limit
- if (!findStatus.isOK()) {
- return findStatus.getStatus();
- }
-
- for (const BSONObj& doc : findStatus.getValue()) {
- auto shardRes = ShardType::fromBSON(doc);
- if (!shardRes.isOK()) {
- shards->clear();
- return {ErrorCodes::FailedToParse,
- stream() << "Failed to parse shard with id ("
- << doc[ShardType::name()].toString() << "): "
- << shardRes.getStatus().reason()};
- }
-
- shards->push_back(shardRes.getValue());
+ for (const BSONObj& doc : findStatus.getValue()) {
+ auto shardRes = ShardType::fromBSON(doc);
+ if (!shardRes.isOK()) {
+ shards->clear();
+ return {ErrorCodes::FailedToParse,
+ stream() << "Failed to parse shard with id ("
+ << doc[ShardType::name()].toString()
+ << "): " << shardRes.getStatus().reason()};
}
- return Status::OK();
- }
-
- bool CatalogManagerReplicaSet::isShardHost(const ConnectionString& connectionString) {
- return false;
+ shards->push_back(shardRes.getValue());
}
- bool CatalogManagerReplicaSet::runUserManagementWriteCommand(const std::string& commandName,
- const std::string& dbname,
- const BSONObj& cmdObj,
- BSONObjBuilder* result) {
- auto scopedDistLock = getDistLockManager()->lock("authorizationData",
- commandName,
- Seconds{5});
- if (!scopedDistLock.isOK()) {
- return Command::appendCommandStatus(*result, scopedDistLock.getStatus());
- }
-
- auto targeter = grid.shardRegistry()->getShard("config")->getTargeter();
+ return Status::OK();
+}
- Status notMasterStatus{ErrorCodes::InternalError, "status not set"};
- for (int i = 0; i < kNotMasterNumRetries; ++i) {
+bool CatalogManagerReplicaSet::isShardHost(const ConnectionString& connectionString) {
+ return false;
+}
- auto target = targeter->findHost(kConfigWriteSelector);
- if (!target.isOK()) {
- if (ErrorCodes::NotMaster == target.getStatus()) {
- notMasterStatus = target.getStatus();
- sleepmillis(kNotMasterRetryInterval.count());
- continue;
- }
- return Command::appendCommandStatus(*result, target.getStatus());
- }
+bool CatalogManagerReplicaSet::runUserManagementWriteCommand(const std::string& commandName,
+ const std::string& dbname,
+ const BSONObj& cmdObj,
+ BSONObjBuilder* result) {
+ auto scopedDistLock = getDistLockManager()->lock("authorizationData", commandName, Seconds{5});
+ if (!scopedDistLock.isOK()) {
+ return Command::appendCommandStatus(*result, scopedDistLock.getStatus());
+ }
- auto response = grid.shardRegistry()->runCommand(target.getValue(), dbname, cmdObj);
- if (!response.isOK()) {
- return Command::appendCommandStatus(*result, response.getStatus());
- }
+ auto targeter = grid.shardRegistry()->getShard("config")->getTargeter();
- Status commandStatus = Command::getStatusFromCommandResult(response.getValue());
- if (ErrorCodes::NotMaster == commandStatus) {
- notMasterStatus = commandStatus;
+ Status notMasterStatus{ErrorCodes::InternalError, "status not set"};
+ for (int i = 0; i < kNotMasterNumRetries; ++i) {
+ auto target = targeter->findHost(kConfigWriteSelector);
+ if (!target.isOK()) {
+ if (ErrorCodes::NotMaster == target.getStatus()) {
+ notMasterStatus = target.getStatus();
sleepmillis(kNotMasterRetryInterval.count());
continue;
}
-
- result->appendElements(response.getValue());
-
- return commandStatus.isOK();
+ return Command::appendCommandStatus(*result, target.getStatus());
}
- invariant(ErrorCodes::NotMaster == notMasterStatus);
- return Command::appendCommandStatus(*result, notMasterStatus);
- }
-
- bool CatalogManagerReplicaSet::runUserManagementReadCommand(const std::string& dbname,
- const BSONObj& cmdObj,
- BSONObjBuilder* result) {
- auto targeter = grid.shardRegistry()->getShard("config")->getTargeter();
- auto target = targeter->findHost(kConfigReadSelector);
- if (!target.isOK()) {
- return Command::appendCommandStatus(*result, target.getStatus());
+ auto response = grid.shardRegistry()->runCommand(target.getValue(), dbname, cmdObj);
+ if (!response.isOK()) {
+ return Command::appendCommandStatus(*result, response.getStatus());
}
- auto resultStatus = grid.shardRegistry()->runCommand(target.getValue(), dbname, cmdObj);
- if (!resultStatus.isOK()) {
- return Command::appendCommandStatus(*result, resultStatus.getStatus());
+ Status commandStatus = Command::getStatusFromCommandResult(response.getValue());
+ if (ErrorCodes::NotMaster == commandStatus) {
+ notMasterStatus = commandStatus;
+ sleepmillis(kNotMasterRetryInterval.count());
+ continue;
}
- result->appendElements(resultStatus.getValue());
+ result->appendElements(response.getValue());
- return Command::getStatusFromCommandResult(resultStatus.getValue()).isOK();
- return false;
+ return commandStatus.isOK();
}
- Status CatalogManagerReplicaSet::applyChunkOpsDeprecated(const BSONArray& updateOps,
- const BSONArray& preCondition) {
- return notYetImplemented;
+ invariant(ErrorCodes::NotMaster == notMasterStatus);
+ return Command::appendCommandStatus(*result, notMasterStatus);
+}
+
+bool CatalogManagerReplicaSet::runUserManagementReadCommand(const std::string& dbname,
+ const BSONObj& cmdObj,
+ BSONObjBuilder* result) {
+ auto targeter = grid.shardRegistry()->getShard("config")->getTargeter();
+ auto target = targeter->findHost(kConfigReadSelector);
+ if (!target.isOK()) {
+ return Command::appendCommandStatus(*result, target.getStatus());
}
- DistLockManager* CatalogManagerReplicaSet::getDistLockManager() {
- invariant(_distLockManager);
- return _distLockManager.get();
+ auto resultStatus = grid.shardRegistry()->runCommand(target.getValue(), dbname, cmdObj);
+ if (!resultStatus.isOK()) {
+ return Command::appendCommandStatus(*result, resultStatus.getStatus());
}
- void CatalogManagerReplicaSet::writeConfigServerDirect(
- const BatchedCommandRequest& batchRequest,
- BatchedCommandResponse* batchResponse) {
- std::string dbname = batchRequest.getNSS().db().toString();
- invariant (dbname == "config" || dbname == "admin");
- const BSONObj cmdObj = batchRequest.toBSON();
- auto targeter = grid.shardRegistry()->getShard("config")->getTargeter();
-
- Status notMasterStatus{ErrorCodes::InternalError, "status not set"};
- for (int i = 0; i < kNotMasterNumRetries; ++i) {
-
- auto target = targeter->findHost(kConfigWriteSelector);
- if (!target.isOK()) {
- if (ErrorCodes::NotMaster == target.getStatus()) {
- notMasterStatus = target.getStatus();
- sleepmillis(kNotMasterRetryInterval.count());
- continue;
- }
- _toBatchError(target.getStatus(), batchResponse);
- return;
- }
+ result->appendElements(resultStatus.getValue());
- auto resultStatus = grid.shardRegistry()->runCommand(target.getValue(),
- batchRequest.getNSS().db().toString(),
- batchRequest.toBSON());
+ return Command::getStatusFromCommandResult(resultStatus.getValue()).isOK();
+ return false;
+}
- if (!resultStatus.isOK()) {
- _toBatchError(resultStatus.getStatus(), batchResponse);
- return;
- }
+Status CatalogManagerReplicaSet::applyChunkOpsDeprecated(const BSONArray& updateOps,
+ const BSONArray& preCondition) {
+ return notYetImplemented;
+}
+
+DistLockManager* CatalogManagerReplicaSet::getDistLockManager() {
+ invariant(_distLockManager);
+ return _distLockManager.get();
+}
- const BSONObj& commandResponse = resultStatus.getValue();
+void CatalogManagerReplicaSet::writeConfigServerDirect(const BatchedCommandRequest& batchRequest,
+ BatchedCommandResponse* batchResponse) {
+ std::string dbname = batchRequest.getNSS().db().toString();
+ invariant(dbname == "config" || dbname == "admin");
+ const BSONObj cmdObj = batchRequest.toBSON();
+ auto targeter = grid.shardRegistry()->getShard("config")->getTargeter();
- Status commandStatus = getStatusFromCommandResult(commandResponse);
- if (commandStatus == ErrorCodes::NotMaster) {
- notMasterStatus = commandStatus;
+ Status notMasterStatus{ErrorCodes::InternalError, "status not set"};
+ for (int i = 0; i < kNotMasterNumRetries; ++i) {
+ auto target = targeter->findHost(kConfigWriteSelector);
+ if (!target.isOK()) {
+ if (ErrorCodes::NotMaster == target.getStatus()) {
+ notMasterStatus = target.getStatus();
sleepmillis(kNotMasterRetryInterval.count());
continue;
}
+ _toBatchError(target.getStatus(), batchResponse);
+ return;
+ }
- string errmsg;
- if (!batchResponse->parseBSON(commandResponse, &errmsg)) {
- _toBatchError(Status(ErrorCodes::FailedToParse,
- str::stream() << "Failed to parse config server response: " <<
- errmsg),
- batchResponse);
- return;
- }
- return; // The normal case return point.
+ auto resultStatus = grid.shardRegistry()->runCommand(
+ target.getValue(), batchRequest.getNSS().db().toString(), batchRequest.toBSON());
+
+ if (!resultStatus.isOK()) {
+ _toBatchError(resultStatus.getStatus(), batchResponse);
+ return;
}
- invariant(ErrorCodes::NotMaster == notMasterStatus);
- _toBatchError(notMasterStatus, batchResponse);
+ const BSONObj& commandResponse = resultStatus.getValue();
+
+ Status commandStatus = getStatusFromCommandResult(commandResponse);
+ if (commandStatus == ErrorCodes::NotMaster) {
+ notMasterStatus = commandStatus;
+ sleepmillis(kNotMasterRetryInterval.count());
+ continue;
+ }
+
+ string errmsg;
+ if (!batchResponse->parseBSON(commandResponse, &errmsg)) {
+ _toBatchError(
+ Status(ErrorCodes::FailedToParse,
+ str::stream() << "Failed to parse config server response: " << errmsg),
+ batchResponse);
+ return;
+ }
+ return; // The normal case return point.
}
-} // namespace mongo
+ invariant(ErrorCodes::NotMaster == notMasterStatus);
+ _toBatchError(notMasterStatus, batchResponse);
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/replset/catalog_manager_replica_set.h b/src/mongo/s/catalog/replset/catalog_manager_replica_set.h
index 9b85640d1fb..c4b93093c26 100644
--- a/src/mongo/s/catalog/replset/catalog_manager_replica_set.h
+++ b/src/mongo/s/catalog/replset/catalog_manager_replica_set.h
@@ -39,115 +39,111 @@
namespace mongo {
+/**
+ * Implements the catalog manager for talking to replica set config servers.
+ */
+class CatalogManagerReplicaSet final : public CatalogManager {
+public:
+ CatalogManagerReplicaSet();
+ virtual ~CatalogManagerReplicaSet();
+
/**
- * Implements the catalog manager for talking to replica set config servers.
+ * Initializes the catalog manager.
+ * Can only be called once for the lifetime of the catalog manager.
+ * TODO(spencer): Take pointer to ShardRegistry rather than getting it from the global
+ * "grid" object.
*/
- class CatalogManagerReplicaSet final : public CatalogManager {
- public:
- CatalogManagerReplicaSet();
- virtual ~CatalogManagerReplicaSet();
-
- /**
- * Initializes the catalog manager.
- * Can only be called once for the lifetime of the catalog manager.
- * TODO(spencer): Take pointer to ShardRegistry rather than getting it from the global
- * "grid" object.
- */
- Status init(const ConnectionString& configCS,
- std::unique_ptr<DistLockManager> distLockManager);
-
- Status startup(bool upgrade) override;
+ Status init(const ConnectionString& configCS, std::unique_ptr<DistLockManager> distLockManager);
- ConnectionString connectionString() const override;
+ Status startup(bool upgrade) override;
- void shutDown() override;
+ ConnectionString connectionString() const override;
- Status enableSharding(const std::string& dbName) override;
+ void shutDown() override;
- Status shardCollection(const std::string& ns,
- const ShardKeyPattern& fieldsAndOrder,
- bool unique,
- std::vector<BSONObj>* initPoints,
- std::set<ShardId>* initShardsIds = nullptr) override;
+ Status enableSharding(const std::string& dbName) override;
- StatusWith<std::string> addShard(const std::string& name,
- const ConnectionString& shardConnectionString,
- const long long maxSize) override;
+ Status shardCollection(const std::string& ns,
+ const ShardKeyPattern& fieldsAndOrder,
+ bool unique,
+ std::vector<BSONObj>* initPoints,
+ std::set<ShardId>* initShardsIds = nullptr) override;
- StatusWith<ShardDrainingStatus> removeShard(OperationContext* txn,
- const std::string& name) override;
+ StatusWith<std::string> addShard(const std::string& name,
+ const ConnectionString& shardConnectionString,
+ const long long maxSize) override;
- Status createDatabase(const std::string& dbName) override;
+ StatusWith<ShardDrainingStatus> removeShard(OperationContext* txn,
+ const std::string& name) override;
- Status updateDatabase(const std::string& dbName, const DatabaseType& db) override;
+ Status createDatabase(const std::string& dbName) override;
- StatusWith<DatabaseType> getDatabase(const std::string& dbName) override;
+ Status updateDatabase(const std::string& dbName, const DatabaseType& db) override;
- Status updateCollection(const std::string& collNs, const CollectionType& coll) override;
+ StatusWith<DatabaseType> getDatabase(const std::string& dbName) override;
- StatusWith<CollectionType> getCollection(const std::string& collNs) override;
+ Status updateCollection(const std::string& collNs, const CollectionType& coll) override;
- Status getCollections(const std::string* dbName,
- std::vector<CollectionType>* collections) override;
+ StatusWith<CollectionType> getCollection(const std::string& collNs) override;
- Status dropCollection(const std::string& collectionNs) override;
+ Status getCollections(const std::string* dbName,
+ std::vector<CollectionType>* collections) override;
- Status getDatabasesForShard(const std::string& shardName,
- std::vector<std::string>* dbs) override;
+ Status dropCollection(const std::string& collectionNs) override;
- Status getChunks(const Query& query,
- int nToReturn,
- std::vector<ChunkType>* chunks) override;
+ Status getDatabasesForShard(const std::string& shardName,
+ std::vector<std::string>* dbs) override;
- Status getTagsForCollection(const std::string& collectionNs,
- std::vector<TagsType>* tags) override;
+ Status getChunks(const Query& query, int nToReturn, std::vector<ChunkType>* chunks) override;
- StatusWith<std::string> getTagForChunk(const std::string& collectionNs,
- const ChunkType& chunk) override;
+ Status getTagsForCollection(const std::string& collectionNs,
+ std::vector<TagsType>* tags) override;
- Status getAllShards(std::vector<ShardType>* shards) override;
+ StatusWith<std::string> getTagForChunk(const std::string& collectionNs,
+ const ChunkType& chunk) override;
- bool isShardHost(const ConnectionString& shardConnectionString) override;
+ Status getAllShards(std::vector<ShardType>* shards) override;
- bool runUserManagementWriteCommand(const std::string& commandName,
- const std::string& dbname,
- const BSONObj& cmdObj,
- BSONObjBuilder* result) override;
+ bool isShardHost(const ConnectionString& shardConnectionString) override;
- bool runUserManagementReadCommand(const std::string& dbname,
- const BSONObj& cmdObj,
- BSONObjBuilder* result) override;
+ bool runUserManagementWriteCommand(const std::string& commandName,
+ const std::string& dbname,
+ const BSONObj& cmdObj,
+ BSONObjBuilder* result) override;
- Status applyChunkOpsDeprecated(const BSONArray& updateOps,
- const BSONArray& preCondition) override;
+ bool runUserManagementReadCommand(const std::string& dbname,
+ const BSONObj& cmdObj,
+ BSONObjBuilder* result) override;
- void logAction(const ActionLogType& actionLog) override;
+ Status applyChunkOpsDeprecated(const BSONArray& updateOps,
+ const BSONArray& preCondition) override;
- void logChange(OperationContext* txn,
- const std::string& what,
- const std::string& ns,
- const BSONObj& detail) override;
+ void logAction(const ActionLogType& actionLog) override;
- StatusWith<SettingsType> getGlobalSettings(const std::string& key) override;
+ void logChange(OperationContext* txn,
+ const std::string& what,
+ const std::string& ns,
+ const BSONObj& detail) override;
- void writeConfigServerDirect(const BatchedCommandRequest& request,
- BatchedCommandResponse* response) override;
+ StatusWith<SettingsType> getGlobalSettings(const std::string& key) override;
- DistLockManager* getDistLockManager() override;
+ void writeConfigServerDirect(const BatchedCommandRequest& request,
+ BatchedCommandResponse* response) override;
- private:
+ DistLockManager* getDistLockManager() override;
- // Config server connection string
- ConnectionString _configServerConnectionString;
+private:
+ // Config server connection string
+ ConnectionString _configServerConnectionString;
- // Distribted lock manager singleton.
- std::unique_ptr<DistLockManager> _distLockManager;
+ // Distribted lock manager singleton.
+ std::unique_ptr<DistLockManager> _distLockManager;
- // protects _inShutdown
- std::mutex _mutex;
+ // protects _inShutdown
+ std::mutex _mutex;
- // True if shutDown() has been called. False, otherwise.
- bool _inShutdown = false;
- };
+ // True if shutDown() has been called. False, otherwise.
+ bool _inShutdown = false;
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/catalog/replset/catalog_manager_replica_set_test.cpp b/src/mongo/s/catalog/replset/catalog_manager_replica_set_test.cpp
index 7ba386e12f0..2d551a855f5 100644
--- a/src/mongo/s/catalog/replset/catalog_manager_replica_set_test.cpp
+++ b/src/mongo/s/catalog/replset/catalog_manager_replica_set_test.cpp
@@ -56,357 +56,473 @@
namespace mongo {
namespace {
- using executor::NetworkInterfaceMock;
- using executor::TaskExecutor;
- using std::async;
- using std::string;
- using std::vector;
- using stdx::chrono::milliseconds;
- using unittest::assertGet;
-
- static const std::chrono::seconds kFutureTimeout{5};
-
- TEST_F(CatalogManagerReplSetTestFixture, GetCollectionExisting) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
-
- CollectionType expectedColl;
- expectedColl.setNs(NamespaceString("TestDB.TestNS"));
- expectedColl.setKeyPattern(BSON("KeyName" << 1));
- expectedColl.setUpdatedAt(Date_t());
- expectedColl.setEpoch(OID::gen());
-
- auto future = async(std::launch::async, [this, &expectedColl] {
- return assertGet(catalogManager()->getCollection(expectedColl.getNs()));
- });
-
- onFindCommand([&expectedColl](const RemoteCommandRequest& request) {
- const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
- ASSERT_EQ(nss.toString(), CollectionType::ConfigNS);
-
- auto query = assertGet(
- LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
-
- // Ensure the query is correct
- ASSERT_EQ(query->ns(), CollectionType::ConfigNS);
- ASSERT_EQ(query->getFilter(), BSON(CollectionType::fullNs(expectedColl.getNs())));
-
- return vector<BSONObj>{ expectedColl.toBSON() };
- });
-
- // Now wait for the getCollection call to return
- const auto& actualColl = future.get();
- ASSERT_EQ(expectedColl.toBSON(), actualColl.toBSON());
- }
-
- TEST_F(CatalogManagerReplSetTestFixture, GetCollectionNotExisting) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
-
- auto future = async(std::launch::async, [this] {
- auto status = catalogManager()->getCollection("NonExistent");
- ASSERT_EQUALS(status.getStatus(), ErrorCodes::NamespaceNotFound);
- });
-
- onFindCommand([](const RemoteCommandRequest& request) {
- return vector<BSONObj>{ };
- });
-
- // Now wait for the getCollection call to return
- future.get();
- }
-
- TEST_F(CatalogManagerReplSetTestFixture, GetDatabaseExisting) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
-
- DatabaseType expectedDb;
- expectedDb.setName("bigdata");
- expectedDb.setPrimary("shard0000");
- expectedDb.setSharded(true);
-
- auto future = async(std::launch::async, [this, &expectedDb] {
- return assertGet(catalogManager()->getDatabase(expectedDb.getName()));
- });
-
- onFindCommand([&expectedDb](const RemoteCommandRequest& request) {
- const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
- ASSERT_EQ(nss.toString(), DatabaseType::ConfigNS);
-
- auto query = assertGet(
- LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
-
- ASSERT_EQ(query->ns(), DatabaseType::ConfigNS);
- ASSERT_EQ(query->getFilter(), BSON(DatabaseType::name(expectedDb.getName())));
+using executor::NetworkInterfaceMock;
+using executor::TaskExecutor;
+using std::async;
+using std::string;
+using std::vector;
+using stdx::chrono::milliseconds;
+using unittest::assertGet;
+
+static const std::chrono::seconds kFutureTimeout{5};
+
+TEST_F(CatalogManagerReplSetTestFixture, GetCollectionExisting) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+
+ CollectionType expectedColl;
+ expectedColl.setNs(NamespaceString("TestDB.TestNS"));
+ expectedColl.setKeyPattern(BSON("KeyName" << 1));
+ expectedColl.setUpdatedAt(Date_t());
+ expectedColl.setEpoch(OID::gen());
+
+ auto future = async(std::launch::async,
+ [this, &expectedColl] {
+ return assertGet(catalogManager()->getCollection(expectedColl.getNs()));
+ });
+
+ onFindCommand([&expectedColl](const RemoteCommandRequest& request) {
+ const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
+ ASSERT_EQ(nss.toString(), CollectionType::ConfigNS);
+
+ auto query = assertGet(LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
+
+ // Ensure the query is correct
+ ASSERT_EQ(query->ns(), CollectionType::ConfigNS);
+ ASSERT_EQ(query->getFilter(), BSON(CollectionType::fullNs(expectedColl.getNs())));
+
+ return vector<BSONObj>{expectedColl.toBSON()};
+ });
+
+ // Now wait for the getCollection call to return
+ const auto& actualColl = future.get();
+ ASSERT_EQ(expectedColl.toBSON(), actualColl.toBSON());
+}
+
+TEST_F(CatalogManagerReplSetTestFixture, GetCollectionNotExisting) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+
+ auto future = async(std::launch::async,
+ [this] {
+ auto status = catalogManager()->getCollection("NonExistent");
+ ASSERT_EQUALS(status.getStatus(), ErrorCodes::NamespaceNotFound);
+ });
+
+ onFindCommand([](const RemoteCommandRequest& request) { return vector<BSONObj>{}; });
+
+ // Now wait for the getCollection call to return
+ future.get();
+}
+
+TEST_F(CatalogManagerReplSetTestFixture, GetDatabaseExisting) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+
+ DatabaseType expectedDb;
+ expectedDb.setName("bigdata");
+ expectedDb.setPrimary("shard0000");
+ expectedDb.setSharded(true);
+
+ auto future = async(std::launch::async,
+ [this, &expectedDb] {
+ return assertGet(catalogManager()->getDatabase(expectedDb.getName()));
+ });
+
+ onFindCommand([&expectedDb](const RemoteCommandRequest& request) {
+ const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
+ ASSERT_EQ(nss.toString(), DatabaseType::ConfigNS);
+
+ auto query = assertGet(LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
+
+ ASSERT_EQ(query->ns(), DatabaseType::ConfigNS);
+ ASSERT_EQ(query->getFilter(), BSON(DatabaseType::name(expectedDb.getName())));
+
+ return vector<BSONObj>{expectedDb.toBSON()};
+ });
+
+ const auto& actualDb = future.get();
+ ASSERT_EQ(expectedDb.toBSON(), actualDb.toBSON());
+}
+
+TEST_F(CatalogManagerReplSetTestFixture, GetDatabaseNotExisting) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+
+ auto future = async(std::launch::async,
+ [this] {
+ auto dbResult = catalogManager()->getDatabase("NonExistent");
+ ASSERT_EQ(dbResult.getStatus(), ErrorCodes::NamespaceNotFound);
+ });
+
+ onFindCommand([](const RemoteCommandRequest& request) { return vector<BSONObj>{}; });
+
+ future.get();
+}
+
+TEST_F(CatalogManagerReplSetTestFixture, UpdateCollection) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+
+ CollectionType collection;
+ collection.setNs(NamespaceString("db.coll"));
+ collection.setUpdatedAt(network()->now());
+ collection.setUnique(true);
+ collection.setEpoch(OID::gen());
+ collection.setKeyPattern(KeyPattern(BSON("_id" << 1)));
+
+ auto future = async(std::launch::async,
+ [this, collection] {
+ auto status = catalogManager()->updateCollection(
+ collection.getNs().toString(), collection);
+ ASSERT_OK(status);
+ });
+
+ onCommand([collection](const RemoteCommandRequest& request) {
+ ASSERT_EQUALS("config", request.dbname);
+
+ BatchedUpdateRequest actualBatchedUpdate;
+ std::string errmsg;
+ ASSERT_TRUE(actualBatchedUpdate.parseBSON(request.cmdObj, &errmsg));
+ ASSERT_EQUALS(CollectionType::ConfigNS, actualBatchedUpdate.getCollName());
+ auto updates = actualBatchedUpdate.getUpdates();
+ ASSERT_EQUALS(1U, updates.size());
+ auto update = updates.front();
+
+ ASSERT_TRUE(update->getUpsert());
+ ASSERT_FALSE(update->getMulti());
+ ASSERT_EQUALS(update->getQuery(),
+ BSON(CollectionType::fullNs(collection.getNs().toString())));
+ ASSERT_EQUALS(update->getUpdateExpr(), collection.toBSON());
+
+ BatchedCommandResponse response;
+ response.setOk(true);
+ response.setNModified(1);
+
+ return response.toBSON();
+ });
+
+ // Now wait for the updateCollection call to return
+ future.wait_for(kFutureTimeout);
+}
+
+TEST_F(CatalogManagerReplSetTestFixture, UpdateCollectionNotMaster) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+
+ CollectionType collection;
+ collection.setNs(NamespaceString("db.coll"));
+ collection.setUpdatedAt(network()->now());
+ collection.setUnique(true);
+ collection.setEpoch(OID::gen());
+ collection.setKeyPattern(KeyPattern(BSON("_id" << 1)));
+
+ auto future = async(std::launch::async,
+ [this, collection] {
+ auto status = catalogManager()->updateCollection(
+ collection.getNs().toString(), collection);
+ ASSERT_EQUALS(ErrorCodes::NotMaster, status);
+ });
+
+ for (int i = 0; i < 3; ++i) {
+ onCommand([](const RemoteCommandRequest& request) {
+ BatchedCommandResponse response;
+ response.setOk(false);
+ response.setErrCode(ErrorCodes::NotMaster);
+ response.setErrMessage("not master");
- return vector<BSONObj>{ expectedDb.toBSON() };
+ return response.toBSON();
});
-
- const auto& actualDb = future.get();
- ASSERT_EQ(expectedDb.toBSON(), actualDb.toBSON());
}
- TEST_F(CatalogManagerReplSetTestFixture, GetDatabaseNotExisting) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
-
- auto future = async(std::launch::async, [this] {
- auto dbResult = catalogManager()->getDatabase("NonExistent");
- ASSERT_EQ(dbResult.getStatus(), ErrorCodes::NamespaceNotFound);
- });
-
- onFindCommand([](const RemoteCommandRequest& request) {
- return vector<BSONObj>{ };
- });
-
- future.get();
+ // Now wait for the updateCollection call to return
+ future.wait_for(kFutureTimeout);
+}
+
+TEST_F(CatalogManagerReplSetTestFixture, UpdateCollectionNotMasterRetrySuccess) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ HostAndPort host1("TestHost1");
+ HostAndPort host2("TestHost2");
+ targeter->setFindHostReturnValue(host1);
+
+ CollectionType collection;
+ collection.setNs(NamespaceString("db.coll"));
+ collection.setUpdatedAt(network()->now());
+ collection.setUnique(true);
+ collection.setEpoch(OID::gen());
+ collection.setKeyPattern(KeyPattern(BSON("_id" << 1)));
+
+ auto future = async(std::launch::async,
+ [this, collection] {
+ auto status = catalogManager()->updateCollection(
+ collection.getNs().toString(), collection);
+ ASSERT_OK(status);
+ });
+
+ onCommand([host1, host2, targeter](const RemoteCommandRequest& request) {
+ ASSERT_EQUALS(host1, request.target);
+
+ BatchedCommandResponse response;
+ response.setOk(false);
+ response.setErrCode(ErrorCodes::NotMaster);
+ response.setErrMessage("not master");
+
+ // Ensure that when the catalog manager tries to retarget after getting the
+ // NotMaster response, it will get back a new target.
+ targeter->setFindHostReturnValue(host2);
+ return response.toBSON();
+ });
+
+ onCommand([host2, collection](const RemoteCommandRequest& request) {
+ ASSERT_EQUALS(host2, request.target);
+
+ BatchedUpdateRequest actualBatchedUpdate;
+ std::string errmsg;
+ ASSERT_TRUE(actualBatchedUpdate.parseBSON(request.cmdObj, &errmsg));
+ ASSERT_EQUALS(CollectionType::ConfigNS, actualBatchedUpdate.getCollName());
+ auto updates = actualBatchedUpdate.getUpdates();
+ ASSERT_EQUALS(1U, updates.size());
+ auto update = updates.front();
+
+ ASSERT_TRUE(update->getUpsert());
+ ASSERT_FALSE(update->getMulti());
+ ASSERT_EQUALS(update->getQuery(),
+ BSON(CollectionType::fullNs(collection.getNs().toString())));
+ ASSERT_EQUALS(update->getUpdateExpr(), collection.toBSON());
+
+ BatchedCommandResponse response;
+ response.setOk(true);
+ response.setNModified(1);
+
+ return response.toBSON();
+ });
+
+ // Now wait for the updateCollection call to return
+ future.wait_for(kFutureTimeout);
+}
+
+TEST_F(CatalogManagerReplSetTestFixture, GetAllShardsValid) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+
+ ShardType s1;
+ s1.setName("shard0000");
+ s1.setHost("ShardHost");
+ s1.setDraining(false);
+ s1.setMaxSizeMB(50);
+ s1.setTags({"tag1", "tag2", "tag3"});
+
+ ShardType s2;
+ s2.setName("shard0001");
+ s2.setHost("ShardHost");
+
+ ShardType s3;
+ s3.setName("shard0002");
+ s3.setHost("ShardHost");
+ s3.setMaxSizeMB(65);
+
+ const vector<ShardType> expectedShardsList = {s1, s2, s3};
+
+ auto future = async(std::launch::async,
+ [this] {
+ vector<ShardType> shards;
+ ASSERT_OK(catalogManager()->getAllShards(&shards));
+ return shards;
+ });
+
+ onFindCommand([&s1, &s2, &s3](const RemoteCommandRequest& request) {
+ const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
+ ASSERT_EQ(nss.toString(), ShardType::ConfigNS);
+
+ auto query = assertGet(LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
+
+ ASSERT_EQ(query->ns(), ShardType::ConfigNS);
+ ASSERT_EQ(query->getFilter(), BSONObj());
+
+ return vector<BSONObj>{s1.toBSON(), s2.toBSON(), s3.toBSON()};
+ });
+
+ const vector<ShardType> actualShardsList = future.get();
+ ASSERT_EQ(actualShardsList.size(), expectedShardsList.size());
+
+ for (size_t i = 0; i < actualShardsList.size(); ++i) {
+ ASSERT_EQ(actualShardsList[i].toBSON(), expectedShardsList[i].toBSON());
}
+}
- TEST_F(CatalogManagerReplSetTestFixture, UpdateCollection) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
-
- CollectionType collection;
- collection.setNs(NamespaceString("db.coll"));
- collection.setUpdatedAt(network()->now());
- collection.setUnique(true);
- collection.setEpoch(OID::gen());
- collection.setKeyPattern(KeyPattern(BSON("_id" << 1)));
-
- auto future = async(std::launch::async, [this, collection] {
- auto status = catalogManager()->updateCollection(collection.getNs().toString(),
- collection);
- ASSERT_OK(status);
- });
-
- onCommand([collection](const RemoteCommandRequest& request) {
- ASSERT_EQUALS("config", request.dbname);
-
- BatchedUpdateRequest actualBatchedUpdate;
- std::string errmsg;
- ASSERT_TRUE(actualBatchedUpdate.parseBSON(request.cmdObj, &errmsg));
- ASSERT_EQUALS(CollectionType::ConfigNS, actualBatchedUpdate.getCollName());
- auto updates = actualBatchedUpdate.getUpdates();
- ASSERT_EQUALS(1U, updates.size());
- auto update = updates.front();
+TEST_F(CatalogManagerReplSetTestFixture, GetAllShardsWithInvalidShard) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
- ASSERT_TRUE(update->getUpsert());
- ASSERT_FALSE(update->getMulti());
- ASSERT_EQUALS(update->getQuery(),
- BSON(CollectionType::fullNs(collection.getNs().toString())));
- ASSERT_EQUALS(update->getUpdateExpr(), collection.toBSON());
+ auto future = async(std::launch::async,
+ [this] {
+ vector<ShardType> shards;
+ Status status = catalogManager()->getAllShards(&shards);
- BatchedCommandResponse response;
- response.setOk(true);
- response.setNModified(1);
+ ASSERT_NOT_OK(status);
+ ASSERT_EQ(0U, shards.size());
+ });
- return response.toBSON();
- });
+ onFindCommand([](const RemoteCommandRequest& request) {
+ const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
+ ASSERT_EQ(nss.toString(), ShardType::ConfigNS);
- // Now wait for the updateCollection call to return
- future.wait_for(kFutureTimeout);
- }
+ auto query = assertGet(LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
- TEST_F(CatalogManagerReplSetTestFixture, UpdateCollectionNotMaster) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
-
- CollectionType collection;
- collection.setNs(NamespaceString("db.coll"));
- collection.setUpdatedAt(network()->now());
- collection.setUnique(true);
- collection.setEpoch(OID::gen());
- collection.setKeyPattern(KeyPattern(BSON("_id" << 1)));
-
- auto future = async(std::launch::async, [this, collection] {
- auto status = catalogManager()->updateCollection(collection.getNs().toString(),
- collection);
- ASSERT_EQUALS(ErrorCodes::NotMaster, status);
- });
+ ASSERT_EQ(query->ns(), ShardType::ConfigNS);
+ ASSERT_EQ(query->getFilter(), BSONObj());
- for (int i = 0; i < 3; ++i) {
- onCommand([](const RemoteCommandRequest& request) {
- BatchedCommandResponse response;
- response.setOk(false);
- response.setErrCode(ErrorCodes::NotMaster);
- response.setErrMessage("not master");
+ // valid ShardType
+ ShardType s1;
+ s1.setName("shard0001");
+ s1.setHost("ShardHost");
- return response.toBSON();
- });
- }
+ return vector<BSONObj>{
+ s1.toBSON(),
+ BSONObj() // empty document is invalid
+ };
+ });
- // Now wait for the updateCollection call to return
- future.wait_for(kFutureTimeout);
- }
+ future.get();
+}
- TEST_F(CatalogManagerReplSetTestFixture, UpdateCollectionNotMasterRetrySuccess) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- HostAndPort host1("TestHost1");
- HostAndPort host2("TestHost2");
- targeter->setFindHostReturnValue(host1);
-
- CollectionType collection;
- collection.setNs(NamespaceString("db.coll"));
- collection.setUpdatedAt(network()->now());
- collection.setUnique(true);
- collection.setEpoch(OID::gen());
- collection.setKeyPattern(KeyPattern(BSON("_id" << 1)));
-
- auto future = async(std::launch::async, [this, collection] {
- auto status = catalogManager()->updateCollection(collection.getNs().toString(),
- collection);
- ASSERT_OK(status);
- });
+TEST_F(CatalogManagerReplSetTestFixture, GetChunksForNS) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
- onCommand([host1, host2, targeter](const RemoteCommandRequest& request) {
- ASSERT_EQUALS(host1, request.target);
+ OID oid = OID::gen();
- BatchedCommandResponse response;
- response.setOk(false);
- response.setErrCode(ErrorCodes::NotMaster);
- response.setErrMessage("not master");
+ ChunkType chunkA;
+ chunkA.setName("chunk0000");
+ chunkA.setNS("TestDB.TestColl");
+ chunkA.setMin(BSON("a" << 1));
+ chunkA.setMax(BSON("a" << 100));
+ chunkA.setVersion({1, 2, oid});
+ chunkA.setShard("shard0000");
- // Ensure that when the catalog manager tries to retarget after getting the
- // NotMaster response, it will get back a new target.
- targeter->setFindHostReturnValue(host2);
- return response.toBSON();
- });
+ ChunkType chunkB;
+ chunkB.setName("chunk0001");
+ chunkB.setNS("TestDB.TestColl");
+ chunkB.setMin(BSON("a" << 100));
+ chunkB.setMax(BSON("a" << 200));
+ chunkB.setVersion({3, 4, oid});
+ chunkB.setShard("shard0001");
- onCommand([host2, collection](const RemoteCommandRequest& request) {
- ASSERT_EQUALS(host2, request.target);
+ ChunkVersion queryChunkVersion({1, 2, oid});
- BatchedUpdateRequest actualBatchedUpdate;
- std::string errmsg;
- ASSERT_TRUE(actualBatchedUpdate.parseBSON(request.cmdObj, &errmsg));
- ASSERT_EQUALS(CollectionType::ConfigNS, actualBatchedUpdate.getCollName());
- auto updates = actualBatchedUpdate.getUpdates();
- ASSERT_EQUALS(1U, updates.size());
- auto update = updates.front();
+ const Query chunksQuery(
+ BSON(ChunkType::ns("TestDB.TestColl")
+ << ChunkType::DEPRECATED_lastmod()
+ << BSON("$gte" << static_cast<long long>(queryChunkVersion.toLong()))));
- ASSERT_TRUE(update->getUpsert());
- ASSERT_FALSE(update->getMulti());
- ASSERT_EQUALS(update->getQuery(),
- BSON(CollectionType::fullNs(collection.getNs().toString())));
- ASSERT_EQUALS(update->getUpdateExpr(), collection.toBSON());
+ auto future = async(std::launch::async,
+ [this, &chunksQuery] {
+ vector<ChunkType> chunks;
- BatchedCommandResponse response;
- response.setOk(true);
- response.setNModified(1);
+ ASSERT_OK(catalogManager()->getChunks(chunksQuery, 0, &chunks));
+ ASSERT_EQ(2U, chunks.size());
- return response.toBSON();
- });
+ return chunks;
+ });
- // Now wait for the updateCollection call to return
- future.wait_for(kFutureTimeout);
- }
+ onFindCommand([&chunksQuery, chunkA, chunkB](const RemoteCommandRequest& request) {
+ const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
+ ASSERT_EQ(nss.toString(), ChunkType::ConfigNS);
- TEST_F(CatalogManagerReplSetTestFixture, GetAllShardsValid) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+ auto query = assertGet(LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
- ShardType s1;
- s1.setName("shard0000");
- s1.setHost("ShardHost");
- s1.setDraining(false);
- s1.setMaxSizeMB(50);
- s1.setTags({ "tag1", "tag2", "tag3" });
+ ASSERT_EQ(query->ns(), ChunkType::ConfigNS);
+ ASSERT_EQ(query->getFilter(), chunksQuery.getFilter());
- ShardType s2;
- s2.setName("shard0001");
- s2.setHost("ShardHost");
+ return vector<BSONObj>{chunkA.toBSON(), chunkB.toBSON()};
+ });
- ShardType s3;
- s3.setName("shard0002");
- s3.setHost("ShardHost");
- s3.setMaxSizeMB(65);
+ const auto& chunks = future.get();
+ ASSERT_EQ(chunkA.toBSON(), chunks[0].toBSON());
+ ASSERT_EQ(chunkB.toBSON(), chunks[1].toBSON());
+}
- const vector<ShardType> expectedShardsList = { s1, s2, s3 };
+TEST_F(CatalogManagerReplSetTestFixture, GetChunksForNSNoChunks) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
- auto future = async(std::launch::async, [this] {
- vector<ShardType> shards;
- ASSERT_OK(catalogManager()->getAllShards(&shards));
- return shards;
- });
+ ChunkVersion queryChunkVersion({1, 2, OID::gen()});
- onFindCommand([&s1, &s2, &s3](const RemoteCommandRequest& request) {
- const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
- ASSERT_EQ(nss.toString(), ShardType::ConfigNS);
+ const Query chunksQuery(
+ BSON(ChunkType::ns("TestDB.TestColl")
+ << ChunkType::DEPRECATED_lastmod()
+ << BSON("$gte" << static_cast<long long>(queryChunkVersion.toLong()))));
- auto query = assertGet(
- LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
+ auto future = async(std::launch::async,
+ [this, &chunksQuery] {
+ vector<ChunkType> chunks;
- ASSERT_EQ(query->ns(), ShardType::ConfigNS);
- ASSERT_EQ(query->getFilter(), BSONObj());
+ ASSERT_OK(catalogManager()->getChunks(chunksQuery, 0, &chunks));
+ ASSERT_EQ(0U, chunks.size());
+ });
- return vector<BSONObj>{ s1.toBSON(), s2.toBSON(), s3.toBSON() };
- });
+ onFindCommand([&chunksQuery](const RemoteCommandRequest& request) {
+ const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
+ ASSERT_EQ(nss.toString(), ChunkType::ConfigNS);
- const vector<ShardType> actualShardsList = future.get();
- ASSERT_EQ(actualShardsList.size(), expectedShardsList.size());
+ auto query = assertGet(LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
- for (size_t i = 0; i < actualShardsList.size(); ++i) {
- ASSERT_EQ(actualShardsList[i].toBSON(), expectedShardsList[i].toBSON());
- }
- }
+ ASSERT_EQ(query->ns(), ChunkType::ConfigNS);
+ ASSERT_EQ(query->getFilter(), chunksQuery.getFilter());
- TEST_F(CatalogManagerReplSetTestFixture, GetAllShardsWithInvalidShard) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+ return vector<BSONObj>{};
+ });
- auto future = async(std::launch::async, [this] {
- vector<ShardType> shards;
- Status status = catalogManager()->getAllShards(&shards);
+ future.get();
+}
- ASSERT_NOT_OK(status);
- ASSERT_EQ(0U, shards.size());
- });
+TEST_F(CatalogManagerReplSetTestFixture, GetChunksForNSInvalidChunk) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
- onFindCommand([](const RemoteCommandRequest& request) {
- const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
- ASSERT_EQ(nss.toString(), ShardType::ConfigNS);
+ ChunkVersion queryChunkVersion({1, 2, OID::gen()});
- auto query = assertGet(
- LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
+ const Query chunksQuery(
+ BSON(ChunkType::ns("TestDB.TestColl")
+ << ChunkType::DEPRECATED_lastmod()
+ << BSON("$gte" << static_cast<long long>(queryChunkVersion.toLong()))));
- ASSERT_EQ(query->ns(), ShardType::ConfigNS);
- ASSERT_EQ(query->getFilter(), BSONObj());
+ auto future = async(std::launch::async,
+ [this, &chunksQuery] {
+ vector<ChunkType> chunks;
+ Status status = catalogManager()->getChunks(chunksQuery, 0, &chunks);
- // valid ShardType
- ShardType s1;
- s1.setName("shard0001");
- s1.setHost("ShardHost");
+ ASSERT_EQUALS(ErrorCodes::FailedToParse, status);
+ ASSERT_EQ(0U, chunks.size());
+ });
- return vector<BSONObj> {
- s1.toBSON(),
- BSONObj() // empty document is invalid
- };
- });
+ onFindCommand([&chunksQuery](const RemoteCommandRequest& request) {
+ const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
+ ASSERT_EQ(nss.toString(), ChunkType::ConfigNS);
- future.get();
- }
-
- TEST_F(CatalogManagerReplSetTestFixture, GetChunksForNS) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+ auto query = assertGet(LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
- OID oid = OID::gen();
+ ASSERT_EQ(query->ns(), ChunkType::ConfigNS);
+ ASSERT_EQ(query->getFilter(), chunksQuery.getFilter());
ChunkType chunkA;
chunkA.setName("chunk0000");
chunkA.setNS("TestDB.TestColl");
chunkA.setMin(BSON("a" << 1));
chunkA.setMax(BSON("a" << 100));
- chunkA.setVersion({ 1, 2, oid });
+ chunkA.setVersion({1, 2, OID::gen()});
chunkA.setShard("shard0000");
ChunkType chunkB;
@@ -414,450 +530,355 @@ namespace {
chunkB.setNS("TestDB.TestColl");
chunkB.setMin(BSON("a" << 100));
chunkB.setMax(BSON("a" << 200));
- chunkB.setVersion({ 3, 4, oid });
- chunkB.setShard("shard0001");
-
- ChunkVersion queryChunkVersion({ 1, 2, oid });
-
- const Query chunksQuery(BSON(ChunkType::ns("TestDB.TestColl") <<
- ChunkType::DEPRECATED_lastmod() <<
- BSON("$gte" << static_cast<long long>(
- queryChunkVersion.toLong()))));
-
- auto future = async(std::launch::async, [this, &chunksQuery] {
- vector<ChunkType> chunks;
-
- ASSERT_OK(catalogManager()->getChunks(chunksQuery, 0, &chunks));
- ASSERT_EQ(2U, chunks.size());
-
- return chunks;
- });
-
- onFindCommand([&chunksQuery, chunkA, chunkB](const RemoteCommandRequest& request) {
- const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
- ASSERT_EQ(nss.toString(), ChunkType::ConfigNS);
-
- auto query = assertGet(
- LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
-
- ASSERT_EQ(query->ns(), ChunkType::ConfigNS);
- ASSERT_EQ(query->getFilter(), chunksQuery.getFilter());
-
- return vector<BSONObj>{ chunkA.toBSON(), chunkB.toBSON() };
- });
-
- const auto& chunks = future.get();
- ASSERT_EQ(chunkA.toBSON(), chunks[0].toBSON());
- ASSERT_EQ(chunkB.toBSON(), chunks[1].toBSON());
- }
-
- TEST_F(CatalogManagerReplSetTestFixture, GetChunksForNSNoChunks) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
-
- ChunkVersion queryChunkVersion({ 1, 2, OID::gen() });
-
- const Query chunksQuery(BSON(ChunkType::ns("TestDB.TestColl") <<
- ChunkType::DEPRECATED_lastmod() <<
- BSON("$gte" << static_cast<long long>(
- queryChunkVersion.toLong()))));
-
- auto future = async(std::launch::async, [this, &chunksQuery] {
- vector<ChunkType> chunks;
-
- ASSERT_OK(catalogManager()->getChunks(chunksQuery, 0, &chunks));
- ASSERT_EQ(0U, chunks.size());
- });
-
- onFindCommand([&chunksQuery](const RemoteCommandRequest& request) {
- const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
- ASSERT_EQ(nss.toString(), ChunkType::ConfigNS);
-
- auto query = assertGet(
- LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
-
- ASSERT_EQ(query->ns(), ChunkType::ConfigNS);
- ASSERT_EQ(query->getFilter(), chunksQuery.getFilter());
-
- return vector<BSONObj>{ };
- });
-
- future.get();
- }
-
- TEST_F(CatalogManagerReplSetTestFixture, GetChunksForNSInvalidChunk) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
-
- ChunkVersion queryChunkVersion({ 1, 2, OID::gen() });
-
- const Query chunksQuery(BSON(ChunkType::ns("TestDB.TestColl") <<
- ChunkType::DEPRECATED_lastmod() <<
- BSON("$gte" << static_cast<long long>(
- queryChunkVersion.toLong()))));
-
- auto future = async(std::launch::async, [this, &chunksQuery] {
- vector<ChunkType> chunks;
- Status status = catalogManager()->getChunks(chunksQuery, 0, &chunks);
-
- ASSERT_EQUALS(ErrorCodes::FailedToParse, status);
- ASSERT_EQ(0U, chunks.size());
- });
-
- onFindCommand([&chunksQuery](const RemoteCommandRequest& request) {
- const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
- ASSERT_EQ(nss.toString(), ChunkType::ConfigNS);
-
- auto query = assertGet(
- LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
-
- ASSERT_EQ(query->ns(), ChunkType::ConfigNS);
- ASSERT_EQ(query->getFilter(), chunksQuery.getFilter());
-
- ChunkType chunkA;
- chunkA.setName("chunk0000");
- chunkA.setNS("TestDB.TestColl");
- chunkA.setMin(BSON("a" << 1));
- chunkA.setMax(BSON("a" << 100));
- chunkA.setVersion({ 1, 2, OID::gen() });
- chunkA.setShard("shard0000");
-
- ChunkType chunkB;
- chunkB.setName("chunk0001");
- chunkB.setNS("TestDB.TestColl");
- chunkB.setMin(BSON("a" << 100));
- chunkB.setMax(BSON("a" << 200));
- chunkB.setVersion({ 3, 4, OID::gen() });
- // Missing shard id
-
- return vector<BSONObj>{ chunkA.toBSON(), chunkB.toBSON() };
- });
-
- future.get();
- }
-
- TEST_F(CatalogManagerReplSetTestFixture, RunUserManagementReadCommand) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
-
- auto future = async(std::launch::async, [this] {
- BSONObjBuilder responseBuilder;
- bool ok = catalogManager()->runUserManagementReadCommand("test",
- BSON("usersInfo" << 1),
- &responseBuilder);
- ASSERT_TRUE(ok);
-
- BSONObj response = responseBuilder.obj();
- ASSERT_TRUE(response["ok"].trueValue());
- auto users = response["users"].Array();
- ASSERT_EQUALS(0U, users.size());
- });
-
- onCommand([](const RemoteCommandRequest& request) {
- ASSERT_EQUALS("test", request.dbname);
- ASSERT_EQUALS(BSON("usersInfo" << 1), request.cmdObj);
-
- return BSON("ok" << 1 << "users" << BSONArrayBuilder().arr());
- });
-
- // Now wait for the runUserManagementReadCommand call to return
- future.wait_for(kFutureTimeout);
- }
-
- TEST_F(CatalogManagerReplSetTestFixture, RunUserManagementReadCommandUnsatisfiedReadPref) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(Status(ErrorCodes::FailedToSatisfyReadPreference,
- "no nodes up"));
-
- BSONObjBuilder responseBuilder;
- bool ok = catalogManager()->runUserManagementReadCommand("test",
- BSON("usersInfo" << 1),
- &responseBuilder);
- ASSERT_FALSE(ok);
-
- Status commandStatus = Command::getStatusFromCommandResult(responseBuilder.obj());
- ASSERT_EQUALS(ErrorCodes::FailedToSatisfyReadPreference, commandStatus);
- }
-
- TEST_F(CatalogManagerReplSetTestFixture, RunUserManagementWriteCommandDistLockHeld) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
-
- distLock()->expectLock(
- [](StringData name,
- StringData whyMessage,
- milliseconds waitFor,
- milliseconds lockTryInterval) {
+ chunkB.setVersion({3, 4, OID::gen()});
+ // Missing shard id
+
+ return vector<BSONObj>{chunkA.toBSON(), chunkB.toBSON()};
+ });
+
+ future.get();
+}
+
+TEST_F(CatalogManagerReplSetTestFixture, RunUserManagementReadCommand) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+
+ auto future = async(std::launch::async,
+ [this] {
+ BSONObjBuilder responseBuilder;
+ bool ok = catalogManager()->runUserManagementReadCommand(
+ "test", BSON("usersInfo" << 1), &responseBuilder);
+ ASSERT_TRUE(ok);
+
+ BSONObj response = responseBuilder.obj();
+ ASSERT_TRUE(response["ok"].trueValue());
+ auto users = response["users"].Array();
+ ASSERT_EQUALS(0U, users.size());
+ });
+
+ onCommand([](const RemoteCommandRequest& request) {
+ ASSERT_EQUALS("test", request.dbname);
+ ASSERT_EQUALS(BSON("usersInfo" << 1), request.cmdObj);
+
+ return BSON("ok" << 1 << "users" << BSONArrayBuilder().arr());
+ });
+
+ // Now wait for the runUserManagementReadCommand call to return
+ future.wait_for(kFutureTimeout);
+}
+
+TEST_F(CatalogManagerReplSetTestFixture, RunUserManagementReadCommandUnsatisfiedReadPref) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(
+ Status(ErrorCodes::FailedToSatisfyReadPreference, "no nodes up"));
+
+ BSONObjBuilder responseBuilder;
+ bool ok = catalogManager()->runUserManagementReadCommand(
+ "test", BSON("usersInfo" << 1), &responseBuilder);
+ ASSERT_FALSE(ok);
+
+ Status commandStatus = Command::getStatusFromCommandResult(responseBuilder.obj());
+ ASSERT_EQUALS(ErrorCodes::FailedToSatisfyReadPreference, commandStatus);
+}
+
+TEST_F(CatalogManagerReplSetTestFixture, RunUserManagementWriteCommandDistLockHeld) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+
+ distLock()->expectLock(
+ [](StringData name,
+ StringData whyMessage,
+ milliseconds waitFor,
+ milliseconds lockTryInterval) {
+ ASSERT_EQUALS("authorizationData", name);
+ ASSERT_EQUALS("dropUser", whyMessage);
+ },
+ Status(ErrorCodes::LockBusy, "lock already held"));
+
+ BSONObjBuilder responseBuilder;
+ bool ok = catalogManager()->runUserManagementWriteCommand("dropUser",
+ "test",
+ BSON("dropUser"
+ << "test"),
+ &responseBuilder);
+ ASSERT_FALSE(ok);
+ BSONObj response = responseBuilder.obj();
+ ASSERT_EQUALS(ErrorCodes::LockBusy, Command::getStatusFromCommandResult(response));
+}
+
+TEST_F(CatalogManagerReplSetTestFixture, RunUserManagementWriteCommandSuccess) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+
+ distLock()->expectLock(
+ [](StringData name,
+ StringData whyMessage,
+ milliseconds waitFor,
+ milliseconds lockTryInterval) {
ASSERT_EQUALS("authorizationData", name);
ASSERT_EQUALS("dropUser", whyMessage);
- }, Status(ErrorCodes::LockBusy, "lock already held"));
+ },
+ Status::OK());
+
+ auto future =
+ async(std::launch::async,
+ [this] {
+ BSONObjBuilder responseBuilder;
+ bool ok = catalogManager()->runUserManagementWriteCommand("dropUser",
+ "test",
+ BSON("dropUser"
+ << "test"),
+ &responseBuilder);
+ ASSERT_FALSE(ok);
+
+ Status commandStatus = Command::getStatusFromCommandResult(responseBuilder.obj());
+ ASSERT_EQUALS(ErrorCodes::UserNotFound, commandStatus);
+ });
+
+ onCommand([](const RemoteCommandRequest& request) {
+ ASSERT_EQUALS("test", request.dbname);
+ ASSERT_EQUALS(BSON("dropUser"
+ << "test"),
+ request.cmdObj);
BSONObjBuilder responseBuilder;
- bool ok = catalogManager()->runUserManagementWriteCommand("dropUser",
- "test",
- BSON("dropUser" << "test"),
- &responseBuilder);
- ASSERT_FALSE(ok);
- BSONObj response = responseBuilder.obj();
- ASSERT_EQUALS(ErrorCodes::LockBusy, Command::getStatusFromCommandResult(response));
- }
-
- TEST_F(CatalogManagerReplSetTestFixture, RunUserManagementWriteCommandSuccess) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
-
- distLock()->expectLock(
- [](StringData name,
- StringData whyMessage,
- milliseconds waitFor,
- milliseconds lockTryInterval) {
+ Command::appendCommandStatus(responseBuilder,
+ Status(ErrorCodes::UserNotFound, "User test@test not found"));
+ return responseBuilder.obj();
+ });
+
+ // Now wait for the runUserManagementWriteCommand call to return
+ future.wait_for(kFutureTimeout);
+}
+
+TEST_F(CatalogManagerReplSetTestFixture, RunUserManagementWriteCommandNotMaster) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+
+ distLock()->expectLock(
+ [](StringData name,
+ StringData whyMessage,
+ milliseconds waitFor,
+ milliseconds lockTryInterval) {
ASSERT_EQUALS("authorizationData", name);
ASSERT_EQUALS("dropUser", whyMessage);
- }, Status::OK());
-
- auto future = async(std::launch::async, [this] {
- BSONObjBuilder responseBuilder;
- bool ok = catalogManager()->runUserManagementWriteCommand("dropUser",
- "test",
- BSON("dropUser" << "test"),
- &responseBuilder);
- ASSERT_FALSE(ok);
-
- Status commandStatus = Command::getStatusFromCommandResult(responseBuilder.obj());
- ASSERT_EQUALS(ErrorCodes::UserNotFound, commandStatus);
- });
-
+ },
+ Status::OK());
+
+ auto future =
+ async(std::launch::async,
+ [this] {
+ BSONObjBuilder responseBuilder;
+ bool ok = catalogManager()->runUserManagementWriteCommand("dropUser",
+ "test",
+ BSON("dropUser"
+ << "test"),
+ &responseBuilder);
+ ASSERT_FALSE(ok);
+
+ Status commandStatus = Command::getStatusFromCommandResult(responseBuilder.obj());
+ ASSERT_EQUALS(ErrorCodes::NotMaster, commandStatus);
+ });
+
+ for (int i = 0; i < 3; ++i) {
onCommand([](const RemoteCommandRequest& request) {
- ASSERT_EQUALS("test", request.dbname);
- ASSERT_EQUALS(BSON("dropUser" << "test"), request.cmdObj);
-
BSONObjBuilder responseBuilder;
Command::appendCommandStatus(responseBuilder,
- Status(ErrorCodes::UserNotFound,
- "User test@test not found"));
+ Status(ErrorCodes::NotMaster, "not master"));
return responseBuilder.obj();
});
-
- // Now wait for the runUserManagementWriteCommand call to return
- future.wait_for(kFutureTimeout);
}
- TEST_F(CatalogManagerReplSetTestFixture, RunUserManagementWriteCommandNotMaster) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
-
- distLock()->expectLock(
- [](StringData name,
- StringData whyMessage,
- milliseconds waitFor,
- milliseconds lockTryInterval) {
+ // Now wait for the runUserManagementWriteCommand call to return
+ future.wait_for(kFutureTimeout);
+}
+
+TEST_F(CatalogManagerReplSetTestFixture, RunUserManagementWriteCommandNotMasterRetrySuccess) {
+ HostAndPort host1("TestHost1");
+ HostAndPort host2("TestHost2");
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(host1);
+
+ distLock()->expectLock(
+ [](StringData name,
+ StringData whyMessage,
+ milliseconds waitFor,
+ milliseconds lockTryInterval) {
ASSERT_EQUALS("authorizationData", name);
ASSERT_EQUALS("dropUser", whyMessage);
- }, Status::OK());
-
- auto future = async(std::launch::async, [this] {
- BSONObjBuilder responseBuilder;
- bool ok = catalogManager()->runUserManagementWriteCommand("dropUser",
- "test",
- BSON("dropUser" << "test"),
- &responseBuilder);
- ASSERT_FALSE(ok);
-
- Status commandStatus = Command::getStatusFromCommandResult(responseBuilder.obj());
- ASSERT_EQUALS(ErrorCodes::NotMaster, commandStatus);
- });
-
- for (int i = 0; i < 3; ++i) {
- onCommand([](const RemoteCommandRequest& request) {
- BSONObjBuilder responseBuilder;
- Command::appendCommandStatus(responseBuilder,
- Status(ErrorCodes::NotMaster, "not master"));
- return responseBuilder.obj();
- });
- }
-
- // Now wait for the runUserManagementWriteCommand call to return
- future.wait_for(kFutureTimeout);
- }
+ },
+ Status::OK());
+
+ auto future =
+ async(std::launch::async,
+ [this] {
+ BSONObjBuilder responseBuilder;
+ bool ok = catalogManager()->runUserManagementWriteCommand("dropUser",
+ "test",
+ BSON("dropUser"
+ << "test"),
+ &responseBuilder);
+ ASSERT_TRUE(ok);
+
+ Status commandStatus = Command::getStatusFromCommandResult(responseBuilder.obj());
+ ASSERT_OK(commandStatus);
+ });
+
+ onCommand([targeter, host1, host2](const RemoteCommandRequest& request) {
+ ASSERT_EQUALS(host1, request.target);
- TEST_F(CatalogManagerReplSetTestFixture, RunUserManagementWriteCommandNotMasterRetrySuccess) {
- HostAndPort host1("TestHost1");
- HostAndPort host2("TestHost2");
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(host1);
-
- distLock()->expectLock(
- [](StringData name,
- StringData whyMessage,
- milliseconds waitFor,
- milliseconds lockTryInterval) {
- ASSERT_EQUALS("authorizationData", name);
- ASSERT_EQUALS("dropUser", whyMessage);
- }, Status::OK());
-
- auto future = async(std::launch::async, [this] {
- BSONObjBuilder responseBuilder;
- bool ok = catalogManager()->runUserManagementWriteCommand("dropUser",
- "test",
- BSON("dropUser" << "test"),
- &responseBuilder);
- ASSERT_TRUE(ok);
-
- Status commandStatus = Command::getStatusFromCommandResult(responseBuilder.obj());
- ASSERT_OK(commandStatus);
- });
-
- onCommand([targeter, host1, host2](const RemoteCommandRequest& request) {
- ASSERT_EQUALS(host1, request.target);
-
- BSONObjBuilder responseBuilder;
- Command::appendCommandStatus(responseBuilder,
- Status(ErrorCodes::NotMaster, "not master"));
-
- // Ensure that when the catalog manager tries to retarget after getting the
- // NotMaster response, it will get back a new target.
- targeter->setFindHostReturnValue(host2);
- return responseBuilder.obj();
- });
-
- onCommand([host2](const RemoteCommandRequest& request) {
- ASSERT_EQUALS(host2, request.target);
- ASSERT_EQUALS("test", request.dbname);
- ASSERT_EQUALS(BSON("dropUser" << "test"), request.cmdObj);
-
- return BSON("ok" << 1);
- });
+ BSONObjBuilder responseBuilder;
+ Command::appendCommandStatus(responseBuilder, Status(ErrorCodes::NotMaster, "not master"));
+
+ // Ensure that when the catalog manager tries to retarget after getting the
+ // NotMaster response, it will get back a new target.
+ targeter->setFindHostReturnValue(host2);
+ return responseBuilder.obj();
+ });
- // Now wait for the runUserManagementWriteCommand call to return
- future.wait_for(kFutureTimeout);
- }
+ onCommand([host2](const RemoteCommandRequest& request) {
+ ASSERT_EQUALS(host2, request.target);
+ ASSERT_EQUALS("test", request.dbname);
+ ASSERT_EQUALS(BSON("dropUser"
+ << "test"),
+ request.cmdObj);
- TEST_F(CatalogManagerReplSetTestFixture, GetGlobalSettingsBalancerDoc) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+ return BSON("ok" << 1);
+ });
- // sample balancer doc
- SettingsType st1;
- st1.setKey(SettingsType::BalancerDocKey);
- st1.setBalancerStopped(true);
+ // Now wait for the runUserManagementWriteCommand call to return
+ future.wait_for(kFutureTimeout);
+}
- auto future = async(std::launch::async, [this] {
- return assertGet(catalogManager()->getGlobalSettings(SettingsType::BalancerDocKey));
- });
+TEST_F(CatalogManagerReplSetTestFixture, GetGlobalSettingsBalancerDoc) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+
+ // sample balancer doc
+ SettingsType st1;
+ st1.setKey(SettingsType::BalancerDocKey);
+ st1.setBalancerStopped(true);
+
+ auto future = async(std::launch::async,
+ [this] {
+ return assertGet(
+ catalogManager()->getGlobalSettings(SettingsType::BalancerDocKey));
+ });
- onFindCommand([st1](const RemoteCommandRequest& request) {
- const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
- ASSERT_EQ(nss.toString(), SettingsType::ConfigNS);
+ onFindCommand([st1](const RemoteCommandRequest& request) {
+ const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
+ ASSERT_EQ(nss.toString(), SettingsType::ConfigNS);
- auto query = assertGet(
- LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
+ auto query = assertGet(LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
- ASSERT_EQ(query->ns(), SettingsType::ConfigNS);
- ASSERT_EQ(query->getFilter(), BSON(SettingsType::key(SettingsType::BalancerDocKey)));
+ ASSERT_EQ(query->ns(), SettingsType::ConfigNS);
+ ASSERT_EQ(query->getFilter(), BSON(SettingsType::key(SettingsType::BalancerDocKey)));
- return vector<BSONObj>{ st1.toBSON() };
- });
+ return vector<BSONObj>{st1.toBSON()};
+ });
- const auto& actualBalSettings = future.get();
- ASSERT_EQ(actualBalSettings.toBSON(), st1.toBSON());
- }
+ const auto& actualBalSettings = future.get();
+ ASSERT_EQ(actualBalSettings.toBSON(), st1.toBSON());
+}
- TEST_F(CatalogManagerReplSetTestFixture, GetGlobalSettingsChunkSizeDoc) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+TEST_F(CatalogManagerReplSetTestFixture, GetGlobalSettingsChunkSizeDoc) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
- // sample chunk size doc
- SettingsType st1;
- st1.setKey(SettingsType::ChunkSizeDocKey);
- st1.setChunkSizeMB(80);
+ // sample chunk size doc
+ SettingsType st1;
+ st1.setKey(SettingsType::ChunkSizeDocKey);
+ st1.setChunkSizeMB(80);
- auto future = async(std::launch::async, [this] {
- return assertGet(catalogManager()->getGlobalSettings(SettingsType::ChunkSizeDocKey));
- });
+ auto future = async(std::launch::async,
+ [this] {
+ return assertGet(
+ catalogManager()->getGlobalSettings(SettingsType::ChunkSizeDocKey));
+ });
- onFindCommand([st1](const RemoteCommandRequest& request) {
- const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
- ASSERT_EQ(nss.toString(), SettingsType::ConfigNS);
+ onFindCommand([st1](const RemoteCommandRequest& request) {
+ const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
+ ASSERT_EQ(nss.toString(), SettingsType::ConfigNS);
- auto query = assertGet(
- LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
+ auto query = assertGet(LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
- ASSERT_EQ(query->ns(), SettingsType::ConfigNS);
- ASSERT_EQ(query->getFilter(), BSON(SettingsType::key(SettingsType::ChunkSizeDocKey)));
+ ASSERT_EQ(query->ns(), SettingsType::ConfigNS);
+ ASSERT_EQ(query->getFilter(), BSON(SettingsType::key(SettingsType::ChunkSizeDocKey)));
- return vector<BSONObj>{ st1.toBSON() };
- });
+ return vector<BSONObj>{st1.toBSON()};
+ });
- const auto& actualBalSettings = future.get();
- ASSERT_EQ(actualBalSettings.toBSON(), st1.toBSON());
- }
+ const auto& actualBalSettings = future.get();
+ ASSERT_EQ(actualBalSettings.toBSON(), st1.toBSON());
+}
- TEST_F(CatalogManagerReplSetTestFixture, GetGlobalSettingsInvalidDoc) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+TEST_F(CatalogManagerReplSetTestFixture, GetGlobalSettingsInvalidDoc) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
- auto future = async(std::launch::async, [this] {
- const auto balSettings = catalogManager()->getGlobalSettings("invalidKey");
+ auto future = async(std::launch::async,
+ [this] {
+ const auto balSettings =
+ catalogManager()->getGlobalSettings("invalidKey");
- ASSERT_EQ(balSettings.getStatus(), ErrorCodes::FailedToParse);
- });
+ ASSERT_EQ(balSettings.getStatus(), ErrorCodes::FailedToParse);
+ });
- onFindCommand([](const RemoteCommandRequest& request) {
- const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
- ASSERT_EQ(nss.toString(), SettingsType::ConfigNS);
+ onFindCommand([](const RemoteCommandRequest& request) {
+ const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
+ ASSERT_EQ(nss.toString(), SettingsType::ConfigNS);
- auto query = assertGet(
- LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
+ auto query = assertGet(LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
- ASSERT_EQ(query->ns(), SettingsType::ConfigNS);
- ASSERT_EQ(query->getFilter(), BSON(SettingsType::key("invalidKey")));
+ ASSERT_EQ(query->ns(), SettingsType::ConfigNS);
+ ASSERT_EQ(query->getFilter(), BSON(SettingsType::key("invalidKey")));
- return vector<BSONObj> {
- BSON("invalidKey" << "some value") // invalid settings document -- key is required
- };
- });
+ return vector<BSONObj>{
+ BSON("invalidKey"
+ << "some value") // invalid settings document -- key is required
+ };
+ });
- future.get();
- }
+ future.get();
+}
- TEST_F(CatalogManagerReplSetTestFixture, GetGlobalSettingsNonExistent) {
- RemoteCommandTargeterMock* targeter =
- RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
- targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
+TEST_F(CatalogManagerReplSetTestFixture, GetGlobalSettingsNonExistent) {
+ RemoteCommandTargeterMock* targeter =
+ RemoteCommandTargeterMock::get(shardRegistry()->getShard("config")->getTargeter());
+ targeter->setFindHostReturnValue(HostAndPort("TestHost1"));
- auto future = async(std::launch::async, [this] {
- const auto chunkSizeSettings = catalogManager()->getGlobalSettings(
- SettingsType::ChunkSizeDocKey);
+ auto future =
+ async(std::launch::async,
+ [this] {
+ const auto chunkSizeSettings =
+ catalogManager()->getGlobalSettings(SettingsType::ChunkSizeDocKey);
- ASSERT_EQ(chunkSizeSettings.getStatus(), ErrorCodes::NoMatchingDocument);
- });
+ ASSERT_EQ(chunkSizeSettings.getStatus(), ErrorCodes::NoMatchingDocument);
+ });
- onFindCommand([](const RemoteCommandRequest& request) {
- const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
- ASSERT_EQ(nss.toString(), SettingsType::ConfigNS);
+ onFindCommand([](const RemoteCommandRequest& request) {
+ const NamespaceString nss(request.dbname, request.cmdObj.firstElement().String());
+ ASSERT_EQ(nss.toString(), SettingsType::ConfigNS);
- auto query = assertGet(
- LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
+ auto query = assertGet(LiteParsedQuery::makeFromFindCommand(nss, request.cmdObj, false));
- ASSERT_EQ(query->ns(), SettingsType::ConfigNS);
- ASSERT_EQ(query->getFilter(), BSON(SettingsType::key(SettingsType::ChunkSizeDocKey)));
+ ASSERT_EQ(query->ns(), SettingsType::ConfigNS);
+ ASSERT_EQ(query->getFilter(), BSON(SettingsType::key(SettingsType::ChunkSizeDocKey)));
- return vector<BSONObj> { };
- });
+ return vector<BSONObj>{};
+ });
- future.get();
- }
+ future.get();
+}
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/catalog/replset/catalog_manager_replica_set_test_fixture.cpp b/src/mongo/s/catalog/replset/catalog_manager_replica_set_test_fixture.cpp
index 3f0d78f451b..795c1d329db 100644
--- a/src/mongo/s/catalog/replset/catalog_manager_replica_set_test_fixture.cpp
+++ b/src/mongo/s/catalog/replset/catalog_manager_replica_set_test_fixture.cpp
@@ -48,133 +48,127 @@
namespace mongo {
- using executor::NetworkInterfaceMock;
- using std::vector;
+using executor::NetworkInterfaceMock;
+using std::vector;
- CatalogManagerReplSetTestFixture::CatalogManagerReplSetTestFixture() = default;
+CatalogManagerReplSetTestFixture::CatalogManagerReplSetTestFixture() = default;
- CatalogManagerReplSetTestFixture::~CatalogManagerReplSetTestFixture() = default;
+CatalogManagerReplSetTestFixture::~CatalogManagerReplSetTestFixture() = default;
- void CatalogManagerReplSetTestFixture::setUp() {
- std::unique_ptr<NetworkInterfaceMock> network(
- stdx::make_unique<executor::NetworkInterfaceMock>());
+void CatalogManagerReplSetTestFixture::setUp() {
+ std::unique_ptr<NetworkInterfaceMock> network(
+ stdx::make_unique<executor::NetworkInterfaceMock>());
- _mockNetwork = network.get();
+ _mockNetwork = network.get();
- std::unique_ptr<repl::ReplicationExecutor> executor(
- stdx::make_unique<repl::ReplicationExecutor>(network.release(),
- nullptr,
- 0));
+ std::unique_ptr<repl::ReplicationExecutor> executor(
+ stdx::make_unique<repl::ReplicationExecutor>(network.release(), nullptr, 0));
- // The executor thread might run after the executor unique_ptr above has been moved to the
- // ShardRegistry, so make sure we get the underlying pointer before that.
- _executorThread = std::thread(std::bind([](repl::ReplicationExecutor* executorPtr) {
- executorPtr->run();
- },
- executor.get()));
+ // The executor thread might run after the executor unique_ptr above has been moved to the
+ // ShardRegistry, so make sure we get the underlying pointer before that.
+ _executorThread = std::thread(std::bind(
+ [](repl::ReplicationExecutor* executorPtr) { executorPtr->run(); }, executor.get()));
- std::unique_ptr<CatalogManagerReplicaSet> cm(
- stdx::make_unique<CatalogManagerReplicaSet>());
+ std::unique_ptr<CatalogManagerReplicaSet> cm(stdx::make_unique<CatalogManagerReplicaSet>());
- ASSERT_OK(cm->init(ConnectionString::forReplicaSet("CatalogManagerReplSetTest",
- { HostAndPort{ "TestHost1" },
- HostAndPort{ "TestHost2" } }),
- stdx::make_unique<DistLockManagerMock>()));
+ ASSERT_OK(cm->init(
+ ConnectionString::forReplicaSet("CatalogManagerReplSetTest",
+ {HostAndPort{"TestHost1"}, HostAndPort{"TestHost2"}}),
+ stdx::make_unique<DistLockManagerMock>()));
- std::unique_ptr<ShardRegistry> shardRegistry(
- stdx::make_unique<ShardRegistry>(stdx::make_unique<RemoteCommandTargeterFactoryMock>(),
- stdx::make_unique<RemoteCommandRunnerMock>(),
- std::move(executor),
- cm.get()));
+ std::unique_ptr<ShardRegistry> shardRegistry(
+ stdx::make_unique<ShardRegistry>(stdx::make_unique<RemoteCommandTargeterFactoryMock>(),
+ stdx::make_unique<RemoteCommandRunnerMock>(),
+ std::move(executor),
+ cm.get()));
- // For now initialize the global grid object. All sharding objects will be accessible
- // from there until we get rid of it.
- grid.init(std::move(cm), std::move(shardRegistry));
- }
+ // For now initialize the global grid object. All sharding objects will be accessible
+ // from there until we get rid of it.
+ grid.init(std::move(cm), std::move(shardRegistry));
+}
- void CatalogManagerReplSetTestFixture::tearDown() {
- // Stop the executor and wait for the executor thread to complete. This means that there
- // will be no more calls into the executor and it can be safely deleted.
- shardRegistry()->getExecutor()->shutdown();
- _executorThread.join();
+void CatalogManagerReplSetTestFixture::tearDown() {
+ // Stop the executor and wait for the executor thread to complete. This means that there
+ // will be no more calls into the executor and it can be safely deleted.
+ shardRegistry()->getExecutor()->shutdown();
+ _executorThread.join();
- // This call will delete the shard registry, which will terminate the executor
- grid.clearForUnitTests();
- }
+ // This call will delete the shard registry, which will terminate the executor
+ grid.clearForUnitTests();
+}
- CatalogManagerReplicaSet* CatalogManagerReplSetTestFixture::catalogManager() const {
- auto cm = dynamic_cast<CatalogManagerReplicaSet*>(grid.catalogManager());
- invariant(cm);
+CatalogManagerReplicaSet* CatalogManagerReplSetTestFixture::catalogManager() const {
+ auto cm = dynamic_cast<CatalogManagerReplicaSet*>(grid.catalogManager());
+ invariant(cm);
- return cm;
- }
+ return cm;
+}
- ShardRegistry* CatalogManagerReplSetTestFixture::shardRegistry() const {
- return grid.shardRegistry();
- }
+ShardRegistry* CatalogManagerReplSetTestFixture::shardRegistry() const {
+ return grid.shardRegistry();
+}
- RemoteCommandRunnerMock* CatalogManagerReplSetTestFixture::commandRunner() const {
- return RemoteCommandRunnerMock::get(shardRegistry()->getCommandRunner());
- }
+RemoteCommandRunnerMock* CatalogManagerReplSetTestFixture::commandRunner() const {
+ return RemoteCommandRunnerMock::get(shardRegistry()->getCommandRunner());
+}
- executor::NetworkInterfaceMock* CatalogManagerReplSetTestFixture::network() const {
- return _mockNetwork;
- }
+executor::NetworkInterfaceMock* CatalogManagerReplSetTestFixture::network() const {
+ return _mockNetwork;
+}
- DistLockManagerMock* CatalogManagerReplSetTestFixture::distLock() const {
- auto distLock = dynamic_cast<DistLockManagerMock*>(catalogManager()->getDistLockManager());
- invariant(distLock);
+DistLockManagerMock* CatalogManagerReplSetTestFixture::distLock() const {
+ auto distLock = dynamic_cast<DistLockManagerMock*>(catalogManager()->getDistLockManager());
+ invariant(distLock);
- return distLock;
- }
+ return distLock;
+}
- void CatalogManagerReplSetTestFixture::onCommand(OnCommandFunction func) {
- network()->enterNetwork();
+void CatalogManagerReplSetTestFixture::onCommand(OnCommandFunction func) {
+ network()->enterNetwork();
- const NetworkInterfaceMock::NetworkOperationIterator noi =
- network()->getNextReadyRequest();
- const RemoteCommandRequest& request = noi->getRequest();
+ const NetworkInterfaceMock::NetworkOperationIterator noi = network()->getNextReadyRequest();
+ const RemoteCommandRequest& request = noi->getRequest();
- const auto& resultStatus = func(request);
+ const auto& resultStatus = func(request);
- BSONObjBuilder result;
+ BSONObjBuilder result;
- if (resultStatus.isOK()) {
- result.appendElements(resultStatus.getValue());
- }
+ if (resultStatus.isOK()) {
+ result.appendElements(resultStatus.getValue());
+ }
- Command::appendCommandStatus(result, resultStatus.getStatus());
+ Command::appendCommandStatus(result, resultStatus.getStatus());
- const RemoteCommandResponse response(result.obj(), Milliseconds(1));
+ const RemoteCommandResponse response(result.obj(), Milliseconds(1));
- network()->scheduleResponse(noi, network()->now(), response);
+ network()->scheduleResponse(noi, network()->now(), response);
- network()->runReadyNetworkOperations();
+ network()->runReadyNetworkOperations();
- network()->exitNetwork();
- }
+ network()->exitNetwork();
+}
- void CatalogManagerReplSetTestFixture::onFindCommand(OnFindCommandFunction func) {
- onCommand([&func](const RemoteCommandRequest& request) -> StatusWith<BSONObj> {
+void CatalogManagerReplSetTestFixture::onFindCommand(OnFindCommandFunction func) {
+ onCommand([&func](const RemoteCommandRequest& request) -> StatusWith<BSONObj> {
- const auto& resultStatus = func(request);
+ const auto& resultStatus = func(request);
- if (!resultStatus.isOK()) {
- return resultStatus.getStatus();
- }
+ if (!resultStatus.isOK()) {
+ return resultStatus.getStatus();
+ }
- BSONArrayBuilder arr;
- for (const auto& obj : resultStatus.getValue()) {
- arr.append(obj);
- }
+ BSONArrayBuilder arr;
+ for (const auto& obj : resultStatus.getValue()) {
+ arr.append(obj);
+ }
- const NamespaceString nss = NamespaceString(request.dbname,
- request.cmdObj.firstElement().String());
- BSONObjBuilder result;
- appendCursorResponseObject(0LL, nss.toString(), arr.arr(), &result);
+ const NamespaceString nss =
+ NamespaceString(request.dbname, request.cmdObj.firstElement().String());
+ BSONObjBuilder result;
+ appendCursorResponseObject(0LL, nss.toString(), arr.arr(), &result);
- return result.obj();
- });
- }
+ return result.obj();
+ });
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/catalog/replset/catalog_manager_replica_set_test_fixture.h b/src/mongo/s/catalog/replset/catalog_manager_replica_set_test_fixture.h
index 05a5dcf4e1f..9331f44ad4f 100644
--- a/src/mongo/s/catalog/replset/catalog_manager_replica_set_test_fixture.h
+++ b/src/mongo/s/catalog/replset/catalog_manager_replica_set_test_fixture.h
@@ -36,74 +36,74 @@
namespace mongo {
- class BSONObj;
- class CatalogManagerReplicaSet;
- class DistLockManagerMock;
- struct RemoteCommandRequest;
- class RemoteCommandRunnerMock;
- class ShardRegistry;
- template<typename T> class StatusWith;
+class BSONObj;
+class CatalogManagerReplicaSet;
+class DistLockManagerMock;
+struct RemoteCommandRequest;
+class RemoteCommandRunnerMock;
+class ShardRegistry;
+template <typename T>
+class StatusWith;
namespace executor {
- class NetworkInterfaceMock;
+class NetworkInterfaceMock;
-} // namespace executor
+} // namespace executor
+/**
+ * Sets up the mocked out objects for testing the replica-set backed catalog manager.
+ */
+class CatalogManagerReplSetTestFixture : public mongo::unittest::Test {
+public:
+ CatalogManagerReplSetTestFixture();
+ ~CatalogManagerReplSetTestFixture();
+
+protected:
/**
- * Sets up the mocked out objects for testing the replica-set backed catalog manager.
+ * Shortcut function to be used for generating mock responses to network requests.
+ *
+ * @param dbName Name of the database for which this request came.
+ * @param cmdObj Contents of the request.
+ *
+ * Return the BSON object representing the response(s) or an error, which will be passed
+ * back on the network.
*/
- class CatalogManagerReplSetTestFixture : public mongo::unittest::Test {
- public:
- CatalogManagerReplSetTestFixture();
- ~CatalogManagerReplSetTestFixture();
+ using OnCommandFunction = std::function<StatusWith<BSONObj>(const RemoteCommandRequest&)>;
- protected:
- /**
- * Shortcut function to be used for generating mock responses to network requests.
- *
- * @param dbName Name of the database for which this request came.
- * @param cmdObj Contents of the request.
- *
- * Return the BSON object representing the response(s) or an error, which will be passed
- * back on the network.
- */
- using OnCommandFunction =
- std::function<StatusWith<BSONObj>(const RemoteCommandRequest&)>;
+ using OnFindCommandFunction =
+ std::function<StatusWith<std::vector<BSONObj>>(const RemoteCommandRequest&)>;
- using OnFindCommandFunction =
- std::function<StatusWith<std::vector<BSONObj>>(const RemoteCommandRequest&)>;
+ CatalogManagerReplicaSet* catalogManager() const;
- CatalogManagerReplicaSet* catalogManager() const;
+ ShardRegistry* shardRegistry() const;
- ShardRegistry* shardRegistry() const;
+ RemoteCommandRunnerMock* commandRunner() const;
- RemoteCommandRunnerMock* commandRunner() const;
+ executor::NetworkInterfaceMock* network() const;
- executor::NetworkInterfaceMock* network() const;
+ DistLockManagerMock* distLock() const;
- DistLockManagerMock* distLock() const;
-
- /**
- * Blocking methods, which receive one message from the network and respond using the
- * responses returned from the input function. This is a syntactic sugar for simple,
- * single request + response or find tests.
- */
- void onCommand(OnCommandFunction func);
- void onFindCommand(OnFindCommandFunction func);
+ /**
+ * Blocking methods, which receive one message from the network and respond using the
+ * responses returned from the input function. This is a syntactic sugar for simple,
+ * single request + response or find tests.
+ */
+ void onCommand(OnCommandFunction func);
+ void onFindCommand(OnFindCommandFunction func);
- private:
- void setUp() override;
+private:
+ void setUp() override;
- void tearDown() override;
+ void tearDown() override;
- // Mocked out network under the task executor. This pointer is owned by the executor on
- // the ShardRegistry, so it must not be accessed once the executor has been shut down.
- executor::NetworkInterfaceMock* _mockNetwork;
+ // Mocked out network under the task executor. This pointer is owned by the executor on
+ // the ShardRegistry, so it must not be accessed once the executor has been shut down.
+ executor::NetworkInterfaceMock* _mockNetwork;
- // Thread used to execute the task executor's loop. This thread will be busy until the
- // shutdown is called on the shard registry's task executor.
- std::thread _executorThread;
- };
+ // Thread used to execute the task executor's loop. This thread will be busy until the
+ // shutdown is called on the shard registry's task executor.
+ std::thread _executorThread;
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/catalog/replset/replset_dist_lock_manager.cpp b/src/mongo/s/catalog/replset/replset_dist_lock_manager.cpp
index 5d38ea337cd..ef329ded1eb 100644
--- a/src/mongo/s/catalog/replset/replset_dist_lock_manager.cpp
+++ b/src/mongo/s/catalog/replset/replset_dist_lock_manager.cpp
@@ -48,342 +48,321 @@
namespace mongo {
- using std::string;
- using std::unique_ptr;
- using stdx::chrono::milliseconds;
- using std::chrono::duration_cast;
-
- ReplSetDistLockManager::ReplSetDistLockManager(ServiceContext* globalContext,
- StringData processID,
- unique_ptr<DistLockCatalog> catalog,
- milliseconds pingInterval,
- milliseconds lockExpiration):
- _serviceContext(globalContext),
- _processID(processID.toString()),
- _catalog(std::move(catalog)),
- _pingInterval(pingInterval),
- _lockExpiration(lockExpiration) {
+using std::string;
+using std::unique_ptr;
+using stdx::chrono::milliseconds;
+using std::chrono::duration_cast;
+
+ReplSetDistLockManager::ReplSetDistLockManager(ServiceContext* globalContext,
+ StringData processID,
+ unique_ptr<DistLockCatalog> catalog,
+ milliseconds pingInterval,
+ milliseconds lockExpiration)
+ : _serviceContext(globalContext),
+ _processID(processID.toString()),
+ _catalog(std::move(catalog)),
+ _pingInterval(pingInterval),
+ _lockExpiration(lockExpiration) {}
+
+ReplSetDistLockManager::~ReplSetDistLockManager() = default;
+
+void ReplSetDistLockManager::startUp() {
+ _execThread = stdx::make_unique<stdx::thread>(&ReplSetDistLockManager::doTask, this);
+}
+
+void ReplSetDistLockManager::shutDown() {
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _isShutDown = true;
+ _shutDownCV.notify_all();
}
- ReplSetDistLockManager::~ReplSetDistLockManager() = default;
+ // Don't grab _mutex, otherwise will deadlock trying to join. Safe to read
+ // _execThread since it is modified only at statrUp().
+ if (_execThread && _execThread->joinable()) {
+ _execThread->join();
+ _execThread.reset();
+ }
- void ReplSetDistLockManager::startUp() {
- _execThread = stdx::make_unique<stdx::thread>(&ReplSetDistLockManager::doTask, this);
+ auto status = _catalog->stopPing(_processID);
+ if (!status.isOK()) {
+ warning() << "error encountered while cleaning up distributed ping entry for " << _processID
+ << causedBy(status);
}
+}
- void ReplSetDistLockManager::shutDown() {
- {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- _isShutDown = true;
- _shutDownCV.notify_all();
- }
+bool ReplSetDistLockManager::isShutDown() {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ return _isShutDown;
+}
- // Don't grab _mutex, otherwise will deadlock trying to join. Safe to read
- // _execThread since it is modified only at statrUp().
- if (_execThread && _execThread->joinable()) {
- _execThread->join();
- _execThread.reset();
- }
+void ReplSetDistLockManager::doTask() {
+ LOG(0) << "creating distributed lock ping thread for process " << _processID
+ << " (sleeping for " << duration_cast<milliseconds>(_pingInterval).count() << " ms)";
+
+ Timer elapsedSincelastPing(_serviceContext->getTickSource());
+ while (!isShutDown()) {
+ auto pingStatus = _catalog->ping(_processID, Date_t::now());
- auto status = _catalog->stopPing(_processID);
- if (!status.isOK()) {
- warning() << "error encountered while cleaning up distributed ping entry for "
- << _processID << causedBy(status);
+ if (!pingStatus.isOK()) {
+ warning() << "pinging failed for distributed lock pinger" << causedBy(pingStatus);
}
- }
- bool ReplSetDistLockManager::isShutDown() {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- return _isShutDown;
- }
+ const milliseconds elapsed(elapsedSincelastPing.millis());
+ if (elapsed > 10 * _pingInterval) {
+ warning() << "Lock pinger for proc: " << _processID << " was inactive for " << elapsed
+ << " ms";
+ }
+ elapsedSincelastPing.reset();
- void ReplSetDistLockManager::doTask() {
- LOG(0) << "creating distributed lock ping thread for process " << _processID
- << " (sleeping for "
- << duration_cast<milliseconds>(_pingInterval).count() << " ms)";
+ std::deque<DistLockHandle> toUnlockBatch;
+ {
+ stdx::unique_lock<stdx::mutex> lk(_mutex);
+ toUnlockBatch.swap(_unlockList);
+ }
- Timer elapsedSincelastPing(_serviceContext->getTickSource());
- while (!isShutDown()) {
- auto pingStatus = _catalog->ping(_processID, Date_t::now());
+ for (const auto& toUnlock : toUnlockBatch) {
+ auto unlockStatus = _catalog->unlock(toUnlock);
- if (!pingStatus.isOK()) {
- warning() << "pinging failed for distributed lock pinger" << causedBy(pingStatus);
+ if (!unlockStatus.isOK()) {
+ warning() << "Failed to unlock lock with " << LocksType::lockID() << ": "
+ << toUnlock << causedBy(unlockStatus);
+ queueUnlock(toUnlock);
+ } else {
+ LOG(0) << "distributed lock with " << LocksType::lockID() << ": " << toUnlock
+ << "' unlocked.";
}
- const milliseconds elapsed(elapsedSincelastPing.millis());
- if (elapsed > 10 * _pingInterval) {
- warning() << "Lock pinger for proc: " << _processID
- << " was inactive for " << elapsed << " ms";
+ if (isShutDown()) {
+ return;
}
- elapsedSincelastPing.reset();
+ }
- std::deque<DistLockHandle> toUnlockBatch;
- {
- stdx::unique_lock<stdx::mutex> lk(_mutex);
- toUnlockBatch.swap(_unlockList);
- }
+ stdx::unique_lock<stdx::mutex> lk(_mutex);
+ _shutDownCV.wait_for(lk, _pingInterval);
+ }
+}
- for (const auto& toUnlock : toUnlockBatch) {
- auto unlockStatus = _catalog->unlock(toUnlock);
+StatusWith<bool> ReplSetDistLockManager::canOvertakeLock(LocksType lockDoc) {
+ const auto& processID = lockDoc.getProcess();
+ auto pingStatus = _catalog->getPing(processID);
+ if (!pingStatus.isOK()) {
+ return pingStatus.getStatus();
+ }
- if (!unlockStatus.isOK()) {
- warning() << "Failed to unlock lock with " << LocksType::lockID()
- << ": " << toUnlock << causedBy(unlockStatus);
- queueUnlock(toUnlock);
- }
- else {
- LOG(0) << "distributed lock with " << LocksType::lockID()
- << ": " << toUnlock << "' unlocked.";
- }
+ const auto& pingDoc = pingStatus.getValue();
+ string errMsg;
+ if (!pingDoc.isValid(&errMsg)) {
+ return {ErrorCodes::UnsupportedFormat,
+ str::stream() << "invalid ping document for " << processID << ": " << errMsg};
+ }
- if (isShutDown()) {
- return;
- }
- }
+ Timer timer(_serviceContext->getTickSource());
+ auto serverInfoStatus = _catalog->getServerInfo();
+ if (!serverInfoStatus.isOK()) {
+ return serverInfoStatus.getStatus();
+ }
- stdx::unique_lock<stdx::mutex> lk(_mutex);
- _shutDownCV.wait_for(lk, _pingInterval);
- }
+ // Be conservative when determining that lock expiration has elapsed by
+ // taking into account the roundtrip delay of trying to get the local
+ // time from the config server.
+ milliseconds delay(timer.millis() / 2); // Assuming symmetrical delay.
+
+ Date_t pingValue = pingDoc.getPing();
+ const auto& serverInfo = serverInfoStatus.getValue();
+
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ auto pingIter = _pingHistory.find(lockDoc.getName());
+
+ if (pingIter == _pingHistory.end()) {
+ // We haven't seen this lock before so we don't have any point of reference
+ // to compare and determine the elapsed time. Save the current ping info
+ // for this lock.
+ _pingHistory.emplace(std::piecewise_construct,
+ std::forward_as_tuple(lockDoc.getName()),
+ std::forward_as_tuple(processID,
+ pingValue,
+ serverInfo.serverTime,
+ lockDoc.getLockID(),
+ serverInfo.electionId));
+ return false;
}
- StatusWith<bool> ReplSetDistLockManager::canOvertakeLock(LocksType lockDoc) {
- const auto& processID = lockDoc.getProcess();
- auto pingStatus = _catalog->getPing(processID);
- if (!pingStatus.isOK()) {
- return pingStatus.getStatus();
- }
+ auto configServerLocalTime = serverInfo.serverTime - delay;
- const auto& pingDoc = pingStatus.getValue();
- string errMsg;
- if (!pingDoc.isValid(&errMsg)) {
- return {ErrorCodes::UnsupportedFormat,
- str::stream() << "invalid ping document for " << processID
- << ": " << errMsg};
- }
+ auto* pingInfo = &pingIter->second;
- Timer timer(_serviceContext->getTickSource());
- auto serverInfoStatus = _catalog->getServerInfo();
- if (!serverInfoStatus.isOK()) {
- return serverInfoStatus.getStatus();
- }
+ LOG(1) << "checking last ping for lock '" << lockDoc.getName() << "' against last seen process "
+ << pingInfo->processId << " and ping " << pingInfo->lastPing;
- // Be conservative when determining that lock expiration has elapsed by
- // taking into account the roundtrip delay of trying to get the local
- // time from the config server.
- milliseconds delay(timer.millis() / 2); // Assuming symmetrical delay.
+ if (pingInfo->lastPing != pingValue || // ping is active
- Date_t pingValue = pingDoc.getPing();
- const auto& serverInfo = serverInfoStatus.getValue();
+ // Owner of this lock is now different from last time so we can't
+ // use the ping data.
+ pingInfo->lockSessionId != lockDoc.getLockID() ||
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- auto pingIter = _pingHistory.find(lockDoc.getName());
-
- if (pingIter == _pingHistory.end()) {
- // We haven't seen this lock before so we don't have any point of reference
- // to compare and determine the elapsed time. Save the current ping info
- // for this lock.
- _pingHistory.emplace(std::piecewise_construct,
- std::forward_as_tuple(lockDoc.getName()),
- std::forward_as_tuple(processID,
- pingValue,
- serverInfo.serverTime,
- lockDoc.getLockID(),
- serverInfo.electionId));
- return false;
- }
+ // Primary changed, we can't trust that clocks are synchronized so
+ // treat as if this is a new entry.
+ pingInfo->electionId != serverInfo.electionId) {
+ pingInfo->lastPing = pingValue;
+ pingInfo->electionId = serverInfo.electionId;
+ pingInfo->configLocalTime = configServerLocalTime;
+ pingInfo->lockSessionId = lockDoc.getLockID();
+ return false;
+ }
+
+ if (configServerLocalTime < pingInfo->configLocalTime) {
+ warning() << "config server local time went backwards, from last seen: "
+ << pingInfo->configLocalTime << " to " << configServerLocalTime;
+ return false;
+ }
- auto configServerLocalTime = serverInfo.serverTime - delay;
+ milliseconds elapsedSinceLastPing(configServerLocalTime - pingInfo->configLocalTime);
+ if (elapsedSinceLastPing >= _lockExpiration) {
+ LOG(0) << "forcing lock '" << lockDoc.getName() << "' because elapsed time "
+ << duration_cast<milliseconds>(elapsedSinceLastPing).count()
+ << " ms >= takeover time " << duration_cast<milliseconds>(_lockExpiration).count()
+ << " ms";
+ return true;
+ }
- auto* pingInfo = &pingIter->second;
+ LOG(1) << "could not force lock '" << lockDoc.getName() << "' because elapsed time "
+ << duration_cast<milliseconds>(elapsedSinceLastPing).count() << " ms < takeover time "
+ << duration_cast<milliseconds>(_lockExpiration).count() << " ms";
+ return false;
+}
- LOG(1) << "checking last ping for lock '" << lockDoc.getName()
- << "' against last seen process " << pingInfo->processId
- << " and ping " << pingInfo->lastPing;
+StatusWith<DistLockManager::ScopedDistLock> ReplSetDistLockManager::lock(
+ StringData name, StringData whyMessage, milliseconds waitFor, milliseconds lockTryInterval) {
+ Timer timer(_serviceContext->getTickSource());
+ Timer msgTimer(_serviceContext->getTickSource());
- if (pingInfo->lastPing != pingValue || // ping is active
+ while (waitFor <= milliseconds::zero() || milliseconds(timer.millis()) < waitFor) {
+ OID lockSessionID = OID::gen();
+ string who = str::stream() << _processID << ":" << getThreadName();
- // Owner of this lock is now different from last time so we can't
- // use the ping data.
- pingInfo->lockSessionId != lockDoc.getLockID() ||
+ LOG(1) << "trying to acquire new distributed lock for " << name
+ << " ( lock timeout : " << duration_cast<milliseconds>(_lockExpiration).count()
+ << " ms, ping interval : " << duration_cast<milliseconds>(_pingInterval).count()
+ << " ms, process : " << _processID << " )"
+ << " with lockSessionID: " << lockSessionID << ", why: " << whyMessage;
- // Primary changed, we can't trust that clocks are synchronized so
- // treat as if this is a new entry.
- pingInfo->electionId != serverInfo.electionId) {
- pingInfo->lastPing = pingValue;
- pingInfo->electionId = serverInfo.electionId;
- pingInfo->configLocalTime = configServerLocalTime;
- pingInfo->lockSessionId = lockDoc.getLockID();
- return false;
- }
+ auto lockResult =
+ _catalog->grabLock(name, lockSessionID, who, _processID, Date_t::now(), whyMessage);
+
+ auto status = lockResult.getStatus();
- if (configServerLocalTime < pingInfo->configLocalTime) {
- warning() << "config server local time went backwards, from last seen: "
- << pingInfo->configLocalTime
- << " to " << configServerLocalTime;
- return false;
+ if (status.isOK()) {
+ // Lock is acquired since findAndModify was able to successfully modify
+ // the lock document.
+ LOG(0) << "distributed lock '" << name << "' acquired, ts : " << lockSessionID;
+ return ScopedDistLock(lockSessionID, this);
}
- milliseconds elapsedSinceLastPing(configServerLocalTime - pingInfo->configLocalTime);
- if (elapsedSinceLastPing >= _lockExpiration) {
- LOG(0) << "forcing lock '" << lockDoc.getName()
- << "' because elapsed time "
- << duration_cast<milliseconds>(elapsedSinceLastPing).count()
- << " ms >= takeover time "
- << duration_cast<milliseconds>(_lockExpiration).count() << " ms";
- return true;
+ if (status != ErrorCodes::LockStateChangeFailed) {
+ // An error occurred but the write might have actually been applied on the
+ // other side. Schedule an unlock to clean it up just in case.
+ queueUnlock(lockSessionID);
+ return status;
}
- LOG(1) << "could not force lock '" << lockDoc.getName()
- << "' because elapsed time "
- << duration_cast<milliseconds>(elapsedSinceLastPing).count()
- << " ms < takeover time "
- << duration_cast<milliseconds>(_lockExpiration).count() << " ms";
- return false;
- }
+ // Get info from current lock and check if we can overtake it.
+ auto getLockStatusResult = _catalog->getLockByName(name);
+ const auto& getLockStatus = getLockStatusResult.getStatus();
- StatusWith<DistLockManager::ScopedDistLock> ReplSetDistLockManager::lock(
- StringData name,
- StringData whyMessage,
- milliseconds waitFor,
- milliseconds lockTryInterval) {
- Timer timer(_serviceContext->getTickSource());
- Timer msgTimer(_serviceContext->getTickSource());
-
- while (waitFor <= milliseconds::zero() || milliseconds(timer.millis()) < waitFor) {
- OID lockSessionID = OID::gen();
- string who = str::stream() << _processID << ":" << getThreadName();
-
- LOG(1) << "trying to acquire new distributed lock for " << name
- << " ( lock timeout : "
- << duration_cast<milliseconds>(_lockExpiration).count()
- << " ms, ping interval : "
- << duration_cast<milliseconds>(_pingInterval).count()
- << " ms, process : " << _processID << " )"
- << " with lockSessionID: " << lockSessionID
- << ", why: " << whyMessage;
-
- auto lockResult = _catalog->grabLock(name,
- lockSessionID,
- who,
- _processID,
- Date_t::now(),
- whyMessage);
-
- auto status = lockResult.getStatus();
-
- if (status.isOK()) {
- // Lock is acquired since findAndModify was able to successfully modify
- // the lock document.
- LOG(0) << "distributed lock '" << name << "' acquired, ts : " << lockSessionID;
- return ScopedDistLock(lockSessionID, this);
- }
+ if (!getLockStatusResult.isOK() && getLockStatus != ErrorCodes::LockNotFound) {
+ return getLockStatus;
+ }
+
+ // Note: Only attempt to overtake locks that actually exists. If lock was not
+ // found, use the normal grab lock path to acquire it.
+ if (getLockStatusResult.isOK()) {
+ auto currentLock = getLockStatusResult.getValue();
+ auto canOvertakeResult = canOvertakeLock(currentLock);
- if (status != ErrorCodes::LockStateChangeFailed) {
- // An error occurred but the write might have actually been applied on the
- // other side. Schedule an unlock to clean it up just in case.
- queueUnlock(lockSessionID);
- return status;
+ if (!canOvertakeResult.isOK()) {
+ return canOvertakeResult.getStatus();
}
- // Get info from current lock and check if we can overtake it.
- auto getLockStatusResult = _catalog->getLockByName(name);
- const auto& getLockStatus = getLockStatusResult.getStatus();
+ if (canOvertakeResult.getValue()) {
+ auto overtakeResult = _catalog->overtakeLock(name,
+ lockSessionID,
+ currentLock.getLockID(),
+ who,
+ _processID,
+ Date_t::now(),
+ whyMessage);
- if (!getLockStatusResult.isOK() && getLockStatus != ErrorCodes::LockNotFound) {
- return getLockStatus;
- }
+ const auto& overtakeStatus = overtakeResult.getStatus();
- // Note: Only attempt to overtake locks that actually exists. If lock was not
- // found, use the normal grab lock path to acquire it.
- if (getLockStatusResult.isOK()) {
- auto currentLock = getLockStatusResult.getValue();
- auto canOvertakeResult = canOvertakeLock(currentLock);
+ if (overtakeResult.isOK()) {
+ // Lock is acquired since findAndModify was able to successfully modify
+ // the lock document.
- if (!canOvertakeResult.isOK()) {
- return canOvertakeResult.getStatus();
+ LOG(0) << "lock '" << name << "' successfully forced";
+ LOG(0) << "distributed lock '" << name << "' acquired, ts : " << lockSessionID;
+ return ScopedDistLock(lockSessionID, this);
}
- if (canOvertakeResult.getValue()) {
- auto overtakeResult = _catalog->overtakeLock(name,
- lockSessionID,
- currentLock.getLockID(),
- who,
- _processID,
- Date_t::now(),
- whyMessage);
-
- const auto& overtakeStatus = overtakeResult.getStatus();
-
- if (overtakeResult.isOK()) {
- // Lock is acquired since findAndModify was able to successfully modify
- // the lock document.
-
- LOG(0) << "lock '" << name << "' successfully forced";
- LOG(0) << "distributed lock '" << name
- << "' acquired, ts : " << lockSessionID;
- return ScopedDistLock(lockSessionID, this);
- }
-
- if (overtakeStatus != ErrorCodes::LockStateChangeFailed) {
- // An error occurred but the write might have actually been applied on the
- // other side. Schedule an unlock to clean it up just in case.
- queueUnlock(lockSessionID);
- return overtakeStatus;
- }
+ if (overtakeStatus != ErrorCodes::LockStateChangeFailed) {
+ // An error occurred but the write might have actually been applied on the
+ // other side. Schedule an unlock to clean it up just in case.
+ queueUnlock(lockSessionID);
+ return overtakeStatus;
}
}
+ }
- LOG(1) << "distributed lock '" << name << "' was not acquired.";
-
- if (waitFor == milliseconds::zero()) {
- break;
- }
+ LOG(1) << "distributed lock '" << name << "' was not acquired.";
- // Periodically message for debugging reasons
- if (msgTimer.seconds() > 10) {
- LOG(0) << "waited " << timer.seconds() << "s for distributed lock " << name
- << " for " << whyMessage;
+ if (waitFor == milliseconds::zero()) {
+ break;
+ }
- msgTimer.reset();
- }
+ // Periodically message for debugging reasons
+ if (msgTimer.seconds() > 10) {
+ LOG(0) << "waited " << timer.seconds() << "s for distributed lock " << name << " for "
+ << whyMessage;
- milliseconds timeRemaining =
- std::max(milliseconds::zero(), waitFor - milliseconds(timer.millis()));
- sleepFor(std::min(lockTryInterval, timeRemaining));
+ msgTimer.reset();
}
- return {ErrorCodes::LockBusy, str::stream() << "timed out waiting for " << name};
+ milliseconds timeRemaining =
+ std::max(milliseconds::zero(), waitFor - milliseconds(timer.millis()));
+ sleepFor(std::min(lockTryInterval, timeRemaining));
}
- void ReplSetDistLockManager::unlock(const DistLockHandle& lockSessionID) {
- auto unlockStatus = _catalog->unlock(lockSessionID);
-
- if (!unlockStatus.isOK()) {
- queueUnlock(lockSessionID);
- }
- else {
- LOG(0) << "distributed lock with " << LocksType::lockID()
- << ": " << lockSessionID << "' unlocked.";
- }
- }
+ return {ErrorCodes::LockBusy, str::stream() << "timed out waiting for " << name};
+}
- Status ReplSetDistLockManager::checkStatus(const DistLockHandle& lockHandle) {
- auto lockStatus = _catalog->getLockByTS(lockHandle);
+void ReplSetDistLockManager::unlock(const DistLockHandle& lockSessionID) {
+ auto unlockStatus = _catalog->unlock(lockSessionID);
- if (!lockStatus.isOK()) {
- return lockStatus.getStatus();
- }
+ if (!unlockStatus.isOK()) {
+ queueUnlock(lockSessionID);
+ } else {
+ LOG(0) << "distributed lock with " << LocksType::lockID() << ": " << lockSessionID
+ << "' unlocked.";
+ }
+}
- auto lockDoc = lockStatus.getValue();
- if (!lockDoc.isValid(nullptr)) {
- return {ErrorCodes::LockNotFound, "lock owner changed"};
- }
+Status ReplSetDistLockManager::checkStatus(const DistLockHandle& lockHandle) {
+ auto lockStatus = _catalog->getLockByTS(lockHandle);
- return Status::OK();
+ if (!lockStatus.isOK()) {
+ return lockStatus.getStatus();
}
- void ReplSetDistLockManager::queueUnlock(const DistLockHandle& lockSessionID) {
- stdx::unique_lock<stdx::mutex> lk(_mutex);
- _unlockList.push_back(lockSessionID);
+ auto lockDoc = lockStatus.getValue();
+ if (!lockDoc.isValid(nullptr)) {
+ return {ErrorCodes::LockNotFound, "lock owner changed"};
}
+
+ return Status::OK();
+}
+
+void ReplSetDistLockManager::queueUnlock(const DistLockHandle& lockSessionID) {
+ stdx::unique_lock<stdx::mutex> lk(_mutex);
+ _unlockList.push_back(lockSessionID);
+}
}
diff --git a/src/mongo/s/catalog/replset/replset_dist_lock_manager.h b/src/mongo/s/catalog/replset/replset_dist_lock_manager.h
index 4e042e2fa41..1779925b8ec 100644
--- a/src/mongo/s/catalog/replset/replset_dist_lock_manager.h
+++ b/src/mongo/s/catalog/replset/replset_dist_lock_manager.h
@@ -43,88 +43,86 @@
namespace mongo {
- class ServiceContext;
-
- class ReplSetDistLockManager final : public DistLockManager {
- public:
- ReplSetDistLockManager(ServiceContext* globalContext,
- StringData processID,
- std::unique_ptr<DistLockCatalog> catalog,
- stdx::chrono::milliseconds pingInterval,
- stdx::chrono::milliseconds lockExpiration);
-
- virtual ~ReplSetDistLockManager();
-
- virtual void startUp() override;
- virtual void shutDown() override;
-
- virtual StatusWith<DistLockManager::ScopedDistLock> lock(
- StringData name,
- StringData whyMessage,
- stdx::chrono::milliseconds waitFor,
- stdx::chrono::milliseconds lockTryInterval) override;
-
- protected:
-
- virtual void unlock(const DistLockHandle& lockSessionID) override;
-
- virtual Status checkStatus(const DistLockHandle& lockSessionID) override;
-
- private:
-
- /**
- * Queue a lock to be unlocked asynchronously with retry until it doesn't error.
- */
- void queueUnlock(const DistLockHandle& lockSessionID);
-
- /**
- * Periodically pings and checks if there are locks queued that needs unlocking.
- */
- void doTask();
-
- /**
- * Returns true if shutDown was called.
- */
- bool isShutDown();
-
- /**
- * Returns true if the current process that owns the lock has no fresh pings since
- * the lock expiration threshold.
- */
- StatusWith<bool> canOvertakeLock(const LocksType lockDoc);
-
- //
- // All member variables are labeled with one of the following codes indicating the
- // synchronization rules for accessing them.
- //
- // (F) Self synchronizing.
- // (M) Must hold _mutex for access.
- // (I) Immutable, no synchronization needed.
- // (S) Can only be called inside startUp/shutDown.
- //
-
- ServiceContext* const _serviceContext; // (F)
-
- const std::string _processID; // (I)
- const std::unique_ptr<DistLockCatalog> _catalog; // (I)
- const stdx::chrono::milliseconds _pingInterval; // (I)
- const stdx::chrono::milliseconds _lockExpiration; // (I)
-
- stdx::mutex _mutex;
- std::unique_ptr<stdx::thread> _execThread; // (S)
-
- // Contains the list of locks queued for unlocking. Cases when unlock operation can
- // be queued include:
- // 1. First attempt on unlocking resulted in an error.
- // 2. Attempting to grab or overtake a lock resulted in an error where we are uncertain
- // whether the modification was actually applied or not, and call unlock to make
- // sure that it was cleaned up.
- std::deque<DistLockHandle> _unlockList; // (M)
-
- bool _isShutDown = false; // (M)
- stdx::condition_variable _shutDownCV; // (M)
-
- // Map of lockName to last ping information.
- std::unordered_map<std::string, DistLockPingInfo> _pingHistory; // (M)
- };
+class ServiceContext;
+
+class ReplSetDistLockManager final : public DistLockManager {
+public:
+ ReplSetDistLockManager(ServiceContext* globalContext,
+ StringData processID,
+ std::unique_ptr<DistLockCatalog> catalog,
+ stdx::chrono::milliseconds pingInterval,
+ stdx::chrono::milliseconds lockExpiration);
+
+ virtual ~ReplSetDistLockManager();
+
+ virtual void startUp() override;
+ virtual void shutDown() override;
+
+ virtual StatusWith<DistLockManager::ScopedDistLock> lock(
+ StringData name,
+ StringData whyMessage,
+ stdx::chrono::milliseconds waitFor,
+ stdx::chrono::milliseconds lockTryInterval) override;
+
+protected:
+ virtual void unlock(const DistLockHandle& lockSessionID) override;
+
+ virtual Status checkStatus(const DistLockHandle& lockSessionID) override;
+
+private:
+ /**
+ * Queue a lock to be unlocked asynchronously with retry until it doesn't error.
+ */
+ void queueUnlock(const DistLockHandle& lockSessionID);
+
+ /**
+ * Periodically pings and checks if there are locks queued that needs unlocking.
+ */
+ void doTask();
+
+ /**
+ * Returns true if shutDown was called.
+ */
+ bool isShutDown();
+
+ /**
+ * Returns true if the current process that owns the lock has no fresh pings since
+ * the lock expiration threshold.
+ */
+ StatusWith<bool> canOvertakeLock(const LocksType lockDoc);
+
+ //
+ // All member variables are labeled with one of the following codes indicating the
+ // synchronization rules for accessing them.
+ //
+ // (F) Self synchronizing.
+ // (M) Must hold _mutex for access.
+ // (I) Immutable, no synchronization needed.
+ // (S) Can only be called inside startUp/shutDown.
+ //
+
+ ServiceContext* const _serviceContext; // (F)
+
+ const std::string _processID; // (I)
+ const std::unique_ptr<DistLockCatalog> _catalog; // (I)
+ const stdx::chrono::milliseconds _pingInterval; // (I)
+ const stdx::chrono::milliseconds _lockExpiration; // (I)
+
+ stdx::mutex _mutex;
+ std::unique_ptr<stdx::thread> _execThread; // (S)
+
+ // Contains the list of locks queued for unlocking. Cases when unlock operation can
+ // be queued include:
+ // 1. First attempt on unlocking resulted in an error.
+ // 2. Attempting to grab or overtake a lock resulted in an error where we are uncertain
+ // whether the modification was actually applied or not, and call unlock to make
+ // sure that it was cleaned up.
+ std::deque<DistLockHandle> _unlockList; // (M)
+
+ bool _isShutDown = false; // (M)
+ stdx::condition_variable _shutDownCV; // (M)
+
+ // Map of lockName to last ping information.
+ std::unordered_map<std::string, DistLockPingInfo> _pingHistory; // (M)
+};
}
diff --git a/src/mongo/s/catalog/replset/replset_dist_lock_manager_test.cpp b/src/mongo/s/catalog/replset/replset_dist_lock_manager_test.cpp
index 50e05ece2cc..52395a65e62 100644
--- a/src/mongo/s/catalog/replset/replset_dist_lock_manager_test.cpp
+++ b/src/mongo/s/catalog/replset/replset_dist_lock_manager_test.cpp
@@ -61,138 +61,132 @@
namespace mongo {
namespace {
- using std::map;
- using std::string;
- using std::vector;
+using std::map;
+using std::string;
+using std::vector;
- const Seconds kUnlockTimeout(30);
- const Milliseconds kPingInterval(2);
- const Seconds kLockExpiration(10);
+const Seconds kUnlockTimeout(30);
+const Milliseconds kPingInterval(2);
+const Seconds kLockExpiration(10);
+
+/**
+ * Basic fixture for ReplSetDistLockManager that starts it up before the test begins
+ * and shuts it down when a test finishes.
+ */
+class ReplSetDistLockManagerFixture : public mongo::unittest::Test {
+public:
+ ReplSetDistLockManagerFixture()
+ : _dummyDoNotUse(stdx::make_unique<DistLockCatalogMock>()),
+ _mockCatalog(_dummyDoNotUse.get()),
+ _processID("test"),
+ _mgr(&_context, _processID, std::move(_dummyDoNotUse), kPingInterval, kLockExpiration) {}
/**
- * Basic fixture for ReplSetDistLockManager that starts it up before the test begins
- * and shuts it down when a test finishes.
+ * Returns the lock manager instance that is being tested.
*/
- class ReplSetDistLockManagerFixture: public mongo::unittest::Test {
- public:
- ReplSetDistLockManagerFixture():
- _dummyDoNotUse(stdx::make_unique<DistLockCatalogMock>()),
- _mockCatalog(_dummyDoNotUse.get()),
- _processID("test"),
- _mgr(&_context,
- _processID,
- std::move(_dummyDoNotUse),
- kPingInterval,
- kLockExpiration) {
- }
-
- /**
- * Returns the lock manager instance that is being tested.
- */
- ReplSetDistLockManager* getMgr() {
- return &_mgr;
- }
+ ReplSetDistLockManager* getMgr() {
+ return &_mgr;
+ }
- /**
- * Returns the mocked catalog used by the lock manager being tested.
- */
- DistLockCatalogMock* getMockCatalog() {
- return _mockCatalog;
- }
+ /**
+ * Returns the mocked catalog used by the lock manager being tested.
+ */
+ DistLockCatalogMock* getMockCatalog() {
+ return _mockCatalog;
+ }
- /**
- * Get the process id that was initialiezd with the lock manager being tested.
- */
- string getProcessID() const {
- return _processID;
- }
+ /**
+ * Get the process id that was initialiezd with the lock manager being tested.
+ */
+ string getProcessID() const {
+ return _processID;
+ }
- protected:
- void setUp() override {
- _context.setTickSource(stdx::make_unique<SystemTickSource>());
- _mgr.startUp();
- }
+protected:
+ void setUp() override {
+ _context.setTickSource(stdx::make_unique<SystemTickSource>());
+ _mgr.startUp();
+ }
- void tearDown() override {
- // Don't care about what shutDown passes to stopPing here.
- _mockCatalog->expectStopPing([](StringData){}, Status::OK());
- _mgr.shutDown();
- }
+ void tearDown() override {
+ // Don't care about what shutDown passes to stopPing here.
+ _mockCatalog->expectStopPing([](StringData) {}, Status::OK());
+ _mgr.shutDown();
+ }
- TickSourceMock _tickSource;
- std::unique_ptr<DistLockCatalogMock> _dummyDoNotUse; // dummy placeholder
- DistLockCatalogMock* _mockCatalog;
- string _processID;
- ServiceContextNoop _context;
- ReplSetDistLockManager _mgr;
- };
+ TickSourceMock _tickSource;
+ std::unique_ptr<DistLockCatalogMock> _dummyDoNotUse; // dummy placeholder
+ DistLockCatalogMock* _mockCatalog;
+ string _processID;
+ ServiceContextNoop _context;
+ ReplSetDistLockManager _mgr;
+};
- class RSDistLockMgrWithMockTickSource: public ReplSetDistLockManagerFixture {
- public:
- /**
- * Returns the mock tick source.
- */
- TickSourceMock* getMockTickSource() {
- return dynamic_cast<TickSourceMock*>(_context.getTickSource());
- }
+class RSDistLockMgrWithMockTickSource : public ReplSetDistLockManagerFixture {
+public:
+ /**
+ * Returns the mock tick source.
+ */
+ TickSourceMock* getMockTickSource() {
+ return dynamic_cast<TickSourceMock*>(_context.getTickSource());
+ }
- protected:
- void setUp() override {
- _context.setTickSource(stdx::make_unique<TickSourceMock>());
- _mgr.startUp();
- }
- };
+protected:
+ void setUp() override {
+ _context.setTickSource(stdx::make_unique<TickSourceMock>());
+ _mgr.startUp();
+ }
+};
- std::string mapToString(const std::map<OID, int>& map) {
- StringBuilder str;
+std::string mapToString(const std::map<OID, int>& map) {
+ StringBuilder str;
- for (const auto& entry : map) {
- str << "(" << entry.first.toString() << ": " << entry.second << ")";
- }
+ for (const auto& entry : map) {
+ str << "(" << entry.first.toString() << ": " << entry.second << ")";
+ }
- return str.str();
- };
+ return str.str();
+};
- std::string vectorToString(const std::vector<OID>& list) {
- StringBuilder str;
+std::string vectorToString(const std::vector<OID>& list) {
+ StringBuilder str;
- for (const auto& entry : list) {
- str << "(" << entry.toString() << ")";
- }
+ for (const auto& entry : list) {
+ str << "(" << entry.toString() << ")";
+ }
- return str.str();
- };
+ return str.str();
+};
- /**
- * Test scenario:
- * 1. Grab lock.
- * 2. Unlock (on destructor of ScopedDistLock).
- * 3. Check lock id used in lock and unlock are the same.
- */
- TEST_F(ReplSetDistLockManagerFixture, BasicLockLifeCycle) {
- string lockName("test");
- Date_t now(Date_t::now());
- string whyMsg("because");
-
- LocksType retLockDoc;
- retLockDoc.setName(lockName);
- retLockDoc.setState(LocksType::LOCKED);
- retLockDoc.setProcess(getProcessID());
- retLockDoc.setWho("me");
- retLockDoc.setWhy(whyMsg);
- // Will be different from the actual lock session id. For testing only.
- retLockDoc.setLockID(OID::gen());
-
- OID lockSessionIDPassed;
-
- getMockCatalog()->expectGrabLock(
- [this, &lockName, &now, &whyMsg, &lockSessionIDPassed](
- StringData lockID,
- const OID& lockSessionID,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
+/**
+ * Test scenario:
+ * 1. Grab lock.
+ * 2. Unlock (on destructor of ScopedDistLock).
+ * 3. Check lock id used in lock and unlock are the same.
+ */
+TEST_F(ReplSetDistLockManagerFixture, BasicLockLifeCycle) {
+ string lockName("test");
+ Date_t now(Date_t::now());
+ string whyMsg("because");
+
+ LocksType retLockDoc;
+ retLockDoc.setName(lockName);
+ retLockDoc.setState(LocksType::LOCKED);
+ retLockDoc.setProcess(getProcessID());
+ retLockDoc.setWho("me");
+ retLockDoc.setWhy(whyMsg);
+ // Will be different from the actual lock session id. For testing only.
+ retLockDoc.setLockID(OID::gen());
+
+ OID lockSessionIDPassed;
+
+ getMockCatalog()->expectGrabLock(
+ [this, &lockName, &now, &whyMsg, &lockSessionIDPassed](StringData lockID,
+ const OID& lockSessionID,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
ASSERT_EQUALS(lockName, lockID);
ASSERT_TRUE(lockSessionID.isSet());
ASSERT_EQUALS(getProcessID(), processId);
@@ -200,72 +194,73 @@ namespace {
ASSERT_EQUALS(whyMsg, why);
lockSessionIDPassed = lockSessionID;
- getMockCatalog()->expectNoGrabLock(); // Call only once.
- }, retLockDoc);
+ getMockCatalog()->expectNoGrabLock(); // Call only once.
+ },
+ retLockDoc);
- int unlockCallCount = 0;
- OID unlockSessionIDPassed;
+ int unlockCallCount = 0;
+ OID unlockSessionIDPassed;
- {
- auto lockStatus = getMgr()->lock(lockName,
- whyMsg,
- DistLockManager::kDefaultSingleLockAttemptTimeout,
- DistLockManager::kDefaultLockRetryInterval);
- ASSERT_OK(lockStatus.getStatus());
+ {
+ auto lockStatus = getMgr()->lock(lockName,
+ whyMsg,
+ DistLockManager::kDefaultSingleLockAttemptTimeout,
+ DistLockManager::kDefaultLockRetryInterval);
+ ASSERT_OK(lockStatus.getStatus());
- getMockCatalog()->expectNoGrabLock();
- getMockCatalog()->expectUnLock(
- [&unlockCallCount, &unlockSessionIDPassed](const OID& lockSessionID) {
+ getMockCatalog()->expectNoGrabLock();
+ getMockCatalog()->expectUnLock(
+ [&unlockCallCount, &unlockSessionIDPassed](const OID& lockSessionID) {
unlockCallCount++;
unlockSessionIDPassed = lockSessionID;
- }, Status::OK());
- }
-
- ASSERT_EQUALS(1, unlockCallCount);
- ASSERT_EQUALS(lockSessionIDPassed, unlockSessionIDPassed);
+ },
+ Status::OK());
}
- /**
- * Test scenario:
- * 1. Grab lock fails up to 3 times.
- * 2. Check that each attempt uses a unique lock session id.
- * 3. Unlock (on destructor of ScopedDistLock).
- * 4. Check lock id used in lock and unlock are the same.
- */
- TEST_F(RSDistLockMgrWithMockTickSource, LockSuccessAfterRetry) {
- string lockName("test");
- string me("me");
- OID lastTS;
- Date_t lastTime(Date_t::now());
- string whyMsg("because");
-
- int retryAttempt = 0;
- const int kMaxRetryAttempt = 3;
-
- LocksType goodLockDoc;
- goodLockDoc.setName(lockName);
- goodLockDoc.setState(LocksType::LOCKED);
- goodLockDoc.setProcess(getProcessID());
- goodLockDoc.setWho("me");
- goodLockDoc.setWhy(whyMsg);
- goodLockDoc.setLockID(OID::gen());
-
- getMockCatalog()->expectGrabLock(
- [this,
- &lockName,
- &lastTS,
- &me,
- &lastTime,
- &whyMsg,
- &retryAttempt,
- &kMaxRetryAttempt,
- &goodLockDoc](
- StringData lockID,
- const OID& lockSessionID,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
+ ASSERT_EQUALS(1, unlockCallCount);
+ ASSERT_EQUALS(lockSessionIDPassed, unlockSessionIDPassed);
+}
+
+/**
+ * Test scenario:
+ * 1. Grab lock fails up to 3 times.
+ * 2. Check that each attempt uses a unique lock session id.
+ * 3. Unlock (on destructor of ScopedDistLock).
+ * 4. Check lock id used in lock and unlock are the same.
+ */
+TEST_F(RSDistLockMgrWithMockTickSource, LockSuccessAfterRetry) {
+ string lockName("test");
+ string me("me");
+ OID lastTS;
+ Date_t lastTime(Date_t::now());
+ string whyMsg("because");
+
+ int retryAttempt = 0;
+ const int kMaxRetryAttempt = 3;
+
+ LocksType goodLockDoc;
+ goodLockDoc.setName(lockName);
+ goodLockDoc.setState(LocksType::LOCKED);
+ goodLockDoc.setProcess(getProcessID());
+ goodLockDoc.setWho("me");
+ goodLockDoc.setWhy(whyMsg);
+ goodLockDoc.setLockID(OID::gen());
+
+ getMockCatalog()->expectGrabLock(
+ [this,
+ &lockName,
+ &lastTS,
+ &me,
+ &lastTime,
+ &whyMsg,
+ &retryAttempt,
+ &kMaxRetryAttempt,
+ &goodLockDoc](StringData lockID,
+ const OID& lockSessionID,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
ASSERT_EQUALS(lockName, lockID);
// Every attempt should have a unique sesssion ID.
ASSERT_NOT_EQUALS(lastTS, lockSessionID);
@@ -279,119 +274,107 @@ namespace {
getMockTickSource()->advance(Milliseconds(1));
if (++retryAttempt >= kMaxRetryAttempt) {
- getMockCatalog()->expectGrabLock([this,
- &lockName,
- &lastTS,
- &me,
- &lastTime,
- &whyMsg](
- StringData lockID,
- const OID& lockSessionID,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
- ASSERT_EQUALS(lockName, lockID);
- // Every attempt should have a unique sesssion ID.
- ASSERT_NOT_EQUALS(lastTS, lockSessionID);
- lastTS = lockSessionID;
- ASSERT_TRUE(lockSessionID.isSet());
- ASSERT_EQUALS(getProcessID(), processId);
- ASSERT_GREATER_THAN_OR_EQUALS(time, lastTime);
- ASSERT_EQUALS(whyMsg, why);
-
- getMockCatalog()->expectNoGrabLock();
-
- getMockCatalog()->expectGetLockByName([](StringData name) {
- FAIL("should not attempt to overtake lock after successful lock");
- }, LocksType());
- }, goodLockDoc);
+ getMockCatalog()->expectGrabLock(
+ [this, &lockName, &lastTS, &me, &lastTime, &whyMsg](StringData lockID,
+ const OID& lockSessionID,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
+ ASSERT_EQUALS(lockName, lockID);
+ // Every attempt should have a unique sesssion ID.
+ ASSERT_NOT_EQUALS(lastTS, lockSessionID);
+ lastTS = lockSessionID;
+ ASSERT_TRUE(lockSessionID.isSet());
+ ASSERT_EQUALS(getProcessID(), processId);
+ ASSERT_GREATER_THAN_OR_EQUALS(time, lastTime);
+ ASSERT_EQUALS(whyMsg, why);
+
+ getMockCatalog()->expectNoGrabLock();
+
+ getMockCatalog()->expectGetLockByName([](StringData name) {
+ FAIL("should not attempt to overtake lock after successful lock");
+ }, LocksType());
+ },
+ goodLockDoc);
}
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
- //
- // Setup mock for lock overtaking.
- //
+ //
+ // Setup mock for lock overtaking.
+ //
- LocksType currentLockDoc;
- currentLockDoc.setName("test");
- currentLockDoc.setState(LocksType::LOCKED);
- currentLockDoc.setProcess("otherProcess");
- currentLockDoc.setLockID(OID::gen());
- currentLockDoc.setWho("me");
- currentLockDoc.setWhy("why");
+ LocksType currentLockDoc;
+ currentLockDoc.setName("test");
+ currentLockDoc.setState(LocksType::LOCKED);
+ currentLockDoc.setProcess("otherProcess");
+ currentLockDoc.setLockID(OID::gen());
+ currentLockDoc.setWho("me");
+ currentLockDoc.setWhy("why");
- getMockCatalog()->expectGetLockByName([](StringData name) {
- ASSERT_EQUALS("test", name);
- }, currentLockDoc);
+ getMockCatalog()->expectGetLockByName([](StringData name) { ASSERT_EQUALS("test", name); },
+ currentLockDoc);
- LockpingsType pingDoc;
- pingDoc.setProcess("otherProcess");
- pingDoc.setPing(Date_t());
+ LockpingsType pingDoc;
+ pingDoc.setProcess("otherProcess");
+ pingDoc.setPing(Date_t());
- getMockCatalog()->expectGetPing([](StringData process) {
- ASSERT_EQUALS("otherProcess", process);
- }, pingDoc);
+ getMockCatalog()->expectGetPing(
+ [](StringData process) { ASSERT_EQUALS("otherProcess", process); }, pingDoc);
- // Config server time is fixed, so overtaking will never succeed.
- getMockCatalog()->expectGetServerInfo([]() {
- }, DistLockCatalog::ServerInfo(Date_t(), OID()));
+ // Config server time is fixed, so overtaking will never succeed.
+ getMockCatalog()->expectGetServerInfo([]() {}, DistLockCatalog::ServerInfo(Date_t(), OID()));
- //
- // Try grabbing lock.
- //
+ //
+ // Try grabbing lock.
+ //
- int unlockCallCount = 0;
- OID unlockSessionIDPassed;
+ int unlockCallCount = 0;
+ OID unlockSessionIDPassed;
- {
- auto lockStatus = getMgr()->lock(lockName, whyMsg, Milliseconds(10), Milliseconds(1));
- ASSERT_OK(lockStatus.getStatus());
+ {
+ auto lockStatus = getMgr()->lock(lockName, whyMsg, Milliseconds(10), Milliseconds(1));
+ ASSERT_OK(lockStatus.getStatus());
- getMockCatalog()->expectNoGrabLock();
- getMockCatalog()->expectUnLock(
- [&unlockCallCount, &unlockSessionIDPassed](const OID& lockSessionID) {
+ getMockCatalog()->expectNoGrabLock();
+ getMockCatalog()->expectUnLock(
+ [&unlockCallCount, &unlockSessionIDPassed](const OID& lockSessionID) {
unlockCallCount++;
unlockSessionIDPassed = lockSessionID;
- }, Status::OK());
- }
-
- ASSERT_EQUALS(1, unlockCallCount);
- ASSERT_EQUALS(lastTS, unlockSessionIDPassed);
+ },
+ Status::OK());
}
- /**
- * Test scenario:
- * 1. Grab lock fails up to 3 times.
- * 2. Check that each attempt uses a unique lock session id.
- * 3. Grab lock errors out on the fourth try.
- * 4. Make sure that unlock is called to cleanup the last lock attempted that error out.
- */
- TEST_F(RSDistLockMgrWithMockTickSource, LockFailsAfterRetry) {
- string lockName("test");
- string me("me");
- OID lastTS;
- Date_t lastTime(Date_t::now());
- string whyMsg("because");
-
- int retryAttempt = 0;
- const int kMaxRetryAttempt = 3;
-
- getMockCatalog()->expectGrabLock(
- [this,
- &lockName,
- &lastTS,
- &me,
- &lastTime,
- &whyMsg,
- &retryAttempt,
- &kMaxRetryAttempt](
- StringData lockID,
- const OID& lockSessionID,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
+ ASSERT_EQUALS(1, unlockCallCount);
+ ASSERT_EQUALS(lastTS, unlockSessionIDPassed);
+}
+
+/**
+ * Test scenario:
+ * 1. Grab lock fails up to 3 times.
+ * 2. Check that each attempt uses a unique lock session id.
+ * 3. Grab lock errors out on the fourth try.
+ * 4. Make sure that unlock is called to cleanup the last lock attempted that error out.
+ */
+TEST_F(RSDistLockMgrWithMockTickSource, LockFailsAfterRetry) {
+ string lockName("test");
+ string me("me");
+ OID lastTS;
+ Date_t lastTime(Date_t::now());
+ string whyMsg("because");
+
+ int retryAttempt = 0;
+ const int kMaxRetryAttempt = 3;
+
+ getMockCatalog()->expectGrabLock(
+ [this, &lockName, &lastTS, &me, &lastTime, &whyMsg, &retryAttempt, &kMaxRetryAttempt](
+ StringData lockID,
+ const OID& lockSessionID,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
ASSERT_EQUALS(lockName, lockID);
// Every attempt should have a unique sesssion ID.
ASSERT_NOT_EQUALS(lastTS, lockSessionID);
@@ -405,124 +388,116 @@ namespace {
getMockTickSource()->advance(Milliseconds(1));
if (++retryAttempt >= kMaxRetryAttempt) {
- getMockCatalog()->expectGrabLock([this,
- &lockName,
- &lastTS,
- &me,
- &lastTime,
- &whyMsg](
- StringData lockID,
- const OID& lockSessionID,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
- ASSERT_EQUALS(lockName, lockID);
- // Every attempt should have a unique sesssion ID.
- ASSERT_NOT_EQUALS(lastTS, lockSessionID);
- lastTS = lockSessionID;
- ASSERT_TRUE(lockSessionID.isSet());
- ASSERT_EQUALS(getProcessID(), processId);
- ASSERT_GREATER_THAN_OR_EQUALS(time, lastTime);
- ASSERT_EQUALS(whyMsg, why);
-
- getMockCatalog()->expectNoGrabLock();
- }, {ErrorCodes::NetworkTimeout, "bad test network"});
+ getMockCatalog()->expectGrabLock(
+ [this, &lockName, &lastTS, &me, &lastTime, &whyMsg](StringData lockID,
+ const OID& lockSessionID,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
+ ASSERT_EQUALS(lockName, lockID);
+ // Every attempt should have a unique sesssion ID.
+ ASSERT_NOT_EQUALS(lastTS, lockSessionID);
+ lastTS = lockSessionID;
+ ASSERT_TRUE(lockSessionID.isSet());
+ ASSERT_EQUALS(getProcessID(), processId);
+ ASSERT_GREATER_THAN_OR_EQUALS(time, lastTime);
+ ASSERT_EQUALS(whyMsg, why);
+
+ getMockCatalog()->expectNoGrabLock();
+ },
+ {ErrorCodes::NetworkTimeout, "bad test network"});
}
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
- // Make mock return lock not found to skip lock overtaking.
- getMockCatalog()->expectGetLockByName([](StringData) {},
- {ErrorCodes::LockNotFound, "not found!"});
+ // Make mock return lock not found to skip lock overtaking.
+ getMockCatalog()->expectGetLockByName([](StringData) {},
+ {ErrorCodes::LockNotFound, "not found!"});
- stdx::mutex unlockMutex;
- stdx::condition_variable unlockCV;
- OID unlockSessionIDPassed;
- int unlockCallCount = 0;
+ stdx::mutex unlockMutex;
+ stdx::condition_variable unlockCV;
+ OID unlockSessionIDPassed;
+ int unlockCallCount = 0;
- getMockCatalog()->expectUnLock(
- [&unlockMutex, &unlockCV, &unlockCallCount, &unlockSessionIDPassed](
- const OID& lockSessionID) {
+ getMockCatalog()->expectUnLock(
+ [&unlockMutex, &unlockCV, &unlockCallCount, &unlockSessionIDPassed](
+ const OID& lockSessionID) {
stdx::unique_lock<stdx::mutex> lk(unlockMutex);
unlockCallCount++;
unlockSessionIDPassed = lockSessionID;
unlockCV.notify_all();
- }, Status::OK());
+ },
+ Status::OK());
- {
- auto lockStatus = getMgr()->lock(lockName, whyMsg, Milliseconds(10), Milliseconds(1));
- ASSERT_NOT_OK(lockStatus.getStatus());
- }
+ {
+ auto lockStatus = getMgr()->lock(lockName, whyMsg, Milliseconds(10), Milliseconds(1));
+ ASSERT_NOT_OK(lockStatus.getStatus());
+ }
- bool didTimeout = false;
- {
- stdx::unique_lock<stdx::mutex> lk(unlockMutex);
- if (unlockCallCount == 0) {
- didTimeout = unlockCV.wait_for(lk, kUnlockTimeout) == stdx::cv_status::timeout;
- }
+ bool didTimeout = false;
+ {
+ stdx::unique_lock<stdx::mutex> lk(unlockMutex);
+ if (unlockCallCount == 0) {
+ didTimeout = unlockCV.wait_for(lk, kUnlockTimeout) == stdx::cv_status::timeout;
}
-
- // Join the background thread before trying to call asserts. Shutdown calls
- // stopPing and we don't care in this test.
- getMockCatalog()->expectStopPing([](StringData){}, Status::OK());
- getMgr()->shutDown();
-
- // No assert until shutDown has been called to make sure that the background thread
- // won't be trying to access the local variables that were captured by lamdas that
- // may have gone out of scope when the assert unwinds the stack.
- // No need to grab unlockMutex since there is only one thread running at this point.
-
- ASSERT_FALSE(didTimeout);
- ASSERT_EQUALS(1, unlockCallCount);
- ASSERT_EQUALS(lastTS, unlockSessionIDPassed);
}
- TEST_F(ReplSetDistLockManagerFixture, LockBusyNoRetry) {
- getMockCatalog()->expectGrabLock([this](
- StringData, const OID&, StringData, StringData, Date_t, StringData) {
- getMockCatalog()->expectNoGrabLock(); // Call only once.
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
+ // Join the background thread before trying to call asserts. Shutdown calls
+ // stopPing and we don't care in this test.
+ getMockCatalog()->expectStopPing([](StringData) {}, Status::OK());
+ getMgr()->shutDown();
+
+ // No assert until shutDown has been called to make sure that the background thread
+ // won't be trying to access the local variables that were captured by lamdas that
+ // may have gone out of scope when the assert unwinds the stack.
+ // No need to grab unlockMutex since there is only one thread running at this point.
+
+ ASSERT_FALSE(didTimeout);
+ ASSERT_EQUALS(1, unlockCallCount);
+ ASSERT_EQUALS(lastTS, unlockSessionIDPassed);
+}
+
+TEST_F(ReplSetDistLockManagerFixture, LockBusyNoRetry) {
+ getMockCatalog()->expectGrabLock(
+ [this](StringData, const OID&, StringData, StringData, Date_t, StringData) {
+ getMockCatalog()->expectNoGrabLock(); // Call only once.
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
+
+ // Make mock return lock not found to skip lock overtaking.
+ getMockCatalog()->expectGetLockByName([](StringData) {},
+ {ErrorCodes::LockNotFound, "not found!"});
+
+ auto status = getMgr()->lock("", "", Milliseconds(0), Milliseconds(0)).getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
+}
- // Make mock return lock not found to skip lock overtaking.
- getMockCatalog()->expectGetLockByName([](StringData) {},
- {ErrorCodes::LockNotFound, "not found!"});
-
- auto status = getMgr()->lock("", "", Milliseconds(0), Milliseconds(0)).getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
- }
-
- /**
- * Test scenario:
- * 1. Attempt to grab lock.
- * 2. Check that each attempt uses a unique lock session id.
- * 3. Times out trying.
- * 4. Checks result is error.
- * 5. Implicitly check that unlock is not called (default setting of mock catalog).
- */
- TEST_F(RSDistLockMgrWithMockTickSource, LockRetryTimeout) {
- string lockName("test");
- string me("me");
- OID lastTS;
- Date_t lastTime(Date_t::now());
- string whyMsg("because");
-
- int retryAttempt = 0;
-
- getMockCatalog()->expectGrabLock(
- [this,
- &lockName,
- &lastTS,
- &me,
- &lastTime,
- &whyMsg,
- &retryAttempt](
- StringData lockID,
- const OID& lockSessionID,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
+/**
+ * Test scenario:
+ * 1. Attempt to grab lock.
+ * 2. Check that each attempt uses a unique lock session id.
+ * 3. Times out trying.
+ * 4. Checks result is error.
+ * 5. Implicitly check that unlock is not called (default setting of mock catalog).
+ */
+TEST_F(RSDistLockMgrWithMockTickSource, LockRetryTimeout) {
+ string lockName("test");
+ string me("me");
+ OID lastTS;
+ Date_t lastTime(Date_t::now());
+ string whyMsg("because");
+
+ int retryAttempt = 0;
+
+ getMockCatalog()->expectGrabLock(
+ [this, &lockName, &lastTS, &me, &lastTime, &whyMsg, &retryAttempt](StringData lockID,
+ const OID& lockSessionID,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
ASSERT_EQUALS(lockName, lockID);
// Every attempt should have a unique sesssion ID.
ASSERT_NOT_EQUALS(lastTS, lockSessionID);
@@ -535,47 +510,41 @@ namespace {
retryAttempt++;
getMockTickSource()->advance(Milliseconds(1));
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
- // Make mock return lock not found to skip lock overtaking.
- getMockCatalog()->expectGetLockByName([](StringData) {},
- {ErrorCodes::LockNotFound, "not found!"});
+ // Make mock return lock not found to skip lock overtaking.
+ getMockCatalog()->expectGetLockByName([](StringData) {},
+ {ErrorCodes::LockNotFound, "not found!"});
- auto lockStatus = getMgr()->lock(lockName,
- whyMsg,
- Milliseconds(5),
- Milliseconds(1)).getStatus();
- ASSERT_NOT_OK(lockStatus);
+ auto lockStatus =
+ getMgr()->lock(lockName, whyMsg, Milliseconds(5), Milliseconds(1)).getStatus();
+ ASSERT_NOT_OK(lockStatus);
- ASSERT_EQUALS(ErrorCodes::LockBusy, lockStatus.code());
- ASSERT_GREATER_THAN(retryAttempt, 1);
- }
+ ASSERT_EQUALS(ErrorCodes::LockBusy, lockStatus.code());
+ ASSERT_GREATER_THAN(retryAttempt, 1);
+}
- /**
- * Test scenario:
- * 1. Set mock to error on grab lock.
- * 2. Grab lock attempted.
- * 3. Wait for unlock to be called.
- * 4. Check that lockSessionID used on all unlock is the same as the one used to grab lock.
- */
- TEST_F(ReplSetDistLockManagerFixture, MustUnlockOnLockError) {
- string lockName("test");
- string me("me");
- OID lastTS;
- string whyMsg("because");
-
- getMockCatalog()->expectGrabLock(
- [this,
- &lockName,
- &lastTS,
- &me,
- &whyMsg](
- StringData lockID,
- const OID& lockSessionID,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
+/**
+ * Test scenario:
+ * 1. Set mock to error on grab lock.
+ * 2. Grab lock attempted.
+ * 3. Wait for unlock to be called.
+ * 4. Check that lockSessionID used on all unlock is the same as the one used to grab lock.
+ */
+TEST_F(ReplSetDistLockManagerFixture, MustUnlockOnLockError) {
+ string lockName("test");
+ string me("me");
+ OID lastTS;
+ string whyMsg("because");
+
+ getMockCatalog()->expectGrabLock(
+ [this, &lockName, &lastTS, &me, &whyMsg](StringData lockID,
+ const OID& lockSessionID,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
ASSERT_EQUALS(lockName, lockID);
// Every attempt should have a unique sesssion ID.
ASSERT_TRUE(lockSessionID.isSet());
@@ -584,69 +553,69 @@ namespace {
lastTS = lockSessionID;
getMockCatalog()->expectNoGrabLock();
- }, {ErrorCodes::NetworkTimeout, "bad test network"});
+ },
+ {ErrorCodes::NetworkTimeout, "bad test network"});
- stdx::mutex unlockMutex;
- stdx::condition_variable unlockCV;
- int unlockCallCount = 0;
- OID unlockSessionIDPassed;
+ stdx::mutex unlockMutex;
+ stdx::condition_variable unlockCV;
+ int unlockCallCount = 0;
+ OID unlockSessionIDPassed;
- getMockCatalog()->expectUnLock(
- [&unlockMutex, &unlockCV, &unlockCallCount, &unlockSessionIDPassed](
- const OID& lockSessionID) {
+ getMockCatalog()->expectUnLock(
+ [&unlockMutex, &unlockCV, &unlockCallCount, &unlockSessionIDPassed](
+ const OID& lockSessionID) {
stdx::unique_lock<stdx::mutex> lk(unlockMutex);
unlockCallCount++;
unlockSessionIDPassed = lockSessionID;
unlockCV.notify_all();
- }, Status::OK());
-
- auto lockStatus = getMgr()->lock(lockName,
- whyMsg,
- Milliseconds(10),
- Milliseconds(1)).getStatus();
- ASSERT_NOT_OK(lockStatus);
- ASSERT_EQUALS(ErrorCodes::NetworkTimeout, lockStatus.code());
-
- bool didTimeout = false;
- {
- stdx::unique_lock<stdx::mutex> lk(unlockMutex);
- if (unlockCallCount == 0) {
- didTimeout = unlockCV.wait_for(lk, kUnlockTimeout) == stdx::cv_status::timeout;
- }
+ },
+ Status::OK());
+
+ auto lockStatus =
+ getMgr()->lock(lockName, whyMsg, Milliseconds(10), Milliseconds(1)).getStatus();
+ ASSERT_NOT_OK(lockStatus);
+ ASSERT_EQUALS(ErrorCodes::NetworkTimeout, lockStatus.code());
+
+ bool didTimeout = false;
+ {
+ stdx::unique_lock<stdx::mutex> lk(unlockMutex);
+ if (unlockCallCount == 0) {
+ didTimeout = unlockCV.wait_for(lk, kUnlockTimeout) == stdx::cv_status::timeout;
}
+ }
- // Join the background thread before trying to call asserts. Shutdown calls
- // stopPing and we don't care in this test.
- getMockCatalog()->expectStopPing([](StringData){}, Status::OK());
- getMgr()->shutDown();
+ // Join the background thread before trying to call asserts. Shutdown calls
+ // stopPing and we don't care in this test.
+ getMockCatalog()->expectStopPing([](StringData) {}, Status::OK());
+ getMgr()->shutDown();
- // No assert until shutDown has been called to make sure that the background thread
- // won't be trying to access the local variables that were captured by lamdas that
- // may have gone out of scope when the assert unwinds the stack.
- // No need to grab unlockMutex since there is only one thread running at this point.
+ // No assert until shutDown has been called to make sure that the background thread
+ // won't be trying to access the local variables that were captured by lamdas that
+ // may have gone out of scope when the assert unwinds the stack.
+ // No need to grab unlockMutex since there is only one thread running at this point.
- ASSERT_FALSE(didTimeout);
- ASSERT_EQUALS(1, unlockCallCount);
- ASSERT_EQUALS(lastTS, unlockSessionIDPassed);
- }
+ ASSERT_FALSE(didTimeout);
+ ASSERT_EQUALS(1, unlockCallCount);
+ ASSERT_EQUALS(lastTS, unlockSessionIDPassed);
+}
- /**
- * Test scenario:
- * 1. Ping thread started during setUp of fixture.
- * 2. Wait until ping was called at least 3 times.
- * 3. Check that correct process is being pinged.
- * 4. Check that ping values are unique (based on the assumption that the system
- * clock supports 2ms granularity).
- */
- TEST_F(ReplSetDistLockManagerFixture, LockPinging) {
- stdx::mutex testMutex;
- stdx::condition_variable ping3TimesCV;
- vector<Date_t> pingValues;
- vector<string> processIDList;
-
- getMockCatalog()->expectPing(
- [&testMutex, &ping3TimesCV, &processIDList, &pingValues](
- StringData processIDArg, Date_t ping) {
+/**
+ * Test scenario:
+ * 1. Ping thread started during setUp of fixture.
+ * 2. Wait until ping was called at least 3 times.
+ * 3. Check that correct process is being pinged.
+ * 4. Check that ping values are unique (based on the assumption that the system
+ * clock supports 2ms granularity).
+ */
+TEST_F(ReplSetDistLockManagerFixture, LockPinging) {
+ stdx::mutex testMutex;
+ stdx::condition_variable ping3TimesCV;
+ vector<Date_t> pingValues;
+ vector<string> processIDList;
+
+ getMockCatalog()->expectPing(
+ [&testMutex, &ping3TimesCV, &processIDList, &pingValues](StringData processIDArg,
+ Date_t ping) {
stdx::lock_guard<stdx::mutex> lk(testMutex);
processIDList.push_back(processIDArg.toString());
pingValues.push_back(ping);
@@ -654,1152 +623,1127 @@ namespace {
if (processIDList.size() >= 3) {
ping3TimesCV.notify_all();
}
- }, Status::OK());
-
- bool didTimeout = false;
- {
- stdx::unique_lock<stdx::mutex> lk(testMutex);
- if (processIDList.size() < 3) {
- didTimeout = ping3TimesCV.wait_for(lk, Milliseconds(50)) ==
- stdx::cv_status::timeout;
- }
+ },
+ Status::OK());
+
+ bool didTimeout = false;
+ {
+ stdx::unique_lock<stdx::mutex> lk(testMutex);
+ if (processIDList.size() < 3) {
+ didTimeout = ping3TimesCV.wait_for(lk, Milliseconds(50)) == stdx::cv_status::timeout;
}
+ }
- // Join the background thread before trying to call asserts. Shutdown calls
- // stopPing and we don't care in this test.
- getMockCatalog()->expectStopPing([](StringData){}, Status::OK());
- getMgr()->shutDown();
+ // Join the background thread before trying to call asserts. Shutdown calls
+ // stopPing and we don't care in this test.
+ getMockCatalog()->expectStopPing([](StringData) {}, Status::OK());
+ getMgr()->shutDown();
- // No assert until shutDown has been called to make sure that the background thread
- // won't be trying to access the local variables that were captured by lamdas that
- // may have gone out of scope when the assert unwinds the stack.
- // No need to grab testMutex since there is only one thread running at this point.
+ // No assert until shutDown has been called to make sure that the background thread
+ // won't be trying to access the local variables that were captured by lamdas that
+ // may have gone out of scope when the assert unwinds the stack.
+ // No need to grab testMutex since there is only one thread running at this point.
- ASSERT_FALSE(didTimeout);
+ ASSERT_FALSE(didTimeout);
- Date_t lastPing;
- for (const auto& ping : pingValues) {
- ASSERT_NOT_EQUALS(lastPing, ping);
- lastPing = ping;
- }
-
- for (const auto& processIDArg : processIDList) {
- ASSERT_EQUALS(getProcessID(), processIDArg);
- }
+ Date_t lastPing;
+ for (const auto& ping : pingValues) {
+ ASSERT_NOT_EQUALS(lastPing, ping);
+ lastPing = ping;
}
- /**
- * Test scenario:
- * 1. Grab lock.
- * 2. Unlock fails 3 times.
- * 3. Unlock finally succeeds at the 4th time.
- * 4. Check that lockSessionID used on all unlock is the same as the one used to grab lock.
- */
- TEST_F(ReplSetDistLockManagerFixture, UnlockUntilNoError) {
- stdx::mutex unlockMutex;
- stdx::condition_variable unlockCV;
- const unsigned int kUnlockErrorCount = 3;
- vector<OID> lockSessionIDPassed;
+ for (const auto& processIDArg : processIDList) {
+ ASSERT_EQUALS(getProcessID(), processIDArg);
+ }
+}
- getMockCatalog()->expectUnLock(
- [this, &unlockMutex, &unlockCV, &kUnlockErrorCount, &lockSessionIDPassed](
- const OID& lockSessionID) {
+/**
+ * Test scenario:
+ * 1. Grab lock.
+ * 2. Unlock fails 3 times.
+ * 3. Unlock finally succeeds at the 4th time.
+ * 4. Check that lockSessionID used on all unlock is the same as the one used to grab lock.
+ */
+TEST_F(ReplSetDistLockManagerFixture, UnlockUntilNoError) {
+ stdx::mutex unlockMutex;
+ stdx::condition_variable unlockCV;
+ const unsigned int kUnlockErrorCount = 3;
+ vector<OID> lockSessionIDPassed;
+
+ getMockCatalog()->expectUnLock(
+ [this, &unlockMutex, &unlockCV, &kUnlockErrorCount, &lockSessionIDPassed](
+ const OID& lockSessionID) {
stdx::unique_lock<stdx::mutex> lk(unlockMutex);
lockSessionIDPassed.push_back(lockSessionID);
if (lockSessionIDPassed.size() >= kUnlockErrorCount) {
getMockCatalog()->expectUnLock(
- [&lockSessionIDPassed, &unlockMutex, &unlockCV](
- const OID& lockSessionID) {
- stdx::unique_lock<stdx::mutex> lk(unlockMutex);
- lockSessionIDPassed.push_back(lockSessionID);
- unlockCV.notify_all();
- }, Status::OK());
- }
- }, {ErrorCodes::NetworkTimeout, "bad test network"});
-
- OID lockSessionID;
- LocksType retLockDoc;
- retLockDoc.setName("test");
- retLockDoc.setState(LocksType::LOCKED);
- retLockDoc.setProcess(getProcessID());
- retLockDoc.setWho("me");
- retLockDoc.setWhy("why");
- // Will be different from the actual lock session id. For testing only.
- retLockDoc.setLockID(OID::gen());
-
- getMockCatalog()->expectGrabLock([&lockSessionID](
- StringData lockID,
- const OID& lockSessionIDArg,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
- lockSessionID = lockSessionIDArg;
- }, retLockDoc);
-
- {
- auto lockStatus = getMgr()->lock("test", "why", Milliseconds(0), Milliseconds(0));
- }
-
- bool didTimeout = false;
- {
- stdx::unique_lock<stdx::mutex> lk(unlockMutex);
- if (lockSessionIDPassed.size() < kUnlockErrorCount) {
- didTimeout = unlockCV.wait_for(lk, kUnlockTimeout) == stdx::cv_status::timeout;
+ [&lockSessionIDPassed, &unlockMutex, &unlockCV](const OID& lockSessionID) {
+ stdx::unique_lock<stdx::mutex> lk(unlockMutex);
+ lockSessionIDPassed.push_back(lockSessionID);
+ unlockCV.notify_all();
+ },
+ Status::OK());
}
+ },
+ {ErrorCodes::NetworkTimeout, "bad test network"});
+
+ OID lockSessionID;
+ LocksType retLockDoc;
+ retLockDoc.setName("test");
+ retLockDoc.setState(LocksType::LOCKED);
+ retLockDoc.setProcess(getProcessID());
+ retLockDoc.setWho("me");
+ retLockDoc.setWhy("why");
+ // Will be different from the actual lock session id. For testing only.
+ retLockDoc.setLockID(OID::gen());
+
+ getMockCatalog()->expectGrabLock(
+ [&lockSessionID](StringData lockID,
+ const OID& lockSessionIDArg,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) { lockSessionID = lockSessionIDArg; },
+ retLockDoc);
+
+ { auto lockStatus = getMgr()->lock("test", "why", Milliseconds(0), Milliseconds(0)); }
+
+ bool didTimeout = false;
+ {
+ stdx::unique_lock<stdx::mutex> lk(unlockMutex);
+ if (lockSessionIDPassed.size() < kUnlockErrorCount) {
+ didTimeout = unlockCV.wait_for(lk, kUnlockTimeout) == stdx::cv_status::timeout;
}
+ }
- // Join the background thread before trying to call asserts. Shutdown calls
- // stopPing and we don't care in this test.
- getMockCatalog()->expectStopPing([](StringData){}, Status::OK());
- getMgr()->shutDown();
+ // Join the background thread before trying to call asserts. Shutdown calls
+ // stopPing and we don't care in this test.
+ getMockCatalog()->expectStopPing([](StringData) {}, Status::OK());
+ getMgr()->shutDown();
- // No assert until shutDown has been called to make sure that the background thread
- // won't be trying to access the local variables that were captured by lamdas that
- // may have gone out of scope when the assert unwinds the stack.
- // No need to grab testMutex since there is only one thread running at this point.
+ // No assert until shutDown has been called to make sure that the background thread
+ // won't be trying to access the local variables that were captured by lamdas that
+ // may have gone out of scope when the assert unwinds the stack.
+ // No need to grab testMutex since there is only one thread running at this point.
- ASSERT_FALSE(didTimeout);
+ ASSERT_FALSE(didTimeout);
- for (const auto& id : lockSessionIDPassed) {
- ASSERT_EQUALS(lockSessionID, id);
- }
+ for (const auto& id : lockSessionIDPassed) {
+ ASSERT_EQUALS(lockSessionID, id);
}
+}
+
+/**
+ * Test scenario:
+ * 1. Grab 2 locks.
+ * 2. Trigger unlocks by making ScopedDistLock go out of scope.
+ * 3. Unlocks fail and will be queued for retry.
+ * 4. Unlocks will keep on failing until we see at least 3 unique ids being unlocked more
+ * than once. This implies that both ids have been retried at least 3 times.
+ * 5. Check that the lock session id used when lock was called matches with unlock.
+ */
+TEST_F(ReplSetDistLockManagerFixture, MultipleQueuedUnlock) {
+ stdx::mutex testMutex;
+ stdx::condition_variable unlockCV;
+
+ vector<OID> lockSessionIDPassed;
+ map<OID, int> unlockIDMap; // id -> count
/**
- * Test scenario:
- * 1. Grab 2 locks.
- * 2. Trigger unlocks by making ScopedDistLock go out of scope.
- * 3. Unlocks fail and will be queued for retry.
- * 4. Unlocks will keep on failing until we see at least 3 unique ids being unlocked more
- * than once. This implies that both ids have been retried at least 3 times.
- * 5. Check that the lock session id used when lock was called matches with unlock.
+ * Returns true if all values in the map are greater than 2.
*/
- TEST_F(ReplSetDistLockManagerFixture, MultipleQueuedUnlock) {
- stdx::mutex testMutex;
- stdx::condition_variable unlockCV;
-
- vector<OID> lockSessionIDPassed;
- map<OID, int> unlockIDMap; // id -> count
-
- /**
- * Returns true if all values in the map are greater than 2.
- */
- auto mapEntriesGreaterThanTwo = [](const decltype(unlockIDMap)& map) -> bool {
- auto iter = find_if(map.begin(), map.end(),
- [](const std::remove_reference<decltype(map)>::type::value_type& entry)
- -> bool {
- return entry.second < 3;
- });
-
- return iter == map.end();
- };
+ auto mapEntriesGreaterThanTwo = [](const decltype(unlockIDMap)& map) -> bool {
+ auto iter = find_if(map.begin(),
+ map.end(),
+ [](const std::remove_reference<decltype(map)>::type::value_type& entry)
+ -> bool { return entry.second < 3; });
- getMockCatalog()->expectUnLock(
- [this, &unlockIDMap, &testMutex, &unlockCV, &mapEntriesGreaterThanTwo](
- const OID& lockSessionID) {
+ return iter == map.end();
+ };
+
+ getMockCatalog()->expectUnLock(
+ [this, &unlockIDMap, &testMutex, &unlockCV, &mapEntriesGreaterThanTwo](
+ const OID& lockSessionID) {
stdx::unique_lock<stdx::mutex> lk(testMutex);
unlockIDMap[lockSessionID]++;
// Wait until we see at least 2 unique lockSessionID more than twice.
if (unlockIDMap.size() >= 2 && mapEntriesGreaterThanTwo(unlockIDMap)) {
- getMockCatalog()->expectUnLock(
- [&testMutex, &unlockCV](const OID& lockSessionID) {
+ getMockCatalog()->expectUnLock([&testMutex, &unlockCV](const OID& lockSessionID) {
stdx::unique_lock<stdx::mutex> lk(testMutex);
unlockCV.notify_all();
}, Status::OK());
}
- }, {ErrorCodes::NetworkTimeout, "bad test network"});
-
- LocksType retLockDoc;
- retLockDoc.setName("test");
- retLockDoc.setState(LocksType::LOCKED);
- retLockDoc.setProcess(getProcessID());
- retLockDoc.setWho("me");
- retLockDoc.setWhy("why");
- // Will be different from the actual lock session id. For testing only.
- retLockDoc.setLockID(OID::gen());
-
- getMockCatalog()->expectGrabLock([&testMutex, &lockSessionIDPassed](
- StringData lockID,
- const OID& lockSessionIDArg,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
+ },
+ {ErrorCodes::NetworkTimeout, "bad test network"});
+
+ LocksType retLockDoc;
+ retLockDoc.setName("test");
+ retLockDoc.setState(LocksType::LOCKED);
+ retLockDoc.setProcess(getProcessID());
+ retLockDoc.setWho("me");
+ retLockDoc.setWhy("why");
+ // Will be different from the actual lock session id. For testing only.
+ retLockDoc.setLockID(OID::gen());
+
+ getMockCatalog()->expectGrabLock(
+ [&testMutex, &lockSessionIDPassed](StringData lockID,
+ const OID& lockSessionIDArg,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
stdx::unique_lock<stdx::mutex> lk(testMutex);
lockSessionIDPassed.push_back(lockSessionIDArg);
- }, retLockDoc);
-
- {
- auto lockStatus = getMgr()->lock("test", "why", Milliseconds(0), Milliseconds(0));
- auto otherStatus = getMgr()->lock("lock", "why", Milliseconds(0), Milliseconds(0));
- }
-
- bool didTimeout = false;
- {
- stdx::unique_lock<stdx::mutex> lk(testMutex);
-
- if (unlockIDMap.size() < 2 || !mapEntriesGreaterThanTwo(unlockIDMap)) {
- didTimeout = unlockCV.wait_for(lk, kUnlockTimeout) == stdx::cv_status::timeout;
- }
- }
-
- // Join the background thread before trying to call asserts. Shutdown calls
- // stopPing and we don't care in this test.
- getMockCatalog()->expectStopPing([](StringData){}, Status::OK());
- getMgr()->shutDown();
+ },
+ retLockDoc);
- // No assert until shutDown has been called to make sure that the background thread
- // won't be trying to access the local variables that were captured by lamdas that
- // may have gone out of scope when the assert unwinds the stack.
- // No need to grab testMutex since there is only one thread running at this point.
+ {
+ auto lockStatus = getMgr()->lock("test", "why", Milliseconds(0), Milliseconds(0));
+ auto otherStatus = getMgr()->lock("lock", "why", Milliseconds(0), Milliseconds(0));
+ }
- ASSERT_FALSE(didTimeout);
- ASSERT_EQUALS(2u, lockSessionIDPassed.size());
+ bool didTimeout = false;
+ {
+ stdx::unique_lock<stdx::mutex> lk(testMutex);
- for (const auto& id : lockSessionIDPassed) {
- ASSERT_GREATER_THAN(unlockIDMap[id], 2)
- << "lockIDList: " << vectorToString(lockSessionIDPassed)
- << ", map: " << mapToString(unlockIDMap);
+ if (unlockIDMap.size() < 2 || !mapEntriesGreaterThanTwo(unlockIDMap)) {
+ didTimeout = unlockCV.wait_for(lk, kUnlockTimeout) == stdx::cv_status::timeout;
}
}
- TEST_F(ReplSetDistLockManagerFixture, CleanupPingOnShutdown) {
- bool stopPingCalled = false;
- getMockCatalog()->expectStopPing([this, & stopPingCalled](
- StringData processID) {
- ASSERT_EQUALS(getProcessID(), processID);
- stopPingCalled = true;
- }, Status::OK());
-
- getMgr()->shutDown();
- ASSERT_TRUE(stopPingCalled);
- }
+ // Join the background thread before trying to call asserts. Shutdown calls
+ // stopPing and we don't care in this test.
+ getMockCatalog()->expectStopPing([](StringData) {}, Status::OK());
+ getMgr()->shutDown();
- TEST_F(ReplSetDistLockManagerFixture, CheckLockStatusOK) {
- LocksType retLockDoc;
- retLockDoc.setName("test");
- retLockDoc.setState(LocksType::LOCKED);
- retLockDoc.setProcess(getProcessID());
- retLockDoc.setWho("me");
- retLockDoc.setWhy("why");
- // Will be different from the actual lock session id. For testing only.
- retLockDoc.setLockID(OID::gen());
+ // No assert until shutDown has been called to make sure that the background thread
+ // won't be trying to access the local variables that were captured by lamdas that
+ // may have gone out of scope when the assert unwinds the stack.
+ // No need to grab testMutex since there is only one thread running at this point.
- OID lockSessionID;
+ ASSERT_FALSE(didTimeout);
+ ASSERT_EQUALS(2u, lockSessionIDPassed.size());
- getMockCatalog()->expectGrabLock([&lockSessionID]
- (StringData, const OID& ts, StringData, StringData, Date_t, StringData) {
+ for (const auto& id : lockSessionIDPassed) {
+ ASSERT_GREATER_THAN(unlockIDMap[id], 2)
+ << "lockIDList: " << vectorToString(lockSessionIDPassed)
+ << ", map: " << mapToString(unlockIDMap);
+ }
+}
+
+TEST_F(ReplSetDistLockManagerFixture, CleanupPingOnShutdown) {
+ bool stopPingCalled = false;
+ getMockCatalog()->expectStopPing([this, &stopPingCalled](StringData processID) {
+ ASSERT_EQUALS(getProcessID(), processID);
+ stopPingCalled = true;
+ }, Status::OK());
+
+ getMgr()->shutDown();
+ ASSERT_TRUE(stopPingCalled);
+}
+
+TEST_F(ReplSetDistLockManagerFixture, CheckLockStatusOK) {
+ LocksType retLockDoc;
+ retLockDoc.setName("test");
+ retLockDoc.setState(LocksType::LOCKED);
+ retLockDoc.setProcess(getProcessID());
+ retLockDoc.setWho("me");
+ retLockDoc.setWhy("why");
+ // Will be different from the actual lock session id. For testing only.
+ retLockDoc.setLockID(OID::gen());
+
+ OID lockSessionID;
+
+ getMockCatalog()->expectGrabLock(
+ [&lockSessionID](StringData, const OID& ts, StringData, StringData, Date_t, StringData) {
lockSessionID = ts;
- }, retLockDoc);
+ },
+ retLockDoc);
- auto lockStatus = getMgr()->lock("a", "", Milliseconds(0), Milliseconds(0));
- ASSERT_OK(lockStatus.getStatus());
+ auto lockStatus = getMgr()->lock("a", "", Milliseconds(0), Milliseconds(0));
+ ASSERT_OK(lockStatus.getStatus());
- getMockCatalog()->expectNoGrabLock();
- getMockCatalog()->expectUnLock([](const OID&) {
+ getMockCatalog()->expectNoGrabLock();
+ getMockCatalog()->expectUnLock(
+ [](const OID&) {
// Don't care
- }, Status::OK());
+ },
+ Status::OK());
- auto& scopedLock = lockStatus.getValue();
+ auto& scopedLock = lockStatus.getValue();
- getMockCatalog()->expectNoGrabLock();
- getMockCatalog()->expectGetLockByTS([&lockSessionID](const OID& ts) {
- ASSERT_EQUALS(lockSessionID, ts);
- }, retLockDoc);
+ getMockCatalog()->expectNoGrabLock();
+ getMockCatalog()->expectGetLockByTS(
+ [&lockSessionID](const OID& ts) { ASSERT_EQUALS(lockSessionID, ts); }, retLockDoc);
- ASSERT_OK(scopedLock.checkStatus());
- }
+ ASSERT_OK(scopedLock.checkStatus());
+}
- TEST_F(ReplSetDistLockManagerFixture, CheckLockStatusNoLongerOwn) {
- LocksType retLockDoc;
- retLockDoc.setName("test");
- retLockDoc.setState(LocksType::LOCKED);
- retLockDoc.setProcess(getProcessID());
- retLockDoc.setWho("me");
- retLockDoc.setWhy("why");
- // Will be different from the actual lock session id. For testing only.
- retLockDoc.setLockID(OID::gen());
+TEST_F(ReplSetDistLockManagerFixture, CheckLockStatusNoLongerOwn) {
+ LocksType retLockDoc;
+ retLockDoc.setName("test");
+ retLockDoc.setState(LocksType::LOCKED);
+ retLockDoc.setProcess(getProcessID());
+ retLockDoc.setWho("me");
+ retLockDoc.setWhy("why");
+ // Will be different from the actual lock session id. For testing only.
+ retLockDoc.setLockID(OID::gen());
- OID lockSessionID;
+ OID lockSessionID;
- getMockCatalog()->expectGrabLock([&lockSessionID]
- (StringData, const OID& ts, StringData, StringData, Date_t, StringData) {
+ getMockCatalog()->expectGrabLock(
+ [&lockSessionID](StringData, const OID& ts, StringData, StringData, Date_t, StringData) {
lockSessionID = ts;
- }, retLockDoc);
+ },
+ retLockDoc);
- auto lockStatus = getMgr()->lock("a", "", Milliseconds(0), Milliseconds(0));
- ASSERT_OK(lockStatus.getStatus());
+ auto lockStatus = getMgr()->lock("a", "", Milliseconds(0), Milliseconds(0));
+ ASSERT_OK(lockStatus.getStatus());
- getMockCatalog()->expectNoGrabLock();
- getMockCatalog()->expectUnLock([](const OID&) {
+ getMockCatalog()->expectNoGrabLock();
+ getMockCatalog()->expectUnLock(
+ [](const OID&) {
// Don't care
- }, Status::OK());
+ },
+ Status::OK());
- auto& scopedLock = lockStatus.getValue();
+ auto& scopedLock = lockStatus.getValue();
- getMockCatalog()->expectNoGrabLock();
- getMockCatalog()->expectGetLockByTS([&lockSessionID](const OID& ts) {
- ASSERT_EQUALS(lockSessionID, ts);
- }, {ErrorCodes::LockNotFound, "no lock"});
+ getMockCatalog()->expectNoGrabLock();
+ getMockCatalog()->expectGetLockByTS([&lockSessionID](const OID& ts) {
+ ASSERT_EQUALS(lockSessionID, ts);
+ }, {ErrorCodes::LockNotFound, "no lock"});
- ASSERT_NOT_OK(scopedLock.checkStatus());
- }
+ ASSERT_NOT_OK(scopedLock.checkStatus());
+}
- TEST_F(ReplSetDistLockManagerFixture, CheckLockStatusError) {
- LocksType retLockDoc;
- retLockDoc.setName("test");
- retLockDoc.setState(LocksType::LOCKED);
- retLockDoc.setProcess(getProcessID());
- retLockDoc.setWho("me");
- retLockDoc.setWhy("why");
- // Will be different from the actual lock session id. For testing only.
- retLockDoc.setLockID(OID::gen());
+TEST_F(ReplSetDistLockManagerFixture, CheckLockStatusError) {
+ LocksType retLockDoc;
+ retLockDoc.setName("test");
+ retLockDoc.setState(LocksType::LOCKED);
+ retLockDoc.setProcess(getProcessID());
+ retLockDoc.setWho("me");
+ retLockDoc.setWhy("why");
+ // Will be different from the actual lock session id. For testing only.
+ retLockDoc.setLockID(OID::gen());
- OID lockSessionID;
+ OID lockSessionID;
- getMockCatalog()->expectGrabLock([&lockSessionID]
- (StringData, const OID& ts, StringData, StringData, Date_t, StringData) {
+ getMockCatalog()->expectGrabLock(
+ [&lockSessionID](StringData, const OID& ts, StringData, StringData, Date_t, StringData) {
lockSessionID = ts;
- }, retLockDoc);
+ },
+ retLockDoc);
- auto lockStatus = getMgr()->lock("a", "", Milliseconds(0), Milliseconds(0));
- ASSERT_OK(lockStatus.getStatus());
+ auto lockStatus = getMgr()->lock("a", "", Milliseconds(0), Milliseconds(0));
+ ASSERT_OK(lockStatus.getStatus());
- getMockCatalog()->expectNoGrabLock();
- getMockCatalog()->expectUnLock([](const OID&) {
+ getMockCatalog()->expectNoGrabLock();
+ getMockCatalog()->expectUnLock(
+ [](const OID&) {
// Don't care
- }, Status::OK());
+ },
+ Status::OK());
- auto& scopedLock = lockStatus.getValue();
+ auto& scopedLock = lockStatus.getValue();
- getMockCatalog()->expectNoGrabLock();
- getMockCatalog()->expectGetLockByTS([&lockSessionID](const OID& ts) {
- ASSERT_EQUALS(lockSessionID, ts);
- }, {ErrorCodes::NetworkTimeout, "bad test network"});
+ getMockCatalog()->expectNoGrabLock();
+ getMockCatalog()->expectGetLockByTS([&lockSessionID](const OID& ts) {
+ ASSERT_EQUALS(lockSessionID, ts);
+ }, {ErrorCodes::NetworkTimeout, "bad test network"});
- ASSERT_NOT_OK(scopedLock.checkStatus());
- }
+ ASSERT_NOT_OK(scopedLock.checkStatus());
+}
- /**
- * Test scenario:
- * 1. Attempt to grab lock fails because lock is already owned.
- * 2. Try to get ping data and config server clock.
- * 3. Since we don't have previous ping data to compare with, we cannot
- * decide whether it's ok to overtake, so we can't.
- * 4. Lock expiration has elapsed and the ping has not been updated since.
- * 5. 2nd attempt to grab lock still fails for the same reason.
- * 6. But since the ping is not fresh anymore, dist lock manager should overtake lock.
- */
- TEST_F(ReplSetDistLockManagerFixture, BasicLockOvertaking) {
- OID lastTS;
+/**
+ * Test scenario:
+ * 1. Attempt to grab lock fails because lock is already owned.
+ * 2. Try to get ping data and config server clock.
+ * 3. Since we don't have previous ping data to compare with, we cannot
+ * decide whether it's ok to overtake, so we can't.
+ * 4. Lock expiration has elapsed and the ping has not been updated since.
+ * 5. 2nd attempt to grab lock still fails for the same reason.
+ * 6. But since the ping is not fresh anymore, dist lock manager should overtake lock.
+ */
+TEST_F(ReplSetDistLockManagerFixture, BasicLockOvertaking) {
+ OID lastTS;
- getMockCatalog()->expectGrabLock([&lastTS](
- StringData, const OID& lockSessionID, StringData, StringData, Date_t, StringData) {
+ getMockCatalog()->expectGrabLock(
+ [&lastTS](
+ StringData, const OID& lockSessionID, StringData, StringData, Date_t, StringData) {
lastTS = lockSessionID;
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
-
- LocksType currentLockDoc;
- currentLockDoc.setName("bar");
- currentLockDoc.setState(LocksType::LOCKED);
- currentLockDoc.setProcess("otherProcess");
- currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
- currentLockDoc.setWho("me");
- currentLockDoc.setWhy("why");
-
- getMockCatalog()->expectGetLockByName([](StringData name) {
- ASSERT_EQUALS("bar", name);
- }, currentLockDoc);
-
- LockpingsType pingDoc;
- pingDoc.setProcess("otherProcess");
- pingDoc.setPing(Date_t());
-
- getMockCatalog()->expectGetPing([](StringData process) {
- ASSERT_EQUALS("otherProcess", process);
- }, pingDoc);
-
- getMockCatalog()->expectGetServerInfo([]() {
- }, DistLockCatalog::ServerInfo(Date_t(), OID()));
-
- // First attempt will record the ping data.
- {
- auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
- }
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
+
+ LocksType currentLockDoc;
+ currentLockDoc.setName("bar");
+ currentLockDoc.setState(LocksType::LOCKED);
+ currentLockDoc.setProcess("otherProcess");
+ currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
+ currentLockDoc.setWho("me");
+ currentLockDoc.setWhy("why");
+
+ getMockCatalog()->expectGetLockByName([](StringData name) { ASSERT_EQUALS("bar", name); },
+ currentLockDoc);
+
+ LockpingsType pingDoc;
+ pingDoc.setProcess("otherProcess");
+ pingDoc.setPing(Date_t());
+
+ getMockCatalog()->expectGetPing(
+ [](StringData process) { ASSERT_EQUALS("otherProcess", process); }, pingDoc);
- // Advance config server time to exceed lock expiration.
- getMockCatalog()->expectGetServerInfo([]() {
- }, DistLockCatalog::ServerInfo(Date_t() + kLockExpiration + Milliseconds(1), OID()));
-
- getMockCatalog()->expectOvertakeLock(
- [this, &lastTS, &currentLockDoc]
- (StringData lockID,
- const OID& lockSessionID,
- const OID& currentHolderTS,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
+ getMockCatalog()->expectGetServerInfo([]() {}, DistLockCatalog::ServerInfo(Date_t(), OID()));
+
+ // First attempt will record the ping data.
+ {
+ auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
+ }
+
+ // Advance config server time to exceed lock expiration.
+ getMockCatalog()->expectGetServerInfo(
+ []() {}, DistLockCatalog::ServerInfo(Date_t() + kLockExpiration + Milliseconds(1), OID()));
+
+ getMockCatalog()->expectOvertakeLock(
+ [this, &lastTS, &currentLockDoc](StringData lockID,
+ const OID& lockSessionID,
+ const OID& currentHolderTS,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
ASSERT_EQUALS("bar", lockID);
ASSERT_EQUALS(lastTS, lockSessionID);
ASSERT_EQUALS(currentLockDoc.getLockID(), currentHolderTS);
ASSERT_EQUALS(getProcessID(), processId);
ASSERT_EQUALS("foo", why);
- }, currentLockDoc); // return arbitrary valid lock document, for testing purposes only.
+ },
+ currentLockDoc); // return arbitrary valid lock document, for testing purposes only.
- int unlockCallCount = 0;
- OID unlockSessionIDPassed;
+ int unlockCallCount = 0;
+ OID unlockSessionIDPassed;
- // Second attempt should overtake lock.
- {
- auto lockStatus = getMgr()->lock("bar",
- "foo",
- Milliseconds(0),
- Milliseconds(0));
+ // Second attempt should overtake lock.
+ {
+ auto lockStatus = getMgr()->lock("bar", "foo", Milliseconds(0), Milliseconds(0));
- ASSERT_OK(lockStatus.getStatus());
+ ASSERT_OK(lockStatus.getStatus());
- getMockCatalog()->expectNoGrabLock();
- getMockCatalog()->expectUnLock(
- [&unlockCallCount, &unlockSessionIDPassed](const OID& lockSessionID) {
+ getMockCatalog()->expectNoGrabLock();
+ getMockCatalog()->expectUnLock(
+ [&unlockCallCount, &unlockSessionIDPassed](const OID& lockSessionID) {
unlockCallCount++;
unlockSessionIDPassed = lockSessionID;
- }, Status::OK());
- }
-
- ASSERT_EQUALS(1, unlockCallCount);
- ASSERT_EQUALS(lastTS, unlockSessionIDPassed);
+ },
+ Status::OK());
}
- TEST_F(ReplSetDistLockManagerFixture, CannotOvertakeIfExpirationHasNotElapsed) {
- getMockCatalog()->expectGrabLock([](
- StringData, const OID&, StringData, StringData, Date_t, StringData) {
- // Don't care.
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
-
- LocksType currentLockDoc;
- currentLockDoc.setName("bar");
- currentLockDoc.setState(LocksType::LOCKED);
- currentLockDoc.setProcess("otherProcess");
- currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
- currentLockDoc.setWho("me");
- currentLockDoc.setWhy("why");
-
- getMockCatalog()->expectGetLockByName([](StringData name) {
- ASSERT_EQUALS("bar", name);
- }, currentLockDoc);
-
- LockpingsType pingDoc;
- pingDoc.setProcess("otherProcess");
- pingDoc.setPing(Date_t());
-
- getMockCatalog()->expectGetPing([](StringData process) {
- ASSERT_EQUALS("otherProcess", process);
- }, pingDoc);
-
- getMockCatalog()->expectGetServerInfo([]() {
- }, DistLockCatalog::ServerInfo(Date_t(), OID()));
-
- // First attempt will record the ping data.
- {
- auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
- }
+ ASSERT_EQUALS(1, unlockCallCount);
+ ASSERT_EQUALS(lastTS, unlockSessionIDPassed);
+}
- // Advance config server time to 1 millisecond before lock expiration.
- getMockCatalog()->expectGetServerInfo([]() {
- }, DistLockCatalog::ServerInfo(Date_t() + kLockExpiration - Milliseconds(1), OID()));
+TEST_F(ReplSetDistLockManagerFixture, CannotOvertakeIfExpirationHasNotElapsed) {
+ getMockCatalog()->expectGrabLock(
+ [](StringData, const OID&, StringData, StringData, Date_t, StringData) {
+ // Don't care.
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
- // Second attempt should still not overtake lock.
- {
- auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
- }
- }
+ LocksType currentLockDoc;
+ currentLockDoc.setName("bar");
+ currentLockDoc.setState(LocksType::LOCKED);
+ currentLockDoc.setProcess("otherProcess");
+ currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
+ currentLockDoc.setWho("me");
+ currentLockDoc.setWhy("why");
- TEST_F(ReplSetDistLockManagerFixture, GetPingErrorWhileOvertaking) {
- getMockCatalog()->expectGrabLock([](
- StringData, const OID&, StringData, StringData, Date_t, StringData) {
- // Don't care
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
+ getMockCatalog()->expectGetLockByName([](StringData name) { ASSERT_EQUALS("bar", name); },
+ currentLockDoc);
- LocksType currentLockDoc;
- currentLockDoc.setName("bar");
- currentLockDoc.setState(LocksType::LOCKED);
- currentLockDoc.setProcess("otherProcess");
- currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
- currentLockDoc.setWho("me");
- currentLockDoc.setWhy("why");
+ LockpingsType pingDoc;
+ pingDoc.setProcess("otherProcess");
+ pingDoc.setPing(Date_t());
- getMockCatalog()->expectGetLockByName([](StringData name) {
- ASSERT_EQUALS("bar", name);
- }, currentLockDoc);
+ getMockCatalog()->expectGetPing(
+ [](StringData process) { ASSERT_EQUALS("otherProcess", process); }, pingDoc);
- getMockCatalog()->expectGetPing([](StringData process) {
- ASSERT_EQUALS("otherProcess", process);
- }, {ErrorCodes::NetworkTimeout, "bad test network"});
+ getMockCatalog()->expectGetServerInfo([]() {}, DistLockCatalog::ServerInfo(Date_t(), OID()));
+ // First attempt will record the ping data.
+ {
auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::NetworkTimeout, status.code());
+ ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
}
- TEST_F(ReplSetDistLockManagerFixture, GetInvalidPingDocumentWhileOvertaking) {
- getMockCatalog()->expectGrabLock([](
- StringData, const OID&, StringData, StringData, Date_t, StringData) {
- // Don't care
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
-
- LocksType currentLockDoc;
- currentLockDoc.setName("bar");
- currentLockDoc.setState(LocksType::LOCKED);
- currentLockDoc.setProcess("otherProcess");
- currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
- currentLockDoc.setWho("me");
- currentLockDoc.setWhy("why");
-
- getMockCatalog()->expectGetLockByName([](StringData name) {
- ASSERT_EQUALS("bar", name);
- }, currentLockDoc);
-
- LockpingsType invalidPing;
- getMockCatalog()->expectGetPing([](StringData process) {
- ASSERT_EQUALS("otherProcess", process);
- }, invalidPing);
+ // Advance config server time to 1 millisecond before lock expiration.
+ getMockCatalog()->expectGetServerInfo(
+ []() {}, DistLockCatalog::ServerInfo(Date_t() + kLockExpiration - Milliseconds(1), OID()));
+ // Second attempt should still not overtake lock.
+ {
auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
+ ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
}
+}
- TEST_F(ReplSetDistLockManagerFixture, GetServerInfoErrorWhileOvertaking) {
- getMockCatalog()->expectGrabLock([](
- StringData, const OID&, StringData, StringData, Date_t, StringData) {
+TEST_F(ReplSetDistLockManagerFixture, GetPingErrorWhileOvertaking) {
+ getMockCatalog()->expectGrabLock(
+ [](StringData, const OID&, StringData, StringData, Date_t, StringData) {
// Don't care
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
-
- LocksType currentLockDoc;
- currentLockDoc.setName("bar");
- currentLockDoc.setState(LocksType::LOCKED);
- currentLockDoc.setProcess("otherProcess");
- currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
- currentLockDoc.setWho("me");
- currentLockDoc.setWhy("why");
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
+
+ LocksType currentLockDoc;
+ currentLockDoc.setName("bar");
+ currentLockDoc.setState(LocksType::LOCKED);
+ currentLockDoc.setProcess("otherProcess");
+ currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
+ currentLockDoc.setWho("me");
+ currentLockDoc.setWhy("why");
+
+ getMockCatalog()->expectGetLockByName([](StringData name) { ASSERT_EQUALS("bar", name); },
+ currentLockDoc);
+
+ getMockCatalog()->expectGetPing([](StringData process) {
+ ASSERT_EQUALS("otherProcess", process);
+ }, {ErrorCodes::NetworkTimeout, "bad test network"});
+
+ auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::NetworkTimeout, status.code());
+}
+
+TEST_F(ReplSetDistLockManagerFixture, GetInvalidPingDocumentWhileOvertaking) {
+ getMockCatalog()->expectGrabLock(
+ [](StringData, const OID&, StringData, StringData, Date_t, StringData) {
+ // Don't care
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
+
+ LocksType currentLockDoc;
+ currentLockDoc.setName("bar");
+ currentLockDoc.setState(LocksType::LOCKED);
+ currentLockDoc.setProcess("otherProcess");
+ currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
+ currentLockDoc.setWho("me");
+ currentLockDoc.setWhy("why");
+
+ getMockCatalog()->expectGetLockByName([](StringData name) { ASSERT_EQUALS("bar", name); },
+ currentLockDoc);
+
+ LockpingsType invalidPing;
+ getMockCatalog()->expectGetPing(
+ [](StringData process) { ASSERT_EQUALS("otherProcess", process); }, invalidPing);
+
+ auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::UnsupportedFormat, status.code());
+}
+
+TEST_F(ReplSetDistLockManagerFixture, GetServerInfoErrorWhileOvertaking) {
+ getMockCatalog()->expectGrabLock(
+ [](StringData, const OID&, StringData, StringData, Date_t, StringData) {
+ // Don't care
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
+
+ LocksType currentLockDoc;
+ currentLockDoc.setName("bar");
+ currentLockDoc.setState(LocksType::LOCKED);
+ currentLockDoc.setProcess("otherProcess");
+ currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
+ currentLockDoc.setWho("me");
+ currentLockDoc.setWhy("why");
+
+ getMockCatalog()->expectGetLockByName([](StringData name) { ASSERT_EQUALS("bar", name); },
+ currentLockDoc);
+
+ LockpingsType pingDoc;
+ pingDoc.setProcess("otherProcess");
+ pingDoc.setPing(Date_t());
+
+ getMockCatalog()->expectGetPing(
+ [](StringData process) { ASSERT_EQUALS("otherProcess", process); }, pingDoc);
+
+ getMockCatalog()->expectGetServerInfo([]() {},
+ {ErrorCodes::NetworkTimeout, "bad test network"});
+
+ auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::NetworkTimeout, status.code());
+}
+
+TEST_F(ReplSetDistLockManagerFixture, GetLockErrorWhileOvertaking) {
+ getMockCatalog()->expectGrabLock(
+ [](StringData, const OID&, StringData, StringData, Date_t, StringData) {
+ // Don't care
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
- getMockCatalog()->expectGetLockByName([](StringData name) {
- ASSERT_EQUALS("bar", name);
- }, currentLockDoc);
+ getMockCatalog()->expectGetLockByName([](StringData name) { ASSERT_EQUALS("bar", name); },
+ {ErrorCodes::NetworkTimeout, "bad test network"});
- LockpingsType pingDoc;
- pingDoc.setProcess("otherProcess");
- pingDoc.setPing(Date_t());
+ auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::NetworkTimeout, status.code());
+}
- getMockCatalog()->expectGetPing([](StringData process) {
- ASSERT_EQUALS("otherProcess", process);
- }, pingDoc);
+TEST_F(ReplSetDistLockManagerFixture, GetLockDisappearedWhileOvertaking) {
+ getMockCatalog()->expectGrabLock(
+ [](StringData, const OID&, StringData, StringData, Date_t, StringData) {
+ // Don't care
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
- getMockCatalog()->expectGetServerInfo([]() {
- }, {ErrorCodes::NetworkTimeout, "bad test network"});
+ getMockCatalog()->expectGetLockByName([](StringData name) { ASSERT_EQUALS("bar", name); },
+ {ErrorCodes::LockNotFound, "disappeared!"});
- auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::NetworkTimeout, status.code());
- }
+ auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
+}
- TEST_F(ReplSetDistLockManagerFixture, GetLockErrorWhileOvertaking) {
- getMockCatalog()->expectGrabLock([](
- StringData, const OID&, StringData, StringData, Date_t, StringData) {
+/**
+ * 1. Try to grab lock multiple times.
+ * 2. For each attempt, the ping is updated and the config server clock is advanced
+ * by increments of lock expiration duration.
+ * 3. All of the previous attempt should result in lock busy.
+ * 4. Try to grab lock again when the ping was not updated and lock expiration has elapsed.
+ */
+TEST_F(ReplSetDistLockManagerFixture, CannotOvertakeIfPingIsActive) {
+ getMockCatalog()->expectGrabLock(
+ [](StringData, const OID&, StringData, StringData, Date_t, StringData) {
// Don't care
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
-
- getMockCatalog()->expectGetLockByName([](StringData name) {
- ASSERT_EQUALS("bar", name);
- }, {ErrorCodes::NetworkTimeout, "bad test network"});
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
+
+ LocksType currentLockDoc;
+ currentLockDoc.setName("bar");
+ currentLockDoc.setState(LocksType::LOCKED);
+ currentLockDoc.setProcess("otherProcess");
+ currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
+ currentLockDoc.setWho("me");
+ currentLockDoc.setWhy("why");
+
+ Date_t currentPing;
+ LockpingsType pingDoc;
+ pingDoc.setProcess("otherProcess");
+
+ Date_t configServerLocalTime;
+ int getServerInfoCallCount = 0;
+
+ getMockCatalog()->expectGetLockByName([](StringData name) { ASSERT_EQUALS("bar", name); },
+ currentLockDoc);
+
+ const int kLoopCount = 5;
+ for (int x = 0; x < kLoopCount; x++) {
+ // Advance config server time to reach lock expiration.
+ configServerLocalTime += kLockExpiration;
- auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::NetworkTimeout, status.code());
- }
+ currentPing += Milliseconds(1);
+ pingDoc.setPing(currentPing);
- TEST_F(ReplSetDistLockManagerFixture, GetLockDisappearedWhileOvertaking) {
- getMockCatalog()->expectGrabLock([](
- StringData, const OID&, StringData, StringData, Date_t, StringData) {
- // Don't care
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
+ getMockCatalog()->expectGetPing(
+ [](StringData process) { ASSERT_EQUALS("otherProcess", process); }, pingDoc);
- getMockCatalog()->expectGetLockByName([](StringData name) {
- ASSERT_EQUALS("bar", name);
- }, {ErrorCodes::LockNotFound, "disappeared!"});
+ getMockCatalog()->expectGetServerInfo([&getServerInfoCallCount]() {
+ getServerInfoCallCount++;
+ }, DistLockCatalog::ServerInfo(configServerLocalTime, OID()));
auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
ASSERT_NOT_OK(status);
ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
}
- /**
- * 1. Try to grab lock multiple times.
- * 2. For each attempt, the ping is updated and the config server clock is advanced
- * by increments of lock expiration duration.
- * 3. All of the previous attempt should result in lock busy.
- * 4. Try to grab lock again when the ping was not updated and lock expiration has elapsed.
- */
- TEST_F(ReplSetDistLockManagerFixture, CannotOvertakeIfPingIsActive) {
- getMockCatalog()->expectGrabLock([](
- StringData, const OID&, StringData, StringData, Date_t, StringData) {
- // Don't care
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
-
- LocksType currentLockDoc;
- currentLockDoc.setName("bar");
- currentLockDoc.setState(LocksType::LOCKED);
- currentLockDoc.setProcess("otherProcess");
- currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
- currentLockDoc.setWho("me");
- currentLockDoc.setWhy("why");
-
- Date_t currentPing;
- LockpingsType pingDoc;
- pingDoc.setProcess("otherProcess");
-
- Date_t configServerLocalTime;
- int getServerInfoCallCount = 0;
-
- getMockCatalog()->expectGetLockByName([](StringData name) {
- ASSERT_EQUALS("bar", name);
- }, currentLockDoc);
-
- const int kLoopCount = 5;
- for (int x = 0; x < kLoopCount; x++) {
- // Advance config server time to reach lock expiration.
- configServerLocalTime += kLockExpiration;
-
- currentPing += Milliseconds(1);
- pingDoc.setPing(currentPing);
-
- getMockCatalog()->expectGetPing([](StringData process) {
- ASSERT_EQUALS("otherProcess", process);
- }, pingDoc);
-
- getMockCatalog()->expectGetServerInfo([&getServerInfoCallCount]() {
- getServerInfoCallCount++;
- }, DistLockCatalog::ServerInfo(configServerLocalTime, OID()));
-
- auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
- }
-
- ASSERT_EQUALS(kLoopCount, getServerInfoCallCount);
-
- configServerLocalTime += kLockExpiration;
- getMockCatalog()->expectGetServerInfo([&getServerInfoCallCount]() {
- getServerInfoCallCount++;
- }, DistLockCatalog::ServerInfo(configServerLocalTime, OID()));
-
- OID lockTS;
- // Make sure that overtake is now ok since ping is no longer updated.
- getMockCatalog()->expectOvertakeLock(
- [this, &lockTS, &currentLockDoc]
- (StringData lockID,
- const OID& lockSessionID,
- const OID& currentHolderTS,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
+ ASSERT_EQUALS(kLoopCount, getServerInfoCallCount);
+
+ configServerLocalTime += kLockExpiration;
+ getMockCatalog()->expectGetServerInfo([&getServerInfoCallCount]() {
+ getServerInfoCallCount++;
+ }, DistLockCatalog::ServerInfo(configServerLocalTime, OID()));
+
+ OID lockTS;
+ // Make sure that overtake is now ok since ping is no longer updated.
+ getMockCatalog()->expectOvertakeLock(
+ [this, &lockTS, &currentLockDoc](StringData lockID,
+ const OID& lockSessionID,
+ const OID& currentHolderTS,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
ASSERT_EQUALS("bar", lockID);
lockTS = lockSessionID;
ASSERT_EQUALS(currentLockDoc.getLockID(), currentHolderTS);
ASSERT_EQUALS(getProcessID(), processId);
ASSERT_EQUALS("foo", why);
- }, currentLockDoc); // return arbitrary valid lock document, for testing purposes only.
+ },
+ currentLockDoc); // return arbitrary valid lock document, for testing purposes only.
- int unlockCallCount = 0;
- OID unlockSessionIDPassed;
+ int unlockCallCount = 0;
+ OID unlockSessionIDPassed;
- {
- auto lockStatus = getMgr()->lock("bar",
- "foo",
- Milliseconds(0),
- Milliseconds(0));
+ {
+ auto lockStatus = getMgr()->lock("bar", "foo", Milliseconds(0), Milliseconds(0));
- ASSERT_OK(lockStatus.getStatus());
+ ASSERT_OK(lockStatus.getStatus());
- getMockCatalog()->expectNoGrabLock();
- getMockCatalog()->expectUnLock(
- [&unlockCallCount, &unlockSessionIDPassed](const OID& lockSessionID) {
+ getMockCatalog()->expectNoGrabLock();
+ getMockCatalog()->expectUnLock(
+ [&unlockCallCount, &unlockSessionIDPassed](const OID& lockSessionID) {
unlockCallCount++;
unlockSessionIDPassed = lockSessionID;
- }, Status::OK());
- }
-
- ASSERT_EQUALS(1, unlockCallCount);
- ASSERT_EQUALS(lockTS, unlockSessionIDPassed);
+ },
+ Status::OK());
}
- /**
- * 1. Try to grab lock multiple times.
- * 2. For each attempt, the owner of the lock is different and the config server clock is
- * advanced by increments of lock expiration duration.
- * 3. All of the previous attempt should result in lock busy.
- * 4. Try to grab lock again when the ping was not updated and lock expiration has elapsed.
- */
- TEST_F(ReplSetDistLockManagerFixture, CannotOvertakeIfOwnerJustChanged) {
- getMockCatalog()->expectGrabLock([](
- StringData, const OID&, StringData, StringData, Date_t, StringData) {
+ ASSERT_EQUALS(1, unlockCallCount);
+ ASSERT_EQUALS(lockTS, unlockSessionIDPassed);
+}
+
+/**
+ * 1. Try to grab lock multiple times.
+ * 2. For each attempt, the owner of the lock is different and the config server clock is
+ * advanced by increments of lock expiration duration.
+ * 3. All of the previous attempt should result in lock busy.
+ * 4. Try to grab lock again when the ping was not updated and lock expiration has elapsed.
+ */
+TEST_F(ReplSetDistLockManagerFixture, CannotOvertakeIfOwnerJustChanged) {
+ getMockCatalog()->expectGrabLock(
+ [](StringData, const OID&, StringData, StringData, Date_t, StringData) {
// Don't care
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
-
- LocksType currentLockDoc;
- currentLockDoc.setName("bar");
- currentLockDoc.setState(LocksType::LOCKED);
- currentLockDoc.setProcess("otherProcess");
- currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
- currentLockDoc.setWho("me");
- currentLockDoc.setWhy("why");
-
- Date_t currentPing;
- LockpingsType pingDoc;
- pingDoc.setProcess("otherProcess");
- pingDoc.setPing(Date_t());
-
- Date_t configServerLocalTime;
- int getServerInfoCallCount = 0;
-
- getMockCatalog()->expectGetPing([](StringData process) {
- ASSERT_EQUALS("otherProcess", process);
- }, pingDoc);
-
- const int kLoopCount = 5;
- for (int x = 0; x < kLoopCount; x++) {
- // Advance config server time to reach lock expiration.
- configServerLocalTime += kLockExpiration;
-
- currentLockDoc.setLockID(OID::gen());
-
- getMockCatalog()->expectGetLockByName([](StringData name) {
- ASSERT_EQUALS("bar", name);
- }, currentLockDoc);
-
- getMockCatalog()->expectGetServerInfo([&getServerInfoCallCount]() {
- getServerInfoCallCount++;
- }, DistLockCatalog::ServerInfo(configServerLocalTime, OID()));
-
- auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
- }
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
+
+ LocksType currentLockDoc;
+ currentLockDoc.setName("bar");
+ currentLockDoc.setState(LocksType::LOCKED);
+ currentLockDoc.setProcess("otherProcess");
+ currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
+ currentLockDoc.setWho("me");
+ currentLockDoc.setWhy("why");
+
+ Date_t currentPing;
+ LockpingsType pingDoc;
+ pingDoc.setProcess("otherProcess");
+ pingDoc.setPing(Date_t());
+
+ Date_t configServerLocalTime;
+ int getServerInfoCallCount = 0;
+
+ getMockCatalog()->expectGetPing(
+ [](StringData process) { ASSERT_EQUALS("otherProcess", process); }, pingDoc);
+
+ const int kLoopCount = 5;
+ for (int x = 0; x < kLoopCount; x++) {
+ // Advance config server time to reach lock expiration.
+ configServerLocalTime += kLockExpiration;
- ASSERT_EQUALS(kLoopCount, getServerInfoCallCount);
+ currentLockDoc.setLockID(OID::gen());
+
+ getMockCatalog()->expectGetLockByName([](StringData name) { ASSERT_EQUALS("bar", name); },
+ currentLockDoc);
- configServerLocalTime += kLockExpiration;
getMockCatalog()->expectGetServerInfo([&getServerInfoCallCount]() {
getServerInfoCallCount++;
}, DistLockCatalog::ServerInfo(configServerLocalTime, OID()));
- OID lockTS;
- // Make sure that overtake is now ok since lock owner didn't change.
- getMockCatalog()->expectOvertakeLock(
- [this, &lockTS, &currentLockDoc]
- (StringData lockID,
- const OID& lockSessionID,
- const OID& currentHolderTS,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
+ auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
+ }
+
+ ASSERT_EQUALS(kLoopCount, getServerInfoCallCount);
+
+ configServerLocalTime += kLockExpiration;
+ getMockCatalog()->expectGetServerInfo([&getServerInfoCallCount]() {
+ getServerInfoCallCount++;
+ }, DistLockCatalog::ServerInfo(configServerLocalTime, OID()));
+
+ OID lockTS;
+ // Make sure that overtake is now ok since lock owner didn't change.
+ getMockCatalog()->expectOvertakeLock(
+ [this, &lockTS, &currentLockDoc](StringData lockID,
+ const OID& lockSessionID,
+ const OID& currentHolderTS,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
ASSERT_EQUALS("bar", lockID);
lockTS = lockSessionID;
ASSERT_EQUALS(currentLockDoc.getLockID(), currentHolderTS);
ASSERT_EQUALS(getProcessID(), processId);
ASSERT_EQUALS("foo", why);
- }, currentLockDoc); // return arbitrary valid lock document, for testing purposes only.
+ },
+ currentLockDoc); // return arbitrary valid lock document, for testing purposes only.
- int unlockCallCount = 0;
- OID unlockSessionIDPassed;
+ int unlockCallCount = 0;
+ OID unlockSessionIDPassed;
- {
- auto lockStatus = getMgr()->lock("bar",
- "foo",
- Milliseconds(0),
- Milliseconds(0));
+ {
+ auto lockStatus = getMgr()->lock("bar", "foo", Milliseconds(0), Milliseconds(0));
- ASSERT_OK(lockStatus.getStatus());
+ ASSERT_OK(lockStatus.getStatus());
- getMockCatalog()->expectNoGrabLock();
- getMockCatalog()->expectUnLock(
- [&unlockCallCount, &unlockSessionIDPassed](const OID& lockSessionID) {
+ getMockCatalog()->expectNoGrabLock();
+ getMockCatalog()->expectUnLock(
+ [&unlockCallCount, &unlockSessionIDPassed](const OID& lockSessionID) {
unlockCallCount++;
unlockSessionIDPassed = lockSessionID;
- }, Status::OK());
- }
-
- ASSERT_EQUALS(1, unlockCallCount);
- ASSERT_EQUALS(lockTS, unlockSessionIDPassed);
+ },
+ Status::OK());
}
- /**
- * 1. Try to grab lock multiple times.
- * 2. For each attempt, the electionId of the config server is different and the
- * config server clock is advanced by increments of lock expiration duration.
- * 3. All of the previous attempt should result in lock busy.
- * 4. Try to grab lock again when the ping was not updated and lock expiration has elapsed.
- */
- TEST_F(ReplSetDistLockManagerFixture, CannotOvertakeIfElectionIdChanged) {
- getMockCatalog()->expectGrabLock([](
- StringData, const OID&, StringData, StringData, Date_t, StringData) {
+ ASSERT_EQUALS(1, unlockCallCount);
+ ASSERT_EQUALS(lockTS, unlockSessionIDPassed);
+}
+
+/**
+ * 1. Try to grab lock multiple times.
+ * 2. For each attempt, the electionId of the config server is different and the
+ * config server clock is advanced by increments of lock expiration duration.
+ * 3. All of the previous attempt should result in lock busy.
+ * 4. Try to grab lock again when the ping was not updated and lock expiration has elapsed.
+ */
+TEST_F(ReplSetDistLockManagerFixture, CannotOvertakeIfElectionIdChanged) {
+ getMockCatalog()->expectGrabLock(
+ [](StringData, const OID&, StringData, StringData, Date_t, StringData) {
// Don't care
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
-
- LocksType currentLockDoc;
- currentLockDoc.setName("bar");
- currentLockDoc.setState(LocksType::LOCKED);
- currentLockDoc.setProcess("otherProcess");
- currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
- currentLockDoc.setWho("me");
- currentLockDoc.setWhy("why");
-
- Date_t currentPing;
- LockpingsType pingDoc;
- pingDoc.setProcess("otherProcess");
- pingDoc.setPing(Date_t());
-
- Date_t configServerLocalTime;
- int getServerInfoCallCount = 0;
-
- const LocksType& fixedLockDoc = currentLockDoc;
- const LockpingsType& fixedPingDoc = pingDoc;
-
- const int kLoopCount = 5;
- OID lastElectionId;
- for (int x = 0; x < kLoopCount; x++) {
- // Advance config server time to reach lock expiration.
- configServerLocalTime += kLockExpiration;
-
- getMockCatalog()->expectGetLockByName([](StringData name) {
- ASSERT_EQUALS("bar", name);
- }, fixedLockDoc);
-
- getMockCatalog()->expectGetPing([](StringData process) {
- ASSERT_EQUALS("otherProcess", process);
- }, fixedPingDoc);
-
- lastElectionId = OID::gen();
- getMockCatalog()->expectGetServerInfo([&getServerInfoCallCount]() {
- getServerInfoCallCount++;
- }, DistLockCatalog::ServerInfo(configServerLocalTime, lastElectionId));
-
- auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
- }
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
+
+ LocksType currentLockDoc;
+ currentLockDoc.setName("bar");
+ currentLockDoc.setState(LocksType::LOCKED);
+ currentLockDoc.setProcess("otherProcess");
+ currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
+ currentLockDoc.setWho("me");
+ currentLockDoc.setWhy("why");
+
+ Date_t currentPing;
+ LockpingsType pingDoc;
+ pingDoc.setProcess("otherProcess");
+ pingDoc.setPing(Date_t());
+
+ Date_t configServerLocalTime;
+ int getServerInfoCallCount = 0;
+
+ const LocksType& fixedLockDoc = currentLockDoc;
+ const LockpingsType& fixedPingDoc = pingDoc;
+
+ const int kLoopCount = 5;
+ OID lastElectionId;
+ for (int x = 0; x < kLoopCount; x++) {
+ // Advance config server time to reach lock expiration.
+ configServerLocalTime += kLockExpiration;
- ASSERT_EQUALS(kLoopCount, getServerInfoCallCount);
+ getMockCatalog()->expectGetLockByName([](StringData name) { ASSERT_EQUALS("bar", name); },
+ fixedLockDoc);
- configServerLocalTime += kLockExpiration;
+ getMockCatalog()->expectGetPing(
+ [](StringData process) { ASSERT_EQUALS("otherProcess", process); }, fixedPingDoc);
+
+ lastElectionId = OID::gen();
getMockCatalog()->expectGetServerInfo([&getServerInfoCallCount]() {
getServerInfoCallCount++;
}, DistLockCatalog::ServerInfo(configServerLocalTime, lastElectionId));
- OID lockTS;
- // Make sure that overtake is now ok since electionId didn't change.
- getMockCatalog()->expectOvertakeLock(
- [this, &lockTS, &currentLockDoc]
- (StringData lockID,
- const OID& lockSessionID,
- const OID& currentHolderTS,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
+ auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
+ }
+
+ ASSERT_EQUALS(kLoopCount, getServerInfoCallCount);
+
+ configServerLocalTime += kLockExpiration;
+ getMockCatalog()->expectGetServerInfo([&getServerInfoCallCount]() {
+ getServerInfoCallCount++;
+ }, DistLockCatalog::ServerInfo(configServerLocalTime, lastElectionId));
+
+ OID lockTS;
+ // Make sure that overtake is now ok since electionId didn't change.
+ getMockCatalog()->expectOvertakeLock(
+ [this, &lockTS, &currentLockDoc](StringData lockID,
+ const OID& lockSessionID,
+ const OID& currentHolderTS,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
ASSERT_EQUALS("bar", lockID);
lockTS = lockSessionID;
ASSERT_EQUALS(currentLockDoc.getLockID(), currentHolderTS);
ASSERT_EQUALS(getProcessID(), processId);
ASSERT_EQUALS("foo", why);
- }, currentLockDoc); // return arbitrary valid lock document, for testing purposes only.
+ },
+ currentLockDoc); // return arbitrary valid lock document, for testing purposes only.
- int unlockCallCount = 0;
- OID unlockSessionIDPassed;
+ int unlockCallCount = 0;
+ OID unlockSessionIDPassed;
- {
- auto lockStatus = getMgr()->lock("bar",
- "foo",
- Milliseconds(0),
- Milliseconds(0));
+ {
+ auto lockStatus = getMgr()->lock("bar", "foo", Milliseconds(0), Milliseconds(0));
- ASSERT_OK(lockStatus.getStatus());
+ ASSERT_OK(lockStatus.getStatus());
- getMockCatalog()->expectNoGrabLock();
- getMockCatalog()->expectUnLock(
- [&unlockCallCount, &unlockSessionIDPassed](const OID& lockSessionID) {
+ getMockCatalog()->expectNoGrabLock();
+ getMockCatalog()->expectUnLock(
+ [&unlockCallCount, &unlockSessionIDPassed](const OID& lockSessionID) {
unlockCallCount++;
unlockSessionIDPassed = lockSessionID;
- }, Status::OK());
- }
-
- ASSERT_EQUALS(1, unlockCallCount);
- ASSERT_EQUALS(lockTS, unlockSessionIDPassed);
+ },
+ Status::OK());
}
- /**
- * Test scenario:
- * 1. Attempt to grab lock fails because lock is already owned.
- * 2. Try to get ping data and config server clock.
- * 3. Since we don't have previous ping data to compare with, we cannot
- * decide whether it's ok to overtake, so we can't.
- * 4. Lock expiration has elapsed and the ping has not been updated since.
- * 5. 2nd attempt to grab lock still fails for the same reason.
- * 6. But since the ping is not fresh anymore, dist lock manager should overtake lock.
- * 7. Attempt to overtake resulted in an error.
- * 8. Check that unlock was called.
- */
- TEST_F(ReplSetDistLockManagerFixture, LockOvertakingResultsInError) {
- getMockCatalog()->expectGrabLock([](
- StringData, const OID&, StringData, StringData, Date_t, StringData) {
+ ASSERT_EQUALS(1, unlockCallCount);
+ ASSERT_EQUALS(lockTS, unlockSessionIDPassed);
+}
+
+/**
+ * Test scenario:
+ * 1. Attempt to grab lock fails because lock is already owned.
+ * 2. Try to get ping data and config server clock.
+ * 3. Since we don't have previous ping data to compare with, we cannot
+ * decide whether it's ok to overtake, so we can't.
+ * 4. Lock expiration has elapsed and the ping has not been updated since.
+ * 5. 2nd attempt to grab lock still fails for the same reason.
+ * 6. But since the ping is not fresh anymore, dist lock manager should overtake lock.
+ * 7. Attempt to overtake resulted in an error.
+ * 8. Check that unlock was called.
+ */
+TEST_F(ReplSetDistLockManagerFixture, LockOvertakingResultsInError) {
+ getMockCatalog()->expectGrabLock(
+ [](StringData, const OID&, StringData, StringData, Date_t, StringData) {
// Don't care
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
-
- LocksType currentLockDoc;
- currentLockDoc.setName("bar");
- currentLockDoc.setState(LocksType::LOCKED);
- currentLockDoc.setProcess("otherProcess");
- currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
- currentLockDoc.setWho("me");
- currentLockDoc.setWhy("why");
-
- getMockCatalog()->expectGetLockByName([](StringData name) {
- ASSERT_EQUALS("bar", name);
- }, currentLockDoc);
-
- LockpingsType pingDoc;
- pingDoc.setProcess("otherProcess");
- pingDoc.setPing(Date_t());
-
- getMockCatalog()->expectGetPing([](StringData process) {
- ASSERT_EQUALS("otherProcess", process);
- }, pingDoc);
-
- getMockCatalog()->expectGetServerInfo([]() {
- }, DistLockCatalog::ServerInfo(Date_t(), OID()));
-
- // First attempt will record the ping data.
- {
- auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
- }
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
+
+ LocksType currentLockDoc;
+ currentLockDoc.setName("bar");
+ currentLockDoc.setState(LocksType::LOCKED);
+ currentLockDoc.setProcess("otherProcess");
+ currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
+ currentLockDoc.setWho("me");
+ currentLockDoc.setWhy("why");
+
+ getMockCatalog()->expectGetLockByName([](StringData name) { ASSERT_EQUALS("bar", name); },
+ currentLockDoc);
+
+ LockpingsType pingDoc;
+ pingDoc.setProcess("otherProcess");
+ pingDoc.setPing(Date_t());
+
+ getMockCatalog()->expectGetPing(
+ [](StringData process) { ASSERT_EQUALS("otherProcess", process); }, pingDoc);
- // Advance config server time to exceed lock expiration.
- getMockCatalog()->expectGetServerInfo([]() {
- }, DistLockCatalog::ServerInfo(Date_t() + kLockExpiration + Milliseconds(1), OID()));
-
- OID lastTS;
- getMockCatalog()->expectOvertakeLock(
- [this, &lastTS, &currentLockDoc]
- (StringData lockID,
- const OID& lockSessionID,
- const OID& currentHolderTS,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
+ getMockCatalog()->expectGetServerInfo([]() {}, DistLockCatalog::ServerInfo(Date_t(), OID()));
+
+ // First attempt will record the ping data.
+ {
+ auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
+ }
+
+ // Advance config server time to exceed lock expiration.
+ getMockCatalog()->expectGetServerInfo(
+ []() {}, DistLockCatalog::ServerInfo(Date_t() + kLockExpiration + Milliseconds(1), OID()));
+
+ OID lastTS;
+ getMockCatalog()->expectOvertakeLock(
+ [this, &lastTS, &currentLockDoc](StringData lockID,
+ const OID& lockSessionID,
+ const OID& currentHolderTS,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
ASSERT_EQUALS("bar", lockID);
lastTS = lockSessionID;
ASSERT_EQUALS(currentLockDoc.getLockID(), currentHolderTS);
ASSERT_EQUALS(getProcessID(), processId);
ASSERT_EQUALS("foo", why);
- }, {ErrorCodes::NetworkTimeout, "bad test network"});
+ },
+ {ErrorCodes::NetworkTimeout, "bad test network"});
- OID unlockSessionIDPassed;
+ OID unlockSessionIDPassed;
- stdx::mutex unlockMutex;
- stdx::condition_variable unlockCV;
- getMockCatalog()->expectUnLock(
- [&unlockSessionIDPassed, &unlockMutex, &unlockCV](
- const OID& lockSessionID) {
+ stdx::mutex unlockMutex;
+ stdx::condition_variable unlockCV;
+ getMockCatalog()->expectUnLock(
+ [&unlockSessionIDPassed, &unlockMutex, &unlockCV](const OID& lockSessionID) {
stdx::unique_lock<stdx::mutex> lk(unlockMutex);
unlockSessionIDPassed = lockSessionID;
unlockCV.notify_all();
- }, Status::OK());
+ },
+ Status::OK());
- // Second attempt should overtake lock.
- auto lockStatus = getMgr()->lock("bar",
- "foo",
- Milliseconds(0),
- Milliseconds(0));
+ // Second attempt should overtake lock.
+ auto lockStatus = getMgr()->lock("bar", "foo", Milliseconds(0), Milliseconds(0));
- ASSERT_NOT_OK(lockStatus.getStatus());
+ ASSERT_NOT_OK(lockStatus.getStatus());
- bool didTimeout = false;
- {
- stdx::unique_lock<stdx::mutex> lk(unlockMutex);
- if (!unlockSessionIDPassed.isSet()) {
- didTimeout = unlockCV.wait_for(lk, kUnlockTimeout) == stdx::cv_status::timeout;
- }
+ bool didTimeout = false;
+ {
+ stdx::unique_lock<stdx::mutex> lk(unlockMutex);
+ if (!unlockSessionIDPassed.isSet()) {
+ didTimeout = unlockCV.wait_for(lk, kUnlockTimeout) == stdx::cv_status::timeout;
}
+ }
- // Join the background thread before trying to call asserts. Shutdown calls
- // stopPing and we don't care in this test.
- getMockCatalog()->expectStopPing([](StringData){}, Status::OK());
- getMgr()->shutDown();
+ // Join the background thread before trying to call asserts. Shutdown calls
+ // stopPing and we don't care in this test.
+ getMockCatalog()->expectStopPing([](StringData) {}, Status::OK());
+ getMgr()->shutDown();
- // No assert until shutDown has been called to make sure that the background thread
- // won't be trying to access the local variables that were captured by lamdas that
- // may have gone out of scope when the assert unwinds the stack.
- // No need to grab testMutex since there is only one thread running at this point.
+ // No assert until shutDown has been called to make sure that the background thread
+ // won't be trying to access the local variables that were captured by lamdas that
+ // may have gone out of scope when the assert unwinds the stack.
+ // No need to grab testMutex since there is only one thread running at this point.
- ASSERT_FALSE(didTimeout);
- ASSERT_EQUALS(lastTS, unlockSessionIDPassed);
- }
+ ASSERT_FALSE(didTimeout);
+ ASSERT_EQUALS(lastTS, unlockSessionIDPassed);
+}
- /**
- * Test scenario:
- * 1. Attempt to grab lock fails because lock is already owned.
- * 2. Try to get ping data and config server clock.
- * 3. Since we don't have previous ping data to compare with, we cannot
- * decide whether it's ok to overtake, so we can't.
- * 4. Lock expiration has elapsed and the ping has not been updated since.
- * 5. 2nd attempt to grab lock still fails for the same reason.
- * 6. But since the ping is not fresh anymore, dist lock manager should overtake lock.
- * 7. Attempt to overtake resulted failed because someone beat us into it.
- */
- TEST_F(ReplSetDistLockManagerFixture, LockOvertakingFailed) {
- getMockCatalog()->expectGrabLock([](
- StringData, const OID&, StringData, StringData, Date_t, StringData) {
+/**
+ * Test scenario:
+ * 1. Attempt to grab lock fails because lock is already owned.
+ * 2. Try to get ping data and config server clock.
+ * 3. Since we don't have previous ping data to compare with, we cannot
+ * decide whether it's ok to overtake, so we can't.
+ * 4. Lock expiration has elapsed and the ping has not been updated since.
+ * 5. 2nd attempt to grab lock still fails for the same reason.
+ * 6. But since the ping is not fresh anymore, dist lock manager should overtake lock.
+ * 7. Attempt to overtake resulted failed because someone beat us into it.
+ */
+TEST_F(ReplSetDistLockManagerFixture, LockOvertakingFailed) {
+ getMockCatalog()->expectGrabLock(
+ [](StringData, const OID&, StringData, StringData, Date_t, StringData) {
// Don't care
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
-
- LocksType currentLockDoc;
- currentLockDoc.setName("bar");
- currentLockDoc.setState(LocksType::LOCKED);
- currentLockDoc.setProcess("otherProcess");
- currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
- currentLockDoc.setWho("me");
- currentLockDoc.setWhy("why");
-
- getMockCatalog()->expectGetLockByName([](StringData name) {
- ASSERT_EQUALS("bar", name);
- }, currentLockDoc);
-
- LockpingsType pingDoc;
- pingDoc.setProcess("otherProcess");
- pingDoc.setPing(Date_t());
-
- getMockCatalog()->expectGetPing([](StringData process) {
- ASSERT_EQUALS("otherProcess", process);
- }, pingDoc);
-
- getMockCatalog()->expectGetServerInfo([]() {
- }, DistLockCatalog::ServerInfo(Date_t(), OID()));
-
- // First attempt will record the ping data.
- {
- auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
- }
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
+
+ LocksType currentLockDoc;
+ currentLockDoc.setName("bar");
+ currentLockDoc.setState(LocksType::LOCKED);
+ currentLockDoc.setProcess("otherProcess");
+ currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
+ currentLockDoc.setWho("me");
+ currentLockDoc.setWhy("why");
+
+ getMockCatalog()->expectGetLockByName([](StringData name) { ASSERT_EQUALS("bar", name); },
+ currentLockDoc);
+
+ LockpingsType pingDoc;
+ pingDoc.setProcess("otherProcess");
+ pingDoc.setPing(Date_t());
- // Advance config server time to exceed lock expiration.
- getMockCatalog()->expectGetServerInfo([]() {
- }, DistLockCatalog::ServerInfo(Date_t() + kLockExpiration + Milliseconds(1), OID()));
-
- // Second attempt should overtake lock.
- getMockCatalog()->expectOvertakeLock([this, &currentLockDoc]
- (StringData lockID,
- const OID& lockSessionID,
- const OID& currentHolderTS,
- StringData who,
- StringData processId,
- Date_t time,
- StringData why) {
+ getMockCatalog()->expectGetPing(
+ [](StringData process) { ASSERT_EQUALS("otherProcess", process); }, pingDoc);
+
+ getMockCatalog()->expectGetServerInfo([]() {}, DistLockCatalog::ServerInfo(Date_t(), OID()));
+
+ // First attempt will record the ping data.
+ {
+ auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
+ }
+
+ // Advance config server time to exceed lock expiration.
+ getMockCatalog()->expectGetServerInfo(
+ []() {}, DistLockCatalog::ServerInfo(Date_t() + kLockExpiration + Milliseconds(1), OID()));
+
+ // Second attempt should overtake lock.
+ getMockCatalog()->expectOvertakeLock(
+ [this, &currentLockDoc](StringData lockID,
+ const OID& lockSessionID,
+ const OID& currentHolderTS,
+ StringData who,
+ StringData processId,
+ Date_t time,
+ StringData why) {
ASSERT_EQUALS("bar", lockID);
ASSERT_EQUALS(currentLockDoc.getLockID(), currentHolderTS);
ASSERT_EQUALS(getProcessID(), processId);
ASSERT_EQUALS("foo", why);
- }, {ErrorCodes::LockStateChangeFailed, "nmod 0"});
-
- {
- auto status = getMgr()->lock("bar",
- "foo",
- Milliseconds(0),
- Milliseconds(0)).getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
- }
+ },
+ {ErrorCodes::LockStateChangeFailed, "nmod 0"});
+
+ {
+ auto status = getMgr()->lock("bar", "foo", Milliseconds(0), Milliseconds(0)).getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
}
+}
- /**
- * Test scenario:
- * 1. Attempt to grab lock fails because lock is already owned.
- * 2. Try to get ping data and config server clock.
- * 3. Since we don't have previous ping data to compare with, we cannot
- * decide whether it's ok to overtake, so we can't.
- * 4. Lock expiration has elapsed and the ping has not been updated since.
- * 5. 2nd attempt to grab lock still fails for the same reason.
- * 6. But since the ping is not fresh anymore, dist lock manager should overtake lock.
- * 7. Attempt to overtake resulted failed because someone beat us into it.
- */
- TEST_F(ReplSetDistLockManagerFixture, CannotOvertakeIfConfigServerClockGoesBackwards) {
- getMockCatalog()->expectGrabLock([](
- StringData, const OID&, StringData, StringData, Date_t, StringData) {
+/**
+ * Test scenario:
+ * 1. Attempt to grab lock fails because lock is already owned.
+ * 2. Try to get ping data and config server clock.
+ * 3. Since we don't have previous ping data to compare with, we cannot
+ * decide whether it's ok to overtake, so we can't.
+ * 4. Lock expiration has elapsed and the ping has not been updated since.
+ * 5. 2nd attempt to grab lock still fails for the same reason.
+ * 6. But since the ping is not fresh anymore, dist lock manager should overtake lock.
+ * 7. Attempt to overtake resulted failed because someone beat us into it.
+ */
+TEST_F(ReplSetDistLockManagerFixture, CannotOvertakeIfConfigServerClockGoesBackwards) {
+ getMockCatalog()->expectGrabLock(
+ [](StringData, const OID&, StringData, StringData, Date_t, StringData) {
// Don't care
- }, {ErrorCodes::LockStateChangeFailed, "nMod 0"});
-
- LocksType currentLockDoc;
- currentLockDoc.setName("bar");
- currentLockDoc.setState(LocksType::LOCKED);
- currentLockDoc.setProcess("otherProcess");
- currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
- currentLockDoc.setWho("me");
- currentLockDoc.setWhy("why");
-
- getMockCatalog()->expectGetLockByName([](StringData name) {
- ASSERT_EQUALS("bar", name);
- }, currentLockDoc);
-
- LockpingsType pingDoc;
- pingDoc.setProcess("otherProcess");
- pingDoc.setPing(Date_t());
-
- getMockCatalog()->expectGetPing([](StringData process) {
- ASSERT_EQUALS("otherProcess", process);
- }, pingDoc);
-
- Date_t configClock(Date_t::now());
- getMockCatalog()->expectGetServerInfo([]() {
- }, DistLockCatalog::ServerInfo(configClock, OID()));
-
- // First attempt will record the ping data.
- {
- auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
- }
+ },
+ {ErrorCodes::LockStateChangeFailed, "nMod 0"});
- // Make config server time go backwards by lock expiration duration.
- getMockCatalog()->expectGetServerInfo([]() {
- }, DistLockCatalog::ServerInfo(configClock - kLockExpiration - Milliseconds(1), OID()));
-
- // Second attempt should not overtake lock.
- {
- auto status = getMgr()->lock("bar",
- "foo",
- Milliseconds(0),
- Milliseconds(0)).getStatus();
- ASSERT_NOT_OK(status);
- ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
- }
+ LocksType currentLockDoc;
+ currentLockDoc.setName("bar");
+ currentLockDoc.setState(LocksType::LOCKED);
+ currentLockDoc.setProcess("otherProcess");
+ currentLockDoc.setLockID(OID("5572007fda9e476582bf3716"));
+ currentLockDoc.setWho("me");
+ currentLockDoc.setWhy("why");
+
+ getMockCatalog()->expectGetLockByName([](StringData name) { ASSERT_EQUALS("bar", name); },
+ currentLockDoc);
+
+ LockpingsType pingDoc;
+ pingDoc.setProcess("otherProcess");
+ pingDoc.setPing(Date_t());
+
+ getMockCatalog()->expectGetPing(
+ [](StringData process) { ASSERT_EQUALS("otherProcess", process); }, pingDoc);
+
+ Date_t configClock(Date_t::now());
+ getMockCatalog()->expectGetServerInfo([]() {}, DistLockCatalog::ServerInfo(configClock, OID()));
+
+ // First attempt will record the ping data.
+ {
+ auto status = getMgr()->lock("bar", "", Milliseconds(0), Milliseconds(0)).getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
+ }
+
+ // Make config server time go backwards by lock expiration duration.
+ getMockCatalog()->expectGetServerInfo([]() {
+ }, DistLockCatalog::ServerInfo(configClock - kLockExpiration - Milliseconds(1), OID()));
+
+ // Second attempt should not overtake lock.
+ {
+ auto status = getMgr()->lock("bar", "foo", Milliseconds(0), Milliseconds(0)).getStatus();
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(ErrorCodes::LockBusy, status.code());
}
+}
-} // unnamed namespace
-} // namespace mongo
+} // unnamed namespace
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_actionlog.cpp b/src/mongo/s/catalog/type_actionlog.cpp
index 49dd09b6958..1cefd2774dc 100644
--- a/src/mongo/s/catalog/type_actionlog.cpp
+++ b/src/mongo/s/catalog/type_actionlog.cpp
@@ -32,124 +32,129 @@
namespace mongo {
- using std::string;
+using std::string;
- using mongo::str::stream;
+using mongo::str::stream;
- const std::string ActionLogType::ConfigNS = "config.actionlog";
+const std::string ActionLogType::ConfigNS = "config.actionlog";
- const BSONField<std::string> ActionLogType::server("server");
- const BSONField<std::string> ActionLogType::what("what");
- const BSONField<Date_t> ActionLogType::time("time");
- const BSONField<BSONObj> ActionLogType::details("details");
+const BSONField<std::string> ActionLogType::server("server");
+const BSONField<std::string> ActionLogType::what("what");
+const BSONField<Date_t> ActionLogType::time("time");
+const BSONField<BSONObj> ActionLogType::details("details");
- ActionLogType::ActionLogType() {
- clear();
- }
+ActionLogType::ActionLogType() {
+ clear();
+}
- ActionLogType::~ActionLogType() {
- }
+ActionLogType::~ActionLogType() {}
- bool ActionLogType::isValid(std::string* errMsg) const {
- std::string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
-
- // All the mandatory fields must be present.
- if (!_isServerSet) {
- *errMsg = stream() << "missing " << server.name() << " field";
- return false;
- }
- if (!_isTimeSet) {
- *errMsg = stream() << "missing " << time.name() << " field";
- return false;
- }
- if (!_isWhatSet) {
- *errMsg = stream() << "missing " << what.name() << " field";
- return false;
- }
- if (!_isDetailsSet) {
- *errMsg = stream() << "missing " << details.name() << " field";
- return false;
- }
-
- return true;
+bool ActionLogType::isValid(std::string* errMsg) const {
+ std::string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
}
- BSONObj ActionLogType::toBSON() const {
- BSONObjBuilder builder;
-
- if (_isServerSet) builder.append(server(), _server);
- if (_isTimeSet) builder.append(time(), _time);
- if (_isWhatSet) builder.append(what(), _what);
- if (_isDetailsSet) builder.append(details(), _details);
-
- return builder.obj();
+ // All the mandatory fields must be present.
+ if (!_isServerSet) {
+ *errMsg = stream() << "missing " << server.name() << " field";
+ return false;
+ }
+ if (!_isTimeSet) {
+ *errMsg = stream() << "missing " << time.name() << " field";
+ return false;
+ }
+ if (!_isWhatSet) {
+ *errMsg = stream() << "missing " << what.name() << " field";
+ return false;
+ }
+ if (!_isDetailsSet) {
+ *errMsg = stream() << "missing " << details.name() << " field";
+ return false;
}
- bool ActionLogType::parseBSON(const BSONObj& source, string* errMsg) {
- clear();
+ return true;
+}
- std::string dummy;
- if (!errMsg) errMsg = &dummy;
+BSONObj ActionLogType::toBSON() const {
+ BSONObjBuilder builder;
- FieldParser::FieldState fieldState;
+ if (_isServerSet)
+ builder.append(server(), _server);
+ if (_isTimeSet)
+ builder.append(time(), _time);
+ if (_isWhatSet)
+ builder.append(what(), _what);
+ if (_isDetailsSet)
+ builder.append(details(), _details);
- fieldState = FieldParser::extract(source, server, &_server, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isServerSet = fieldState == FieldParser::FIELD_SET;
+ return builder.obj();
+}
- fieldState = FieldParser::extract(source, time, &_time, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isTimeSet = fieldState == FieldParser::FIELD_SET;
+bool ActionLogType::parseBSON(const BSONObj& source, string* errMsg) {
+ clear();
- fieldState = FieldParser::extract(source, what, &_what, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isWhatSet = fieldState == FieldParser::FIELD_SET;
+ std::string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
- fieldState = FieldParser::extract(source, details, &_details, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isDetailsSet = fieldState == FieldParser::FIELD_SET;
+ FieldParser::FieldState fieldState;
- return true;
- }
+ fieldState = FieldParser::extract(source, server, &_server, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isServerSet = fieldState == FieldParser::FIELD_SET;
- void ActionLogType::clear() {
+ fieldState = FieldParser::extract(source, time, &_time, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isTimeSet = fieldState == FieldParser::FIELD_SET;
- _server.clear();
- _isServerSet = false;
+ fieldState = FieldParser::extract(source, what, &_what, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isWhatSet = fieldState == FieldParser::FIELD_SET;
- _what.clear();
- _isWhatSet = false;
+ fieldState = FieldParser::extract(source, details, &_details, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isDetailsSet = fieldState == FieldParser::FIELD_SET;
- _time = Date_t();
- _isTimeSet = false;
+ return true;
+}
- _details = BSONObj();
- _isDetailsSet = false;
+void ActionLogType::clear() {
+ _server.clear();
+ _isServerSet = false;
- }
+ _what.clear();
+ _isWhatSet = false;
- void ActionLogType::cloneTo(ActionLogType* other) const {
- other->clear();
+ _time = Date_t();
+ _isTimeSet = false;
- other->_server = _server;
- other->_isServerSet = _isServerSet;
+ _details = BSONObj();
+ _isDetailsSet = false;
+}
- other->_what = _what;
- other->_isWhatSet = _isWhatSet;
+void ActionLogType::cloneTo(ActionLogType* other) const {
+ other->clear();
- other->_time = _time;
- other->_isTimeSet = _isTimeSet;
+ other->_server = _server;
+ other->_isServerSet = _isServerSet;
- other->_details = _details;
- other->_isDetailsSet = _isDetailsSet;
+ other->_what = _what;
+ other->_isWhatSet = _isWhatSet;
- }
+ other->_time = _time;
+ other->_isTimeSet = _isTimeSet;
- std::string ActionLogType::toString() const {
- return toBSON().toString();
- }
+ other->_details = _details;
+ other->_isDetailsSet = _isDetailsSet;
+}
+
+std::string ActionLogType::toString() const {
+ return toBSON().toString();
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_actionlog.h b/src/mongo/s/catalog/type_actionlog.h
index bba7703371c..b7ff387a823 100644
--- a/src/mongo/s/catalog/type_actionlog.h
+++ b/src/mongo/s/catalog/type_actionlog.h
@@ -36,169 +36,184 @@
namespace mongo {
+/**
+ * This class represents the layout and contents of documents contained in the
+ * config.actionlog collection. All manipulation of documents coming from that
+ * collection should be done with this class.
+ *
+ * Usage Example:
+ *
+ * // Contact the config. 'conn' has been obtained before.
+ * DBClientBase* conn;
+ * BSONObj query = QUERY(ActionLogType::exampleField("exampleFieldName"));
+ * exampleDoc = conn->findOne(ActionLogType::ConfigNS, query);
+ *
+ * // Process the response.
+ * ActionLogType exampleType;
+ * std::string errMsg;
+ * if (!exampleType.parseBSON(exampleDoc, &errMsg) || !exampleType.isValid(&errMsg)) {
+ * // Can't use 'exampleType'. Take action.
+ * }
+ * // use 'exampleType'
+ *
+ */
+class ActionLogType {
+ MONGO_DISALLOW_COPYING(ActionLogType);
+
+public:
+ //
+ // schema declarations
+ //
+
+ // Name of the actionlog collection in the config server.
+ static const std::string ConfigNS;
+
+ // Field names and types in the actionlog collection type.
+ static const BSONField<std::string> server;
+ static const BSONField<std::string> what;
+ static const BSONField<Date_t> time;
+ static const BSONField<BSONObj> details;
+
+ // Common field names included under details
+ static const BSONField<int> candidateChunks;
+ static const BSONField<int> chunksMoved;
+ static const BSONField<bool> didError;
+ static const BSONField<long long> executionTimeMicros;
+ static const BSONField<std::string> errmsg;
+
+
+ //
+ // actionlog type methods
+ //
+
+ ActionLogType();
+ ~ActionLogType();
+
/**
- * This class represents the layout and contents of documents contained in the
- * config.actionlog collection. All manipulation of documents coming from that
- * collection should be done with this class.
- *
- * Usage Example:
- *
- * // Contact the config. 'conn' has been obtained before.
- * DBClientBase* conn;
- * BSONObj query = QUERY(ActionLogType::exampleField("exampleFieldName"));
- * exampleDoc = conn->findOne(ActionLogType::ConfigNS, query);
- *
- * // Process the response.
- * ActionLogType exampleType;
- * std::string errMsg;
- * if (!exampleType.parseBSON(exampleDoc, &errMsg) || !exampleType.isValid(&errMsg)) {
- * // Can't use 'exampleType'. Take action.
- * }
- * // use 'exampleType'
- *
+ * Returns true if all the mandatory fields are present and have valid
+ * representations. Otherwise returns false and fills in the optional 'errMsg' string.
*/
- class ActionLogType {
- MONGO_DISALLOW_COPYING(ActionLogType);
- public:
-
- //
- // schema declarations
- //
-
- // Name of the actionlog collection in the config server.
- static const std::string ConfigNS;
+ bool isValid(std::string* errMsg) const;
- // Field names and types in the actionlog collection type.
- static const BSONField<std::string> server;
- static const BSONField<std::string> what;
- static const BSONField<Date_t> time;
- static const BSONField<BSONObj> details;
-
- // Common field names included under details
- static const BSONField<int> candidateChunks;
- static const BSONField<int> chunksMoved;
- static const BSONField<bool> didError;
- static const BSONField<long long> executionTimeMicros;
- static const BSONField<std::string> errmsg;
+ /**
+ * Returns the BSON representation of the entry.
+ */
+ BSONObj toBSON() const;
+ void buildDetails();
- //
- // actionlog type methods
- //
-
- ActionLogType();
- ~ActionLogType();
-
- /**
- * Returns true if all the mandatory fields are present and have valid
- * representations. Otherwise returns false and fills in the optional 'errMsg' string.
- */
- bool isValid(std::string* errMsg) const;
-
- /**
- * Returns the BSON representation of the entry.
- */
- BSONObj toBSON() const;
-
- void buildDetails();
-
- /**
- * Clears and populates the internal state using the 'source' BSON object if the
- * latter contains valid values. Otherwise sets errMsg and returns false.
- */
- bool parseBSON(const BSONObj& source, std::string* errMsg);
-
- /**
- * Clears the internal state.
- */
- void clear();
-
- /**
- * Copies all the fields present in 'this' to 'other'.
- */
- void cloneTo(ActionLogType* other) const;
-
- /**
- * Returns a std::string representation of the current internal state.
- */
- std::string toString() const;
-
- //
- // individual field accessors
- //
-
- void setServer(StringData server) {
- _server = server.toString();
- _isServerSet = true;
- }
-
- void unsetServer() { _isServerSet = false; }
-
- bool isServerSet() const { return _isServerSet; }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- const std::string& getServer() const {
- dassert(_isServerSet);
- return _what;
- }
-
- void setWhat(StringData what) {
- _what = what.toString();
- _isWhatSet = true;
- }
-
- void unsetWhat() { _isWhatSet = false; }
-
- bool isWhatSet() const { return _isWhatSet; }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- const std::string& getWhat() const {
- dassert(_isWhatSet);
- return _what;
- }
-
- void setTime(const Date_t time) {
- _time = time;
- _isTimeSet = true;
- }
-
- void unsetTime() { _isTimeSet = false; }
-
- bool isTimeSet() const { return _isTimeSet; }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- const Date_t getTime() const {
- dassert(_isTimeSet);
- return _time;
- }
-
- void setDetails(const BSONObj& details) {
- _details = details.getOwned();
- _isDetailsSet = true;
- }
-
- void unsetDetails() { _isDetailsSet = false; }
-
- bool isDetailsSet() const { return _isDetailsSet; }
+ /**
+ * Clears and populates the internal state using the 'source' BSON object if the
+ * latter contains valid values. Otherwise sets errMsg and returns false.
+ */
+ bool parseBSON(const BSONObj& source, std::string* errMsg);
- // Calling get*() methods when the member is not set results in undefined behavior
- const BSONObj getDetails() const {
- dassert(_isDetailsSet);
- return _details;
- }
-
- private:
- // Convention: (M)andatory, (O)ptional, (S)pecial rule.
- std::string _server; // (M) hostname of server that we are making the change on.
- // Does not include port.
- bool _isServerSet;
- std::string _what; // (M) what the action being performed was.
- bool _isWhatSet;
- Date_t _time; // (M) time this change was made
- bool _isTimeSet;
- BSONObj _details; // (M) A BSONObj containing extra information about some operations
- bool _isDetailsSet;
-
- };
-
-} // namespace mongo
+ /**
+ * Clears the internal state.
+ */
+ void clear();
+
+ /**
+ * Copies all the fields present in 'this' to 'other'.
+ */
+ void cloneTo(ActionLogType* other) const;
+
+ /**
+ * Returns a std::string representation of the current internal state.
+ */
+ std::string toString() const;
+
+ //
+ // individual field accessors
+ //
+
+ void setServer(StringData server) {
+ _server = server.toString();
+ _isServerSet = true;
+ }
+
+ void unsetServer() {
+ _isServerSet = false;
+ }
+
+ bool isServerSet() const {
+ return _isServerSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const std::string& getServer() const {
+ dassert(_isServerSet);
+ return _what;
+ }
+
+ void setWhat(StringData what) {
+ _what = what.toString();
+ _isWhatSet = true;
+ }
+
+ void unsetWhat() {
+ _isWhatSet = false;
+ }
+
+ bool isWhatSet() const {
+ return _isWhatSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const std::string& getWhat() const {
+ dassert(_isWhatSet);
+ return _what;
+ }
+
+ void setTime(const Date_t time) {
+ _time = time;
+ _isTimeSet = true;
+ }
+
+ void unsetTime() {
+ _isTimeSet = false;
+ }
+
+ bool isTimeSet() const {
+ return _isTimeSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const Date_t getTime() const {
+ dassert(_isTimeSet);
+ return _time;
+ }
+
+ void setDetails(const BSONObj& details) {
+ _details = details.getOwned();
+ _isDetailsSet = true;
+ }
+
+ void unsetDetails() {
+ _isDetailsSet = false;
+ }
+
+ bool isDetailsSet() const {
+ return _isDetailsSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const BSONObj getDetails() const {
+ dassert(_isDetailsSet);
+ return _details;
+ }
+
+private:
+ // Convention: (M)andatory, (O)ptional, (S)pecial rule.
+ std::string _server; // (M) hostname of server that we are making the change on.
+ // Does not include port.
+ bool _isServerSet;
+ std::string _what; // (M) what the action being performed was.
+ bool _isWhatSet;
+ Date_t _time; // (M) time this change was made
+ bool _isTimeSet;
+ BSONObj _details; // (M) A BSONObj containing extra information about some operations
+ bool _isDetailsSet;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_changelog.cpp b/src/mongo/s/catalog/type_changelog.cpp
index 40db60dbf94..d31b5a83806 100644
--- a/src/mongo/s/catalog/type_changelog.cpp
+++ b/src/mongo/s/catalog/type_changelog.cpp
@@ -35,171 +35,182 @@
namespace mongo {
- using std::string;
+using std::string;
- using mongoutils::str::stream;
+using mongoutils::str::stream;
- const std::string ChangelogType::ConfigNS = "config.changelog";
+const std::string ChangelogType::ConfigNS = "config.changelog";
- const BSONField<std::string> ChangelogType::changeID("_id");
- const BSONField<std::string> ChangelogType::server("server");
- const BSONField<std::string> ChangelogType::clientAddr("clientAddr");
- const BSONField<Date_t> ChangelogType::time("time");
- const BSONField<std::string> ChangelogType::what("what");
- const BSONField<std::string> ChangelogType::ns("ns");
- const BSONField<BSONObj> ChangelogType::details("details");
+const BSONField<std::string> ChangelogType::changeID("_id");
+const BSONField<std::string> ChangelogType::server("server");
+const BSONField<std::string> ChangelogType::clientAddr("clientAddr");
+const BSONField<Date_t> ChangelogType::time("time");
+const BSONField<std::string> ChangelogType::what("what");
+const BSONField<std::string> ChangelogType::ns("ns");
+const BSONField<BSONObj> ChangelogType::details("details");
- ChangelogType::ChangelogType() {
- clear();
- }
+ChangelogType::ChangelogType() {
+ clear();
+}
- ChangelogType::~ChangelogType() {
- }
+ChangelogType::~ChangelogType() {}
- bool ChangelogType::isValid(std::string* errMsg) const {
- std::string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
-
- // All the mandatory fields must be present.
- if (!_isChangeIDSet) {
- *errMsg = stream() << "missing " << changeID.name() << " field";
- return false;
- }
- if (!_isServerSet) {
- *errMsg = stream() << "missing " << server.name() << " field";
- return false;
- }
- if (!_isClientAddrSet) {
- *errMsg = stream() << "missing " << clientAddr.name() << " field";
- return false;
- }
- if (!_isTimeSet) {
- *errMsg = stream() << "missing " << time.name() << " field";
- return false;
- }
- if (!_isWhatSet) {
- *errMsg = stream() << "missing " << what.name() << " field";
- return false;
- }
- if (!_isNsSet) {
- *errMsg = stream() << "missing " << ns.name() << " field";
- return false;
- }
- if (!_isDetailsSet) {
- *errMsg = stream() << "missing " << details.name() << " field";
- return false;
- }
-
- return true;
+bool ChangelogType::isValid(std::string* errMsg) const {
+ std::string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
}
- BSONObj ChangelogType::toBSON() const {
- BSONObjBuilder builder;
-
- if (_isChangeIDSet) builder.append(changeID(), _changeID);
- if (_isServerSet) builder.append(server(), _server);
- if (_isClientAddrSet) builder.append(clientAddr(), _clientAddr);
- if (_isTimeSet) builder.append(time(), _time);
- if (_isWhatSet) builder.append(what(), _what);
- if (_isNsSet) builder.append(ns(), _ns);
- if (_isDetailsSet) builder.append(details(), _details);
-
- return builder.obj();
+ // All the mandatory fields must be present.
+ if (!_isChangeIDSet) {
+ *errMsg = stream() << "missing " << changeID.name() << " field";
+ return false;
+ }
+ if (!_isServerSet) {
+ *errMsg = stream() << "missing " << server.name() << " field";
+ return false;
+ }
+ if (!_isClientAddrSet) {
+ *errMsg = stream() << "missing " << clientAddr.name() << " field";
+ return false;
+ }
+ if (!_isTimeSet) {
+ *errMsg = stream() << "missing " << time.name() << " field";
+ return false;
+ }
+ if (!_isWhatSet) {
+ *errMsg = stream() << "missing " << what.name() << " field";
+ return false;
+ }
+ if (!_isNsSet) {
+ *errMsg = stream() << "missing " << ns.name() << " field";
+ return false;
+ }
+ if (!_isDetailsSet) {
+ *errMsg = stream() << "missing " << details.name() << " field";
+ return false;
}
- bool ChangelogType::parseBSON(const BSONObj& source, string* errMsg) {
- clear();
-
- std::string dummy;
- if (!errMsg) errMsg = &dummy;
+ return true;
+}
+
+BSONObj ChangelogType::toBSON() const {
+ BSONObjBuilder builder;
- FieldParser::FieldState fieldState;
- fieldState = FieldParser::extract(source, changeID, &_changeID, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isChangeIDSet = fieldState == FieldParser::FIELD_SET;
+ if (_isChangeIDSet)
+ builder.append(changeID(), _changeID);
+ if (_isServerSet)
+ builder.append(server(), _server);
+ if (_isClientAddrSet)
+ builder.append(clientAddr(), _clientAddr);
+ if (_isTimeSet)
+ builder.append(time(), _time);
+ if (_isWhatSet)
+ builder.append(what(), _what);
+ if (_isNsSet)
+ builder.append(ns(), _ns);
+ if (_isDetailsSet)
+ builder.append(details(), _details);
- fieldState = FieldParser::extract(source, server, &_server, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isServerSet = fieldState == FieldParser::FIELD_SET;
+ return builder.obj();
+}
- fieldState = FieldParser::extract(source, clientAddr, &_clientAddr, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isClientAddrSet = fieldState == FieldParser::FIELD_SET;
+bool ChangelogType::parseBSON(const BSONObj& source, string* errMsg) {
+ clear();
- fieldState = FieldParser::extract(source, time, &_time, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isTimeSet = fieldState == FieldParser::FIELD_SET;
+ std::string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
- fieldState = FieldParser::extract(source, what, &_what, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isWhatSet = fieldState == FieldParser::FIELD_SET;
+ FieldParser::FieldState fieldState;
+ fieldState = FieldParser::extract(source, changeID, &_changeID, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isChangeIDSet = fieldState == FieldParser::FIELD_SET;
- fieldState = FieldParser::extract(source, ns, &_ns, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isNsSet = fieldState == FieldParser::FIELD_SET;
+ fieldState = FieldParser::extract(source, server, &_server, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isServerSet = fieldState == FieldParser::FIELD_SET;
- fieldState = FieldParser::extract(source, details, &_details, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isDetailsSet = fieldState == FieldParser::FIELD_SET;
+ fieldState = FieldParser::extract(source, clientAddr, &_clientAddr, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isClientAddrSet = fieldState == FieldParser::FIELD_SET;
- return true;
- }
+ fieldState = FieldParser::extract(source, time, &_time, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isTimeSet = fieldState == FieldParser::FIELD_SET;
- void ChangelogType::clear() {
+ fieldState = FieldParser::extract(source, what, &_what, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isWhatSet = fieldState == FieldParser::FIELD_SET;
- _changeID.clear();
- _isChangeIDSet = false;
+ fieldState = FieldParser::extract(source, ns, &_ns, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isNsSet = fieldState == FieldParser::FIELD_SET;
- _server.clear();
- _isServerSet = false;
+ fieldState = FieldParser::extract(source, details, &_details, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isDetailsSet = fieldState == FieldParser::FIELD_SET;
- _clientAddr.clear();
- _isClientAddrSet = false;
+ return true;
+}
- _time = Date_t();
- _isTimeSet = false;
+void ChangelogType::clear() {
+ _changeID.clear();
+ _isChangeIDSet = false;
- _what.clear();
- _isWhatSet = false;
+ _server.clear();
+ _isServerSet = false;
- _ns.clear();
- _isNsSet = false;
+ _clientAddr.clear();
+ _isClientAddrSet = false;
- _details = BSONObj();
- _isDetailsSet = false;
+ _time = Date_t();
+ _isTimeSet = false;
- }
+ _what.clear();
+ _isWhatSet = false;
- void ChangelogType::cloneTo(ChangelogType* other) const {
- other->clear();
+ _ns.clear();
+ _isNsSet = false;
- other->_changeID = _changeID;
- other->_isChangeIDSet = _isChangeIDSet;
+ _details = BSONObj();
+ _isDetailsSet = false;
+}
- other->_server = _server;
- other->_isServerSet = _isServerSet;
+void ChangelogType::cloneTo(ChangelogType* other) const {
+ other->clear();
- other->_clientAddr = _clientAddr;
- other->_isClientAddrSet = _isClientAddrSet;
+ other->_changeID = _changeID;
+ other->_isChangeIDSet = _isChangeIDSet;
- other->_time = _time;
- other->_isTimeSet = _isTimeSet;
+ other->_server = _server;
+ other->_isServerSet = _isServerSet;
- other->_what = _what;
- other->_isWhatSet = _isWhatSet;
+ other->_clientAddr = _clientAddr;
+ other->_isClientAddrSet = _isClientAddrSet;
- other->_ns = _ns;
- other->_isNsSet = _isNsSet;
+ other->_time = _time;
+ other->_isTimeSet = _isTimeSet;
+
+ other->_what = _what;
+ other->_isWhatSet = _isWhatSet;
- other->_details = _details;
- other->_isDetailsSet = _isDetailsSet;
+ other->_ns = _ns;
+ other->_isNsSet = _isNsSet;
- }
+ other->_details = _details;
+ other->_isDetailsSet = _isDetailsSet;
+}
- std::string ChangelogType::toString() const {
- return toBSON().toString();
- }
+std::string ChangelogType::toString() const {
+ return toBSON().toString();
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_changelog.h b/src/mongo/s/catalog/type_changelog.h
index 1124f6a16c3..7a0360fd7a1 100644
--- a/src/mongo/s/catalog/type_changelog.h
+++ b/src/mongo/s/catalog/type_changelog.h
@@ -35,214 +35,243 @@
namespace mongo {
+/**
+ * This class represents the layout and contents of documents contained in the
+ * config.changelog collection. All manipulation of documents coming from that
+ * collection should be done with this class.
+ *
+ * Usage Example:
+ *
+ * // Contact the config. 'conn' has been obtained before.
+ * DBClientBase* conn;
+ * BSONObj query = QUERY(ChangelogType::exampleField("exampleFieldName"));
+ * exampleDoc = conn->findOne(ChangelogType::ConfigNS, query);
+ *
+ * // Process the response.
+ * ChangelogType exampleType;
+ * std::string errMsg;
+ * if (!exampleType.parseBSON(exampleDoc, &errMsg) || !exampleType.isValid(&errMsg)) {
+ * // Can't use 'exampleType'. Take action.
+ * }
+ * // use 'exampleType'
+ *
+ */
+class ChangelogType {
+ MONGO_DISALLOW_COPYING(ChangelogType);
+
+public:
+ //
+ // schema declarations
+ //
+
+ // Name of the changelog collection in the config server.
+ static const std::string ConfigNS;
+
+ // Field names and types in the changelog collection type.
+ static const BSONField<std::string> changeID;
+ static const BSONField<std::string> server;
+ static const BSONField<std::string> clientAddr;
+ static const BSONField<Date_t> time;
+ static const BSONField<std::string> what;
+ static const BSONField<std::string> ns;
+ static const BSONField<BSONObj> details;
+
+ //
+ // changelog type methods
+ //
+
+ ChangelogType();
+ ~ChangelogType();
+
/**
- * This class represents the layout and contents of documents contained in the
- * config.changelog collection. All manipulation of documents coming from that
- * collection should be done with this class.
- *
- * Usage Example:
- *
- * // Contact the config. 'conn' has been obtained before.
- * DBClientBase* conn;
- * BSONObj query = QUERY(ChangelogType::exampleField("exampleFieldName"));
- * exampleDoc = conn->findOne(ChangelogType::ConfigNS, query);
- *
- * // Process the response.
- * ChangelogType exampleType;
- * std::string errMsg;
- * if (!exampleType.parseBSON(exampleDoc, &errMsg) || !exampleType.isValid(&errMsg)) {
- * // Can't use 'exampleType'. Take action.
- * }
- * // use 'exampleType'
- *
+ * Returns true if all the mandatory fields are present and have valid
+ * representations. Otherwise returns false and fills in the optional 'errMsg' string.
*/
- class ChangelogType {
- MONGO_DISALLOW_COPYING(ChangelogType);
- public:
-
- //
- // schema declarations
- //
-
- // Name of the changelog collection in the config server.
- static const std::string ConfigNS;
-
- // Field names and types in the changelog collection type.
- static const BSONField<std::string> changeID;
- static const BSONField<std::string> server;
- static const BSONField<std::string> clientAddr;
- static const BSONField<Date_t> time;
- static const BSONField<std::string> what;
- static const BSONField<std::string> ns;
- static const BSONField<BSONObj> details;
-
- //
- // changelog type methods
- //
-
- ChangelogType();
- ~ChangelogType();
-
- /**
- * Returns true if all the mandatory fields are present and have valid
- * representations. Otherwise returns false and fills in the optional 'errMsg' string.
- */
- bool isValid(std::string* errMsg) const;
-
- /**
- * Returns the BSON representation of the entry.
- */
- BSONObj toBSON() const;
-
- /**
- * Clears and populates the internal state using the 'source' BSON object if the
- * latter contains valid values. Otherwise sets errMsg and returns false.
- */
- bool parseBSON(const BSONObj& source, std::string* errMsg);
-
- /**
- * Clears the internal state.
- */
- void clear();
-
- /**
- * Copies all the fields present in 'this' to 'other'.
- */
- void cloneTo(ChangelogType* other) const;
-
- /**
- * Returns a std::string representation of the current internal state.
- */
- std::string toString() const;
-
- //
- // individual field accessors
- //
+ bool isValid(std::string* errMsg) const;
- // Mandatory Fields
- void setChangeID(StringData changeID) {
- _changeID = changeID.toString();
- _isChangeIDSet = true;
- }
-
- void unsetChangeID() { _isChangeIDSet = false; }
+ /**
+ * Returns the BSON representation of the entry.
+ */
+ BSONObj toBSON() const;
+
+ /**
+ * Clears and populates the internal state using the 'source' BSON object if the
+ * latter contains valid values. Otherwise sets errMsg and returns false.
+ */
+ bool parseBSON(const BSONObj& source, std::string* errMsg);
+
+ /**
+ * Clears the internal state.
+ */
+ void clear();
- bool isChangeIDSet() const { return _isChangeIDSet; }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- const std::string& getChangeID() const {
- dassert(_isChangeIDSet);
- return _changeID;
- }
-
- void setServer(StringData server) {
- _server = server.toString();
- _isServerSet = true;
- }
-
- void unsetServer() { _isServerSet = false; }
-
- bool isServerSet() const { return _isServerSet; }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- const std::string& getServer() const {
- dassert(_isServerSet);
- return _server;
- }
-
- void setClientAddr(StringData clientAddr) {
- _clientAddr = clientAddr.toString();
- _isClientAddrSet = true;
- }
-
- void unsetClientAddr() { _isClientAddrSet = false; }
-
- bool isClientAddrSet() const { return _isClientAddrSet; }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- const std::string& getClientAddr() const {
- dassert(_isClientAddrSet);
- return _clientAddr;
- }
-
- void setTime(const Date_t time) {
- _time = time;
- _isTimeSet = true;
- }
-
- void unsetTime() { _isTimeSet = false; }
-
- bool isTimeSet() const { return _isTimeSet; }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- const Date_t getTime() const {
- dassert(_isTimeSet);
- return _time;
- }
-
- void setWhat(StringData what) {
- _what = what.toString();
- _isWhatSet = true;
- }
-
- void unsetWhat() { _isWhatSet = false; }
-
- bool isWhatSet() const { return _isWhatSet; }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- const std::string& getWhat() const {
- dassert(_isWhatSet);
- return _what;
- }
-
- void setNS(StringData ns) {
- _ns = ns.toString();
- _isNsSet = true;
- }
-
- void unsetNS() { _isNsSet = false; }
-
- bool isNSSet() const { return _isNsSet; }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- const std::string& getNS() const {
- dassert(_isNsSet);
- return _ns;
- }
-
- void setDetails(const BSONObj& details) {
- _details = details.getOwned();
- _isDetailsSet = true;
- }
-
- void unsetDetails() { _isDetailsSet = false; }
-
- bool isDetailsSet() const { return _isDetailsSet; }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- const BSONObj getDetails() const {
- dassert(_isDetailsSet);
- return _details;
- }
+ /**
+ * Copies all the fields present in 'this' to 'other'.
+ */
+ void cloneTo(ChangelogType* other) const;
- // Optional Fields
-
- private:
- // Convention: (M)andatory, (O)ptional, (S)pecial rule.
- std::string _changeID; // (M) id for this change "<hostname>-<current_time>-<increment>"
- bool _isChangeIDSet;
- std::string _server; // (M) hostname of server that we are making the change on. Does not include port.
- bool _isServerSet;
- std::string _clientAddr; // (M) hostname:port of the client that made this change
- bool _isClientAddrSet;
- Date_t _time; // (M) time this change was made
- bool _isTimeSet;
- std::string _what; // (M) description of the change
- bool _isWhatSet;
- std::string _ns; // (M) database or collection this change applies to
- bool _isNsSet;
- BSONObj _details; // (M) A BSONObj containing extra information about some operations
- bool _isDetailsSet;
- };
-
-} // namespace mongo
+ /**
+ * Returns a std::string representation of the current internal state.
+ */
+ std::string toString() const;
+
+ //
+ // individual field accessors
+ //
+
+ // Mandatory Fields
+ void setChangeID(StringData changeID) {
+ _changeID = changeID.toString();
+ _isChangeIDSet = true;
+ }
+
+ void unsetChangeID() {
+ _isChangeIDSet = false;
+ }
+
+ bool isChangeIDSet() const {
+ return _isChangeIDSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const std::string& getChangeID() const {
+ dassert(_isChangeIDSet);
+ return _changeID;
+ }
+
+ void setServer(StringData server) {
+ _server = server.toString();
+ _isServerSet = true;
+ }
+
+ void unsetServer() {
+ _isServerSet = false;
+ }
+
+ bool isServerSet() const {
+ return _isServerSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const std::string& getServer() const {
+ dassert(_isServerSet);
+ return _server;
+ }
+
+ void setClientAddr(StringData clientAddr) {
+ _clientAddr = clientAddr.toString();
+ _isClientAddrSet = true;
+ }
+
+ void unsetClientAddr() {
+ _isClientAddrSet = false;
+ }
+
+ bool isClientAddrSet() const {
+ return _isClientAddrSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const std::string& getClientAddr() const {
+ dassert(_isClientAddrSet);
+ return _clientAddr;
+ }
+
+ void setTime(const Date_t time) {
+ _time = time;
+ _isTimeSet = true;
+ }
+
+ void unsetTime() {
+ _isTimeSet = false;
+ }
+
+ bool isTimeSet() const {
+ return _isTimeSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const Date_t getTime() const {
+ dassert(_isTimeSet);
+ return _time;
+ }
+
+ void setWhat(StringData what) {
+ _what = what.toString();
+ _isWhatSet = true;
+ }
+
+ void unsetWhat() {
+ _isWhatSet = false;
+ }
+
+ bool isWhatSet() const {
+ return _isWhatSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const std::string& getWhat() const {
+ dassert(_isWhatSet);
+ return _what;
+ }
+
+ void setNS(StringData ns) {
+ _ns = ns.toString();
+ _isNsSet = true;
+ }
+
+ void unsetNS() {
+ _isNsSet = false;
+ }
+
+ bool isNSSet() const {
+ return _isNsSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const std::string& getNS() const {
+ dassert(_isNsSet);
+ return _ns;
+ }
+
+ void setDetails(const BSONObj& details) {
+ _details = details.getOwned();
+ _isDetailsSet = true;
+ }
+
+ void unsetDetails() {
+ _isDetailsSet = false;
+ }
+
+ bool isDetailsSet() const {
+ return _isDetailsSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const BSONObj getDetails() const {
+ dassert(_isDetailsSet);
+ return _details;
+ }
+
+ // Optional Fields
+
+private:
+ // Convention: (M)andatory, (O)ptional, (S)pecial rule.
+ std::string _changeID; // (M) id for this change "<hostname>-<current_time>-<increment>"
+ bool _isChangeIDSet;
+ std::string
+ _server; // (M) hostname of server that we are making the change on. Does not include port.
+ bool _isServerSet;
+ std::string _clientAddr; // (M) hostname:port of the client that made this change
+ bool _isClientAddrSet;
+ Date_t _time; // (M) time this change was made
+ bool _isTimeSet;
+ std::string _what; // (M) description of the change
+ bool _isWhatSet;
+ std::string _ns; // (M) database or collection this change applies to
+ bool _isNsSet;
+ BSONObj _details; // (M) A BSONObj containing extra information about some operations
+ bool _isDetailsSet;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_changelog_test.cpp b/src/mongo/s/catalog/type_changelog_test.cpp
index 0844b231497..22d6746dfcc 100644
--- a/src/mongo/s/catalog/type_changelog_test.cpp
+++ b/src/mongo/s/catalog/type_changelog_test.cpp
@@ -34,145 +34,145 @@
namespace {
- using std::string;
- using mongo::ChangelogType;
- using mongo::BSONObj;
- using mongo::Date_t;
+using std::string;
+using mongo::ChangelogType;
+using mongo::BSONObj;
+using mongo::Date_t;
- TEST(ChangelogType, Empty) {
- ChangelogType logEntry;
- BSONObj emptyObj = BSONObj();
- string errMsg;
- ASSERT(logEntry.parseBSON(emptyObj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(logEntry.isValid(NULL));
- }
+TEST(ChangelogType, Empty) {
+ ChangelogType logEntry;
+ BSONObj emptyObj = BSONObj();
+ string errMsg;
+ ASSERT(logEntry.parseBSON(emptyObj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(logEntry.isValid(NULL));
+}
- TEST(ChangelogType, Valid) {
- ChangelogType logEntry;
- BSONObj obj = BSON(ChangelogType::changeID("host.local-2012-11-21T19:14:10-8") <<
- ChangelogType::server("host.local") <<
- ChangelogType::clientAddr("192.168.0.189:51128") <<
- ChangelogType::time(Date_t::fromMillisSinceEpoch(1)) <<
- ChangelogType::what("split") <<
- ChangelogType::ns("test.test") <<
- ChangelogType::details(BSON("dummy" << "info")));
- string errMsg;
- ASSERT(logEntry.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_TRUE(logEntry.isValid(NULL));
- ASSERT_EQUALS(logEntry.getChangeID(), "host.local-2012-11-21T19:14:10-8");
- ASSERT_EQUALS(logEntry.getServer(), "host.local");
- ASSERT_EQUALS(logEntry.getClientAddr(), "192.168.0.189:51128");
- ASSERT_EQUALS(logEntry.getTime(), Date_t::fromMillisSinceEpoch(1));
- ASSERT_EQUALS(logEntry.getWhat(), "split");
- ASSERT_EQUALS(logEntry.getNS(), "test.test");
- ASSERT_EQUALS(logEntry.getDetails(), BSON("dummy" << "info"));
- }
+TEST(ChangelogType, Valid) {
+ ChangelogType logEntry;
+ BSONObj obj = BSON(
+ ChangelogType::changeID("host.local-2012-11-21T19:14:10-8")
+ << ChangelogType::server("host.local") << ChangelogType::clientAddr("192.168.0.189:51128")
+ << ChangelogType::time(Date_t::fromMillisSinceEpoch(1)) << ChangelogType::what("split")
+ << ChangelogType::ns("test.test") << ChangelogType::details(BSON("dummy"
+ << "info")));
+ string errMsg;
+ ASSERT(logEntry.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_TRUE(logEntry.isValid(NULL));
+ ASSERT_EQUALS(logEntry.getChangeID(), "host.local-2012-11-21T19:14:10-8");
+ ASSERT_EQUALS(logEntry.getServer(), "host.local");
+ ASSERT_EQUALS(logEntry.getClientAddr(), "192.168.0.189:51128");
+ ASSERT_EQUALS(logEntry.getTime(), Date_t::fromMillisSinceEpoch(1));
+ ASSERT_EQUALS(logEntry.getWhat(), "split");
+ ASSERT_EQUALS(logEntry.getNS(), "test.test");
+ ASSERT_EQUALS(logEntry.getDetails(),
+ BSON("dummy"
+ << "info"));
+}
- TEST(ChangelogType, MissingChangeID) {
- ChangelogType logEntry;
- BSONObj obj = BSON(ChangelogType::server("host.local") <<
- ChangelogType::clientAddr("192.168.0.189:51128") <<
- ChangelogType::time(Date_t::fromMillisSinceEpoch(1)) <<
- ChangelogType::what("split") <<
- ChangelogType::ns("test.test") <<
- ChangelogType::details(BSON("dummy" << "info")));
- string errMsg;
- ASSERT(logEntry.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(logEntry.isValid(NULL));
- }
+TEST(ChangelogType, MissingChangeID) {
+ ChangelogType logEntry;
+ BSONObj obj =
+ BSON(ChangelogType::server("host.local")
+ << ChangelogType::clientAddr("192.168.0.189:51128")
+ << ChangelogType::time(Date_t::fromMillisSinceEpoch(1)) << ChangelogType::what("split")
+ << ChangelogType::ns("test.test") << ChangelogType::details(BSON("dummy"
+ << "info")));
+ string errMsg;
+ ASSERT(logEntry.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(logEntry.isValid(NULL));
+}
- TEST(ChangelogType, MissingServer) {
- ChangelogType logEntry;
- BSONObj obj = BSON(ChangelogType::changeID("host.local-2012-11-21T19:14:10-8") <<
- ChangelogType::clientAddr("192.168.0.189:51128") <<
- ChangelogType::time(Date_t::fromMillisSinceEpoch(1)) <<
- ChangelogType::what("split") <<
- ChangelogType::ns("test.test") <<
- ChangelogType::details(BSON("dummy" << "info")));
- string errMsg;
- ASSERT(logEntry.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(logEntry.isValid(NULL));
- }
+TEST(ChangelogType, MissingServer) {
+ ChangelogType logEntry;
+ BSONObj obj =
+ BSON(ChangelogType::changeID("host.local-2012-11-21T19:14:10-8")
+ << ChangelogType::clientAddr("192.168.0.189:51128")
+ << ChangelogType::time(Date_t::fromMillisSinceEpoch(1)) << ChangelogType::what("split")
+ << ChangelogType::ns("test.test") << ChangelogType::details(BSON("dummy"
+ << "info")));
+ string errMsg;
+ ASSERT(logEntry.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(logEntry.isValid(NULL));
+}
- TEST(ChangelogType, MissingClientAddr) {
- ChangelogType logEntry;
- BSONObj obj = BSON(ChangelogType::changeID("host.local-2012-11-21T19:14:10-8") <<
- ChangelogType::server("host.local") <<
- ChangelogType::time(Date_t::fromMillisSinceEpoch(1)) <<
- ChangelogType::what("split") <<
- ChangelogType::ns("test.test") <<
- ChangelogType::details(BSON("dummy" << "info")));
- string errMsg;
- ASSERT(logEntry.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(logEntry.isValid(NULL));
- }
+TEST(ChangelogType, MissingClientAddr) {
+ ChangelogType logEntry;
+ BSONObj obj =
+ BSON(ChangelogType::changeID("host.local-2012-11-21T19:14:10-8")
+ << ChangelogType::server("host.local")
+ << ChangelogType::time(Date_t::fromMillisSinceEpoch(1)) << ChangelogType::what("split")
+ << ChangelogType::ns("test.test") << ChangelogType::details(BSON("dummy"
+ << "info")));
+ string errMsg;
+ ASSERT(logEntry.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(logEntry.isValid(NULL));
+}
- TEST(ChangelogType, MissingTime) {
- ChangelogType logEntry;
- BSONObj obj = BSON(ChangelogType::changeID("host.local-2012-11-21T19:14:10-8") <<
- ChangelogType::server("host.local") <<
- ChangelogType::clientAddr("192.168.0.189:51128") <<
- ChangelogType::what("split") <<
- ChangelogType::ns("test.test") <<
- ChangelogType::details(BSON("dummy" << "info")));
- string errMsg;
- ASSERT(logEntry.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(logEntry.isValid(NULL));
- }
+TEST(ChangelogType, MissingTime) {
+ ChangelogType logEntry;
+ BSONObj obj =
+ BSON(ChangelogType::changeID("host.local-2012-11-21T19:14:10-8")
+ << ChangelogType::server("host.local")
+ << ChangelogType::clientAddr("192.168.0.189:51128") << ChangelogType::what("split")
+ << ChangelogType::ns("test.test") << ChangelogType::details(BSON("dummy"
+ << "info")));
+ string errMsg;
+ ASSERT(logEntry.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(logEntry.isValid(NULL));
+}
- TEST(ChangelogType, MissingWhat) {
- ChangelogType logEntry;
- BSONObj obj = BSON(ChangelogType::changeID("host.local-2012-11-21T19:14:10-8") <<
- ChangelogType::server("host.local") <<
- ChangelogType::clientAddr("192.168.0.189:51128") <<
- ChangelogType::time(Date_t::fromMillisSinceEpoch(1)) <<
- ChangelogType::ns("test.test") <<
- ChangelogType::details(BSON("dummy" << "info")));
- string errMsg;
- ASSERT(logEntry.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(logEntry.isValid(NULL));
- }
+TEST(ChangelogType, MissingWhat) {
+ ChangelogType logEntry;
+ BSONObj obj = BSON(
+ ChangelogType::changeID("host.local-2012-11-21T19:14:10-8")
+ << ChangelogType::server("host.local") << ChangelogType::clientAddr("192.168.0.189:51128")
+ << ChangelogType::time(Date_t::fromMillisSinceEpoch(1)) << ChangelogType::ns("test.test")
+ << ChangelogType::details(BSON("dummy"
+ << "info")));
+ string errMsg;
+ ASSERT(logEntry.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(logEntry.isValid(NULL));
+}
- TEST(ChangelogType, MissingNS) {
- ChangelogType logEntry;
- BSONObj obj = BSON(ChangelogType::changeID("host.local-2012-11-21T19:14:10-8") <<
- ChangelogType::server("host.local") <<
- ChangelogType::clientAddr("192.168.0.189:51128") <<
- ChangelogType::time(Date_t::fromMillisSinceEpoch(1)) <<
- ChangelogType::what("split") <<
- ChangelogType::details(BSON("dummy" << "info")));
- string errMsg;
- ASSERT(logEntry.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(logEntry.isValid(NULL));
- }
+TEST(ChangelogType, MissingNS) {
+ ChangelogType logEntry;
+ BSONObj obj = BSON(ChangelogType::changeID("host.local-2012-11-21T19:14:10-8")
+ << ChangelogType::server("host.local")
+ << ChangelogType::clientAddr("192.168.0.189:51128")
+ << ChangelogType::time(Date_t::fromMillisSinceEpoch(1))
+ << ChangelogType::what("split") << ChangelogType::details(BSON("dummy"
+ << "info")));
+ string errMsg;
+ ASSERT(logEntry.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(logEntry.isValid(NULL));
+}
- TEST(ChangelogType, MissingDetails) {
- ChangelogType logEntry;
- BSONObj obj = BSON(ChangelogType::changeID("host.local-2012-11-21T19:14:10-8") <<
- ChangelogType::server("host.local") <<
- ChangelogType::clientAddr("192.168.0.189:51128") <<
- ChangelogType::time(Date_t::fromMillisSinceEpoch(1)) <<
- ChangelogType::what("split") <<
- ChangelogType::ns("test.test"));
- string errMsg;
- ASSERT(logEntry.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(logEntry.isValid(NULL));
- }
+TEST(ChangelogType, MissingDetails) {
+ ChangelogType logEntry;
+ BSONObj obj = BSON(ChangelogType::changeID("host.local-2012-11-21T19:14:10-8")
+ << ChangelogType::server("host.local")
+ << ChangelogType::clientAddr("192.168.0.189:51128")
+ << ChangelogType::time(Date_t::fromMillisSinceEpoch(1))
+ << ChangelogType::what("split") << ChangelogType::ns("test.test"));
+ string errMsg;
+ ASSERT(logEntry.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(logEntry.isValid(NULL));
+}
- TEST(ChangelogType, BadType) {
- ChangelogType logEntry;
- BSONObj obj = BSON(ChangelogType::changeID() << 0);
- string errMsg;
- ASSERT((!logEntry.parseBSON(obj, &errMsg)) && (errMsg != ""));
- }
+TEST(ChangelogType, BadType) {
+ ChangelogType logEntry;
+ BSONObj obj = BSON(ChangelogType::changeID() << 0);
+ string errMsg;
+ ASSERT((!logEntry.parseBSON(obj, &errMsg)) && (errMsg != ""));
+}
-} // unnamed namespace
+} // unnamed namespace
diff --git a/src/mongo/s/catalog/type_chunk.cpp b/src/mongo/s/catalog/type_chunk.cpp
index 41498d5373a..68b9c016cae 100644
--- a/src/mongo/s/catalog/type_chunk.cpp
+++ b/src/mongo/s/catalog/type_chunk.cpp
@@ -41,198 +41,202 @@
namespace mongo {
- const std::string ChunkType::ConfigNS = "config.chunks";
-
- const BSONField<std::string> ChunkType::name("_id");
- const BSONField<std::string> ChunkType::ns("ns");
- const BSONField<BSONObj> ChunkType::min("min");
- const BSONField<BSONObj> ChunkType::max("max");
- const BSONField<BSONArray> ChunkType::version("version");
- const BSONField<std::string> ChunkType::shard("shard");
- const BSONField<bool> ChunkType::jumbo("jumbo");
- const BSONField<Date_t> ChunkType::DEPRECATED_lastmod("lastmod");
- const BSONField<OID> ChunkType::DEPRECATED_epoch("lastmodEpoch");
-
-
- StatusWith<ChunkType> ChunkType::fromBSON(const BSONObj& source) {
- ChunkType chunk;
-
- {
- std::string chunkName;
- Status status = bsonExtractStringField(source, name.name(), &chunkName);
- if (!status.isOK()) return status;
- chunk._name = chunkName;
- }
-
- {
- std::string chunkNS;
- Status status = bsonExtractStringField(source, ns.name(), &chunkNS);
- if (!status.isOK()) return status;
- chunk._ns = chunkNS;
- }
-
- {
- BSONElement chunkMinElement;
- Status status = bsonExtractTypedField(source, min.name(), Object, &chunkMinElement);
- if (!status.isOK()) return status;
- chunk._min = chunkMinElement.Obj().getOwned();
- }
-
- {
- BSONElement chunkMaxElement;
- Status status = bsonExtractTypedField(source, max.name(), Object, &chunkMaxElement);
- if (!status.isOK()) return status;
- chunk._max = chunkMaxElement.Obj().getOwned();
- }
-
- {
- std::string chunkShard;
- Status status = bsonExtractStringField(source, shard.name(), &chunkShard);
- if (!status.isOK()) return status;
- chunk._shard = chunkShard;
- }
-
- {
- bool chunkJumbo;
- Status status = bsonExtractBooleanField(source, jumbo.name(), &chunkJumbo);
- if (status.isOK()) {
- chunk._jumbo = chunkJumbo;
- }
- else if (status == ErrorCodes::NoSuchKey) {
- // Jumbo status is missing, so it will be presumed false
- }
- else {
- return status;
- }
- }
-
- //
- // ChunkVersion backward compatibility logic contained in ChunkVersion
- //
-
- // ChunkVersion is currently encoded as { 'version': [<TS>,<OID>] }
-
- if (ChunkVersion::canParseBSON(source, version())) {
- chunk._version = ChunkVersion::fromBSON(source, version());
- }
- else if (ChunkVersion::canParseBSON(source, DEPRECATED_lastmod())) {
- chunk._version = ChunkVersion::fromBSON(source, DEPRECATED_lastmod());
- }
-
- return chunk;
+const std::string ChunkType::ConfigNS = "config.chunks";
+
+const BSONField<std::string> ChunkType::name("_id");
+const BSONField<std::string> ChunkType::ns("ns");
+const BSONField<BSONObj> ChunkType::min("min");
+const BSONField<BSONObj> ChunkType::max("max");
+const BSONField<BSONArray> ChunkType::version("version");
+const BSONField<std::string> ChunkType::shard("shard");
+const BSONField<bool> ChunkType::jumbo("jumbo");
+const BSONField<Date_t> ChunkType::DEPRECATED_lastmod("lastmod");
+const BSONField<OID> ChunkType::DEPRECATED_epoch("lastmodEpoch");
+
+
+StatusWith<ChunkType> ChunkType::fromBSON(const BSONObj& source) {
+ ChunkType chunk;
+
+ {
+ std::string chunkName;
+ Status status = bsonExtractStringField(source, name.name(), &chunkName);
+ if (!status.isOK())
+ return status;
+ chunk._name = chunkName;
}
- Status ChunkType::validate() const {
-
- if (!_name.is_initialized() || _name->empty()) {
- return Status(ErrorCodes::NoSuchKey,
- str::stream() << "missing " << name.name() << " field");
- }
-
- if (!_ns.is_initialized() || _ns->empty()) {
- return Status(ErrorCodes::NoSuchKey,
- str::stream() << "missing " << ns.name() << " field");
- }
+ {
+ std::string chunkNS;
+ Status status = bsonExtractStringField(source, ns.name(), &chunkNS);
+ if (!status.isOK())
+ return status;
+ chunk._ns = chunkNS;
+ }
- if (!_min.is_initialized() || _min->isEmpty()) {
- return Status(ErrorCodes::NoSuchKey,
- str::stream() << "missing " << min.name() << " field");
- }
+ {
+ BSONElement chunkMinElement;
+ Status status = bsonExtractTypedField(source, min.name(), Object, &chunkMinElement);
+ if (!status.isOK())
+ return status;
+ chunk._min = chunkMinElement.Obj().getOwned();
+ }
- if (!_max.is_initialized() || _max->isEmpty()) {
- return Status(ErrorCodes::NoSuchKey,
- str::stream() << "missing " << max.name() << " field");
- }
+ {
+ BSONElement chunkMaxElement;
+ Status status = bsonExtractTypedField(source, max.name(), Object, &chunkMaxElement);
+ if (!status.isOK())
+ return status;
+ chunk._max = chunkMaxElement.Obj().getOwned();
+ }
- if (!_version.is_initialized() || !_version->isSet()) {
- return Status(ErrorCodes::NoSuchKey,
- str::stream() << "missing " << version.name() << " field");
- }
+ {
+ std::string chunkShard;
+ Status status = bsonExtractStringField(source, shard.name(), &chunkShard);
+ if (!status.isOK())
+ return status;
+ chunk._shard = chunkShard;
+ }
- if (!_shard.is_initialized() || _shard->empty()) {
- return Status(ErrorCodes::NoSuchKey,
- str::stream() << "missing " << shard.name() << " field");
- }
-
- // 'min' and 'max' must share the same fields.
- if (_min->nFields() != _max->nFields()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "min and max have a different number of keys");
+ {
+ bool chunkJumbo;
+ Status status = bsonExtractBooleanField(source, jumbo.name(), &chunkJumbo);
+ if (status.isOK()) {
+ chunk._jumbo = chunkJumbo;
+ } else if (status == ErrorCodes::NoSuchKey) {
+ // Jumbo status is missing, so it will be presumed false
+ } else {
+ return status;
}
+ }
- BSONObjIterator minIt(getMin());
- BSONObjIterator maxIt(getMax());
- while (minIt.more() && maxIt.more()) {
- BSONElement minElem = minIt.next();
- BSONElement maxElem = maxIt.next();
- if (strcmp(minElem.fieldName(), maxElem.fieldName())) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "min and max must have the same set of keys");
- }
- }
+ //
+ // ChunkVersion backward compatibility logic contained in ChunkVersion
+ //
- // 'max' should be greater than 'min'.
- if (_min->woCompare(getMax()) >= 0) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "max key must be greater than min key");
- }
+ // ChunkVersion is currently encoded as { 'version': [<TS>,<OID>] }
- return Status::OK();
+ if (ChunkVersion::canParseBSON(source, version())) {
+ chunk._version = ChunkVersion::fromBSON(source, version());
+ } else if (ChunkVersion::canParseBSON(source, DEPRECATED_lastmod())) {
+ chunk._version = ChunkVersion::fromBSON(source, DEPRECATED_lastmod());
}
- BSONObj ChunkType::toBSON() const {
- BSONObjBuilder builder;
- if (_name) builder.append(name.name(), getName());
- if (_ns) builder.append(ns.name(), getNS());
- if (_min) builder.append(min.name(), getMin());
- if (_max) builder.append(max.name(), getMax());
- if (_shard) builder.append(shard.name(), getShard());
- if (_version) {
- // For now, write both the deprecated *and* the new fields
- _version->addToBSON(builder, version());
- _version->addToBSON(builder, DEPRECATED_lastmod());
- }
- if (_jumbo) builder.append(jumbo.name(), getJumbo());
+ return chunk;
+}
- return builder.obj();
+Status ChunkType::validate() const {
+ if (!_name.is_initialized() || _name->empty()) {
+ return Status(ErrorCodes::NoSuchKey,
+ str::stream() << "missing " << name.name() << " field");
}
- std::string ChunkType::toString() const {
- return toBSON().toString();
+ if (!_ns.is_initialized() || _ns->empty()) {
+ return Status(ErrorCodes::NoSuchKey, str::stream() << "missing " << ns.name() << " field");
}
- void ChunkType::setName(const std::string& name) {
- invariant(!name.empty());
- _name = name;
+ if (!_min.is_initialized() || _min->isEmpty()) {
+ return Status(ErrorCodes::NoSuchKey, str::stream() << "missing " << min.name() << " field");
}
- void ChunkType::setNS(const std::string& ns) {
- invariant(!ns.empty());
- _ns = ns;
+ if (!_max.is_initialized() || _max->isEmpty()) {
+ return Status(ErrorCodes::NoSuchKey, str::stream() << "missing " << max.name() << " field");
}
- void ChunkType::setMin(const BSONObj& min) {
- invariant(!min.isEmpty());
- _min = min;
+ if (!_version.is_initialized() || !_version->isSet()) {
+ return Status(ErrorCodes::NoSuchKey,
+ str::stream() << "missing " << version.name() << " field");
}
- void ChunkType::setMax(const BSONObj& max) {
- invariant(!max.isEmpty());
- _max = max;
+ if (!_shard.is_initialized() || _shard->empty()) {
+ return Status(ErrorCodes::NoSuchKey,
+ str::stream() << "missing " << shard.name() << " field");
}
- void ChunkType::setVersion(const ChunkVersion& version) {
- invariant(version.isSet());
- _version = version;
+ // 'min' and 'max' must share the same fields.
+ if (_min->nFields() != _max->nFields()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "min and max have a different number of keys");
}
- void ChunkType::setShard(const std::string& shard) {
- invariant(!shard.empty());
- _shard = shard;
+ BSONObjIterator minIt(getMin());
+ BSONObjIterator maxIt(getMax());
+ while (minIt.more() && maxIt.more()) {
+ BSONElement minElem = minIt.next();
+ BSONElement maxElem = maxIt.next();
+ if (strcmp(minElem.fieldName(), maxElem.fieldName())) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "min and max must have the same set of keys");
+ }
}
- void ChunkType::setJumbo(bool jumbo) {
- _jumbo = jumbo;
+ // 'max' should be greater than 'min'.
+ if (_min->woCompare(getMax()) >= 0) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "max key must be greater than min key");
}
-} // namespace mongo
+ return Status::OK();
+}
+
+BSONObj ChunkType::toBSON() const {
+ BSONObjBuilder builder;
+ if (_name)
+ builder.append(name.name(), getName());
+ if (_ns)
+ builder.append(ns.name(), getNS());
+ if (_min)
+ builder.append(min.name(), getMin());
+ if (_max)
+ builder.append(max.name(), getMax());
+ if (_shard)
+ builder.append(shard.name(), getShard());
+ if (_version) {
+ // For now, write both the deprecated *and* the new fields
+ _version->addToBSON(builder, version());
+ _version->addToBSON(builder, DEPRECATED_lastmod());
+ }
+ if (_jumbo)
+ builder.append(jumbo.name(), getJumbo());
+
+ return builder.obj();
+}
+
+std::string ChunkType::toString() const {
+ return toBSON().toString();
+}
+
+void ChunkType::setName(const std::string& name) {
+ invariant(!name.empty());
+ _name = name;
+}
+
+void ChunkType::setNS(const std::string& ns) {
+ invariant(!ns.empty());
+ _ns = ns;
+}
+
+void ChunkType::setMin(const BSONObj& min) {
+ invariant(!min.isEmpty());
+ _min = min;
+}
+
+void ChunkType::setMax(const BSONObj& max) {
+ invariant(!max.isEmpty());
+ _max = max;
+}
+
+void ChunkType::setVersion(const ChunkVersion& version) {
+ invariant(version.isSet());
+ _version = version;
+}
+
+void ChunkType::setShard(const std::string& shard) {
+ invariant(!shard.empty());
+ _shard = shard;
+}
+
+void ChunkType::setJumbo(bool jumbo) {
+ _jumbo = jumbo;
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_chunk.h b/src/mongo/s/catalog/type_chunk.h
index 5945f722f6f..52a452a20f2 100644
--- a/src/mongo/s/catalog/type_chunk.h
+++ b/src/mongo/s/catalog/type_chunk.h
@@ -36,95 +36,110 @@
namespace mongo {
- class BSONObj;
- class Status;
- template<typename T> class StatusWith;
+class BSONObj;
+class Status;
+template <typename T>
+class StatusWith;
+
+/**
+ * This class represents the layout and contents of documents contained in the
+ * config.chunks collection. All manipulation of documents coming from that
+ * collection should be done with this class.
+ */
+class ChunkType {
+public:
+ // Name of the chunks collection in the config server.
+ static const std::string ConfigNS;
+
+ // Field names and types in the chunks collection type.
+ static const BSONField<std::string> name;
+ static const BSONField<std::string> ns;
+ static const BSONField<BSONObj> min;
+ static const BSONField<BSONObj> max;
+ static const BSONField<BSONArray> version;
+ static const BSONField<std::string> shard;
+ static const BSONField<bool> jumbo;
+ static const BSONField<Date_t> DEPRECATED_lastmod;
+ static const BSONField<OID> DEPRECATED_epoch;
+
+
+ /**
+ * Constructs a new ChunkType object from BSON.
+ * Also does validation of the contents.
+ */
+ static StatusWith<ChunkType> fromBSON(const BSONObj& source);
+
+ /**
+ * Returns OK if all fields have been set. Otherwise returns NoSuchKey
+ * and information about the first field that is missing.
+ */
+ Status validate() const;
+
+ /**
+ * Returns the BSON representation of the entry.
+ */
+ BSONObj toBSON() const;
/**
- * This class represents the layout and contents of documents contained in the
- * config.chunks collection. All manipulation of documents coming from that
- * collection should be done with this class.
+ * Returns a std::string representation of the current internal state.
*/
- class ChunkType {
- public:
-
- // Name of the chunks collection in the config server.
- static const std::string ConfigNS;
-
- // Field names and types in the chunks collection type.
- static const BSONField<std::string> name;
- static const BSONField<std::string> ns;
- static const BSONField<BSONObj> min;
- static const BSONField<BSONObj> max;
- static const BSONField<BSONArray> version;
- static const BSONField<std::string> shard;
- static const BSONField<bool> jumbo;
- static const BSONField<Date_t> DEPRECATED_lastmod;
- static const BSONField<OID> DEPRECATED_epoch;
-
-
- /**
- * Constructs a new ChunkType object from BSON.
- * Also does validation of the contents.
- */
- static StatusWith<ChunkType> fromBSON(const BSONObj& source);
-
- /**
- * Returns OK if all fields have been set. Otherwise returns NoSuchKey
- * and information about the first field that is missing.
- */
- Status validate() const;
-
- /**
- * Returns the BSON representation of the entry.
- */
- BSONObj toBSON() const;
-
- /**
- * Returns a std::string representation of the current internal state.
- */
- std::string toString() const;
-
- const std::string& getName() const { return _name.get(); }
- void setName(const std::string& name);
-
- const std::string& getNS() const { return _ns.get(); }
- void setNS(const std::string& name);
-
- const BSONObj& getMin() const { return _min.get(); }
- void setMin(const BSONObj& min);
-
- const BSONObj& getMax() const { return _max.get(); }
- void setMax(const BSONObj& max);
-
- bool isVersionSet() const { return _version.is_initialized(); }
- const ChunkVersion& getVersion() const { return _version.get(); }
- void setVersion(const ChunkVersion& version);
-
- const std::string& getShard() const { return _shard.get(); }
- void setShard(const std::string& shard);
-
- bool getJumbo() const { return _jumbo.get_value_or(false); }
- void setJumbo(bool jumbo);
-
- private:
-
- // Convention: (M)andatory, (O)ptional, (S)pecial rule.
-
- // (M) chunk's id
- boost::optional<std::string> _name;
- // (M) collection this chunk is in
- boost::optional<std::string> _ns;
- // (M) first key of the range, inclusive
- boost::optional<BSONObj> _min;
- // (M) last key of the range, non-inclusive
- boost::optional<BSONObj> _max;
- // (M) version of this chunk
- boost::optional<ChunkVersion> _version;
- // (M) shard this chunk lives in
- boost::optional<std::string> _shard;
- // (O) too big to move?
- boost::optional<bool> _jumbo;
- };
-
-} // namespace mongo
+ std::string toString() const;
+
+ const std::string& getName() const {
+ return _name.get();
+ }
+ void setName(const std::string& name);
+
+ const std::string& getNS() const {
+ return _ns.get();
+ }
+ void setNS(const std::string& name);
+
+ const BSONObj& getMin() const {
+ return _min.get();
+ }
+ void setMin(const BSONObj& min);
+
+ const BSONObj& getMax() const {
+ return _max.get();
+ }
+ void setMax(const BSONObj& max);
+
+ bool isVersionSet() const {
+ return _version.is_initialized();
+ }
+ const ChunkVersion& getVersion() const {
+ return _version.get();
+ }
+ void setVersion(const ChunkVersion& version);
+
+ const std::string& getShard() const {
+ return _shard.get();
+ }
+ void setShard(const std::string& shard);
+
+ bool getJumbo() const {
+ return _jumbo.get_value_or(false);
+ }
+ void setJumbo(bool jumbo);
+
+private:
+ // Convention: (M)andatory, (O)ptional, (S)pecial rule.
+
+ // (M) chunk's id
+ boost::optional<std::string> _name;
+ // (M) collection this chunk is in
+ boost::optional<std::string> _ns;
+ // (M) first key of the range, inclusive
+ boost::optional<BSONObj> _min;
+ // (M) last key of the range, non-inclusive
+ boost::optional<BSONObj> _max;
+ // (M) version of this chunk
+ boost::optional<ChunkVersion> _version;
+ // (M) shard this chunk lives in
+ boost::optional<std::string> _shard;
+ // (O) too big to move?
+ boost::optional<bool> _jumbo;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_chunk_test.cpp b/src/mongo/s/catalog/type_chunk_test.cpp
index 35d9f578d5f..bc22f23ddb1 100644
--- a/src/mongo/s/catalog/type_chunk_test.cpp
+++ b/src/mongo/s/catalog/type_chunk_test.cpp
@@ -37,139 +37,126 @@
namespace {
- using namespace mongo;
-
- using std::string;
-
- TEST(ChunkType, MissingRequiredFields) {
- ChunkType chunk;
- BSONArray version = BSON_ARRAY(Date_t::fromMillisSinceEpoch(1) << OID::gen());
-
- BSONObj objModNS = BSON(ChunkType::name("test.mycol-a_MinKey") <<
- ChunkType::min(BSON("a" << 10 << "b" << 10)) <<
- ChunkType::max(BSON("a" << 20)) <<
- ChunkType::version(version) <<
- ChunkType::shard("shard0001"));
- StatusWith<ChunkType> chunkRes = ChunkType::fromBSON(objModNS);
- ASSERT_FALSE(chunkRes.isOK());
-
- BSONObj objModName = BSON(ChunkType::ns("test.mycol") <<
- ChunkType::min(BSON("a" << 10 << "b" << 10)) <<
- ChunkType::max(BSON("a" << 20)) <<
- ChunkType::version(version) <<
- ChunkType::shard("shard0001"));
- chunkRes = ChunkType::fromBSON(objModName);
- ASSERT_FALSE(chunkRes.isOK());
-
- BSONObj objModKeys = BSON(ChunkType::name("test.mycol-a_MinKey") <<
- ChunkType::ns("test.mycol") <<
- ChunkType::version(version) <<
- ChunkType::shard("shard0001"));
- chunkRes = ChunkType::fromBSON(objModKeys);
- ASSERT_FALSE(chunkRes.isOK());
-
- BSONObj objModShard = BSON(ChunkType::name("test.mycol-a_MinKey") <<
- ChunkType::ns("test.mycol") <<
- ChunkType::min(BSON("a" << 10 << "b" << 10)) <<
- ChunkType::max(BSON("a" << 20)) <<
- ChunkType::version(version));
- chunkRes = ChunkType::fromBSON(objModShard);
- ASSERT_FALSE(chunkRes.isOK());
- }
-
- TEST(ChunkType, DifferentNumberOfColumns) {
- BSONArray version = BSON_ARRAY(Date_t::fromMillisSinceEpoch(1) << OID::gen());
- BSONObj obj = BSON(ChunkType::name("test.mycol-a_MinKey") <<
- ChunkType::ns("test.mycol") <<
- ChunkType::min(BSON("a" << 10 << "b" << 10)) <<
- ChunkType::max(BSON("a" << 20)) <<
- ChunkType::version(version) <<
- ChunkType::shard("shard0001"));
- StatusWith<ChunkType> chunkRes = ChunkType::fromBSON(obj);
- ASSERT(chunkRes.isOK());
- ASSERT_FALSE(chunkRes.getValue().validate().isOK());
- }
-
- TEST(ChunkType, DifferentColumns) {
- BSONArray version = BSON_ARRAY(Date_t::fromMillisSinceEpoch(1) << OID::gen());
- BSONObj obj = BSON(ChunkType::name("test.mycol-a_MinKey") <<
- ChunkType::ns("test.mycol") <<
- ChunkType::min(BSON("a" << 10)) <<
- ChunkType::max(BSON("b" << 20)) <<
- ChunkType::version(version) <<
- ChunkType::shard("shard0001"));
- StatusWith<ChunkType> chunkRes = ChunkType::fromBSON(obj);
- ASSERT(chunkRes.isOK());
- ASSERT_FALSE(chunkRes.getValue().validate().isOK());
- }
-
- TEST(ChunkType, NotAscending) {
- BSONArray version = BSON_ARRAY(Date_t::fromMillisSinceEpoch(1) << OID::gen());
- BSONObj obj = BSON(ChunkType::name("test.mycol-a_MinKey") <<
- ChunkType::ns("test.mycol") <<
- ChunkType::min(BSON("a" << 20)) <<
- ChunkType::max(BSON("a" << 10)) <<
- ChunkType::version(version) <<
- ChunkType::shard("shard0001"));
- StatusWith<ChunkType> chunkRes = ChunkType::fromBSON(obj);
- ASSERT(chunkRes.isOK());
- ASSERT_FALSE(chunkRes.getValue().validate().isOK());
- }
-
- TEST(ChunkType, NewFormatVersion) {
- ChunkType chunk;
- OID epoch = OID::gen();
- BSONArray version = BSON_ARRAY(Date_t::fromMillisSinceEpoch(1) << epoch);
- BSONObj obj = BSON(ChunkType::name("test.mycol-a_MinKey") <<
- ChunkType::ns("test.mycol") <<
- ChunkType::min(BSON("a" << 10)) <<
- ChunkType::max(BSON("a" << 20)) <<
- ChunkType::version(version) <<
- ChunkType::shard("shard0001"));
- StatusWith<ChunkType> chunkRes = ChunkType::fromBSON(obj);
- ASSERT(chunkRes.isOK());
- chunk = chunkRes.getValue();
-
- ASSERT_EQUALS(chunk.getName(), "test.mycol-a_MinKey");
- ASSERT_EQUALS(chunk.getNS(), "test.mycol");
- ASSERT_EQUALS(chunk.getMin(), BSON("a" << 10));
- ASSERT_EQUALS(chunk.getMax(), BSON("a" << 20));
- ChunkVersion fetchedVersion = chunk.getVersion();
- ASSERT_EQUALS(fetchedVersion._combined, 1ULL);
- ASSERT_EQUALS(fetchedVersion._epoch, epoch);
- ASSERT_EQUALS(chunk.getShard(), "shard0001");
- ASSERT_TRUE(chunk.validate().isOK());
- }
-
- TEST(ChunkType, OldFormatVersion) {
- ChunkType chunk;
- OID epoch = OID::gen();
- BSONObj obj = BSON(ChunkType::name("test.mycol-a_MinKey") <<
- ChunkType::ns("test.mycol") <<
- ChunkType::min(BSON("a" << 10)) <<
- ChunkType::max(BSON("a" << 20)) <<
- ChunkType::DEPRECATED_lastmod(Date_t::fromMillisSinceEpoch(1)) <<
- ChunkType::DEPRECATED_epoch(epoch) <<
- ChunkType::shard("shard0001"));
- StatusWith<ChunkType> chunkRes = ChunkType::fromBSON(obj);
- ASSERT(chunkRes.isOK());
- chunk = chunkRes.getValue();
-
- ASSERT_EQUALS(chunk.getName(), "test.mycol-a_MinKey");
- ASSERT_EQUALS(chunk.getNS(), "test.mycol");
- ASSERT_EQUALS(chunk.getMin(), BSON("a" << 10));
- ASSERT_EQUALS(chunk.getMax(), BSON("a" << 20));
- ChunkVersion fetchedVersion = chunk.getVersion();
- ASSERT_EQUALS(fetchedVersion._combined, 1ULL);
- ASSERT_EQUALS(fetchedVersion._epoch, epoch);
- ASSERT_EQUALS(chunk.getShard(), "shard0001");
- ASSERT_TRUE(chunk.validate().isOK());
- }
-
- TEST(ChunkType, BadType) {
- BSONObj obj = BSON(ChunkType::name() << 0);
- StatusWith<ChunkType> chunkRes = ChunkType::fromBSON(obj);
- ASSERT_FALSE(chunkRes.isOK());
- }
-
-} // unnamed namespace
+using namespace mongo;
+
+using std::string;
+
+TEST(ChunkType, MissingRequiredFields) {
+ ChunkType chunk;
+ BSONArray version = BSON_ARRAY(Date_t::fromMillisSinceEpoch(1) << OID::gen());
+
+ BSONObj objModNS =
+ BSON(ChunkType::name("test.mycol-a_MinKey")
+ << ChunkType::min(BSON("a" << 10 << "b" << 10)) << ChunkType::max(BSON("a" << 20))
+ << ChunkType::version(version) << ChunkType::shard("shard0001"));
+ StatusWith<ChunkType> chunkRes = ChunkType::fromBSON(objModNS);
+ ASSERT_FALSE(chunkRes.isOK());
+
+ BSONObj objModName =
+ BSON(ChunkType::ns("test.mycol")
+ << ChunkType::min(BSON("a" << 10 << "b" << 10)) << ChunkType::max(BSON("a" << 20))
+ << ChunkType::version(version) << ChunkType::shard("shard0001"));
+ chunkRes = ChunkType::fromBSON(objModName);
+ ASSERT_FALSE(chunkRes.isOK());
+
+ BSONObj objModKeys = BSON(ChunkType::name("test.mycol-a_MinKey")
+ << ChunkType::ns("test.mycol") << ChunkType::version(version)
+ << ChunkType::shard("shard0001"));
+ chunkRes = ChunkType::fromBSON(objModKeys);
+ ASSERT_FALSE(chunkRes.isOK());
+
+ BSONObj objModShard =
+ BSON(ChunkType::name("test.mycol-a_MinKey")
+ << ChunkType::ns("test.mycol") << ChunkType::min(BSON("a" << 10 << "b" << 10))
+ << ChunkType::max(BSON("a" << 20)) << ChunkType::version(version));
+ chunkRes = ChunkType::fromBSON(objModShard);
+ ASSERT_FALSE(chunkRes.isOK());
+}
+
+TEST(ChunkType, DifferentNumberOfColumns) {
+ BSONArray version = BSON_ARRAY(Date_t::fromMillisSinceEpoch(1) << OID::gen());
+ BSONObj obj =
+ BSON(ChunkType::name("test.mycol-a_MinKey")
+ << ChunkType::ns("test.mycol") << ChunkType::min(BSON("a" << 10 << "b" << 10))
+ << ChunkType::max(BSON("a" << 20)) << ChunkType::version(version)
+ << ChunkType::shard("shard0001"));
+ StatusWith<ChunkType> chunkRes = ChunkType::fromBSON(obj);
+ ASSERT(chunkRes.isOK());
+ ASSERT_FALSE(chunkRes.getValue().validate().isOK());
+}
+
+TEST(ChunkType, DifferentColumns) {
+ BSONArray version = BSON_ARRAY(Date_t::fromMillisSinceEpoch(1) << OID::gen());
+ BSONObj obj = BSON(ChunkType::name("test.mycol-a_MinKey")
+ << ChunkType::ns("test.mycol") << ChunkType::min(BSON("a" << 10))
+ << ChunkType::max(BSON("b" << 20)) << ChunkType::version(version)
+ << ChunkType::shard("shard0001"));
+ StatusWith<ChunkType> chunkRes = ChunkType::fromBSON(obj);
+ ASSERT(chunkRes.isOK());
+ ASSERT_FALSE(chunkRes.getValue().validate().isOK());
+}
+
+TEST(ChunkType, NotAscending) {
+ BSONArray version = BSON_ARRAY(Date_t::fromMillisSinceEpoch(1) << OID::gen());
+ BSONObj obj = BSON(ChunkType::name("test.mycol-a_MinKey")
+ << ChunkType::ns("test.mycol") << ChunkType::min(BSON("a" << 20))
+ << ChunkType::max(BSON("a" << 10)) << ChunkType::version(version)
+ << ChunkType::shard("shard0001"));
+ StatusWith<ChunkType> chunkRes = ChunkType::fromBSON(obj);
+ ASSERT(chunkRes.isOK());
+ ASSERT_FALSE(chunkRes.getValue().validate().isOK());
+}
+
+TEST(ChunkType, NewFormatVersion) {
+ ChunkType chunk;
+ OID epoch = OID::gen();
+ BSONArray version = BSON_ARRAY(Date_t::fromMillisSinceEpoch(1) << epoch);
+ BSONObj obj = BSON(ChunkType::name("test.mycol-a_MinKey")
+ << ChunkType::ns("test.mycol") << ChunkType::min(BSON("a" << 10))
+ << ChunkType::max(BSON("a" << 20)) << ChunkType::version(version)
+ << ChunkType::shard("shard0001"));
+ StatusWith<ChunkType> chunkRes = ChunkType::fromBSON(obj);
+ ASSERT(chunkRes.isOK());
+ chunk = chunkRes.getValue();
+
+ ASSERT_EQUALS(chunk.getName(), "test.mycol-a_MinKey");
+ ASSERT_EQUALS(chunk.getNS(), "test.mycol");
+ ASSERT_EQUALS(chunk.getMin(), BSON("a" << 10));
+ ASSERT_EQUALS(chunk.getMax(), BSON("a" << 20));
+ ChunkVersion fetchedVersion = chunk.getVersion();
+ ASSERT_EQUALS(fetchedVersion._combined, 1ULL);
+ ASSERT_EQUALS(fetchedVersion._epoch, epoch);
+ ASSERT_EQUALS(chunk.getShard(), "shard0001");
+ ASSERT_TRUE(chunk.validate().isOK());
+}
+
+TEST(ChunkType, OldFormatVersion) {
+ ChunkType chunk;
+ OID epoch = OID::gen();
+ BSONObj obj = BSON(ChunkType::name("test.mycol-a_MinKey")
+ << ChunkType::ns("test.mycol") << ChunkType::min(BSON("a" << 10))
+ << ChunkType::max(BSON("a" << 20))
+ << ChunkType::DEPRECATED_lastmod(Date_t::fromMillisSinceEpoch(1))
+ << ChunkType::DEPRECATED_epoch(epoch) << ChunkType::shard("shard0001"));
+ StatusWith<ChunkType> chunkRes = ChunkType::fromBSON(obj);
+ ASSERT(chunkRes.isOK());
+ chunk = chunkRes.getValue();
+
+ ASSERT_EQUALS(chunk.getName(), "test.mycol-a_MinKey");
+ ASSERT_EQUALS(chunk.getNS(), "test.mycol");
+ ASSERT_EQUALS(chunk.getMin(), BSON("a" << 10));
+ ASSERT_EQUALS(chunk.getMax(), BSON("a" << 20));
+ ChunkVersion fetchedVersion = chunk.getVersion();
+ ASSERT_EQUALS(fetchedVersion._combined, 1ULL);
+ ASSERT_EQUALS(fetchedVersion._epoch, epoch);
+ ASSERT_EQUALS(chunk.getShard(), "shard0001");
+ ASSERT_TRUE(chunk.validate().isOK());
+}
+
+TEST(ChunkType, BadType) {
+ BSONObj obj = BSON(ChunkType::name() << 0);
+ StatusWith<ChunkType> chunkRes = ChunkType::fromBSON(obj);
+ ASSERT_FALSE(chunkRes.isOK());
+}
+
+} // unnamed namespace
diff --git a/src/mongo/s/catalog/type_collection.cpp b/src/mongo/s/catalog/type_collection.cpp
index bd24ca9c44c..7a125e52062 100644
--- a/src/mongo/s/catalog/type_collection.cpp
+++ b/src/mongo/s/catalog/type_collection.cpp
@@ -38,198 +38,191 @@
namespace mongo {
- const std::string CollectionType::ConfigNS = "config.collections";
+const std::string CollectionType::ConfigNS = "config.collections";
- const BSONField<std::string> CollectionType::fullNs("_id");
- const BSONField<OID> CollectionType::epoch("lastmodEpoch");
- const BSONField<Date_t> CollectionType::updatedAt("lastmod");
- const BSONField<BSONObj> CollectionType::keyPattern("key");
- const BSONField<bool> CollectionType::unique("unique");
- const BSONField<bool> CollectionType::noBalance("noBalance");
- const BSONField<bool> CollectionType::dropped("dropped");
+const BSONField<std::string> CollectionType::fullNs("_id");
+const BSONField<OID> CollectionType::epoch("lastmodEpoch");
+const BSONField<Date_t> CollectionType::updatedAt("lastmod");
+const BSONField<BSONObj> CollectionType::keyPattern("key");
+const BSONField<bool> CollectionType::unique("unique");
+const BSONField<bool> CollectionType::noBalance("noBalance");
+const BSONField<bool> CollectionType::dropped("dropped");
- StatusWith<CollectionType> CollectionType::fromBSON(const BSONObj& source) {
- CollectionType coll;
+StatusWith<CollectionType> CollectionType::fromBSON(const BSONObj& source) {
+ CollectionType coll;
- {
- std::string collFullNs;
- Status status = bsonExtractStringField(source, fullNs.name(), &collFullNs);
- if (!status.isOK()) return status;
+ {
+ std::string collFullNs;
+ Status status = bsonExtractStringField(source, fullNs.name(), &collFullNs);
+ if (!status.isOK())
+ return status;
- coll._fullNs = NamespaceString{collFullNs};
- }
-
- {
- OID collEpoch;
- Status status = bsonExtractOIDField(source, epoch.name(), &collEpoch);
- if (!status.isOK()) return status;
+ coll._fullNs = NamespaceString{collFullNs};
+ }
- coll._epoch = collEpoch;
- }
+ {
+ OID collEpoch;
+ Status status = bsonExtractOIDField(source, epoch.name(), &collEpoch);
+ if (!status.isOK())
+ return status;
- {
- BSONElement collUpdatedAt;
- Status status = bsonExtractTypedField(source, updatedAt.name(), Date, &collUpdatedAt);
- if (!status.isOK()) return status;
+ coll._epoch = collEpoch;
+ }
- coll._updatedAt = collUpdatedAt.Date();
- }
+ {
+ BSONElement collUpdatedAt;
+ Status status = bsonExtractTypedField(source, updatedAt.name(), Date, &collUpdatedAt);
+ if (!status.isOK())
+ return status;
- {
- bool collDropped;
- Status status = bsonExtractBooleanField(source, dropped.name(), &collDropped);
- if (status.isOK()) {
- coll._dropped = collDropped;
- }
- else if (status == ErrorCodes::NoSuchKey) {
- // Dropped can be missing in which case it is presumed false
- }
- else {
- return status;
- }
- }
+ coll._updatedAt = collUpdatedAt.Date();
+ }
- {
- BSONElement collKeyPattern;
- Status status =
- bsonExtractTypedField(source, keyPattern.name(), Object, &collKeyPattern);
- if (status.isOK()) {
- BSONObj obj = collKeyPattern.Obj();
- if (obj.isEmpty()) {
- return Status(ErrorCodes::ShardKeyNotFound, "empty shard key");
- }
-
- coll._keyPattern = KeyPattern(obj.getOwned());
- }
- else if ((status == ErrorCodes::NoSuchKey) && coll.getDropped()) {
- // Sharding key can be missing if the collection is dropped
- }
- else {
- return status;
- }
+ {
+ bool collDropped;
+ Status status = bsonExtractBooleanField(source, dropped.name(), &collDropped);
+ if (status.isOK()) {
+ coll._dropped = collDropped;
+ } else if (status == ErrorCodes::NoSuchKey) {
+ // Dropped can be missing in which case it is presumed false
+ } else {
+ return status;
}
+ }
- {
- bool collUnique;
- Status status = bsonExtractBooleanField(source, unique.name(), &collUnique);
- if (status.isOK()) {
- coll._unique = collUnique;
+ {
+ BSONElement collKeyPattern;
+ Status status = bsonExtractTypedField(source, keyPattern.name(), Object, &collKeyPattern);
+ if (status.isOK()) {
+ BSONObj obj = collKeyPattern.Obj();
+ if (obj.isEmpty()) {
+ return Status(ErrorCodes::ShardKeyNotFound, "empty shard key");
}
- else if (status == ErrorCodes::NoSuchKey) {
- // Key uniqueness can be missing in which case it is presumed false
- }
- else {
- return status;
- }
- }
- {
- bool collNoBalance;
- Status status = bsonExtractBooleanField(source, noBalance.name(), &collNoBalance);
- if (status.isOK()) {
- coll._allowBalance = !collNoBalance;
- }
- else if (status == ErrorCodes::NoSuchKey) {
- // No balance can be missing in which case it is presumed as false
- }
- else {
- return status;
- }
+ coll._keyPattern = KeyPattern(obj.getOwned());
+ } else if ((status == ErrorCodes::NoSuchKey) && coll.getDropped()) {
+ // Sharding key can be missing if the collection is dropped
+ } else {
+ return status;
}
-
- return StatusWith<CollectionType>(coll);
}
- Status CollectionType::validate() const {
- // These fields must always be set
- if (!_fullNs.is_initialized()) {
- return Status(ErrorCodes::NoSuchKey, "missing ns");
- }
-
- if (!_fullNs->isValid()) {
- return Status(ErrorCodes::BadValue, "invalid namespace " + _fullNs->toString());
- }
-
- if (!_epoch.is_initialized()) {
- return Status(ErrorCodes::NoSuchKey, "missing epoch");
+ {
+ bool collUnique;
+ Status status = bsonExtractBooleanField(source, unique.name(), &collUnique);
+ if (status.isOK()) {
+ coll._unique = collUnique;
+ } else if (status == ErrorCodes::NoSuchKey) {
+ // Key uniqueness can be missing in which case it is presumed false
+ } else {
+ return status;
}
+ }
- if (!_updatedAt.is_initialized()) {
- return Status(ErrorCodes::NoSuchKey, "missing updated at timestamp");
+ {
+ bool collNoBalance;
+ Status status = bsonExtractBooleanField(source, noBalance.name(), &collNoBalance);
+ if (status.isOK()) {
+ coll._allowBalance = !collNoBalance;
+ } else if (status == ErrorCodes::NoSuchKey) {
+ // No balance can be missing in which case it is presumed as false
+ } else {
+ return status;
}
+ }
- if (!_dropped.get_value_or(false)) {
- if (!_epoch->isSet()) {
- return Status(ErrorCodes::BadValue, "invalid epoch");
- }
+ return StatusWith<CollectionType>(coll);
+}
- if (Date_t() == _updatedAt.get()) {
- return Status(ErrorCodes::BadValue, "invalid updated at timestamp");
- }
+Status CollectionType::validate() const {
+ // These fields must always be set
+ if (!_fullNs.is_initialized()) {
+ return Status(ErrorCodes::NoSuchKey, "missing ns");
+ }
- if (!_keyPattern.is_initialized()) {
- return Status(ErrorCodes::NoSuchKey, "missing key pattern");
- }
- else {
- invariant(!_keyPattern->toBSON().isEmpty());
- }
- }
+ if (!_fullNs->isValid()) {
+ return Status(ErrorCodes::BadValue, "invalid namespace " + _fullNs->toString());
+ }
- return Status::OK();
+ if (!_epoch.is_initialized()) {
+ return Status(ErrorCodes::NoSuchKey, "missing epoch");
}
- BSONObj CollectionType::toBSON() const {
- BSONObjBuilder builder;
+ if (!_updatedAt.is_initialized()) {
+ return Status(ErrorCodes::NoSuchKey, "missing updated at timestamp");
+ }
- if (_fullNs) {
- builder.append(fullNs.name(), _fullNs->toString());
+ if (!_dropped.get_value_or(false)) {
+ if (!_epoch->isSet()) {
+ return Status(ErrorCodes::BadValue, "invalid epoch");
}
- builder.append(epoch.name(), _epoch.get_value_or(OID()));
- builder.append(updatedAt.name(), _updatedAt.get_value_or(Date_t()));
- // These fields are optional, so do not include them in the metadata for the purposes of
- // consuming less space on the config servers.
-
- if (_dropped.is_initialized()) {
- builder.append(dropped.name(), _dropped.get());
+ if (Date_t() == _updatedAt.get()) {
+ return Status(ErrorCodes::BadValue, "invalid updated at timestamp");
}
- if (_keyPattern.is_initialized()) {
- builder.append(keyPattern.name(), _keyPattern->toBSON());
+ if (!_keyPattern.is_initialized()) {
+ return Status(ErrorCodes::NoSuchKey, "missing key pattern");
+ } else {
+ invariant(!_keyPattern->toBSON().isEmpty());
}
+ }
- if (_unique.is_initialized()) {
- builder.append(unique.name(), _unique.get());
- }
+ return Status::OK();
+}
- if (_allowBalance.is_initialized()) {
- builder.append(noBalance.name(), !_allowBalance.get());
- }
+BSONObj CollectionType::toBSON() const {
+ BSONObjBuilder builder;
- return builder.obj();
+ if (_fullNs) {
+ builder.append(fullNs.name(), _fullNs->toString());
}
+ builder.append(epoch.name(), _epoch.get_value_or(OID()));
+ builder.append(updatedAt.name(), _updatedAt.get_value_or(Date_t()));
- std::string CollectionType::toString() const {
- return toBSON().toString();
- }
+ // These fields are optional, so do not include them in the metadata for the purposes of
+ // consuming less space on the config servers.
- void CollectionType::setNs(const NamespaceString& fullNs) {
- invariant(fullNs.isValid());
- _fullNs = fullNs;
+ if (_dropped.is_initialized()) {
+ builder.append(dropped.name(), _dropped.get());
}
- void CollectionType::setEpoch(OID epoch) {
- _epoch = epoch;
+ if (_keyPattern.is_initialized()) {
+ builder.append(keyPattern.name(), _keyPattern->toBSON());
}
- void CollectionType::setUpdatedAt(Date_t updatedAt) {
- _updatedAt = updatedAt;
+ if (_unique.is_initialized()) {
+ builder.append(unique.name(), _unique.get());
}
- void CollectionType::setKeyPattern(const KeyPattern& keyPattern) {
- invariant(!keyPattern.toBSON().isEmpty());
- _keyPattern = keyPattern;
+ if (_allowBalance.is_initialized()) {
+ builder.append(noBalance.name(), !_allowBalance.get());
}
-} // namespace mongo
+ return builder.obj();
+}
+
+std::string CollectionType::toString() const {
+ return toBSON().toString();
+}
+
+void CollectionType::setNs(const NamespaceString& fullNs) {
+ invariant(fullNs.isValid());
+ _fullNs = fullNs;
+}
+
+void CollectionType::setEpoch(OID epoch) {
+ _epoch = epoch;
+}
+
+void CollectionType::setUpdatedAt(Date_t updatedAt) {
+ _updatedAt = updatedAt;
+}
+
+void CollectionType::setKeyPattern(const KeyPattern& keyPattern) {
+ invariant(!keyPattern.toBSON().isEmpty());
+ _keyPattern = keyPattern;
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_collection.h b/src/mongo/s/catalog/type_collection.h
index 12360d7fef3..545c949e81e 100644
--- a/src/mongo/s/catalog/type_collection.h
+++ b/src/mongo/s/catalog/type_collection.h
@@ -37,91 +37,110 @@
namespace mongo {
- class Status;
- template<typename T> class StatusWith;
+class Status;
+template <typename T>
+class StatusWith;
- /**
- * This class represents the layout and contents of documents contained in the
- * config.collections collection. All manipulation of documents coming from that collection
- * should be done with this class.
- */
- class CollectionType {
- public:
- // Name of the collections collection in the config server.
- static const std::string ConfigNS;
-
- static const BSONField<std::string> fullNs;
- static const BSONField<OID> epoch;
- static const BSONField<Date_t> updatedAt;
- static const BSONField<BSONObj> keyPattern;
- static const BSONField<bool> unique;
- static const BSONField<bool> noBalance;
- static const BSONField<bool> dropped;
-
-
- /**
- * Constructs a new DatabaseType object from BSON. Also does validation of the contents.
- */
- static StatusWith<CollectionType> fromBSON(const BSONObj& source);
-
- /**
- * Returns OK if all fields have been set. Otherwise returns NoSuchKey and information
- * about what is the first field which is missing.
- */
- Status validate() const;
-
- /**
- * Returns the BSON representation of the entry.
- */
- BSONObj toBSON() const;
-
- /**
- * Returns a std::string representation of the current internal state.
- */
- std::string toString() const;
-
- const NamespaceString& getNs() const { return _fullNs.get(); }
- void setNs(const NamespaceString& fullNs);
-
- OID getEpoch() const { return _epoch.get(); }
- void setEpoch(OID epoch);
-
- Date_t getUpdatedAt() const { return _updatedAt.get(); }
- void setUpdatedAt(Date_t updatedAt);
-
- bool getDropped() const { return _dropped.get_value_or(false); }
- void setDropped(bool dropped) { _dropped = dropped; }
-
- const KeyPattern& getKeyPattern() const { return _keyPattern.get(); }
- void setKeyPattern(const KeyPattern& keyPattern);
-
- bool getUnique() const { return _unique.get_value_or(false); }
- void setUnique(bool unique) { _unique = unique; }
-
- bool getAllowBalance() const { return _allowBalance.get_value_or(true); }
-
- private:
- // Required full namespace (with the database prefix).
- boost::optional<NamespaceString> _fullNs;
-
- // Required to disambiguate collection namespace incarnations.
- boost::optional<OID> _epoch;
+/**
+ * This class represents the layout and contents of documents contained in the
+ * config.collections collection. All manipulation of documents coming from that collection
+ * should be done with this class.
+ */
+class CollectionType {
+public:
+ // Name of the collections collection in the config server.
+ static const std::string ConfigNS;
- // Required last updated time.
- boost::optional<Date_t> _updatedAt;
+ static const BSONField<std::string> fullNs;
+ static const BSONField<OID> epoch;
+ static const BSONField<Date_t> updatedAt;
+ static const BSONField<BSONObj> keyPattern;
+ static const BSONField<bool> unique;
+ static const BSONField<bool> noBalance;
+ static const BSONField<bool> dropped;
- // Optional, whether the collection has been dropped. If missing, implies false.
- boost::optional<bool> _dropped;
- // Sharding key. Required, if collection is not dropped.
- boost::optional<KeyPattern> _keyPattern;
+ /**
+ * Constructs a new DatabaseType object from BSON. Also does validation of the contents.
+ */
+ static StatusWith<CollectionType> fromBSON(const BSONObj& source);
- // Optional uniqueness of the sharding key. If missing, implies false.
- boost::optional<bool> _unique;
+ /**
+ * Returns OK if all fields have been set. Otherwise returns NoSuchKey and information
+ * about what is the first field which is missing.
+ */
+ Status validate() const;
- // Optional whether balancing is allowed for this collection. If missing, implies true.
- boost::optional<bool> _allowBalance;
- };
+ /**
+ * Returns the BSON representation of the entry.
+ */
+ BSONObj toBSON() const;
-} // namespace mongo
+ /**
+ * Returns a std::string representation of the current internal state.
+ */
+ std::string toString() const;
+
+ const NamespaceString& getNs() const {
+ return _fullNs.get();
+ }
+ void setNs(const NamespaceString& fullNs);
+
+ OID getEpoch() const {
+ return _epoch.get();
+ }
+ void setEpoch(OID epoch);
+
+ Date_t getUpdatedAt() const {
+ return _updatedAt.get();
+ }
+ void setUpdatedAt(Date_t updatedAt);
+
+ bool getDropped() const {
+ return _dropped.get_value_or(false);
+ }
+ void setDropped(bool dropped) {
+ _dropped = dropped;
+ }
+
+ const KeyPattern& getKeyPattern() const {
+ return _keyPattern.get();
+ }
+ void setKeyPattern(const KeyPattern& keyPattern);
+
+ bool getUnique() const {
+ return _unique.get_value_or(false);
+ }
+ void setUnique(bool unique) {
+ _unique = unique;
+ }
+
+ bool getAllowBalance() const {
+ return _allowBalance.get_value_or(true);
+ }
+
+private:
+ // Required full namespace (with the database prefix).
+ boost::optional<NamespaceString> _fullNs;
+
+ // Required to disambiguate collection namespace incarnations.
+ boost::optional<OID> _epoch;
+
+ // Required last updated time.
+ boost::optional<Date_t> _updatedAt;
+
+ // Optional, whether the collection has been dropped. If missing, implies false.
+ boost::optional<bool> _dropped;
+
+ // Sharding key. Required, if collection is not dropped.
+ boost::optional<KeyPattern> _keyPattern;
+
+ // Optional uniqueness of the sharding key. If missing, implies false.
+ boost::optional<bool> _unique;
+
+ // Optional whether balancing is allowed for this collection. If missing, implies true.
+ boost::optional<bool> _allowBalance;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_collection_test.cpp b/src/mongo/s/catalog/type_collection_test.cpp
index cde1bff5ce9..9d3d3553ef7 100644
--- a/src/mongo/s/catalog/type_collection_test.cpp
+++ b/src/mongo/s/catalog/type_collection_test.cpp
@@ -36,58 +36,53 @@
namespace {
- using namespace mongo;
+using namespace mongo;
- TEST(CollectionType, Empty) {
- StatusWith<CollectionType> status = CollectionType::fromBSON(BSONObj());
- ASSERT_FALSE(status.isOK());
- }
+TEST(CollectionType, Empty) {
+ StatusWith<CollectionType> status = CollectionType::fromBSON(BSONObj());
+ ASSERT_FALSE(status.isOK());
+}
- TEST(CollectionType, Basic) {
- const OID oid = OID::gen();
- StatusWith<CollectionType> status = CollectionType::fromBSON(
- BSON(CollectionType::fullNs("db.coll") <<
- CollectionType::epoch(oid) <<
- CollectionType::updatedAt(Date_t::fromMillisSinceEpoch(1)) <<
- CollectionType::keyPattern(BSON("a" << 1)) <<
- CollectionType::unique(true)));
- ASSERT_TRUE(status.isOK());
+TEST(CollectionType, Basic) {
+ const OID oid = OID::gen();
+ StatusWith<CollectionType> status = CollectionType::fromBSON(BSON(
+ CollectionType::fullNs("db.coll")
+ << CollectionType::epoch(oid) << CollectionType::updatedAt(Date_t::fromMillisSinceEpoch(1))
+ << CollectionType::keyPattern(BSON("a" << 1)) << CollectionType::unique(true)));
+ ASSERT_TRUE(status.isOK());
- CollectionType coll = status.getValue();
- ASSERT_TRUE(coll.validate().isOK());
- ASSERT(coll.getNs() == NamespaceString{"db.coll"});
- ASSERT_EQUALS(coll.getEpoch(), oid);
- ASSERT_EQUALS(coll.getUpdatedAt(), Date_t::fromMillisSinceEpoch(1));
- ASSERT_EQUALS(coll.getKeyPattern().toBSON(), BSON("a" << 1));
- ASSERT_EQUALS(coll.getUnique(), true);
- ASSERT_EQUALS(coll.getAllowBalance(), true);
- ASSERT_EQUALS(coll.getDropped(), false);
- }
+ CollectionType coll = status.getValue();
+ ASSERT_TRUE(coll.validate().isOK());
+ ASSERT(coll.getNs() == NamespaceString{"db.coll"});
+ ASSERT_EQUALS(coll.getEpoch(), oid);
+ ASSERT_EQUALS(coll.getUpdatedAt(), Date_t::fromMillisSinceEpoch(1));
+ ASSERT_EQUALS(coll.getKeyPattern().toBSON(), BSON("a" << 1));
+ ASSERT_EQUALS(coll.getUnique(), true);
+ ASSERT_EQUALS(coll.getAllowBalance(), true);
+ ASSERT_EQUALS(coll.getDropped(), false);
+}
- TEST(CollectionType, InvalidCollectionNamespace) {
- const OID oid = OID::gen();
- StatusWith<CollectionType> result = CollectionType::fromBSON(
- BSON(CollectionType::fullNs("foo\\bar.coll") <<
- CollectionType::epoch(oid) <<
- CollectionType::updatedAt(Date_t::fromMillisSinceEpoch(1)) <<
- CollectionType::keyPattern(BSON("a" << 1)) <<
- CollectionType::unique(true)));
- ASSERT_TRUE(result.isOK());
- CollectionType collType = result.getValue();
- ASSERT_FALSE(collType.validate().isOK());
- }
+TEST(CollectionType, InvalidCollectionNamespace) {
+ const OID oid = OID::gen();
+ StatusWith<CollectionType> result = CollectionType::fromBSON(BSON(
+ CollectionType::fullNs("foo\\bar.coll")
+ << CollectionType::epoch(oid) << CollectionType::updatedAt(Date_t::fromMillisSinceEpoch(1))
+ << CollectionType::keyPattern(BSON("a" << 1)) << CollectionType::unique(true)));
+ ASSERT_TRUE(result.isOK());
+ CollectionType collType = result.getValue();
+ ASSERT_FALSE(collType.validate().isOK());
+}
- TEST(CollectionType, BadType) {
- const OID oid = OID::gen();
- StatusWith<CollectionType> status = CollectionType::fromBSON(
- BSON(CollectionType::fullNs() << 1 <<
- CollectionType::epoch(oid) <<
- CollectionType::updatedAt(Date_t::fromMillisSinceEpoch(1)) <<
- CollectionType::keyPattern(BSON("a" << 1)) <<
- CollectionType::unique(true)));
+TEST(CollectionType, BadType) {
+ const OID oid = OID::gen();
+ StatusWith<CollectionType> status = CollectionType::fromBSON(
+ BSON(CollectionType::fullNs() << 1 << CollectionType::epoch(oid)
+ << CollectionType::updatedAt(Date_t::fromMillisSinceEpoch(1))
+ << CollectionType::keyPattern(BSON("a" << 1))
+ << CollectionType::unique(true)));
- ASSERT_FALSE(status.isOK());
- }
+ ASSERT_FALSE(status.isOK());
+}
-} // namespace
+} // namespace
diff --git a/src/mongo/s/catalog/type_database.cpp b/src/mongo/s/catalog/type_database.cpp
index 222f46cf301..24de84c838c 100644
--- a/src/mongo/s/catalog/type_database.cpp
+++ b/src/mongo/s/catalog/type_database.cpp
@@ -38,82 +38,86 @@
namespace mongo {
- using std::string;
+using std::string;
- const std::string DatabaseType::ConfigNS = "config.databases";
+const std::string DatabaseType::ConfigNS = "config.databases";
- const BSONField<std::string> DatabaseType::name("_id");
- const BSONField<std::string> DatabaseType::primary("primary");
- const BSONField<bool> DatabaseType::sharded("partitioned");
+const BSONField<std::string> DatabaseType::name("_id");
+const BSONField<std::string> DatabaseType::primary("primary");
+const BSONField<bool> DatabaseType::sharded("partitioned");
- StatusWith<DatabaseType> DatabaseType::fromBSON(const BSONObj& source) {
- DatabaseType dbt;
+StatusWith<DatabaseType> DatabaseType::fromBSON(const BSONObj& source) {
+ DatabaseType dbt;
- {
- std::string dbtName;
- Status status = bsonExtractStringField(source, name.name(), &dbtName);
- if (!status.isOK()) return status;
+ {
+ std::string dbtName;
+ Status status = bsonExtractStringField(source, name.name(), &dbtName);
+ if (!status.isOK())
+ return status;
- dbt._name = dbtName;
- }
-
- {
- std::string dbtPrimary;
- Status status = bsonExtractStringField(source, primary.name(), &dbtPrimary);
- if (!status.isOK()) return status;
+ dbt._name = dbtName;
+ }
- dbt._primary = dbtPrimary;
- }
+ {
+ std::string dbtPrimary;
+ Status status = bsonExtractStringField(source, primary.name(), &dbtPrimary);
+ if (!status.isOK())
+ return status;
- {
- bool dbtSharded;
- Status status = bsonExtractBooleanFieldWithDefault(source, sharded.name(), false, &dbtSharded);
- if (!status.isOK()) return status;
+ dbt._primary = dbtPrimary;
+ }
- dbt._sharded = dbtSharded;
- }
+ {
+ bool dbtSharded;
+ Status status =
+ bsonExtractBooleanFieldWithDefault(source, sharded.name(), false, &dbtSharded);
+ if (!status.isOK())
+ return status;
- return StatusWith<DatabaseType>(dbt);
+ dbt._sharded = dbtSharded;
}
- Status DatabaseType::validate() const {
- if (!_name.is_initialized() || _name->empty()) {
- return Status(ErrorCodes::NoSuchKey, "missing name");
- }
+ return StatusWith<DatabaseType>(dbt);
+}
- if (!_primary.is_initialized() || _primary->empty()) {
- return Status(ErrorCodes::NoSuchKey, "missing primary");
- }
+Status DatabaseType::validate() const {
+ if (!_name.is_initialized() || _name->empty()) {
+ return Status(ErrorCodes::NoSuchKey, "missing name");
+ }
- if (!_sharded.is_initialized()) {
- return Status(ErrorCodes::NoSuchKey, "missing sharded");
- }
+ if (!_primary.is_initialized() || _primary->empty()) {
+ return Status(ErrorCodes::NoSuchKey, "missing primary");
+ }
- return Status::OK();
+ if (!_sharded.is_initialized()) {
+ return Status(ErrorCodes::NoSuchKey, "missing sharded");
}
- BSONObj DatabaseType::toBSON() const {
- BSONObjBuilder builder;
- builder.append(name.name(), _name.get_value_or(""));
- builder.append(primary.name(), _primary.get_value_or(""));
- builder.append(sharded.name(), _sharded.get_value_or(false));
+ return Status::OK();
+}
- return builder.obj();
- }
+BSONObj DatabaseType::toBSON() const {
+ BSONObjBuilder builder;
+ builder.append(name.name(), _name.get_value_or(""));
+ builder.append(primary.name(), _primary.get_value_or(""));
+ builder.append(sharded.name(), _sharded.get_value_or(false));
- std::string DatabaseType::toString() const {
- return toBSON().toString();
- }
+ return builder.obj();
+}
- void DatabaseType::setName(const std::string& name) {
- invariant(!name.empty());
- _name = name;
- }
+std::string DatabaseType::toString() const {
+ return toBSON().toString();
+}
- void DatabaseType::setPrimary(const std::string& primary) {
- invariant(!primary.empty());
- _primary = primary;
- }
+void DatabaseType::setName(const std::string& name) {
+ invariant(!name.empty());
+ _name = name;
+}
+
+void DatabaseType::setPrimary(const std::string& primary) {
+ invariant(!primary.empty());
+ _primary = primary;
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_database.h b/src/mongo/s/catalog/type_database.h
index 95735903307..e452bd826a2 100644
--- a/src/mongo/s/catalog/type_database.h
+++ b/src/mongo/s/catalog/type_database.h
@@ -35,67 +35,76 @@
namespace mongo {
- class BSONObj;
- class Status;
- template<typename T> class StatusWith;
+class BSONObj;
+class Status;
+template <typename T>
+class StatusWith;
- /**
- * This class represents the layout and contents of documents contained in the config.databases
- * collection. All manipulation of documents coming from that collection should be done with
- * this class.
- */
- class DatabaseType {
- public:
- // Name of the databases collection in the config server.
- static const std::string ConfigNS;
-
- static const BSONField<std::string> name;
- static const BSONField<std::string> primary;
- static const BSONField<bool> sharded;
-
-
- /**
- * Constructs a new DatabaseType object from BSON. Also does validation of the contents.
- */
- static StatusWith<DatabaseType> fromBSON(const BSONObj& source);
-
- /**
- * Returns OK if all fields have been set. Otherwise returns NoSuchKey and information
- * about what is the first field which is missing.
- */
- Status validate() const;
-
- /**
- * Returns the BSON representation of the entry.
- */
- BSONObj toBSON() const;
-
- /**
- * Returns a std::string representation of the current internal state.
- */
- std::string toString() const;
-
- const std::string& getName() const { return _name.get(); }
- void setName(const std::string& name);
+/**
+ * This class represents the layout and contents of documents contained in the config.databases
+ * collection. All manipulation of documents coming from that collection should be done with
+ * this class.
+ */
+class DatabaseType {
+public:
+ // Name of the databases collection in the config server.
+ static const std::string ConfigNS;
- const std::string& getPrimary() const { return _primary.get(); }
- void setPrimary(const std::string& primary);
+ static const BSONField<std::string> name;
+ static const BSONField<std::string> primary;
+ static const BSONField<bool> sharded;
- bool getSharded() const { return _sharded.get(); }
- void setSharded(bool sharded) { _sharded = sharded; }
- private:
- // Requred database name
- boost::optional<std::string> _name;
+ /**
+ * Constructs a new DatabaseType object from BSON. Also does validation of the contents.
+ */
+ static StatusWith<DatabaseType> fromBSON(const BSONObj& source);
- // Required primary shard (must be set even if the database is sharded, because there
- // might be collections, which are unsharded).
- boost::optional<std::string> _primary;
+ /**
+ * Returns OK if all fields have been set. Otherwise returns NoSuchKey and information
+ * about what is the first field which is missing.
+ */
+ Status validate() const;
- // Required whether sharding is enabled for this database. Even though this field is of
- // type optional, it is only used as an indicator that the value was explicitly set.
- boost::optional<bool> _sharded;
- };
+ /**
+ * Returns the BSON representation of the entry.
+ */
+ BSONObj toBSON() const;
-} // namespace mongo
+ /**
+ * Returns a std::string representation of the current internal state.
+ */
+ std::string toString() const;
+
+ const std::string& getName() const {
+ return _name.get();
+ }
+ void setName(const std::string& name);
+
+ const std::string& getPrimary() const {
+ return _primary.get();
+ }
+ void setPrimary(const std::string& primary);
+
+ bool getSharded() const {
+ return _sharded.get();
+ }
+ void setSharded(bool sharded) {
+ _sharded = sharded;
+ }
+
+private:
+ // Requred database name
+ boost::optional<std::string> _name;
+
+ // Required primary shard (must be set even if the database is sharded, because there
+ // might be collections, which are unsharded).
+ boost::optional<std::string> _primary;
+
+ // Required whether sharding is enabled for this database. Even though this field is of
+ // type optional, it is only used as an indicator that the value was explicitly set.
+ boost::optional<bool> _sharded;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_database_test.cpp b/src/mongo/s/catalog/type_database_test.cpp
index db7f9be9575..f038ee65bb4 100644
--- a/src/mongo/s/catalog/type_database_test.cpp
+++ b/src/mongo/s/catalog/type_database_test.cpp
@@ -35,36 +35,34 @@
namespace {
- using namespace mongo;
- using std::string;
+using namespace mongo;
+using std::string;
- TEST(DatabaseType, Empty) {
- StatusWith<DatabaseType> status = DatabaseType::fromBSON(BSONObj());
- ASSERT_FALSE(status.isOK());
- }
+TEST(DatabaseType, Empty) {
+ StatusWith<DatabaseType> status = DatabaseType::fromBSON(BSONObj());
+ ASSERT_FALSE(status.isOK());
+}
- TEST(DatabaseType, Basic) {
- StatusWith<DatabaseType> status = DatabaseType::fromBSON(
- BSON(DatabaseType::name("mydb") <<
- DatabaseType::primary("shard") <<
- DatabaseType::sharded(true)));
- ASSERT_TRUE(status.isOK());
+TEST(DatabaseType, Basic) {
+ StatusWith<DatabaseType> status =
+ DatabaseType::fromBSON(BSON(DatabaseType::name("mydb") << DatabaseType::primary("shard")
+ << DatabaseType::sharded(true)));
+ ASSERT_TRUE(status.isOK());
- DatabaseType db = status.getValue();
- ASSERT_EQUALS(db.getName(), "mydb");
- ASSERT_EQUALS(db.getPrimary(), "shard");
- ASSERT_TRUE(db.getSharded());
- }
+ DatabaseType db = status.getValue();
+ ASSERT_EQUALS(db.getName(), "mydb");
+ ASSERT_EQUALS(db.getPrimary(), "shard");
+ ASSERT_TRUE(db.getSharded());
+}
- TEST(DatabaseType, BadType) {
- StatusWith<DatabaseType> status = DatabaseType::fromBSON(BSON(DatabaseType::name() << 0));
- ASSERT_FALSE(status.isOK());
- }
+TEST(DatabaseType, BadType) {
+ StatusWith<DatabaseType> status = DatabaseType::fromBSON(BSON(DatabaseType::name() << 0));
+ ASSERT_FALSE(status.isOK());
+}
- TEST(DatabaseType, MissingRequired) {
- StatusWith<DatabaseType> status = DatabaseType::fromBSON(
- BSON(DatabaseType::name("mydb")));
- ASSERT_FALSE(status.isOK());
- }
+TEST(DatabaseType, MissingRequired) {
+ StatusWith<DatabaseType> status = DatabaseType::fromBSON(BSON(DatabaseType::name("mydb")));
+ ASSERT_FALSE(status.isOK());
+}
-} // unnamed namespace
+} // unnamed namespace
diff --git a/src/mongo/s/catalog/type_settings.cpp b/src/mongo/s/catalog/type_settings.cpp
index 1f71888d9cc..c19809efa30 100644
--- a/src/mongo/s/catalog/type_settings.cpp
+++ b/src/mongo/s/catalog/type_settings.cpp
@@ -46,261 +46,249 @@
namespace mongo {
- const std::string SettingsType::ConfigNS = "config.settings";
- const std::string SettingsType::BalancerDocKey("balancer");
- const std::string SettingsType::ChunkSizeDocKey("chunksize");
-
- const BSONField<std::string> SettingsType::key("_id");
- const BSONField<long long> SettingsType::chunkSizeMB("value");
- const BSONField<bool> SettingsType::balancerStopped("stopped");
- const BSONField<BSONObj> SettingsType::balancerActiveWindow("activeWindow");
- const BSONField<bool> SettingsType::deprecated_secondaryThrottle("_secondaryThrottle");
- const BSONField<BSONObj> SettingsType::migrationWriteConcern("_secondaryThrottle");
- const BSONField<bool> SettingsType::waitForDelete("_waitForDelete");
-
- StatusWith<SettingsType> SettingsType::fromBSON(const BSONObj& source) {
- SettingsType settings;
+const std::string SettingsType::ConfigNS = "config.settings";
+const std::string SettingsType::BalancerDocKey("balancer");
+const std::string SettingsType::ChunkSizeDocKey("chunksize");
+
+const BSONField<std::string> SettingsType::key("_id");
+const BSONField<long long> SettingsType::chunkSizeMB("value");
+const BSONField<bool> SettingsType::balancerStopped("stopped");
+const BSONField<BSONObj> SettingsType::balancerActiveWindow("activeWindow");
+const BSONField<bool> SettingsType::deprecated_secondaryThrottle("_secondaryThrottle");
+const BSONField<BSONObj> SettingsType::migrationWriteConcern("_secondaryThrottle");
+const BSONField<bool> SettingsType::waitForDelete("_waitForDelete");
+
+StatusWith<SettingsType> SettingsType::fromBSON(const BSONObj& source) {
+ SettingsType settings;
+
+ {
+ std::string settingsKey;
+ Status status = bsonExtractStringField(source, key.name(), &settingsKey);
+ if (!status.isOK())
+ return status;
+ settings._key = settingsKey;
+ }
+ if (settings._key == ChunkSizeDocKey) {
+ long long settingsChunkSizeMB;
+ Status status = bsonExtractIntegerField(source, chunkSizeMB.name(), &settingsChunkSizeMB);
+ if (!status.isOK())
+ return status;
+ settings._chunkSizeMB = settingsChunkSizeMB;
+ } else if (settings._key == BalancerDocKey) {
{
- std::string settingsKey;
- Status status = bsonExtractStringField(source, key.name(), &settingsKey);
- if (!status.isOK()) return status;
- settings._key = settingsKey;
+ bool settingsBalancerStopped;
+ Status status = bsonExtractBooleanFieldWithDefault(
+ source, balancerStopped.name(), false, &settingsBalancerStopped);
+ if (!status.isOK())
+ return status;
+ settings._balancerStopped = settingsBalancerStopped;
}
- if (settings._key == ChunkSizeDocKey) {
- long long settingsChunkSizeMB;
- Status status = bsonExtractIntegerField(source,
- chunkSizeMB.name(),
- &settingsChunkSizeMB);
- if (!status.isOK()) return status;
- settings._chunkSizeMB = settingsChunkSizeMB;
- }
- else if (settings._key == BalancerDocKey) {
- {
- bool settingsBalancerStopped;
- Status status = bsonExtractBooleanFieldWithDefault(source,
- balancerStopped.name(),
- false,
- &settingsBalancerStopped);
- if (!status.isOK()) return status;
- settings._balancerStopped = settingsBalancerStopped;
- }
-
- {
- BSONElement settingsBalancerActiveWindowElem;
- Status status = bsonExtractTypedField(source,
- balancerActiveWindow.name(),
- Object,
- &settingsBalancerActiveWindowElem);
- if (status != ErrorCodes::NoSuchKey) {
- if (!status.isOK()) return status;
- StatusWith<BoostTimePair> timePairResult =
- settings._parseBalancingWindow(settingsBalancerActiveWindowElem.Obj());
- if (!timePairResult.isOK()) return timePairResult.getStatus();
- settings._balancerActiveWindow = timePairResult.getValue();
- }
- }
-
- {
- BSONElement settingsMigrationWriteConcernElem;
- Status status = bsonExtractTypedField(source,
- migrationWriteConcern.name(),
- Object,
- &settingsMigrationWriteConcernElem);
- if (status == ErrorCodes::TypeMismatch) {
- bool settingsSecondaryThrottle;
- status = bsonExtractBooleanFieldWithDefault(source,
- deprecated_secondaryThrottle
- .name(),
- true,
- &settingsSecondaryThrottle);
- if (!status.isOK()) return status;
- settings._secondaryThrottle = settingsSecondaryThrottle;
- }
- else if (status != ErrorCodes::NoSuchKey) {
- if (!status.isOK()) return status;
- settings._migrationWriteConcern = WriteConcernOptions();
- status = settings._migrationWriteConcern->parse(
- settingsMigrationWriteConcernElem.Obj()
- );
- if (!status.isOK()) return status;
- }
- }
-
- {
- bool settingsWaitForDelete;
- Status status = bsonExtractBooleanField(source,
- waitForDelete.name(),
- &settingsWaitForDelete);
- if (status != ErrorCodes::NoSuchKey) {
- if (!status.isOK()) return status;
- settings._waitForDelete = settingsWaitForDelete;
- }
+ {
+ BSONElement settingsBalancerActiveWindowElem;
+ Status status = bsonExtractTypedField(
+ source, balancerActiveWindow.name(), Object, &settingsBalancerActiveWindowElem);
+ if (status != ErrorCodes::NoSuchKey) {
+ if (!status.isOK())
+ return status;
+ StatusWith<BoostTimePair> timePairResult =
+ settings._parseBalancingWindow(settingsBalancerActiveWindowElem.Obj());
+ if (!timePairResult.isOK())
+ return timePairResult.getStatus();
+ settings._balancerActiveWindow = timePairResult.getValue();
}
}
- return settings;
- }
-
- Status SettingsType::validate() const {
- if (!_key.is_initialized() || _key->empty()) {
- return Status(ErrorCodes::NoSuchKey,
- str::stream() << "missing " << key.name() << " field");
- }
-
- if (_key == ChunkSizeDocKey) {
- if (!(getChunkSizeMB() > 0)) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "chunksize specified in " << chunkSizeMB.name()
- << " field must be greater than zero");
+ {
+ BSONElement settingsMigrationWriteConcernElem;
+ Status status = bsonExtractTypedField(
+ source, migrationWriteConcern.name(), Object, &settingsMigrationWriteConcernElem);
+ if (status == ErrorCodes::TypeMismatch) {
+ bool settingsSecondaryThrottle;
+ status = bsonExtractBooleanFieldWithDefault(
+ source, deprecated_secondaryThrottle.name(), true, &settingsSecondaryThrottle);
+ if (!status.isOK())
+ return status;
+ settings._secondaryThrottle = settingsSecondaryThrottle;
+ } else if (status != ErrorCodes::NoSuchKey) {
+ if (!status.isOK())
+ return status;
+ settings._migrationWriteConcern = WriteConcernOptions();
+ status =
+ settings._migrationWriteConcern->parse(settingsMigrationWriteConcernElem.Obj());
+ if (!status.isOK())
+ return status;
}
}
- else if (_key == BalancerDocKey) {
- if (_secondaryThrottle.is_initialized() &&
- _migrationWriteConcern.is_initialized()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "cannot have both secondary throttle and "
- << "migration write concern set at the same time");
+
+ {
+ bool settingsWaitForDelete;
+ Status status =
+ bsonExtractBooleanField(source, waitForDelete.name(), &settingsWaitForDelete);
+ if (status != ErrorCodes::NoSuchKey) {
+ if (!status.isOK())
+ return status;
+ settings._waitForDelete = settingsWaitForDelete;
}
}
- else {
- return Status(ErrorCodes::UnsupportedFormat,
- str::stream() << "unsupported key in " << key.name() << " field");
- }
-
- return Status::OK();
}
- BSONObj SettingsType::toBSON() const {
- BSONObjBuilder builder;
+ return settings;
+}
- if (_key) builder.append(key(), getKey());
- if (_chunkSizeMB) builder.append(chunkSizeMB(), getChunkSizeMB());
- if (_balancerStopped) builder.append(balancerStopped(), getBalancerStopped());
- if (_secondaryThrottle) {
- builder.append(deprecated_secondaryThrottle(), getSecondaryThrottle());
- }
- if (_migrationWriteConcern) {
- builder.append(migrationWriteConcern(), getMigrationWriteConcern().toBSON());
- }
- if (_waitForDelete) builder.append(waitForDelete(), getWaitForDelete());
-
- return builder.obj();
+Status SettingsType::validate() const {
+ if (!_key.is_initialized() || _key->empty()) {
+ return Status(ErrorCodes::NoSuchKey, str::stream() << "missing " << key.name() << " field");
}
- std::string SettingsType::toString() const {
- return toBSON().toString();
- }
-
- std::unique_ptr<WriteConcernOptions> SettingsType::getWriteConcern() const {
- dassert(_key.is_initialized());
- dassert(_key == BalancerDocKey);
-
- if (isSecondaryThrottleSet() && !getSecondaryThrottle()) {
- return stdx::make_unique<WriteConcernOptions>(1, WriteConcernOptions::NONE, 0);
- }
- else if (!isMigrationWriteConcernSet()) {
- // Default setting.
- return nullptr;
- }
- else {
- return stdx::make_unique<WriteConcernOptions>(getMigrationWriteConcern());
- }
- }
-
- StatusWith<BoostTimePair> SettingsType::_parseBalancingWindow(const BSONObj& balancingWindowObj) {
- if (balancingWindowObj.isEmpty()) {
+ if (_key == ChunkSizeDocKey) {
+ if (!(getChunkSizeMB() > 0)) {
return Status(ErrorCodes::BadValue,
- "'activeWindow' can't be empty");
+ str::stream() << "chunksize specified in " << chunkSizeMB.name()
+ << " field must be greater than zero");
}
-
- // check if both 'start' and 'stop' are present
- std::string start = balancingWindowObj.getField("start").str();
- std::string stop = balancingWindowObj.getField("stop").str();
- if (start.empty() || stop.empty()) {
+ } else if (_key == BalancerDocKey) {
+ if (_secondaryThrottle.is_initialized() && _migrationWriteConcern.is_initialized()) {
return Status(ErrorCodes::BadValue,
- str::stream() << "must specify both start and end of balancing window: "
- << balancingWindowObj);
+ str::stream() << "cannot have both secondary throttle and "
+ << "migration write concern set at the same time");
}
-
- // check that both 'start' and 'stop' are valid time-of-day
- boost::posix_time::ptime startTime, stopTime;
- if (!toPointInTime(start, &startTime) || !toPointInTime(stop, &stopTime)) {
- return Status(ErrorCodes::BadValue,
- str::stream() << balancerActiveWindow.name() << " format is "
- << " { start: \"hh:mm\" , stop: \"hh:mm\" }");
- }
-
- return std::make_pair(startTime, stopTime);
+ } else {
+ return Status(ErrorCodes::UnsupportedFormat,
+ str::stream() << "unsupported key in " << key.name() << " field");
}
- bool SettingsType::inBalancingWindow(const boost::posix_time::ptime& now) const {
- if (!_balancerActiveWindow.is_initialized()) {
- return true;
- }
- const boost::posix_time::ptime& startTime = _balancerActiveWindow->first;
- const boost::posix_time::ptime& stopTime = _balancerActiveWindow->second;
-
- LOG(1).stream() << "inBalancingWindow: "
- << " now: " << now
- << " startTime: " << startTime
- << " stopTime: " << stopTime;
-
- // allow balancing if during the activeWindow
- // note that a window may be open during the night
- if (stopTime > startTime) {
- if ((now >= startTime) && (now <= stopTime)) {
- return true;
- }
- }
- else if (startTime > stopTime) {
- if ((now >= startTime) || (now <= stopTime)) {
- return true;
- }
- }
+ return Status::OK();
+}
- return false;
- }
+BSONObj SettingsType::toBSON() const {
+ BSONObjBuilder builder;
- void SettingsType::setKey(const std::string& key) {
- invariant(!key.empty());
- _key = key;
+ if (_key)
+ builder.append(key(), getKey());
+ if (_chunkSizeMB)
+ builder.append(chunkSizeMB(), getChunkSizeMB());
+ if (_balancerStopped)
+ builder.append(balancerStopped(), getBalancerStopped());
+ if (_secondaryThrottle) {
+ builder.append(deprecated_secondaryThrottle(), getSecondaryThrottle());
}
-
- void SettingsType::setChunkSizeMB(const long long chunkSizeMB) {
- invariant(_key == ChunkSizeDocKey);
- invariant(chunkSizeMB > 0);
- _chunkSizeMB = chunkSizeMB;
+ if (_migrationWriteConcern) {
+ builder.append(migrationWriteConcern(), getMigrationWriteConcern().toBSON());
+ }
+ if (_waitForDelete)
+ builder.append(waitForDelete(), getWaitForDelete());
+
+ return builder.obj();
+}
+
+std::string SettingsType::toString() const {
+ return toBSON().toString();
+}
+
+std::unique_ptr<WriteConcernOptions> SettingsType::getWriteConcern() const {
+ dassert(_key.is_initialized());
+ dassert(_key == BalancerDocKey);
+
+ if (isSecondaryThrottleSet() && !getSecondaryThrottle()) {
+ return stdx::make_unique<WriteConcernOptions>(1, WriteConcernOptions::NONE, 0);
+ } else if (!isMigrationWriteConcernSet()) {
+ // Default setting.
+ return nullptr;
+ } else {
+ return stdx::make_unique<WriteConcernOptions>(getMigrationWriteConcern());
}
+}
- void SettingsType::setBalancerStopped(const bool balancerStopped) {
- invariant(_key == BalancerDocKey);
- _balancerStopped = balancerStopped;
+StatusWith<BoostTimePair> SettingsType::_parseBalancingWindow(const BSONObj& balancingWindowObj) {
+ if (balancingWindowObj.isEmpty()) {
+ return Status(ErrorCodes::BadValue, "'activeWindow' can't be empty");
}
- void SettingsType::setBalancerActiveWindow(const BSONObj& balancerActiveWindow) {
- invariant(_key == BalancerDocKey);
- StatusWith<BoostTimePair> timePairResult = _parseBalancingWindow(balancerActiveWindow);
- invariant(timePairResult.isOK());
- _balancerActiveWindow = timePairResult.getValue();
+ // check if both 'start' and 'stop' are present
+ std::string start = balancingWindowObj.getField("start").str();
+ std::string stop = balancingWindowObj.getField("stop").str();
+ if (start.empty() || stop.empty()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "must specify both start and end of balancing window: "
+ << balancingWindowObj);
}
- void SettingsType::setSecondaryThrottle(const bool secondaryThrottle) {
- invariant(_key == BalancerDocKey);
- _secondaryThrottle = secondaryThrottle;
+ // check that both 'start' and 'stop' are valid time-of-day
+ boost::posix_time::ptime startTime, stopTime;
+ if (!toPointInTime(start, &startTime) || !toPointInTime(stop, &stopTime)) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << balancerActiveWindow.name() << " format is "
+ << " { start: \"hh:mm\" , stop: \"hh:mm\" }");
}
- void SettingsType::setMigrationWriteConcern(const BSONObj& migrationWCBSONObj) {
- invariant(_key == BalancerDocKey);
- invariant(!migrationWCBSONObj.isEmpty());
- Status status = _migrationWriteConcern->parse(migrationWCBSONObj);
- invariant(status.isOK());
+ return std::make_pair(startTime, stopTime);
+}
+
+bool SettingsType::inBalancingWindow(const boost::posix_time::ptime& now) const {
+ if (!_balancerActiveWindow.is_initialized()) {
+ return true;
}
+ const boost::posix_time::ptime& startTime = _balancerActiveWindow->first;
+ const boost::posix_time::ptime& stopTime = _balancerActiveWindow->second;
+
+ LOG(1).stream() << "inBalancingWindow: "
+ << " now: " << now << " startTime: " << startTime << " stopTime: " << stopTime;
- void SettingsType::setWaitForDelete(const bool waitForDelete) {
- invariant(_key == BalancerDocKey);
- _waitForDelete = waitForDelete;
+ // allow balancing if during the activeWindow
+ // note that a window may be open during the night
+ if (stopTime > startTime) {
+ if ((now >= startTime) && (now <= stopTime)) {
+ return true;
+ }
+ } else if (startTime > stopTime) {
+ if ((now >= startTime) || (now <= stopTime)) {
+ return true;
+ }
}
-} // namespace mongo
+ return false;
+}
+
+void SettingsType::setKey(const std::string& key) {
+ invariant(!key.empty());
+ _key = key;
+}
+
+void SettingsType::setChunkSizeMB(const long long chunkSizeMB) {
+ invariant(_key == ChunkSizeDocKey);
+ invariant(chunkSizeMB > 0);
+ _chunkSizeMB = chunkSizeMB;
+}
+
+void SettingsType::setBalancerStopped(const bool balancerStopped) {
+ invariant(_key == BalancerDocKey);
+ _balancerStopped = balancerStopped;
+}
+
+void SettingsType::setBalancerActiveWindow(const BSONObj& balancerActiveWindow) {
+ invariant(_key == BalancerDocKey);
+ StatusWith<BoostTimePair> timePairResult = _parseBalancingWindow(balancerActiveWindow);
+ invariant(timePairResult.isOK());
+ _balancerActiveWindow = timePairResult.getValue();
+}
+
+void SettingsType::setSecondaryThrottle(const bool secondaryThrottle) {
+ invariant(_key == BalancerDocKey);
+ _secondaryThrottle = secondaryThrottle;
+}
+
+void SettingsType::setMigrationWriteConcern(const BSONObj& migrationWCBSONObj) {
+ invariant(_key == BalancerDocKey);
+ invariant(!migrationWCBSONObj.isEmpty());
+ Status status = _migrationWriteConcern->parse(migrationWCBSONObj);
+ invariant(status.isOK());
+}
+
+void SettingsType::setWaitForDelete(const bool waitForDelete) {
+ invariant(_key == BalancerDocKey);
+ _waitForDelete = waitForDelete;
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_settings.h b/src/mongo/s/catalog/type_settings.h
index 098d7466997..b6954f9d5e4 100644
--- a/src/mongo/s/catalog/type_settings.h
+++ b/src/mongo/s/catalog/type_settings.h
@@ -38,153 +38,178 @@
namespace mongo {
- struct WriteConcernOptions;
- class BSONObj;
- template<typename T> class StatusWith;
+struct WriteConcernOptions;
+class BSONObj;
+template <typename T>
+class StatusWith;
- using BoostTimePair = std::pair<boost::posix_time::ptime, boost::posix_time::ptime>;
+using BoostTimePair = std::pair<boost::posix_time::ptime, boost::posix_time::ptime>;
+/**
+ * This class represents the layout and contents of documents contained in the
+ * config.settings collection. All manipulation of documents coming from that
+ * collection should be done with this class.
+ *
+ * Usage Example:
+ *
+ * // Contact the config. 'conn' has been obtained before.
+ * DBClientBase* conn;
+ * BSONObj query = QUERY(SettingsType::exampleField(SettingsType::ExampleFieldName));
+ * exampleDoc = conn->findOne(SettingsType::ConfigNS, query);
+ *
+ * // Process the response.
+ * StatusWith<SettingsType> exampleResult = SettingsType::fromBSON(exampleDoc);
+ * if (!exampleResult.isOK()) {
+ * if (exampleResult.getStatus() == ErrorCodes::NoSuchKey) {
+ * // exampleDoc has no key set or is empty
+ * }
+ * // handle error -- exampleResult.getStatus()
+ * }
+ * SettingsType exampleType = exampleResult.getValue();
+ */
+class SettingsType {
+public:
+ // Name of the settings collection in the config server.
+ static const std::string ConfigNS;
+
+ static const std::string BalancerDocKey;
+ static const std::string ChunkSizeDocKey;
+
+ // Field names and types in the settings collection type.
+ static const BSONField<std::string> key;
+ static const BSONField<long long> chunkSizeMB;
+ static const BSONField<bool> balancerStopped;
+ static const BSONField<BSONObj> balancerActiveWindow;
+ static const BSONField<bool> deprecated_secondaryThrottle;
+ static const BSONField<BSONObj> migrationWriteConcern;
+ static const BSONField<bool> waitForDelete;
+
+ /**
+ * Returns OK if all mandatory fields have been set and their corresponding
+ * values are valid.
+ */
+ Status validate() const;
+
+ /**
+ * Returns the BSON representation of the entry.
+ */
+ BSONObj toBSON() const;
+
+ /**
+ * Constructs a new ShardType object from BSON.
+ * Also does validation of the contents.
+ */
+ static StatusWith<SettingsType> fromBSON(const BSONObj& source);
+
+ /**
+ * Returns a std::string representation of the current internal state.
+ */
+ std::string toString() const;
+
+ /**
+ * Returns the write concern to use for balancing.
+ * Uses *deprecated* secondary throttle if migration write concern is not set.
+ */
+ std::unique_ptr<WriteConcernOptions> getWriteConcern() const;
+
+ /**
+ * Returns true if either 'now' is in the balancing window or
+ * if no balancing window exists.
+ */
+ bool inBalancingWindow(const boost::posix_time::ptime& now) const;
+
+ bool isKeySet() const {
+ return _key.is_initialized();
+ }
+ const std::string& getKey() const {
+ return _key.get();
+ }
+ void setKey(const std::string& key);
+
+ bool isChunkSizeMBSet() const {
+ return _chunkSizeMB.is_initialized();
+ }
+ long long getChunkSizeMB() const {
+ return _chunkSizeMB.get();
+ }
+ void setChunkSizeMB(const long long chunkSizeMB);
+
+ bool isBalancerStoppedSet() const {
+ return _balancerStopped.is_initialized();
+ }
+ bool getBalancerStopped() const {
+ return _balancerStopped.get();
+ }
+ void setBalancerStopped(const bool balancerStopped);
+
+ bool isBalancerActiveWindowSet() const {
+ return _balancerActiveWindow.is_initialized();
+ }
+ const BoostTimePair& getBalancerActiveWindow() const {
+ return _balancerActiveWindow.get();
+ }
+ void setBalancerActiveWindow(const BSONObj& balancerActiveWindow);
+
+ bool isSecondaryThrottleSet() const {
+ return _secondaryThrottle.is_initialized();
+ }
+ bool getSecondaryThrottle() const {
+ return _secondaryThrottle.get();
+ }
+ void setSecondaryThrottle(const bool secondaryThrottle);
+
+ bool isMigrationWriteConcernSet() const {
+ return _migrationWriteConcern.is_initialized();
+ }
+ const WriteConcernOptions& getMigrationWriteConcern() const {
+ return _migrationWriteConcern.get();
+ }
+ void setMigrationWriteConcern(const BSONObj& migrationWriteConcern);
+
+ bool isWaitForDeleteSet() const {
+ return _waitForDelete.is_initialized();
+ }
+ bool getWaitForDelete() const {
+ return _waitForDelete.get();
+ }
+ void setWaitForDelete(const bool waitForDelete);
+
+private:
/**
- * This class represents the layout and contents of documents contained in the
- * config.settings collection. All manipulation of documents coming from that
- * collection should be done with this class.
- *
- * Usage Example:
- *
- * // Contact the config. 'conn' has been obtained before.
- * DBClientBase* conn;
- * BSONObj query = QUERY(SettingsType::exampleField(SettingsType::ExampleFieldName));
- * exampleDoc = conn->findOne(SettingsType::ConfigNS, query);
- *
- * // Process the response.
- * StatusWith<SettingsType> exampleResult = SettingsType::fromBSON(exampleDoc);
- * if (!exampleResult.isOK()) {
- * if (exampleResult.getStatus() == ErrorCodes::NoSuchKey) {
- * // exampleDoc has no key set or is empty
- * }
- * // handle error -- exampleResult.getStatus()
- * }
- * SettingsType exampleType = exampleResult.getValue();
+ * Used to parse balancing 'activeWindow'.
+ * See '_balancerActiveWindow' member variable doc comments below.
*/
- class SettingsType {
- public:
-
- // Name of the settings collection in the config server.
- static const std::string ConfigNS;
-
- static const std::string BalancerDocKey;
- static const std::string ChunkSizeDocKey;
-
- // Field names and types in the settings collection type.
- static const BSONField<std::string> key;
- static const BSONField<long long> chunkSizeMB;
- static const BSONField<bool> balancerStopped;
- static const BSONField<BSONObj> balancerActiveWindow;
- static const BSONField<bool> deprecated_secondaryThrottle;
- static const BSONField<BSONObj> migrationWriteConcern;
- static const BSONField<bool> waitForDelete;
-
- /**
- * Returns OK if all mandatory fields have been set and their corresponding
- * values are valid.
- */
- Status validate() const;
-
- /**
- * Returns the BSON representation of the entry.
- */
- BSONObj toBSON() const;
-
- /**
- * Constructs a new ShardType object from BSON.
- * Also does validation of the contents.
- */
- static StatusWith<SettingsType> fromBSON(const BSONObj& source);
-
- /**
- * Returns a std::string representation of the current internal state.
- */
- std::string toString() const;
-
- /**
- * Returns the write concern to use for balancing.
- * Uses *deprecated* secondary throttle if migration write concern is not set.
- */
- std::unique_ptr<WriteConcernOptions> getWriteConcern() const;
-
- /**
- * Returns true if either 'now' is in the balancing window or
- * if no balancing window exists.
- */
- bool inBalancingWindow(const boost::posix_time::ptime& now) const;
-
- bool isKeySet() const { return _key.is_initialized(); }
- const std::string& getKey() const { return _key.get(); }
- void setKey(const std::string& key);
-
- bool isChunkSizeMBSet() const { return _chunkSizeMB.is_initialized(); }
- long long getChunkSizeMB() const { return _chunkSizeMB.get(); }
- void setChunkSizeMB(const long long chunkSizeMB);
-
- bool isBalancerStoppedSet() const { return _balancerStopped.is_initialized(); }
- bool getBalancerStopped() const { return _balancerStopped.get(); }
- void setBalancerStopped(const bool balancerStopped);
-
- bool isBalancerActiveWindowSet() const { return _balancerActiveWindow.is_initialized(); }
- const BoostTimePair& getBalancerActiveWindow() const { return _balancerActiveWindow.get(); }
- void setBalancerActiveWindow(const BSONObj& balancerActiveWindow);
-
- bool isSecondaryThrottleSet() const { return _secondaryThrottle.is_initialized(); }
- bool getSecondaryThrottle() const { return _secondaryThrottle.get(); }
- void setSecondaryThrottle(const bool secondaryThrottle);
-
- bool isMigrationWriteConcernSet() const { return _migrationWriteConcern.is_initialized(); }
- const WriteConcernOptions& getMigrationWriteConcern() const {
- return _migrationWriteConcern.get();
- }
- void setMigrationWriteConcern(const BSONObj& migrationWriteConcern);
-
- bool isWaitForDeleteSet() const { return _waitForDelete.is_initialized(); }
- bool getWaitForDelete() const { return _waitForDelete.get(); }
- void setWaitForDelete(const bool waitForDelete);
-
- private:
-
- /**
- * Used to parse balancing 'activeWindow'.
- * See '_balancerActiveWindow' member variable doc comments below.
- */
- StatusWith<BoostTimePair> _parseBalancingWindow(const BSONObj& balancingWindowObj);
-
- // Convention: (M)andatory, (O)ptional, (S)pecial rule.
-
- // (M) key determining the type of options to use
- boost::optional<std::string> _key;
-
- // (S) size of the chunks in our cluster in MB
- // Required if key is chunkSize
- boost::optional<long long> _chunkSizeMB;
-
- // (O) is balancer enabled or disabled (default)
- // Defaults to false.
- boost::optional<bool> _balancerStopped;
-
- // (O) if present, balancerActiveWindow is an interval during the day
- // when the balancer should be active.
- // Stored as (<start time>, <stop time>) pair. strftime format is %H:%M
- boost::optional<BoostTimePair> _balancerActiveWindow;
-
- // (O) only migrate chunks as fast as at least one secondary can keep up with
- boost::optional<bool> _secondaryThrottle;
-
- // (O) detailed write concern for *individual* writes during migration.
- // From side: deletes during cleanup.
- // To side: deletes to clear the incoming range, deletes to undo migration at abort,
- // and writes during cloning.
- boost::optional<WriteConcernOptions> _migrationWriteConcern;
-
- // (O) synchronous migration cleanup.
- boost::optional<bool> _waitForDelete;
- };
-
-} // namespace mongo
+ StatusWith<BoostTimePair> _parseBalancingWindow(const BSONObj& balancingWindowObj);
+
+ // Convention: (M)andatory, (O)ptional, (S)pecial rule.
+
+ // (M) key determining the type of options to use
+ boost::optional<std::string> _key;
+
+ // (S) size of the chunks in our cluster in MB
+ // Required if key is chunkSize
+ boost::optional<long long> _chunkSizeMB;
+
+ // (O) is balancer enabled or disabled (default)
+ // Defaults to false.
+ boost::optional<bool> _balancerStopped;
+
+ // (O) if present, balancerActiveWindow is an interval during the day
+ // when the balancer should be active.
+ // Stored as (<start time>, <stop time>) pair. strftime format is %H:%M
+ boost::optional<BoostTimePair> _balancerActiveWindow;
+
+ // (O) only migrate chunks as fast as at least one secondary can keep up with
+ boost::optional<bool> _secondaryThrottle;
+
+ // (O) detailed write concern for *individual* writes during migration.
+ // From side: deletes during cleanup.
+ // To side: deletes to clear the incoming range, deletes to undo migration at abort,
+ // and writes during cloning.
+ boost::optional<WriteConcernOptions> _migrationWriteConcern;
+
+ // (O) synchronous migration cleanup.
+ boost::optional<bool> _waitForDelete;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_settings_test.cpp b/src/mongo/s/catalog/type_settings_test.cpp
index 0fbe6fee3b6..b7ca2f0bd2f 100644
--- a/src/mongo/s/catalog/type_settings_test.cpp
+++ b/src/mongo/s/catalog/type_settings_test.cpp
@@ -36,178 +36,179 @@
namespace {
- using namespace mongo;
-
- TEST(SettingsType, MissingKey) {
- BSONObj objNoKey = BSONObj();
- StatusWith<SettingsType> result = SettingsType::fromBSON(objNoKey);
- ASSERT_FALSE(result.isOK());
- ASSERT_EQUALS(result.getStatus(), ErrorCodes::NoSuchKey);
- }
-
- TEST(SettingsType, ChunkSize) {
- BSONObj objChunkSizeZero = BSON(SettingsType::key(SettingsType::ChunkSizeDocKey) <<
- SettingsType::chunkSizeMB(0));
- StatusWith<SettingsType> result = SettingsType::fromBSON(objChunkSizeZero);
- ASSERT(result.isOK());
- SettingsType settings = result.getValue();
- ASSERT_EQUALS(settings.getChunkSizeMB(), 0);
- Status validationStatus = settings.validate();
- ASSERT_FALSE(validationStatus.isOK());
- ASSERT_EQUALS(validationStatus, ErrorCodes::BadValue);
- }
-
- TEST(SettingsType, UnsupportedSetting) {
- BSONObj objBadSetting = BSON(SettingsType::key("badsetting"));
- StatusWith<SettingsType> result = SettingsType::fromBSON(objBadSetting);
- ASSERT(result.isOK());
- SettingsType settings = result.getValue();
- Status validationStatus = settings.validate();
- ASSERT_FALSE(validationStatus.isOK());
- ASSERT_EQUALS(validationStatus, ErrorCodes::UnsupportedFormat);
- }
-
- TEST(SettingsType, InvalidBalancerWindow) {
- BSONObj objBalancerBadKeys = BSON(SettingsType::key(SettingsType::BalancerDocKey) <<
- SettingsType::balancerActiveWindow(BSON("begin" <<
- "23:00" <<
- "end" <<
- "6:00" )));
- StatusWith<SettingsType> result = SettingsType::fromBSON(objBalancerBadKeys);
- ASSERT_FALSE(result.isOK());
-
- BSONObj objBalancerBadTimes = BSON(SettingsType::key(SettingsType::BalancerDocKey) <<
- SettingsType::balancerActiveWindow(BSON("start" <<
- "23" <<
- "stop" <<
- "6" )));
- result = SettingsType::fromBSON(objBalancerBadTimes);
- ASSERT_FALSE(result.isOK());
- }
-
- TEST(SettingsType, ValidValues) {
- BSONObj objChunkSize = BSON(SettingsType::key(SettingsType::ChunkSizeDocKey) <<
- SettingsType::chunkSizeMB(1));
- StatusWith<SettingsType> result = SettingsType::fromBSON(objChunkSize);
- SettingsType settings = result.getValue();
- ASSERT(result.isOK());
- Status validationStatus = settings.validate();
- ASSERT(validationStatus.isOK());
- ASSERT_EQUALS(settings.getKey(), SettingsType::ChunkSizeDocKey);
- ASSERT_EQUALS(settings.getChunkSizeMB(), 1);
-
- BSONObj objBalancer = BSON(SettingsType::key(SettingsType::BalancerDocKey) <<
- SettingsType::balancerStopped(true) <<
- SettingsType::balancerActiveWindow(BSON("start" << "23:00" <<
- "stop" << "6:00" )) <<
- SettingsType::migrationWriteConcern(BSON("w" << 2)));
- result = SettingsType::fromBSON(objBalancer);
- settings = result.getValue();
- ASSERT(result.isOK());
- validationStatus = settings.validate();
- ASSERT(validationStatus.isOK());
- ASSERT_EQUALS(settings.getKey(), SettingsType::BalancerDocKey);
- ASSERT_EQUALS(settings.getBalancerStopped(), true);
-
- WriteConcernOptions wc;
- wc.parse(BSON("w" << 2));
- ASSERT_EQUALS(settings.getMigrationWriteConcern().toBSON(), wc.toBSON());
- }
-
- TEST(SettingsType, ValidWithDeprecatedThrottle) {
- BSONObj objChunkSize = BSON(SettingsType::key(SettingsType::ChunkSizeDocKey) <<
- SettingsType::chunkSizeMB(1));
- StatusWith<SettingsType> result = SettingsType::fromBSON(objChunkSize);
- ASSERT(result.isOK());
- SettingsType settings = result.getValue();
- ASSERT_EQUALS(settings.getKey(), SettingsType::ChunkSizeDocKey);
- ASSERT_EQUALS(settings.getChunkSizeMB(), 1);
-
- BSONObj objBalancer = BSON(SettingsType::key(SettingsType::BalancerDocKey) <<
- SettingsType::deprecated_secondaryThrottle(true));
- result = SettingsType::fromBSON(objBalancer);
- ASSERT_EQUALS(result.getStatus(), Status::OK());
- settings = result.getValue();
- ASSERT_EQUALS(settings.getKey(), SettingsType::BalancerDocKey);
- ASSERT(settings.getSecondaryThrottle());
- }
-
- TEST(SettingsType, BadType) {
- BSONObj badTypeObj = BSON(SettingsType::key() << 0);
- StatusWith<SettingsType> result = SettingsType::fromBSON(badTypeObj);
- ASSERT_FALSE(result.isOK());
- }
-
- TEST(SettingsType, BalancingWindow) {
- // T0 < T1 < now < T2 < T3 and Error
- const std::string T0 = "9:00";
- const std::string T1 = "11:00";
- boost::posix_time::ptime now(currentDate(),
- boost::posix_time::hours(13) +
- boost::posix_time::minutes(48));
- const std::string T2 = "17:00";
- const std::string T3 = "21:30";
- const std::string E = "28:35";
-
- // closed in the past
- BSONObj w1 = BSON(SettingsType::key(SettingsType::BalancerDocKey) <<
- SettingsType::balancerActiveWindow(BSON("start" << T0 << "stop" << T1)));
- StatusWith<SettingsType> result = SettingsType::fromBSON(w1);
- ASSERT(result.isOK());
- ASSERT_FALSE(result.getValue().inBalancingWindow(now));
-
- // not opened until the future
- BSONObj w2 = BSON(SettingsType::key(SettingsType::BalancerDocKey) <<
- SettingsType::balancerActiveWindow(BSON("start" << T2 << "stop" << T3)));
- result = SettingsType::fromBSON(w2);
- ASSERT(result.isOK());
- ASSERT_FALSE(result.getValue().inBalancingWindow(now));
-
- // open now
- BSONObj w3 = BSON(SettingsType::key(SettingsType::BalancerDocKey) <<
- SettingsType::balancerActiveWindow(BSON("start" << T1 << "stop" << T2)));
- result = SettingsType::fromBSON(w3);
- ASSERT(result.isOK());
- ASSERT(result.getValue().inBalancingWindow(now));
-
- // open since last day
- BSONObj w4 = BSON(SettingsType::key(SettingsType::BalancerDocKey) <<
- SettingsType::balancerActiveWindow(BSON("start" << T3 << "stop" << T2)));
- result = SettingsType::fromBSON(w4);
- ASSERT(result.isOK());
- ASSERT(result.getValue().inBalancingWindow(now));
-
- // bad input should not stop the balancer
-
- // empty window
- BSONObj w5 = BSON(SettingsType::key(SettingsType::BalancerDocKey));
- result = SettingsType::fromBSON(w5);
- ASSERT(result.isOK());
- ASSERT(result.getValue().inBalancingWindow(now));
-
- // missing stop
- BSONObj w6 = BSON(SettingsType::key(SettingsType::BalancerDocKey) <<
- SettingsType::balancerActiveWindow(BSON("start" << 1)));
- result = SettingsType::fromBSON(w6);
- ASSERT_FALSE(result.isOK());
-
- // missing start
- BSONObj w7 = BSON(SettingsType::key(SettingsType::BalancerDocKey) <<
- SettingsType::balancerActiveWindow(BSON("stop" << 1)));
- result = SettingsType::fromBSON(w7);
- ASSERT_FALSE(result.isOK());
-
- // active window marker missing
- BSONObj w8 = BSON(SettingsType::key(SettingsType::BalancerDocKey) <<
- "wrongMarker" << BSON("start" << 1 << "stop" << 1));
- result = SettingsType::fromBSON(w8);
- ASSERT(result.isOK());
- ASSERT(result.getValue().inBalancingWindow(now));
-
- // garbage in window
- BSONObj w9 = BSON(SettingsType::balancerActiveWindow(BSON("start" << T3 << "stop" << E)));
- result = SettingsType::fromBSON(w9);
- ASSERT_FALSE(result.isOK());
- }
-
-} // unnamed namespace
+using namespace mongo;
+
+TEST(SettingsType, MissingKey) {
+ BSONObj objNoKey = BSONObj();
+ StatusWith<SettingsType> result = SettingsType::fromBSON(objNoKey);
+ ASSERT_FALSE(result.isOK());
+ ASSERT_EQUALS(result.getStatus(), ErrorCodes::NoSuchKey);
+}
+
+TEST(SettingsType, ChunkSize) {
+ BSONObj objChunkSizeZero =
+ BSON(SettingsType::key(SettingsType::ChunkSizeDocKey) << SettingsType::chunkSizeMB(0));
+ StatusWith<SettingsType> result = SettingsType::fromBSON(objChunkSizeZero);
+ ASSERT(result.isOK());
+ SettingsType settings = result.getValue();
+ ASSERT_EQUALS(settings.getChunkSizeMB(), 0);
+ Status validationStatus = settings.validate();
+ ASSERT_FALSE(validationStatus.isOK());
+ ASSERT_EQUALS(validationStatus, ErrorCodes::BadValue);
+}
+
+TEST(SettingsType, UnsupportedSetting) {
+ BSONObj objBadSetting = BSON(SettingsType::key("badsetting"));
+ StatusWith<SettingsType> result = SettingsType::fromBSON(objBadSetting);
+ ASSERT(result.isOK());
+ SettingsType settings = result.getValue();
+ Status validationStatus = settings.validate();
+ ASSERT_FALSE(validationStatus.isOK());
+ ASSERT_EQUALS(validationStatus, ErrorCodes::UnsupportedFormat);
+}
+
+TEST(SettingsType, InvalidBalancerWindow) {
+ BSONObj objBalancerBadKeys = BSON(SettingsType::key(SettingsType::BalancerDocKey)
+ << SettingsType::balancerActiveWindow(BSON("begin"
+ << "23:00"
+ << "end"
+ << "6:00")));
+ StatusWith<SettingsType> result = SettingsType::fromBSON(objBalancerBadKeys);
+ ASSERT_FALSE(result.isOK());
+
+ BSONObj objBalancerBadTimes = BSON(SettingsType::key(SettingsType::BalancerDocKey)
+ << SettingsType::balancerActiveWindow(BSON("start"
+ << "23"
+ << "stop"
+ << "6")));
+ result = SettingsType::fromBSON(objBalancerBadTimes);
+ ASSERT_FALSE(result.isOK());
+}
+
+TEST(SettingsType, ValidValues) {
+ BSONObj objChunkSize =
+ BSON(SettingsType::key(SettingsType::ChunkSizeDocKey) << SettingsType::chunkSizeMB(1));
+ StatusWith<SettingsType> result = SettingsType::fromBSON(objChunkSize);
+ SettingsType settings = result.getValue();
+ ASSERT(result.isOK());
+ Status validationStatus = settings.validate();
+ ASSERT(validationStatus.isOK());
+ ASSERT_EQUALS(settings.getKey(), SettingsType::ChunkSizeDocKey);
+ ASSERT_EQUALS(settings.getChunkSizeMB(), 1);
+
+ BSONObj objBalancer = BSON(SettingsType::key(SettingsType::BalancerDocKey)
+ << SettingsType::balancerStopped(true)
+ << SettingsType::balancerActiveWindow(BSON("start"
+ << "23:00"
+ << "stop"
+ << "6:00"))
+ << SettingsType::migrationWriteConcern(BSON("w" << 2)));
+ result = SettingsType::fromBSON(objBalancer);
+ settings = result.getValue();
+ ASSERT(result.isOK());
+ validationStatus = settings.validate();
+ ASSERT(validationStatus.isOK());
+ ASSERT_EQUALS(settings.getKey(), SettingsType::BalancerDocKey);
+ ASSERT_EQUALS(settings.getBalancerStopped(), true);
+
+ WriteConcernOptions wc;
+ wc.parse(BSON("w" << 2));
+ ASSERT_EQUALS(settings.getMigrationWriteConcern().toBSON(), wc.toBSON());
+}
+
+TEST(SettingsType, ValidWithDeprecatedThrottle) {
+ BSONObj objChunkSize =
+ BSON(SettingsType::key(SettingsType::ChunkSizeDocKey) << SettingsType::chunkSizeMB(1));
+ StatusWith<SettingsType> result = SettingsType::fromBSON(objChunkSize);
+ ASSERT(result.isOK());
+ SettingsType settings = result.getValue();
+ ASSERT_EQUALS(settings.getKey(), SettingsType::ChunkSizeDocKey);
+ ASSERT_EQUALS(settings.getChunkSizeMB(), 1);
+
+ BSONObj objBalancer = BSON(SettingsType::key(SettingsType::BalancerDocKey)
+ << SettingsType::deprecated_secondaryThrottle(true));
+ result = SettingsType::fromBSON(objBalancer);
+ ASSERT_EQUALS(result.getStatus(), Status::OK());
+ settings = result.getValue();
+ ASSERT_EQUALS(settings.getKey(), SettingsType::BalancerDocKey);
+ ASSERT(settings.getSecondaryThrottle());
+}
+
+TEST(SettingsType, BadType) {
+ BSONObj badTypeObj = BSON(SettingsType::key() << 0);
+ StatusWith<SettingsType> result = SettingsType::fromBSON(badTypeObj);
+ ASSERT_FALSE(result.isOK());
+}
+
+TEST(SettingsType, BalancingWindow) {
+ // T0 < T1 < now < T2 < T3 and Error
+ const std::string T0 = "9:00";
+ const std::string T1 = "11:00";
+ boost::posix_time::ptime now(currentDate(),
+ boost::posix_time::hours(13) + boost::posix_time::minutes(48));
+ const std::string T2 = "17:00";
+ const std::string T3 = "21:30";
+ const std::string E = "28:35";
+
+ // closed in the past
+ BSONObj w1 = BSON(SettingsType::key(SettingsType::BalancerDocKey)
+ << SettingsType::balancerActiveWindow(BSON("start" << T0 << "stop" << T1)));
+ StatusWith<SettingsType> result = SettingsType::fromBSON(w1);
+ ASSERT(result.isOK());
+ ASSERT_FALSE(result.getValue().inBalancingWindow(now));
+
+ // not opened until the future
+ BSONObj w2 = BSON(SettingsType::key(SettingsType::BalancerDocKey)
+ << SettingsType::balancerActiveWindow(BSON("start" << T2 << "stop" << T3)));
+ result = SettingsType::fromBSON(w2);
+ ASSERT(result.isOK());
+ ASSERT_FALSE(result.getValue().inBalancingWindow(now));
+
+ // open now
+ BSONObj w3 = BSON(SettingsType::key(SettingsType::BalancerDocKey)
+ << SettingsType::balancerActiveWindow(BSON("start" << T1 << "stop" << T2)));
+ result = SettingsType::fromBSON(w3);
+ ASSERT(result.isOK());
+ ASSERT(result.getValue().inBalancingWindow(now));
+
+ // open since last day
+ BSONObj w4 = BSON(SettingsType::key(SettingsType::BalancerDocKey)
+ << SettingsType::balancerActiveWindow(BSON("start" << T3 << "stop" << T2)));
+ result = SettingsType::fromBSON(w4);
+ ASSERT(result.isOK());
+ ASSERT(result.getValue().inBalancingWindow(now));
+
+ // bad input should not stop the balancer
+
+ // empty window
+ BSONObj w5 = BSON(SettingsType::key(SettingsType::BalancerDocKey));
+ result = SettingsType::fromBSON(w5);
+ ASSERT(result.isOK());
+ ASSERT(result.getValue().inBalancingWindow(now));
+
+ // missing stop
+ BSONObj w6 = BSON(SettingsType::key(SettingsType::BalancerDocKey)
+ << SettingsType::balancerActiveWindow(BSON("start" << 1)));
+ result = SettingsType::fromBSON(w6);
+ ASSERT_FALSE(result.isOK());
+
+ // missing start
+ BSONObj w7 = BSON(SettingsType::key(SettingsType::BalancerDocKey)
+ << SettingsType::balancerActiveWindow(BSON("stop" << 1)));
+ result = SettingsType::fromBSON(w7);
+ ASSERT_FALSE(result.isOK());
+
+ // active window marker missing
+ BSONObj w8 = BSON(SettingsType::key(SettingsType::BalancerDocKey)
+ << "wrongMarker" << BSON("start" << 1 << "stop" << 1));
+ result = SettingsType::fromBSON(w8);
+ ASSERT(result.isOK());
+ ASSERT(result.getValue().inBalancingWindow(now));
+
+ // garbage in window
+ BSONObj w9 = BSON(SettingsType::balancerActiveWindow(BSON("start" << T3 << "stop" << E)));
+ result = SettingsType::fromBSON(w9);
+ ASSERT_FALSE(result.isOK());
+}
+
+} // unnamed namespace
diff --git a/src/mongo/s/catalog/type_shard.cpp b/src/mongo/s/catalog/type_shard.cpp
index 8fafe50c549..898d784cf75 100644
--- a/src/mongo/s/catalog/type_shard.cpp
+++ b/src/mongo/s/catalog/type_shard.cpp
@@ -39,143 +39,143 @@
namespace mongo {
- const std::string ShardType::ConfigNS = "config.shards";
+const std::string ShardType::ConfigNS = "config.shards";
- const BSONField<std::string> ShardType::name("_id");
- const BSONField<std::string> ShardType::host("host");
- const BSONField<bool> ShardType::draining("draining");
- const BSONField<long long> ShardType::maxSizeMB("maxSize");
- const BSONField<BSONArray> ShardType::tags("tags");
+const BSONField<std::string> ShardType::name("_id");
+const BSONField<std::string> ShardType::host("host");
+const BSONField<bool> ShardType::draining("draining");
+const BSONField<long long> ShardType::maxSizeMB("maxSize");
+const BSONField<BSONArray> ShardType::tags("tags");
- StatusWith<ShardType> ShardType::fromBSON(const BSONObj& source) {
- ShardType shard;
+StatusWith<ShardType> ShardType::fromBSON(const BSONObj& source) {
+ ShardType shard;
- {
- std::string shardName;
- Status status = bsonExtractStringField(source, name.name(), &shardName);
- if (!status.isOK()) return status;
- shard._name = shardName;
- }
-
- {
- std::string shardHost;
- Status status = bsonExtractStringField(source, host.name(), &shardHost);
- if (!status.isOK()) return status;
- shard._host = shardHost;
- }
-
- {
- bool isShardDraining;
- Status status = bsonExtractBooleanField(source,
- draining.name(),
- &isShardDraining);
- if (status.isOK()) {
- shard._draining = isShardDraining;
- }
- else if (status == ErrorCodes::NoSuchKey) {
- // draining field can be mssing in which case it is presumed false
- }
- else {
- return status;
- }
- }
-
- {
- long long shardMaxSizeMB;
- // maxSizeMB == 0 means there's no limitation to space usage.
- Status status = bsonExtractIntegerField(source,
- maxSizeMB.name(),
- &shardMaxSizeMB);
- if (status.isOK()) {
- shard._maxSizeMB = shardMaxSizeMB;
- }
- else if (status == ErrorCodes::NoSuchKey) {
- // maxSizeMB field can be missing in which case it is presumed false
- }
- else {
- return status;
- }
- }
-
- if (source.hasField(tags.name())) {
- shard._tags = std::vector<std::string>();
- BSONElement tagsElement;
- Status status = bsonExtractTypedField(source, tags.name(), Array, &tagsElement);
- if (!status.isOK()) return status;
-
- BSONObjIterator it(tagsElement.Obj());
- while (it.more()) {
- BSONElement tagElement = it.next();
- if (tagElement.type() != String) {
- return Status(ErrorCodes::TypeMismatch,
- str::stream() << "Elements in \"" << tags.name()
- << "\" array must be strings but found "
- << typeName(tagElement.type()));
- }
- shard._tags->push_back(tagElement.String());
- }
- }
-
- return shard;
+ {
+ std::string shardName;
+ Status status = bsonExtractStringField(source, name.name(), &shardName);
+ if (!status.isOK())
+ return status;
+ shard._name = shardName;
}
- Status ShardType::validate() const {
- if (!_name.is_initialized() || _name->empty()) {
- return Status(ErrorCodes::NoSuchKey,
- str::stream() << "missing " << name.name() << " field");
- }
-
- if (!_host.is_initialized() || _host->empty()) {
- return Status(ErrorCodes::NoSuchKey,
- str::stream() << "missing " << host.name() << " field");
- }
-
- if (_maxSizeMB.is_initialized() && getMaxSizeMB() < 0) {
- return Status(ErrorCodes::BadValue, str::stream() << "maxSize can't be negative");
- }
-
- return Status::OK();
+ {
+ std::string shardHost;
+ Status status = bsonExtractStringField(source, host.name(), &shardHost);
+ if (!status.isOK())
+ return status;
+ shard._host = shardHost;
}
- BSONObj ShardType::toBSON() const {
- BSONObjBuilder builder;
-
- if (_name) builder.append(name(), getName());
- if (_host) builder.append(host(), getHost());
- if (_draining) builder.append(draining(), getDraining());
- if (_maxSizeMB) builder.append(maxSizeMB(), getMaxSizeMB());
- if (_tags) builder.append(tags(), getTags());
-
- return builder.obj();
+ {
+ bool isShardDraining;
+ Status status = bsonExtractBooleanField(source, draining.name(), &isShardDraining);
+ if (status.isOK()) {
+ shard._draining = isShardDraining;
+ } else if (status == ErrorCodes::NoSuchKey) {
+ // draining field can be mssing in which case it is presumed false
+ } else {
+ return status;
+ }
}
- std::string ShardType::toString() const {
- return toBSON().toString();
+ {
+ long long shardMaxSizeMB;
+ // maxSizeMB == 0 means there's no limitation to space usage.
+ Status status = bsonExtractIntegerField(source, maxSizeMB.name(), &shardMaxSizeMB);
+ if (status.isOK()) {
+ shard._maxSizeMB = shardMaxSizeMB;
+ } else if (status == ErrorCodes::NoSuchKey) {
+ // maxSizeMB field can be missing in which case it is presumed false
+ } else {
+ return status;
+ }
}
- void ShardType::setName(const std::string& name) {
- invariant(!name.empty());
- _name = name;
+ if (source.hasField(tags.name())) {
+ shard._tags = std::vector<std::string>();
+ BSONElement tagsElement;
+ Status status = bsonExtractTypedField(source, tags.name(), Array, &tagsElement);
+ if (!status.isOK())
+ return status;
+
+ BSONObjIterator it(tagsElement.Obj());
+ while (it.more()) {
+ BSONElement tagElement = it.next();
+ if (tagElement.type() != String) {
+ return Status(ErrorCodes::TypeMismatch,
+ str::stream() << "Elements in \"" << tags.name()
+ << "\" array must be strings but found "
+ << typeName(tagElement.type()));
+ }
+ shard._tags->push_back(tagElement.String());
+ }
}
- void ShardType::setHost(const std::string& host) {
- invariant(!host.empty());
- _host = host;
- }
+ return shard;
+}
- void ShardType::setDraining(const bool isDraining) {
- _draining = isDraining;
+Status ShardType::validate() const {
+ if (!_name.is_initialized() || _name->empty()) {
+ return Status(ErrorCodes::NoSuchKey,
+ str::stream() << "missing " << name.name() << " field");
}
- void ShardType::setMaxSizeMB(const long long maxSizeMB) {
- invariant(maxSizeMB >= 0);
- _maxSizeMB = maxSizeMB;
+ if (!_host.is_initialized() || _host->empty()) {
+ return Status(ErrorCodes::NoSuchKey,
+ str::stream() << "missing " << host.name() << " field");
}
- void ShardType::setTags(const std::vector<std::string>& tags) {
- invariant(tags.size() > 0);
- _tags = tags;
+ if (_maxSizeMB.is_initialized() && getMaxSizeMB() < 0) {
+ return Status(ErrorCodes::BadValue, str::stream() << "maxSize can't be negative");
}
-} // namespace mongo
+ return Status::OK();
+}
+
+BSONObj ShardType::toBSON() const {
+ BSONObjBuilder builder;
+
+ if (_name)
+ builder.append(name(), getName());
+ if (_host)
+ builder.append(host(), getHost());
+ if (_draining)
+ builder.append(draining(), getDraining());
+ if (_maxSizeMB)
+ builder.append(maxSizeMB(), getMaxSizeMB());
+ if (_tags)
+ builder.append(tags(), getTags());
+
+ return builder.obj();
+}
+
+std::string ShardType::toString() const {
+ return toBSON().toString();
+}
+
+void ShardType::setName(const std::string& name) {
+ invariant(!name.empty());
+ _name = name;
+}
+
+void ShardType::setHost(const std::string& host) {
+ invariant(!host.empty());
+ _host = host;
+}
+
+void ShardType::setDraining(const bool isDraining) {
+ _draining = isDraining;
+}
+
+void ShardType::setMaxSizeMB(const long long maxSizeMB) {
+ invariant(maxSizeMB >= 0);
+ _maxSizeMB = maxSizeMB;
+}
+
+void ShardType::setTags(const std::vector<std::string>& tags) {
+ invariant(tags.size() > 0);
+ _tags = tags;
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_shard.h b/src/mongo/s/catalog/type_shard.h
index 9a4a5c5c55b..9b30e1a9505 100644
--- a/src/mongo/s/catalog/type_shard.h
+++ b/src/mongo/s/catalog/type_shard.h
@@ -36,82 +36,90 @@
namespace mongo {
- struct BSONArray;
- class BSONObj;
- class Status;
- template<typename T> class StatusWith;
+struct BSONArray;
+class BSONObj;
+class Status;
+template <typename T>
+class StatusWith;
+
+/**
+ * This class represents the layout and contents of documents contained in the
+ * config.shards collection. All manipulation of documents coming from that
+ * collection should be done with this class.
+ */
+class ShardType {
+public:
+ // Name of the shards collection in the config server.
+ static const std::string ConfigNS;
+
+ // Field names and types in the shards collection type.
+ static const BSONField<std::string> name;
+ static const BSONField<std::string> host;
+ static const BSONField<bool> draining;
+ static const BSONField<long long> maxSizeMB;
+ static const BSONField<BSONArray> tags;
+
+
+ /**
+ * Constructs a new ShardType object from BSON.
+ * Also does validation of the contents.
+ */
+ static StatusWith<ShardType> fromBSON(const BSONObj& source);
+
+ /**
+ * Returns OK if all fields have been set. Otherwise returns NoSuchKey
+ * and information about the first field that is missing.
+ */
+ Status validate() const;
+
+ /**
+ * Returns the BSON representation of the entry.
+ */
+ BSONObj toBSON() const;
/**
- * This class represents the layout and contents of documents contained in the
- * config.shards collection. All manipulation of documents coming from that
- * collection should be done with this class.
+ * Returns a std::string representation of the current internal state.
*/
- class ShardType {
- public:
-
- // Name of the shards collection in the config server.
- static const std::string ConfigNS;
-
- // Field names and types in the shards collection type.
- static const BSONField<std::string> name;
- static const BSONField<std::string> host;
- static const BSONField<bool> draining;
- static const BSONField<long long> maxSizeMB;
- static const BSONField<BSONArray> tags;
-
-
- /**
- * Constructs a new ShardType object from BSON.
- * Also does validation of the contents.
- */
- static StatusWith<ShardType> fromBSON(const BSONObj& source);
-
- /**
- * Returns OK if all fields have been set. Otherwise returns NoSuchKey
- * and information about the first field that is missing.
- */
- Status validate() const;
-
- /**
- * Returns the BSON representation of the entry.
- */
- BSONObj toBSON() const;
-
- /**
- * Returns a std::string representation of the current internal state.
- */
- std::string toString() const;
-
- const std::string& getName() const { return _name.get(); }
- void setName(const std::string& name);
-
- const std::string& getHost() const { return _host.get(); }
- void setHost(const std::string& host);
-
- bool getDraining() const { return _draining.value_or(false); }
- void setDraining(const bool draining);
-
- long long getMaxSizeMB() const { return _maxSizeMB.value_or(0); }
- void setMaxSizeMB(const long long maxSizeMB);
-
- const std::vector<std::string> getTags() const {
- return _tags.value_or(std::vector<std::string>());
- }
- void setTags(const std::vector<std::string>& tags);
-
- private:
- // Convention: (M)andatory, (O)ptional, (S)pecial rule.
-
- // (M) shard's id
- boost::optional<std::string> _name;
- // (M) connection string for the host(s)
- boost::optional<std::string> _host;
- // (O) is it draining chunks?
- boost::optional<bool> _draining;
- // (O) maximum allowed disk space in MB
- boost::optional<long long> _maxSizeMB;
- // (O) shard tags
- boost::optional<std::vector<std::string>> _tags;
- };
-
-} // namespace mongo
+ std::string toString() const;
+
+ const std::string& getName() const {
+ return _name.get();
+ }
+ void setName(const std::string& name);
+
+ const std::string& getHost() const {
+ return _host.get();
+ }
+ void setHost(const std::string& host);
+
+ bool getDraining() const {
+ return _draining.value_or(false);
+ }
+ void setDraining(const bool draining);
+
+ long long getMaxSizeMB() const {
+ return _maxSizeMB.value_or(0);
+ }
+ void setMaxSizeMB(const long long maxSizeMB);
+
+ const std::vector<std::string> getTags() const {
+ return _tags.value_or(std::vector<std::string>());
+ }
+ void setTags(const std::vector<std::string>& tags);
+
+private:
+ // Convention: (M)andatory, (O)ptional, (S)pecial rule.
+
+ // (M) shard's id
+ boost::optional<std::string> _name;
+ // (M) connection string for the host(s)
+ boost::optional<std::string> _host;
+ // (O) is it draining chunks?
+ boost::optional<bool> _draining;
+ // (O) maximum allowed disk space in MB
+ boost::optional<long long> _maxSizeMB;
+ // (O) shard tags
+ boost::optional<std::vector<std::string>> _tags;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_shard_test.cpp b/src/mongo/s/catalog/type_shard_test.cpp
index a7db4f079f2..f89a54d5a3f 100644
--- a/src/mongo/s/catalog/type_shard_test.cpp
+++ b/src/mongo/s/catalog/type_shard_test.cpp
@@ -36,56 +36,53 @@
namespace {
- using namespace mongo;
+using namespace mongo;
- using std::string;
+using std::string;
- TEST(ShardType, MissingName) {
- BSONObj obj = BSON(ShardType::host("localhost:27017"));
- StatusWith<ShardType> shardRes = ShardType::fromBSON(obj);
- ASSERT_FALSE(shardRes.isOK());
- }
+TEST(ShardType, MissingName) {
+ BSONObj obj = BSON(ShardType::host("localhost:27017"));
+ StatusWith<ShardType> shardRes = ShardType::fromBSON(obj);
+ ASSERT_FALSE(shardRes.isOK());
+}
- TEST(ShardType, MissingHost) {
- BSONObj obj = BSON(ShardType::name("shard0000"));
- StatusWith<ShardType> shardRes = ShardType::fromBSON(obj);
- ASSERT_FALSE(shardRes.isOK());
- }
+TEST(ShardType, MissingHost) {
+ BSONObj obj = BSON(ShardType::name("shard0000"));
+ StatusWith<ShardType> shardRes = ShardType::fromBSON(obj);
+ ASSERT_FALSE(shardRes.isOK());
+}
- TEST(ShardType, OnlyMandatory) {
- BSONObj obj = BSON(ShardType::name("shard0000") <<
- ShardType::host("localhost:27017"));
- StatusWith<ShardType> shardRes = ShardType::fromBSON(obj);
- ASSERT(shardRes.isOK());
- ShardType shard = shardRes.getValue();
- ASSERT(shard.validate().isOK());
- }
+TEST(ShardType, OnlyMandatory) {
+ BSONObj obj = BSON(ShardType::name("shard0000") << ShardType::host("localhost:27017"));
+ StatusWith<ShardType> shardRes = ShardType::fromBSON(obj);
+ ASSERT(shardRes.isOK());
+ ShardType shard = shardRes.getValue();
+ ASSERT(shard.validate().isOK());
+}
- TEST(ShardType, AllOptionalsPresent) {
- BSONObj obj = BSON(ShardType::name("shard0000") <<
- ShardType::host("localhost:27017") <<
- ShardType::draining(true) <<
- ShardType::maxSizeMB(100));
- StatusWith<ShardType> shardRes = ShardType::fromBSON(obj);
- ASSERT(shardRes.isOK());
- ShardType shard = shardRes.getValue();
- ASSERT(shard.validate().isOK());
- }
+TEST(ShardType, AllOptionalsPresent) {
+ BSONObj obj = BSON(ShardType::name("shard0000") << ShardType::host("localhost:27017")
+ << ShardType::draining(true)
+ << ShardType::maxSizeMB(100));
+ StatusWith<ShardType> shardRes = ShardType::fromBSON(obj);
+ ASSERT(shardRes.isOK());
+ ShardType shard = shardRes.getValue();
+ ASSERT(shard.validate().isOK());
+}
- TEST(ShardType, MaxSizeAsFloat) {
- BSONObj obj = BSON(ShardType::name("shard0000") <<
- ShardType::host("localhost:27017") <<
- ShardType::maxSizeMB() << 100.0);
- StatusWith<ShardType> shardRes = ShardType::fromBSON(obj);
- ASSERT(shardRes.isOK());
- ShardType shard = shardRes.getValue();
- ASSERT(shard.validate().isOK());
- }
+TEST(ShardType, MaxSizeAsFloat) {
+ BSONObj obj = BSON(ShardType::name("shard0000") << ShardType::host("localhost:27017")
+ << ShardType::maxSizeMB() << 100.0);
+ StatusWith<ShardType> shardRes = ShardType::fromBSON(obj);
+ ASSERT(shardRes.isOK());
+ ShardType shard = shardRes.getValue();
+ ASSERT(shard.validate().isOK());
+}
- TEST(ShardType, BadType) {
- BSONObj obj = BSON(ShardType::name() << 0);
- StatusWith<ShardType> shardRes = ShardType::fromBSON(obj);
- ASSERT_FALSE(shardRes.isOK());
- }
+TEST(ShardType, BadType) {
+ BSONObj obj = BSON(ShardType::name() << 0);
+ StatusWith<ShardType> shardRes = ShardType::fromBSON(obj);
+ ASSERT_FALSE(shardRes.isOK());
+}
} // unnamed namespace
diff --git a/src/mongo/s/catalog/type_tags.cpp b/src/mongo/s/catalog/type_tags.cpp
index a0fd4dff70c..9b45e4553a8 100644
--- a/src/mongo/s/catalog/type_tags.cpp
+++ b/src/mongo/s/catalog/type_tags.cpp
@@ -39,111 +39,115 @@
namespace mongo {
- using std::string;
+using std::string;
- const std::string TagsType::ConfigNS = "config.tags";
+const std::string TagsType::ConfigNS = "config.tags";
- const BSONField<std::string> TagsType::ns("ns");
- const BSONField<std::string> TagsType::tag("tag");
- const BSONField<BSONObj> TagsType::min("min");
- const BSONField<BSONObj> TagsType::max("max");
+const BSONField<std::string> TagsType::ns("ns");
+const BSONField<std::string> TagsType::tag("tag");
+const BSONField<BSONObj> TagsType::min("min");
+const BSONField<BSONObj> TagsType::max("max");
- StatusWith<TagsType> TagsType::fromBSON(const BSONObj& source) {
- TagsType tags;
+StatusWith<TagsType> TagsType::fromBSON(const BSONObj& source) {
+ TagsType tags;
- {
- std::string tagsNs;
- Status status = bsonExtractStringField(source, ns.name(), &tagsNs);
- if (!status.isOK()) return status;
+ {
+ std::string tagsNs;
+ Status status = bsonExtractStringField(source, ns.name(), &tagsNs);
+ if (!status.isOK())
+ return status;
- tags._ns = tagsNs;
- }
-
- {
- std::string tagsTag;
- Status status = bsonExtractStringField(source, tag.name(), &tagsTag);
- if (!status.isOK()) return status;
+ tags._ns = tagsNs;
+ }
- tags._tag = tagsTag;
- }
+ {
+ std::string tagsTag;
+ Status status = bsonExtractStringField(source, tag.name(), &tagsTag);
+ if (!status.isOK())
+ return status;
- {
- BSONElement tagsMinKey;
- Status status = bsonExtractTypedField(source, min.name(), Object, &tagsMinKey);
- if (!status.isOK()) return status;
+ tags._tag = tagsTag;
+ }
- tags._minKey = tagsMinKey.Obj().getOwned();
- }
+ {
+ BSONElement tagsMinKey;
+ Status status = bsonExtractTypedField(source, min.name(), Object, &tagsMinKey);
+ if (!status.isOK())
+ return status;
- {
- BSONElement tagsMaxKey;
- Status status = bsonExtractTypedField(source, max.name(), Object, &tagsMaxKey);
- if (!status.isOK()) return status;
+ tags._minKey = tagsMinKey.Obj().getOwned();
+ }
- tags._maxKey = tagsMaxKey.Obj().getOwned();
- }
+ {
+ BSONElement tagsMaxKey;
+ Status status = bsonExtractTypedField(source, max.name(), Object, &tagsMaxKey);
+ if (!status.isOK())
+ return status;
- return tags;
+ tags._maxKey = tagsMaxKey.Obj().getOwned();
}
- Status TagsType::validate() const {
- if (!_ns.is_initialized() || _ns->empty()) {
- return Status(ErrorCodes::NoSuchKey,
- str::stream() << "missing " << ns.name() << " field");
- }
+ return tags;
+}
- if (!_tag.is_initialized() || _tag->empty()) {
- return Status(ErrorCodes::NoSuchKey,
- str::stream() << "missing " << tag.name() << " field");
- }
+Status TagsType::validate() const {
+ if (!_ns.is_initialized() || _ns->empty()) {
+ return Status(ErrorCodes::NoSuchKey, str::stream() << "missing " << ns.name() << " field");
+ }
- if (!_minKey.is_initialized() || _minKey->isEmpty()) {
- return Status(ErrorCodes::NoSuchKey,
- str::stream() << "missing " << min.name() << " field");
- }
+ if (!_tag.is_initialized() || _tag->empty()) {
+ return Status(ErrorCodes::NoSuchKey, str::stream() << "missing " << tag.name() << " field");
+ }
- if (!_maxKey.is_initialized() || _maxKey->isEmpty()) {
- return Status(ErrorCodes::NoSuchKey,
- str::stream() << "missing " << max.name() << " field");
- }
+ if (!_minKey.is_initialized() || _minKey->isEmpty()) {
+ return Status(ErrorCodes::NoSuchKey, str::stream() << "missing " << min.name() << " field");
+ }
- // 'min' and 'max' must share the same fields.
- if (_minKey->nFields() != _maxKey->nFields()) {
- return Status(ErrorCodes::BadValue, "min and max have a different number of keys");
- }
+ if (!_maxKey.is_initialized() || _maxKey->isEmpty()) {
+ return Status(ErrorCodes::NoSuchKey, str::stream() << "missing " << max.name() << " field");
+ }
- BSONObjIterator minIt(_minKey.get());
- BSONObjIterator maxIt(_maxKey.get());
- while (minIt.more() && maxIt.more()) {
- BSONElement minElem = minIt.next();
- BSONElement maxElem = maxIt.next();
- if (strcmp(minElem.fieldName(), maxElem.fieldName())) {
- return Status(ErrorCodes::BadValue, "min and max have different set of keys");
- }
- }
+ // 'min' and 'max' must share the same fields.
+ if (_minKey->nFields() != _maxKey->nFields()) {
+ return Status(ErrorCodes::BadValue, "min and max have a different number of keys");
+ }
- // 'max' should be greater than 'min'.
- if (_minKey->woCompare(_maxKey.get()) >= 0) {
- return Status(ErrorCodes::BadValue, "max key must be greater than min key");
+ BSONObjIterator minIt(_minKey.get());
+ BSONObjIterator maxIt(_maxKey.get());
+ while (minIt.more() && maxIt.more()) {
+ BSONElement minElem = minIt.next();
+ BSONElement maxElem = maxIt.next();
+ if (strcmp(minElem.fieldName(), maxElem.fieldName())) {
+ return Status(ErrorCodes::BadValue, "min and max have different set of keys");
}
+ }
- return Status::OK();
+ // 'max' should be greater than 'min'.
+ if (_minKey->woCompare(_maxKey.get()) >= 0) {
+ return Status(ErrorCodes::BadValue, "max key must be greater than min key");
}
- BSONObj TagsType::toBSON() const {
- BSONObjBuilder builder;
+ return Status::OK();
+}
- if (_ns) builder.append(ns.name(), getNS());
- if (_tag) builder.append(tag.name(), getTag());
- if (_minKey) builder.append(min.name(), getMinKey());
- if (_maxKey) builder.append(max.name(), getMaxKey());
+BSONObj TagsType::toBSON() const {
+ BSONObjBuilder builder;
- return builder.obj();
- }
+ if (_ns)
+ builder.append(ns.name(), getNS());
+ if (_tag)
+ builder.append(tag.name(), getTag());
+ if (_minKey)
+ builder.append(min.name(), getMinKey());
+ if (_maxKey)
+ builder.append(max.name(), getMaxKey());
- std::string TagsType::toString() const {
- return toBSON().toString();
- }
+ return builder.obj();
+}
+
+std::string TagsType::toString() const {
+ return toBSON().toString();
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_tags.h b/src/mongo/s/catalog/type_tags.h
index a21e4cdf327..d6890af5a7f 100644
--- a/src/mongo/s/catalog/type_tags.h
+++ b/src/mongo/s/catalog/type_tags.h
@@ -35,74 +35,83 @@
namespace mongo {
- class BSONObj;
- class Status;
- template<typename T> class StatusWith;
+class BSONObj;
+class Status;
+template <typename T>
+class StatusWith;
- /**
- * This class represents the layout and contents of documents contained in the config.tags
- * collection. All manipulation of documents coming from that collection should be done with
- * this class.
- */
- class TagsType {
- public:
- // Name of the tags collection in the config server.
- static const std::string ConfigNS;
+/**
+ * This class represents the layout and contents of documents contained in the config.tags
+ * collection. All manipulation of documents coming from that collection should be done with
+ * this class.
+ */
+class TagsType {
+public:
+ // Name of the tags collection in the config server.
+ static const std::string ConfigNS;
- // Field names and types in the tags collection type.
- static const BSONField<std::string> ns;
- static const BSONField<std::string> tag;
- static const BSONField<BSONObj> min;
- static const BSONField<BSONObj> max;
+ // Field names and types in the tags collection type.
+ static const BSONField<std::string> ns;
+ static const BSONField<std::string> tag;
+ static const BSONField<BSONObj> min;
+ static const BSONField<BSONObj> max;
- /**
- * Constructs a new DatabaseType object from BSON. Validates that all required fields are
- * present.
- */
- static StatusWith<TagsType> fromBSON(const BSONObj& source);
+ /**
+ * Constructs a new DatabaseType object from BSON. Validates that all required fields are
+ * present.
+ */
+ static StatusWith<TagsType> fromBSON(const BSONObj& source);
- /**
- * Returns OK if all fields have been set. Otherwise returns NoSuchKey and information
- * about what is the first field which is missing.
- */
- Status validate() const;
+ /**
+ * Returns OK if all fields have been set. Otherwise returns NoSuchKey and information
+ * about what is the first field which is missing.
+ */
+ Status validate() const;
- /**
- * Returns the BSON representation of the entry.
- */
- BSONObj toBSON() const;
+ /**
+ * Returns the BSON representation of the entry.
+ */
+ BSONObj toBSON() const;
- /**
- * Returns a std::string representation of the current internal state.
- */
- std::string toString() const;
+ /**
+ * Returns a std::string representation of the current internal state.
+ */
+ std::string toString() const;
- const std::string& getNS() const { return _ns.get(); }
- void setNS(const std::string& ns);
+ const std::string& getNS() const {
+ return _ns.get();
+ }
+ void setNS(const std::string& ns);
- const std::string& getTag() const { return _tag.get(); }
- void setTag(const std::string& tag);
+ const std::string& getTag() const {
+ return _tag.get();
+ }
+ void setTag(const std::string& tag);
- const BSONObj& getMinKey() const { return _minKey.get(); }
- void setMinKey(const BSONObj& minKey);
+ const BSONObj& getMinKey() const {
+ return _minKey.get();
+ }
+ void setMinKey(const BSONObj& minKey);
- const BSONObj& getMaxKey() const { return _maxKey.get(); }
- void setMaxKey(const BSONObj& max);
+ const BSONObj& getMaxKey() const {
+ return _maxKey.get();
+ }
+ void setMaxKey(const BSONObj& max);
- private:
- // Required namespace to which this tag belongs
- boost::optional<std::string> _ns;
+private:
+ // Required namespace to which this tag belongs
+ boost::optional<std::string> _ns;
- // Required tag name
- boost::optional<std::string> _tag;
+ // Required tag name
+ boost::optional<std::string> _tag;
- // Required first key of the tag (inclusive)
- boost::optional<BSONObj> _minKey;
+ // Required first key of the tag (inclusive)
+ boost::optional<BSONObj> _minKey;
- // Required last key of the tag (not-inclusive)
- boost::optional<BSONObj> _maxKey;
- };
+ // Required last key of the tag (not-inclusive)
+ boost::optional<BSONObj> _maxKey;
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/catalog/type_tags_test.cpp b/src/mongo/s/catalog/type_tags_test.cpp
index 58a0d4a7d95..3f1a7a1175d 100644
--- a/src/mongo/s/catalog/type_tags_test.cpp
+++ b/src/mongo/s/catalog/type_tags_test.cpp
@@ -36,105 +36,97 @@
namespace {
- using namespace mongo;
-
- using std::string;
-
- TEST(TagsType, Valid) {
- BSONObj obj = BSON(TagsType::ns("test.mycol") <<
- TagsType::tag("tag") <<
- TagsType::min(BSON("a" << 10)) <<
- TagsType::max(BSON("a" << 20)));
-
- StatusWith<TagsType> status = TagsType::fromBSON(obj);
- ASSERT_TRUE(status.isOK());
-
- TagsType tag = status.getValue();
-
- ASSERT_EQUALS(tag.getNS(), "test.mycol");
- ASSERT_EQUALS(tag.getTag(), "tag");
- ASSERT_EQUALS(tag.getMinKey(), BSON("a" << 10));
- ASSERT_EQUALS(tag.getMaxKey(), BSON("a" << 20));
- }
-
- TEST(TagsType, MissingNsField) {
- BSONObj obj = BSON(TagsType::tag("tag") <<
- TagsType::min(BSON("a" << 10)) <<
- TagsType::max(BSON("a" << 20)));
-
- StatusWith<TagsType> status = TagsType::fromBSON(obj);
- ASSERT_FALSE(status.isOK());
- ASSERT_EQUALS(ErrorCodes::NoSuchKey, status.getStatus());
- }
-
- TEST(TagsType, MissingTagField) {
- BSONObj obj = BSON(TagsType::ns("test.mycol") <<
- TagsType::min(BSON("a" << 10)) <<
- TagsType::max(BSON("a" << 20)));
-
- StatusWith<TagsType> status = TagsType::fromBSON(obj);
- ASSERT_FALSE(status.isOK());
- ASSERT_EQUALS(ErrorCodes::NoSuchKey, status.getStatus());
- }
-
- TEST(TagsType, MissingMinKey) {
- BSONObj obj = BSON(TagsType::ns("test.mycol") <<
- TagsType::tag("tag") <<
- TagsType::max(BSON("a" << 20)));
-
- StatusWith<TagsType> status = TagsType::fromBSON(obj);
- ASSERT_FALSE(status.isOK());
- ASSERT_EQUALS(ErrorCodes::NoSuchKey, status.getStatus());
- }
-
- TEST(TagsType, MissingMaxKey) {
- BSONObj obj = BSON(TagsType::ns("test.mycol") <<
- TagsType::tag("tag") <<
- TagsType::min(BSON("a" << 10)));
-
- StatusWith<TagsType> status = TagsType::fromBSON(obj);
- ASSERT_FALSE(status.isOK());
- ASSERT_EQUALS(ErrorCodes::NoSuchKey, status.getStatus());
- }
-
- TEST(TagsType, KeysWithDifferentNumberOfColumns) {
- BSONObj obj = BSON(TagsType::ns("test.mycol") <<
- TagsType::tag("tag") <<
- TagsType::min(BSON("a" << 10 << "b" << 10)) <<
- TagsType::max(BSON("a" << 20)));
-
- StatusWith<TagsType> status = TagsType::fromBSON(obj);
- const TagsType& tag = status.getValue();
- ASSERT_EQUALS(ErrorCodes::BadValue, tag.validate());
- }
-
- TEST(TagsType, KeysWithDifferentColumns) {
- BSONObj obj = BSON(TagsType::ns("test.mycol") <<
- TagsType::tag("tag") <<
- TagsType::min(BSON("a" << 10)) <<
- TagsType::max(BSON("b" << 20)));
-
- StatusWith<TagsType> status = TagsType::fromBSON(obj);
- const TagsType& tag = status.getValue();
- ASSERT_EQUALS(ErrorCodes::BadValue, tag.validate());
- }
-
- TEST(TagsType, KeysNotAscending) {
- BSONObj obj = BSON(TagsType::tag("tag") <<
- TagsType::ns("test.mycol") <<
- TagsType::min(BSON("a" << 20)) <<
- TagsType::max(BSON("a" << 10)));
-
- StatusWith<TagsType> status = TagsType::fromBSON(obj);
- const TagsType& tag = status.getValue();
- ASSERT_EQUALS(ErrorCodes::BadValue, tag.validate());
- }
-
- TEST(TagsType, BadType) {
- BSONObj obj = BSON(TagsType::tag() << 0);
-
- StatusWith<TagsType> status = TagsType::fromBSON(obj);
- ASSERT_EQUALS(ErrorCodes::NoSuchKey, status.getStatus());
- }
-
-} // namespace
+using namespace mongo;
+
+using std::string;
+
+TEST(TagsType, Valid) {
+ BSONObj obj =
+ BSON(TagsType::ns("test.mycol") << TagsType::tag("tag") << TagsType::min(BSON("a" << 10))
+ << TagsType::max(BSON("a" << 20)));
+
+ StatusWith<TagsType> status = TagsType::fromBSON(obj);
+ ASSERT_TRUE(status.isOK());
+
+ TagsType tag = status.getValue();
+
+ ASSERT_EQUALS(tag.getNS(), "test.mycol");
+ ASSERT_EQUALS(tag.getTag(), "tag");
+ ASSERT_EQUALS(tag.getMinKey(), BSON("a" << 10));
+ ASSERT_EQUALS(tag.getMaxKey(), BSON("a" << 20));
+}
+
+TEST(TagsType, MissingNsField) {
+ BSONObj obj = BSON(TagsType::tag("tag") << TagsType::min(BSON("a" << 10))
+ << TagsType::max(BSON("a" << 20)));
+
+ StatusWith<TagsType> status = TagsType::fromBSON(obj);
+ ASSERT_FALSE(status.isOK());
+ ASSERT_EQUALS(ErrorCodes::NoSuchKey, status.getStatus());
+}
+
+TEST(TagsType, MissingTagField) {
+ BSONObj obj = BSON(TagsType::ns("test.mycol") << TagsType::min(BSON("a" << 10))
+ << TagsType::max(BSON("a" << 20)));
+
+ StatusWith<TagsType> status = TagsType::fromBSON(obj);
+ ASSERT_FALSE(status.isOK());
+ ASSERT_EQUALS(ErrorCodes::NoSuchKey, status.getStatus());
+}
+
+TEST(TagsType, MissingMinKey) {
+ BSONObj obj =
+ BSON(TagsType::ns("test.mycol") << TagsType::tag("tag") << TagsType::max(BSON("a" << 20)));
+
+ StatusWith<TagsType> status = TagsType::fromBSON(obj);
+ ASSERT_FALSE(status.isOK());
+ ASSERT_EQUALS(ErrorCodes::NoSuchKey, status.getStatus());
+}
+
+TEST(TagsType, MissingMaxKey) {
+ BSONObj obj =
+ BSON(TagsType::ns("test.mycol") << TagsType::tag("tag") << TagsType::min(BSON("a" << 10)));
+
+ StatusWith<TagsType> status = TagsType::fromBSON(obj);
+ ASSERT_FALSE(status.isOK());
+ ASSERT_EQUALS(ErrorCodes::NoSuchKey, status.getStatus());
+}
+
+TEST(TagsType, KeysWithDifferentNumberOfColumns) {
+ BSONObj obj = BSON(TagsType::ns("test.mycol") << TagsType::tag("tag")
+ << TagsType::min(BSON("a" << 10 << "b" << 10))
+ << TagsType::max(BSON("a" << 20)));
+
+ StatusWith<TagsType> status = TagsType::fromBSON(obj);
+ const TagsType& tag = status.getValue();
+ ASSERT_EQUALS(ErrorCodes::BadValue, tag.validate());
+}
+
+TEST(TagsType, KeysWithDifferentColumns) {
+ BSONObj obj =
+ BSON(TagsType::ns("test.mycol") << TagsType::tag("tag") << TagsType::min(BSON("a" << 10))
+ << TagsType::max(BSON("b" << 20)));
+
+ StatusWith<TagsType> status = TagsType::fromBSON(obj);
+ const TagsType& tag = status.getValue();
+ ASSERT_EQUALS(ErrorCodes::BadValue, tag.validate());
+}
+
+TEST(TagsType, KeysNotAscending) {
+ BSONObj obj =
+ BSON(TagsType::tag("tag") << TagsType::ns("test.mycol") << TagsType::min(BSON("a" << 20))
+ << TagsType::max(BSON("a" << 10)));
+
+ StatusWith<TagsType> status = TagsType::fromBSON(obj);
+ const TagsType& tag = status.getValue();
+ ASSERT_EQUALS(ErrorCodes::BadValue, tag.validate());
+}
+
+TEST(TagsType, BadType) {
+ BSONObj obj = BSON(TagsType::tag() << 0);
+
+ StatusWith<TagsType> status = TagsType::fromBSON(obj);
+ ASSERT_EQUALS(ErrorCodes::NoSuchKey, status.getStatus());
+}
+
+} // namespace
diff --git a/src/mongo/s/chunk.cpp b/src/mongo/s/chunk.cpp
index 5e475771c07..ede622930f5 100644
--- a/src/mongo/s/chunk.cpp
+++ b/src/mongo/s/chunk.cpp
@@ -54,721 +54,689 @@
namespace mongo {
- using std::shared_ptr;
- using std::unique_ptr;
- using std::map;
- using std::ostringstream;
- using std::set;
- using std::string;
- using std::stringstream;
- using std::vector;
+using std::shared_ptr;
+using std::unique_ptr;
+using std::map;
+using std::ostringstream;
+using std::set;
+using std::string;
+using std::stringstream;
+using std::vector;
namespace {
- const int kTooManySplitPoints = 4;
+const int kTooManySplitPoints = 4;
- /**
- * Attempts to move the given chunk to another shard.
- *
- * Returns true if the chunk was actually moved.
- */
- bool tryMoveToOtherShard(const ChunkManager& manager, const ChunkType& chunk) {
- // reload sharding metadata before starting migration
- ChunkManagerPtr chunkMgr = manager.reload(false /* just reloaded in mulitsplit */);
+/**
+ * Attempts to move the given chunk to another shard.
+ *
+ * Returns true if the chunk was actually moved.
+ */
+bool tryMoveToOtherShard(const ChunkManager& manager, const ChunkType& chunk) {
+ // reload sharding metadata before starting migration
+ ChunkManagerPtr chunkMgr = manager.reload(false /* just reloaded in mulitsplit */);
- ShardInfoMap shardInfo;
- Status loadStatus = DistributionStatus::populateShardInfoMap(&shardInfo);
+ ShardInfoMap shardInfo;
+ Status loadStatus = DistributionStatus::populateShardInfoMap(&shardInfo);
- if (!loadStatus.isOK()) {
- warning() << "failed to load shard metadata while trying to moveChunk after "
- << "auto-splitting" << causedBy(loadStatus);
- return false;
- }
+ if (!loadStatus.isOK()) {
+ warning() << "failed to load shard metadata while trying to moveChunk after "
+ << "auto-splitting" << causedBy(loadStatus);
+ return false;
+ }
- if (shardInfo.size() < 2) {
- LOG(0) << "no need to move top chunk since there's only 1 shard";
- return false;
- }
+ if (shardInfo.size() < 2) {
+ LOG(0) << "no need to move top chunk since there's only 1 shard";
+ return false;
+ }
- map<string, vector<ChunkType>> shardToChunkMap;
- DistributionStatus::populateShardToChunksMap(shardInfo, *chunkMgr, &shardToChunkMap);
+ map<string, vector<ChunkType>> shardToChunkMap;
+ DistributionStatus::populateShardToChunksMap(shardInfo, *chunkMgr, &shardToChunkMap);
- StatusWith<string> tagStatus = grid.catalogManager()->getTagForChunk(manager.getns(),
- chunk);
- if (!tagStatus.isOK()) {
- warning() << "Not auto-moving chunk because of an error encountered while "
- << "checking tag for chunk: " << tagStatus.getStatus();
- return false;
- }
+ StatusWith<string> tagStatus = grid.catalogManager()->getTagForChunk(manager.getns(), chunk);
+ if (!tagStatus.isOK()) {
+ warning() << "Not auto-moving chunk because of an error encountered while "
+ << "checking tag for chunk: " << tagStatus.getStatus();
+ return false;
+ }
- DistributionStatus chunkDistribution(shardInfo, shardToChunkMap);
- const string newLocation(
- chunkDistribution.getBestReceieverShard(tagStatus.getValue()));
+ DistributionStatus chunkDistribution(shardInfo, shardToChunkMap);
+ const string newLocation(chunkDistribution.getBestReceieverShard(tagStatus.getValue()));
- if (newLocation.empty()) {
- LOG(1) << "recently split chunk: " << chunk
- << " but no suitable shard to move to";
- return false;
- }
+ if (newLocation.empty()) {
+ LOG(1) << "recently split chunk: " << chunk << " but no suitable shard to move to";
+ return false;
+ }
- if (chunk.getShard() == newLocation) {
- // if this is the best shard, then we shouldn't do anything.
- LOG(1) << "recently split chunk: " << chunk
- << " already in the best shard";
- return false;
- }
+ if (chunk.getShard() == newLocation) {
+ // if this is the best shard, then we shouldn't do anything.
+ LOG(1) << "recently split chunk: " << chunk << " already in the best shard";
+ return false;
+ }
- ChunkPtr toMove = chunkMgr->findIntersectingChunk(chunk.getMin());
+ ChunkPtr toMove = chunkMgr->findIntersectingChunk(chunk.getMin());
- if (!(toMove->getMin() == chunk.getMin() && toMove->getMax() == chunk.getMax())) {
- LOG(1) << "recently split chunk: " << chunk
- << " modified before we could migrate " << toMove->toString();
- return false;
- }
+ if (!(toMove->getMin() == chunk.getMin() && toMove->getMax() == chunk.getMax())) {
+ LOG(1) << "recently split chunk: " << chunk << " modified before we could migrate "
+ << toMove->toString();
+ return false;
+ }
- log() << "moving chunk (auto): " << toMove->toString() << " to: " << newLocation;
+ log() << "moving chunk (auto): " << toMove->toString() << " to: " << newLocation;
- shared_ptr<Shard> newShard = grid.shardRegistry()->getShard(newLocation);
- if (!newShard) {
- warning() << "Newly selected shard " << newLocation << " could not be found.";
- return false;
- }
+ shared_ptr<Shard> newShard = grid.shardRegistry()->getShard(newLocation);
+ if (!newShard) {
+ warning() << "Newly selected shard " << newLocation << " could not be found.";
+ return false;
+ }
- BSONObj res;
- WriteConcernOptions noThrottle;
- if (!toMove->moveAndCommit(newShard->getId(),
- Chunk::MaxChunkSize,
- &noThrottle, /* secondaryThrottle */
- false, /* waitForDelete - small chunk, no need */
- 0, /* maxTimeMS - don't time out */
- res)) {
- msgassertedNoTrace(10412, str::stream() << "moveAndCommit failed: " << res);
- }
+ BSONObj res;
+ WriteConcernOptions noThrottle;
+ if (!toMove->moveAndCommit(newShard->getId(),
+ Chunk::MaxChunkSize,
+ &noThrottle, /* secondaryThrottle */
+ false, /* waitForDelete - small chunk, no need */
+ 0, /* maxTimeMS - don't time out */
+ res)) {
+ msgassertedNoTrace(10412, str::stream() << "moveAndCommit failed: " << res);
+ }
- // update our config
- manager.reload();
+ // update our config
+ manager.reload();
- return true;
- }
+ return true;
+}
-} // namespace
+} // namespace
- long long Chunk::MaxChunkSize = 1024 * 1024 * 64;
- int Chunk::MaxObjectPerChunk = 250000;
+long long Chunk::MaxChunkSize = 1024 * 1024 * 64;
+int Chunk::MaxObjectPerChunk = 250000;
- // Can be overridden from command line
- bool Chunk::ShouldAutoSplit = true;
+// Can be overridden from command line
+bool Chunk::ShouldAutoSplit = true;
- Chunk::Chunk(const ChunkManager * manager, BSONObj from)
- : _manager(manager), _lastmod(0, 0, OID()), _dataWritten(mkDataWritten())
- {
- string ns = from.getStringField(ChunkType::ns().c_str());
- _shardId = from.getStringField(ChunkType::shard().c_str());
+Chunk::Chunk(const ChunkManager* manager, BSONObj from)
+ : _manager(manager), _lastmod(0, 0, OID()), _dataWritten(mkDataWritten()) {
+ string ns = from.getStringField(ChunkType::ns().c_str());
+ _shardId = from.getStringField(ChunkType::shard().c_str());
- _lastmod = ChunkVersion::fromBSON(from[ChunkType::DEPRECATED_lastmod()]);
- verify( _lastmod.isSet() );
+ _lastmod = ChunkVersion::fromBSON(from[ChunkType::DEPRECATED_lastmod()]);
+ verify(_lastmod.isSet());
- _min = from.getObjectField(ChunkType::min().c_str()).getOwned();
- _max = from.getObjectField(ChunkType::max().c_str()).getOwned();
-
- _jumbo = from[ChunkType::jumbo()].trueValue();
+ _min = from.getObjectField(ChunkType::min().c_str()).getOwned();
+ _max = from.getObjectField(ChunkType::max().c_str()).getOwned();
- uassert( 10170 , "Chunk needs a ns" , ! ns.empty() );
- uassert( 13327 , "Chunk ns must match server ns" , ns == _manager->getns() );
+ _jumbo = from[ChunkType::jumbo()].trueValue();
- {
- const auto shard = grid.shardRegistry()->getShard(_shardId);
- uassert(10171, "Chunk needs a server", shard);
- }
+ uassert(10170, "Chunk needs a ns", !ns.empty());
+ uassert(13327, "Chunk ns must match server ns", ns == _manager->getns());
- uassert( 10172 , "Chunk needs a min" , ! _min.isEmpty() );
- uassert( 10173 , "Chunk needs a max" , ! _max.isEmpty() );
+ {
+ const auto shard = grid.shardRegistry()->getShard(_shardId);
+ uassert(10171, "Chunk needs a server", shard);
}
- Chunk::Chunk(const ChunkManager * info,
- const BSONObj& min,
- const BSONObj& max,
- const ShardId& shardId,
- ChunkVersion lastmod)
- : _manager(info),
- _min(min),
- _max(max),
- _shardId(shardId),
- _lastmod(lastmod),
- _jumbo(false),
- _dataWritten(mkDataWritten())
- {}
+ uassert(10172, "Chunk needs a min", !_min.isEmpty());
+ uassert(10173, "Chunk needs a max", !_max.isEmpty());
+}
+
+Chunk::Chunk(const ChunkManager* info,
+ const BSONObj& min,
+ const BSONObj& max,
+ const ShardId& shardId,
+ ChunkVersion lastmod)
+ : _manager(info),
+ _min(min),
+ _max(max),
+ _shardId(shardId),
+ _lastmod(lastmod),
+ _jumbo(false),
+ _dataWritten(mkDataWritten()) {}
+
+int Chunk::mkDataWritten() {
+ PseudoRandom r(static_cast<int64_t>(time(0)));
+ return r.nextInt32(MaxChunkSize / ChunkManager::SplitHeuristics::splitTestFactor);
+}
+
+bool Chunk::containsKey(const BSONObj& shardKey) const {
+ return getMin().woCompare(shardKey) <= 0 && shardKey.woCompare(getMax()) < 0;
+}
+
+bool ChunkRange::containsKey(const BSONObj& shardKey) const {
+ // same as Chunk method
+ return getMin().woCompare(shardKey) <= 0 && shardKey.woCompare(getMax()) < 0;
+}
+
+bool Chunk::_minIsInf() const {
+ return 0 == _manager->getShardKeyPattern().getKeyPattern().globalMin().woCompare(getMin());
+}
+
+bool Chunk::_maxIsInf() const {
+ return 0 == _manager->getShardKeyPattern().getKeyPattern().globalMax().woCompare(getMax());
+}
+
+BSONObj Chunk::_getExtremeKey(bool doSplitAtLower) const {
+ Query q;
+ if (doSplitAtLower) {
+ q.sort(_manager->getShardKeyPattern().toBSON());
+ } else {
+ // need to invert shard key pattern to sort backwards
+ // TODO: make a helper in ShardKeyPattern?
+
+ BSONObj k = _manager->getShardKeyPattern().toBSON();
+ BSONObjBuilder r;
+
+ BSONObjIterator i(k);
+ while (i.more()) {
+ BSONElement e = i.next();
+ uassert(10163, "can only handle numbers here - which i think is correct", e.isNumber());
+ r.append(e.fieldName(), -1 * e.number());
+ }
- int Chunk::mkDataWritten() {
- PseudoRandom r(static_cast<int64_t>(time(0)));
- return r.nextInt32( MaxChunkSize / ChunkManager::SplitHeuristics::splitTestFactor );
+ q.sort(r.obj());
}
- bool Chunk::containsKey( const BSONObj& shardKey ) const {
- return getMin().woCompare( shardKey ) <= 0 && shardKey.woCompare( getMax() ) < 0;
- }
+ // find the extreme key
+ ScopedDbConnection conn(_getShardConnectionString());
+ BSONObj end;
+
+ if (doSplitAtLower) {
+ // Splitting close to the lower bound means that the split point will be the
+ // upper bound. Chunk range upper bounds are exclusive so skip a document to
+ // make the lower half of the split end up with a single document.
+ unique_ptr<DBClientCursor> cursor = conn->query(_manager->getns(),
+ q,
+ 1, /* nToReturn */
+ 1 /* nToSkip */);
- bool ChunkRange::containsKey( const BSONObj& shardKey ) const {
- // same as Chunk method
- return getMin().woCompare( shardKey ) <= 0 && shardKey.woCompare( getMax() ) < 0;
+ if (cursor->more()) {
+ end = cursor->next().getOwned();
+ }
+ } else {
+ end = conn->findOne(_manager->getns(), q);
}
- bool Chunk::_minIsInf() const {
- return 0 ==
- _manager->getShardKeyPattern().getKeyPattern().globalMin().woCompare(getMin());
+ conn.done();
+ if (end.isEmpty())
+ return BSONObj();
+ return _manager->getShardKeyPattern().extractShardKeyFromDoc(end);
+}
+
+void Chunk::pickMedianKey(BSONObj& medianKey) const {
+ // Ask the mongod holding this chunk to figure out the split points.
+ ScopedDbConnection conn(_getShardConnectionString());
+ BSONObj result;
+ BSONObjBuilder cmd;
+ cmd.append("splitVector", _manager->getns());
+ cmd.append("keyPattern", _manager->getShardKeyPattern().toBSON());
+ cmd.append("min", getMin());
+ cmd.append("max", getMax());
+ cmd.appendBool("force", true);
+ BSONObj cmdObj = cmd.obj();
+
+ if (!conn->runCommand("admin", cmdObj, result)) {
+ conn.done();
+ ostringstream os;
+ os << "splitVector command (median key) failed: " << result;
+ uassert(13503, os.str(), 0);
}
- bool Chunk::_maxIsInf() const {
- return 0 ==
- _manager->getShardKeyPattern().getKeyPattern().globalMax().woCompare(getMax());
+ BSONObjIterator it(result.getObjectField("splitKeys"));
+ if (it.more()) {
+ medianKey = it.next().Obj().getOwned();
}
- BSONObj Chunk::_getExtremeKey(bool doSplitAtLower) const {
- Query q;
- if (doSplitAtLower) {
- q.sort( _manager->getShardKeyPattern().toBSON() );
- }
- else {
- // need to invert shard key pattern to sort backwards
- // TODO: make a helper in ShardKeyPattern?
-
- BSONObj k = _manager->getShardKeyPattern().toBSON();
- BSONObjBuilder r;
-
- BSONObjIterator i(k);
- while( i.more() ) {
- BSONElement e = i.next();
- uassert( 10163 , "can only handle numbers here - which i think is correct" , e.isNumber() );
- r.append( e.fieldName() , -1 * e.number() );
- }
+ conn.done();
+}
+
+void Chunk::pickSplitVector(vector<BSONObj>& splitPoints,
+ long long chunkSize /* bytes */,
+ int maxPoints,
+ int maxObjs) const {
+ // Ask the mongod holding this chunk to figure out the split points.
+ ScopedDbConnection conn(_getShardConnectionString());
+ BSONObj result;
+ BSONObjBuilder cmd;
+ cmd.append("splitVector", _manager->getns());
+ cmd.append("keyPattern", _manager->getShardKeyPattern().toBSON());
+ cmd.append("min", getMin());
+ cmd.append("max", getMax());
+ cmd.append("maxChunkSizeBytes", chunkSize);
+ cmd.append("maxSplitPoints", maxPoints);
+ cmd.append("maxChunkObjects", maxObjs);
+ BSONObj cmdObj = cmd.obj();
+
+ if (!conn->runCommand("admin", cmdObj, result)) {
+ conn.done();
+ ostringstream os;
+ os << "splitVector command failed: " << result;
+ uassert(13345, os.str(), 0);
+ }
- q.sort( r.obj() );
+ BSONObjIterator it(result.getObjectField("splitKeys"));
+ while (it.more()) {
+ splitPoints.push_back(it.next().Obj().getOwned());
+ }
+ conn.done();
+}
+
+void Chunk::determineSplitPoints(bool atMedian, vector<BSONObj>* splitPoints) const {
+ // if splitting is not obligatory we may return early if there are not enough data
+ // we cap the number of objects that would fall in the first half (before the split point)
+ // the rationale is we'll find a split point without traversing all the data
+ if (atMedian) {
+ BSONObj medianKey;
+ pickMedianKey(medianKey);
+ if (!medianKey.isEmpty())
+ splitPoints->push_back(medianKey);
+ } else {
+ long long chunkSize = _manager->getCurrentDesiredChunkSize();
+
+ // Note: One split point for every 1/2 chunk size.
+ const int estNumSplitPoints = _dataWritten / chunkSize * 2;
+ if (estNumSplitPoints >= kTooManySplitPoints) {
+ // The current desired chunk size will split the chunk into lots of small chunks
+ // (At the worst case, this can result into thousands of chunks); so check and
+ // see if a bigger value can be used.
+
+ chunkSize = std::min(_dataWritten, Chunk::MaxChunkSize);
+ }
+
+ pickSplitVector(*splitPoints, chunkSize, 0, MaxObjectPerChunk);
+
+ if (splitPoints->size() <= 1) {
+ // no split points means there isn't enough data to split on
+ // 1 split point means we have between half the chunk size to full chunk size
+ // so we shouldn't split
+ splitPoints->clear();
}
+ }
+}
- // find the extreme key
- ScopedDbConnection conn(_getShardConnectionString());
- BSONObj end;
-
- if (doSplitAtLower) {
- // Splitting close to the lower bound means that the split point will be the
- // upper bound. Chunk range upper bounds are exclusive so skip a document to
- // make the lower half of the split end up with a single document.
- unique_ptr<DBClientCursor> cursor = conn->query(_manager->getns(),
- q,
- 1, /* nToReturn */
- 1 /* nToSkip */);
-
- if (cursor->more()) {
- end = cursor->next().getOwned();
- }
- }
- else {
- end = conn->findOne(_manager->getns(), q);
- }
+Status Chunk::split(SplitPointMode mode, size_t* resultingSplits, BSONObj* res) const {
+ size_t dummy;
+ if (resultingSplits == NULL) {
+ resultingSplits = &dummy;
+ }
- conn.done();
- if ( end.isEmpty() )
- return BSONObj();
- return _manager->getShardKeyPattern().extractShardKeyFromDoc(end);
- }
-
- void Chunk::pickMedianKey( BSONObj& medianKey ) const {
- // Ask the mongod holding this chunk to figure out the split points.
- ScopedDbConnection conn(_getShardConnectionString());
- BSONObj result;
- BSONObjBuilder cmd;
- cmd.append( "splitVector" , _manager->getns() );
- cmd.append( "keyPattern" , _manager->getShardKeyPattern().toBSON() );
- cmd.append( "min" , getMin() );
- cmd.append( "max" , getMax() );
- cmd.appendBool( "force" , true );
- BSONObj cmdObj = cmd.obj();
-
- if ( ! conn->runCommand( "admin" , cmdObj , result )) {
- conn.done();
- ostringstream os;
- os << "splitVector command (median key) failed: " << result;
- uassert( 13503 , os.str() , 0 );
- }
+ bool atMedian = mode == Chunk::atMedian;
+ vector<BSONObj> splitPoints;
- BSONObjIterator it( result.getObjectField( "splitKeys" ) );
- if ( it.more() ) {
- medianKey = it.next().Obj().getOwned();
+ determineSplitPoints(atMedian, &splitPoints);
+ if (splitPoints.empty()) {
+ string msg;
+ if (atMedian) {
+ msg = "cannot find median in chunk, possibly empty";
+ } else {
+ msg = "chunk not full enough to trigger auto-split";
}
- conn.done();
+ LOG(1) << msg;
+ return Status(ErrorCodes::CannotSplit, msg);
}
- void Chunk::pickSplitVector(vector<BSONObj>& splitPoints,
- long long chunkSize /* bytes */,
- int maxPoints,
- int maxObjs) const {
- // Ask the mongod holding this chunk to figure out the split points.
- ScopedDbConnection conn(_getShardConnectionString());
- BSONObj result;
- BSONObjBuilder cmd;
- cmd.append( "splitVector" , _manager->getns() );
- cmd.append( "keyPattern" , _manager->getShardKeyPattern().toBSON() );
- cmd.append( "min" , getMin() );
- cmd.append( "max" , getMax() );
- cmd.append( "maxChunkSizeBytes" , chunkSize );
- cmd.append( "maxSplitPoints" , maxPoints );
- cmd.append( "maxChunkObjects" , maxObjs );
- BSONObj cmdObj = cmd.obj();
-
- if ( ! conn->runCommand( "admin" , cmdObj , result )) {
- conn.done();
- ostringstream os;
- os << "splitVector command failed: " << result;
- uassert( 13345 , os.str() , 0 );
- }
-
- BSONObjIterator it( result.getObjectField( "splitKeys" ) );
- while ( it.more() ) {
- splitPoints.push_back( it.next().Obj().getOwned() );
+ // We assume that if the chunk being split is the first (or last) one on the collection,
+ // this chunk is likely to see more insertions. Instead of splitting mid-chunk, we use
+ // the very first (or last) key as a split point.
+ // This heuristic is skipped for "special" shard key patterns that are not likely to
+ // produce monotonically increasing or decreasing values (e.g. hashed shard keys).
+ if (mode == Chunk::autoSplitInternal &&
+ KeyPattern::isOrderedKeyPattern(_manager->getShardKeyPattern().toBSON())) {
+ if (_minIsInf()) {
+ BSONObj key = _getExtremeKey(true);
+ if (!key.isEmpty()) {
+ splitPoints[0] = key.getOwned();
+ }
+ } else if (_maxIsInf()) {
+ BSONObj key = _getExtremeKey(false);
+ if (!key.isEmpty()) {
+ splitPoints.pop_back();
+ splitPoints.push_back(key);
+ }
}
- conn.done();
}
- void Chunk::determineSplitPoints(bool atMedian, vector<BSONObj>* splitPoints) const {
- // if splitting is not obligatory we may return early if there are not enough data
- // we cap the number of objects that would fall in the first half (before the split point)
- // the rationale is we'll find a split point without traversing all the data
- if ( atMedian ) {
- BSONObj medianKey;
- pickMedianKey( medianKey );
- if ( ! medianKey.isEmpty() )
- splitPoints->push_back( medianKey );
- }
- else {
- long long chunkSize = _manager->getCurrentDesiredChunkSize();
+ // Normally, we'd have a sound split point here if the chunk is not empty.
+ // It's also a good place to sanity check.
+ if (_min == splitPoints.front()) {
+ string msg(str::stream() << "not splitting chunk " << toString() << ", split point "
+ << splitPoints.front() << " is exactly on chunk bounds");
+ log() << msg;
+ return Status(ErrorCodes::CannotSplit, msg);
+ }
- // Note: One split point for every 1/2 chunk size.
- const int estNumSplitPoints = _dataWritten / chunkSize * 2;
- if (estNumSplitPoints >= kTooManySplitPoints) {
- // The current desired chunk size will split the chunk into lots of small chunks
- // (At the worst case, this can result into thousands of chunks); so check and
- // see if a bigger value can be used.
+ if (_max == splitPoints.back()) {
+ string msg(str::stream() << "not splitting chunk " << toString() << ", split point "
+ << splitPoints.back() << " is exactly on chunk bounds");
+ log() << msg;
+ return Status(ErrorCodes::CannotSplit, msg);
+ }
- chunkSize = std::min(_dataWritten, Chunk::MaxChunkSize);
- }
+ Status status = multiSplit(splitPoints, res);
+ *resultingSplits = splitPoints.size();
+ return status;
+}
+
+Status Chunk::multiSplit(const vector<BSONObj>& m, BSONObj* res) const {
+ const size_t maxSplitPoints = 8192;
+
+ uassert(10165, "can't split as shard doesn't have a manager", _manager);
+ uassert(13332, "need a split key to split chunk", !m.empty());
+ uassert(13333, "can't split a chunk in that many parts", m.size() < maxSplitPoints);
+ uassert(13003, "can't split a chunk with only one distinct value", _min.woCompare(_max));
+
+ ScopedDbConnection conn(_getShardConnectionString());
+
+ BSONObjBuilder cmd;
+ cmd.append("splitChunk", _manager->getns());
+ cmd.append("keyPattern", _manager->getShardKeyPattern().toBSON());
+ cmd.append("min", getMin());
+ cmd.append("max", getMax());
+ cmd.append("from", getShardId());
+ cmd.append("splitKeys", m);
+ cmd.append("configdb", grid.catalogManager()->connectionString().toString());
+ cmd.append("epoch", _manager->getVersion().epoch());
+ BSONObj cmdObj = cmd.obj();
+
+ BSONObj dummy;
+ if (res == NULL) {
+ res = &dummy;
+ }
- pickSplitVector(*splitPoints, chunkSize, 0, MaxObjectPerChunk);
+ if (!conn->runCommand("admin", cmdObj, *res)) {
+ string msg(str::stream() << "splitChunk failed - cmd: " << cmdObj << " result: " << *res);
+ warning() << msg;
+ conn.done();
- if ( splitPoints->size() <= 1 ) {
- // no split points means there isn't enough data to split on
- // 1 split point means we have between half the chunk size to full chunk size
- // so we shouldn't split
- splitPoints->clear();
- }
- }
+ return Status(ErrorCodes::SplitFailed, msg);
}
- Status Chunk::split(SplitPointMode mode, size_t* resultingSplits, BSONObj* res) const {
- size_t dummy;
- if (resultingSplits == NULL) {
- resultingSplits = &dummy;
- }
-
- bool atMedian = mode == Chunk::atMedian;
- vector<BSONObj> splitPoints;
+ conn.done();
- determineSplitPoints( atMedian, &splitPoints );
- if (splitPoints.empty()) {
- string msg;
- if (atMedian) {
- msg = "cannot find median in chunk, possibly empty";
- }
- else {
- msg = "chunk not full enough to trigger auto-split";
- }
+ // force reload of config
+ _manager->reload();
- LOG(1) << msg;
- return Status(ErrorCodes::CannotSplit, msg);
- }
+ return Status::OK();
+}
- // We assume that if the chunk being split is the first (or last) one on the collection,
- // this chunk is likely to see more insertions. Instead of splitting mid-chunk, we use
- // the very first (or last) key as a split point.
- // This heuristic is skipped for "special" shard key patterns that are not likely to
- // produce monotonically increasing or decreasing values (e.g. hashed shard keys).
- if (mode == Chunk::autoSplitInternal &&
- KeyPattern::isOrderedKeyPattern(_manager->getShardKeyPattern().toBSON())) {
-
- if (_minIsInf()) {
- BSONObj key = _getExtremeKey(true);
- if (!key.isEmpty()) {
- splitPoints[0] = key.getOwned();
- }
- }
- else if (_maxIsInf()) {
- BSONObj key = _getExtremeKey(false);
- if (!key.isEmpty()) {
- splitPoints.pop_back();
- splitPoints.push_back(key);
- }
- }
- }
+bool Chunk::moveAndCommit(const ShardId& toShardId,
+ long long chunkSize /* bytes */,
+ const WriteConcernOptions* writeConcern,
+ bool waitForDelete,
+ int maxTimeMS,
+ BSONObj& res) const {
+ uassert(10167, "can't move shard to its current location!", getShardId() != toShardId);
- // Normally, we'd have a sound split point here if the chunk is not empty.
- // It's also a good place to sanity check.
- if ( _min == splitPoints.front() ) {
- string msg(str::stream() << "not splitting chunk " << toString()
- << ", split point " << splitPoints.front()
- << " is exactly on chunk bounds");
- log() << msg;
- return Status(ErrorCodes::CannotSplit, msg);
- }
+ log() << "moving chunk ns: " << _manager->getns() << " moving ( " << toString() << ") "
+ << getShardId() << " -> " << toShardId;
- if ( _max == splitPoints.back() ) {
- string msg(str::stream() << "not splitting chunk " << toString()
- << ", split point " << splitPoints.back()
- << " is exactly on chunk bounds");
- log() << msg;
- return Status(ErrorCodes::CannotSplit, msg);
- }
+ const auto from = grid.shardRegistry()->getShard(getShardId());
- Status status = multiSplit(splitPoints, res);
- *resultingSplits = splitPoints.size();
- return status;
+ BSONObjBuilder builder;
+ builder.append("moveChunk", _manager->getns());
+ builder.append("from", from->getConnString().toString());
+ {
+ const auto toShard = grid.shardRegistry()->getShard(toShardId);
+ builder.append("to", toShard->getConnString().toString());
+ }
+ // NEEDED FOR 2.0 COMPATIBILITY
+ builder.append("fromShard", from->getId());
+ builder.append("toShard", toShardId);
+ ///////////////////////////////
+ builder.append("min", _min);
+ builder.append("max", _max);
+ builder.append("maxChunkSizeBytes", chunkSize);
+ builder.append("configdb", grid.catalogManager()->connectionString().toString());
+
+ // For legacy secondary throttle setting.
+ bool secondaryThrottle = true;
+ if (writeConcern && writeConcern->wNumNodes <= 1 && writeConcern->wMode.empty()) {
+ secondaryThrottle = false;
}
- Status Chunk::multiSplit(const vector<BSONObj>& m, BSONObj* res) const {
- const size_t maxSplitPoints = 8192;
-
- uassert( 10165 , "can't split as shard doesn't have a manager" , _manager );
- uassert( 13332 , "need a split key to split chunk" , !m.empty() );
- uassert( 13333 , "can't split a chunk in that many parts", m.size() < maxSplitPoints );
- uassert( 13003 , "can't split a chunk with only one distinct value" , _min.woCompare(_max) );
+ builder.append("secondaryThrottle", secondaryThrottle);
- ScopedDbConnection conn(_getShardConnectionString());
+ if (secondaryThrottle && writeConcern) {
+ builder.append("writeConcern", writeConcern->toBSON());
+ }
- BSONObjBuilder cmd;
- cmd.append( "splitChunk" , _manager->getns() );
- cmd.append( "keyPattern" , _manager->getShardKeyPattern().toBSON() );
- cmd.append( "min" , getMin() );
- cmd.append( "max" , getMax() );
- cmd.append( "from" , getShardId());
- cmd.append( "splitKeys" , m );
- cmd.append("configdb", grid.catalogManager()->connectionString().toString());
- cmd.append("epoch", _manager->getVersion().epoch());
- BSONObj cmdObj = cmd.obj();
+ builder.append("waitForDelete", waitForDelete);
+ builder.append(LiteParsedQuery::cmdOptionMaxTimeMS, maxTimeMS);
+ builder.append("epoch", _manager->getVersion().epoch());
- BSONObj dummy;
- if (res == NULL) {
- res = &dummy;
- }
+ ScopedDbConnection fromconn(from->getConnString());
+ bool worked = fromconn->runCommand("admin", builder.done(), res);
+ fromconn.done();
- if (!conn->runCommand("admin", cmdObj, *res)) {
- string msg(str::stream() << "splitChunk failed - cmd: "
- << cmdObj << " result: " << *res);
- warning() << msg;
- conn.done();
+ LOG(worked ? 1 : 0) << "moveChunk result: " << res;
- return Status(ErrorCodes::SplitFailed, msg);
- }
+ // if succeeded, needs to reload to pick up the new location
+ // if failed, mongos may be stale
+ // reload is excessive here as the failure could be simply because collection metadata is taken
+ _manager->reload();
- conn.done();
-
- // force reload of config
- _manager->reload();
-
- return Status::OK();
- }
-
- bool Chunk::moveAndCommit(const ShardId& toShardId,
- long long chunkSize /* bytes */,
- const WriteConcernOptions* writeConcern,
- bool waitForDelete,
- int maxTimeMS,
- BSONObj& res) const {
- uassert(10167,
- "can't move shard to its current location!",
- getShardId() != toShardId);
-
- log() << "moving chunk ns: " << _manager->getns() << " moving ( " << toString() << ") "
- << getShardId() << " -> " << toShardId;
-
- const auto from = grid.shardRegistry()->getShard(getShardId());
-
- BSONObjBuilder builder;
- builder.append("moveChunk", _manager->getns());
- builder.append("from", from->getConnString().toString());
- {
- const auto toShard = grid.shardRegistry()->getShard(toShardId);
- builder.append("to", toShard->getConnString().toString());
- }
- // NEEDED FOR 2.0 COMPATIBILITY
- builder.append("fromShard", from->getId());
- builder.append("toShard", toShardId);
- ///////////////////////////////
- builder.append("min", _min);
- builder.append("max", _max);
- builder.append("maxChunkSizeBytes", chunkSize);
- builder.append("configdb", grid.catalogManager()->connectionString().toString());
-
- // For legacy secondary throttle setting.
- bool secondaryThrottle = true;
- if (writeConcern &&
- writeConcern->wNumNodes <= 1 &&
- writeConcern->wMode.empty()) {
- secondaryThrottle = false;
- }
+ return worked;
+}
- builder.append("secondaryThrottle", secondaryThrottle);
+bool Chunk::splitIfShould(long dataWritten) const {
+ dassert(ShouldAutoSplit);
+ LastError::Disabled d(&LastError::get(cc()));
- if (secondaryThrottle && writeConcern) {
- builder.append("writeConcern", writeConcern->toBSON());
+ try {
+ _dataWritten += dataWritten;
+ int splitThreshold = getManager()->getCurrentDesiredChunkSize();
+ if (_minIsInf() || _maxIsInf()) {
+ splitThreshold = (int)((double)splitThreshold * .9);
}
- builder.append("waitForDelete", waitForDelete);
- builder.append(LiteParsedQuery::cmdOptionMaxTimeMS, maxTimeMS);
- builder.append("epoch", _manager->getVersion().epoch());
-
- ScopedDbConnection fromconn(from->getConnString());
- bool worked = fromconn->runCommand("admin", builder.done(), res);
- fromconn.done();
+ if (_dataWritten < splitThreshold / ChunkManager::SplitHeuristics::splitTestFactor)
+ return false;
- LOG( worked ? 1 : 0 ) << "moveChunk result: " << res;
+ if (!getManager()->_splitHeuristics._splitTickets.tryAcquire()) {
+ LOG(1) << "won't auto split because not enough tickets: " << getManager()->getns();
+ return false;
+ }
+ TicketHolderReleaser releaser(&(getManager()->_splitHeuristics._splitTickets));
- // if succeeded, needs to reload to pick up the new location
- // if failed, mongos may be stale
- // reload is excessive here as the failure could be simply because collection metadata is taken
- _manager->reload();
+ // this is a bit ugly
+ // we need it so that mongos blocks for the writes to actually be committed
+ // this does mean mongos has more back pressure than mongod alone
+ // since it nots 100% tcp queue bound
+ // this was implicit before since we did a splitVector on the same socket
+ ShardConnection::sync();
- return worked;
- }
+ LOG(1) << "about to initiate autosplit: " << *this << " dataWritten: " << _dataWritten
+ << " splitThreshold: " << splitThreshold;
- bool Chunk::splitIfShould( long dataWritten ) const {
- dassert( ShouldAutoSplit );
- LastError::Disabled d(&LastError::get(cc()));
+ BSONObj res;
+ size_t splitCount = 0;
+ Status status = split(Chunk::autoSplitInternal, &splitCount, &res);
+ if (!status.isOK()) {
+ // Split would have issued a message if we got here. This means there wasn't enough
+ // data to split, so don't want to try again until considerable more data
+ _dataWritten = 0;
+ return false;
+ }
- try {
- _dataWritten += dataWritten;
- int splitThreshold = getManager()->getCurrentDesiredChunkSize();
- if (_minIsInf() || _maxIsInf()) {
- splitThreshold = (int)((double)splitThreshold * .9);
- }
+ if (_maxIsInf() || _minIsInf()) {
+ // we don't want to reset _dataWritten since we kind of want to check the other side right away
+ } else {
+ // we're splitting, so should wait a bit
+ _dataWritten = 0;
+ }
- if ( _dataWritten < splitThreshold / ChunkManager::SplitHeuristics::splitTestFactor )
- return false;
-
- if ( ! getManager()->_splitHeuristics._splitTickets.tryAcquire() ) {
- LOG(1) << "won't auto split because not enough tickets: " << getManager()->getns();
- return false;
- }
- TicketHolderReleaser releaser( &(getManager()->_splitHeuristics._splitTickets) );
-
- // this is a bit ugly
- // we need it so that mongos blocks for the writes to actually be committed
- // this does mean mongos has more back pressure than mongod alone
- // since it nots 100% tcp queue bound
- // this was implicit before since we did a splitVector on the same socket
- ShardConnection::sync();
-
- LOG(1) << "about to initiate autosplit: " << *this << " dataWritten: " << _dataWritten << " splitThreshold: " << splitThreshold;
-
- BSONObj res;
- size_t splitCount = 0;
- Status status = split(Chunk::autoSplitInternal,
- &splitCount,
- &res);
+ bool shouldBalance = grid.getConfigShouldBalance();
+ if (shouldBalance) {
+ auto status = grid.catalogManager()->getCollection(_manager->getns());
if (!status.isOK()) {
- // Split would have issued a message if we got here. This means there wasn't enough
- // data to split, so don't want to try again until considerable more data
- _dataWritten = 0;
+ log() << "Auto-split for " << _manager->getns()
+ << " failed to load collection metadata due to " << status.getStatus();
return false;
}
-
- if (_maxIsInf() || _minIsInf()) {
- // we don't want to reset _dataWritten since we kind of want to check the other side right away
- }
- else {
- // we're splitting, so should wait a bit
- _dataWritten = 0;
- }
-
- bool shouldBalance = grid.getConfigShouldBalance();
- if (shouldBalance) {
- auto status = grid.catalogManager()->getCollection(_manager->getns());
- if (!status.isOK()) {
- log() << "Auto-split for " << _manager->getns()
- << " failed to load collection metadata due to " << status.getStatus();
- return false;
- }
- shouldBalance = status.getValue().getAllowBalance();
- }
+ shouldBalance = status.getValue().getAllowBalance();
+ }
- log() << "autosplitted " << _manager->getns()
- << " shard: " << toString()
- << " into " << (splitCount + 1)
- << " (splitThreshold " << splitThreshold << ")"
+ log() << "autosplitted " << _manager->getns() << " shard: " << toString() << " into "
+ << (splitCount + 1) << " (splitThreshold " << splitThreshold << ")"
#ifdef MONGO_CONFIG_DEBUG_BUILD
- << " size: " << getPhysicalSize() // slow - but can be useful when debugging
+ << " size: " << getPhysicalSize() // slow - but can be useful when debugging
#endif
- << ( res["shouldMigrate"].eoo() ? "" : (string)" (migrate suggested" +
- ( shouldBalance ? ")" : ", but no migrations allowed)" ) );
-
- // Top chunk optimization - try to move the top chunk out of this shard
- // to prevent the hot spot from staying on a single shard. This is based on
- // the assumption that succeeding inserts will fall on the top chunk.
- BSONElement shouldMigrate = res["shouldMigrate"]; // not in mongod < 1.9.1 but that is ok
- if ( ! shouldMigrate.eoo() && shouldBalance ){
- BSONObj range = shouldMigrate.embeddedObject();
-
- ChunkType chunkToMove;
- {
- const auto shard = grid.shardRegistry()->getShard(getShardId());
- chunkToMove.setShard(shard->toString());
- }
- chunkToMove.setMin(range["min"].embeddedObject());
- chunkToMove.setMax(range["max"].embeddedObject());
-
- tryMoveToOtherShard(*_manager, chunkToMove);
+ << (res["shouldMigrate"].eoo() ? "" : (string) " (migrate suggested" +
+ (shouldBalance ? ")" : ", but no migrations allowed)"));
+
+ // Top chunk optimization - try to move the top chunk out of this shard
+ // to prevent the hot spot from staying on a single shard. This is based on
+ // the assumption that succeeding inserts will fall on the top chunk.
+ BSONElement shouldMigrate = res["shouldMigrate"]; // not in mongod < 1.9.1 but that is ok
+ if (!shouldMigrate.eoo() && shouldBalance) {
+ BSONObj range = shouldMigrate.embeddedObject();
+
+ ChunkType chunkToMove;
+ {
+ const auto shard = grid.shardRegistry()->getShard(getShardId());
+ chunkToMove.setShard(shard->toString());
}
+ chunkToMove.setMin(range["min"].embeddedObject());
+ chunkToMove.setMax(range["max"].embeddedObject());
- return true;
-
+ tryMoveToOtherShard(*_manager, chunkToMove);
}
- catch ( DBException& e ) {
- // TODO: Make this better - there are lots of reasons a split could fail
- // Random so that we don't sync up with other failed splits
- _dataWritten = mkDataWritten();
-
- // if the collection lock is taken (e.g. we're migrating), it is fine for the split to fail.
- warning() << "could not autosplit collection " << _manager->getns() << causedBy( e );
- return false;
- }
- }
-
- const ConnectionString& Chunk::_getShardConnectionString() const {
- const auto shard = grid.shardRegistry()->getShard(getShardId());
- return shard->getConnString();
- }
-
- long Chunk::getPhysicalSize() const {
- ScopedDbConnection conn(_getShardConnectionString());
+ return true;
- BSONObj result;
- uassert( 10169 , "datasize failed!" , conn->runCommand( "admin" ,
- BSON( "datasize" << _manager->getns()
- << "keyPattern" << _manager->getShardKeyPattern().toBSON()
- << "min" << getMin()
- << "max" << getMax()
- << "maxSize" << ( MaxChunkSize + 1 )
- << "estimate" << true
- ) , result ) );
+ } catch (DBException& e) {
+ // TODO: Make this better - there are lots of reasons a split could fail
+ // Random so that we don't sync up with other failed splits
+ _dataWritten = mkDataWritten();
- conn.done();
- return (long)result["size"].number();
+ // if the collection lock is taken (e.g. we're migrating), it is fine for the split to fail.
+ warning() << "could not autosplit collection " << _manager->getns() << causedBy(e);
+ return false;
}
-
- void Chunk::appendShortVersion( const char * name , BSONObjBuilder& b ) const {
- BSONObjBuilder bb( b.subobjStart( name ) );
- bb.append(ChunkType::min(), _min);
- bb.append(ChunkType::max(), _max);
- bb.done();
+}
+
+const ConnectionString& Chunk::_getShardConnectionString() const {
+ const auto shard = grid.shardRegistry()->getShard(getShardId());
+ return shard->getConnString();
+}
+
+long Chunk::getPhysicalSize() const {
+ ScopedDbConnection conn(_getShardConnectionString());
+
+ BSONObj result;
+ uassert(10169,
+ "datasize failed!",
+ conn->runCommand("admin",
+ BSON("datasize" << _manager->getns() << "keyPattern"
+ << _manager->getShardKeyPattern().toBSON() << "min"
+ << getMin() << "max" << getMax() << "maxSize"
+ << (MaxChunkSize + 1) << "estimate" << true),
+ result));
+
+ conn.done();
+ return (long)result["size"].number();
+}
+
+void Chunk::appendShortVersion(const char* name, BSONObjBuilder& b) const {
+ BSONObjBuilder bb(b.subobjStart(name));
+ bb.append(ChunkType::min(), _min);
+ bb.append(ChunkType::max(), _max);
+ bb.done();
+}
+
+bool Chunk::operator==(const Chunk& s) const {
+ return _min.woCompare(s._min) == 0 && _max.woCompare(s._max) == 0;
+}
+
+void Chunk::serialize(BSONObjBuilder& to, ChunkVersion myLastMod) {
+ to.append("_id", genID(_manager->getns(), _min));
+
+ if (myLastMod.isSet()) {
+ myLastMod.addToBSON(to, ChunkType::DEPRECATED_lastmod());
+ } else if (_lastmod.isSet()) {
+ _lastmod.addToBSON(to, ChunkType::DEPRECATED_lastmod());
+ } else {
+ verify(0);
}
- bool Chunk::operator==( const Chunk& s ) const {
- return _min.woCompare( s._min ) == 0 && _max.woCompare( s._max ) == 0;
- }
+ to << ChunkType::ns(_manager->getns());
+ to << ChunkType::min(_min);
+ to << ChunkType::max(_max);
+ to << ChunkType::shard(_shardId);
+}
- void Chunk::serialize(BSONObjBuilder& to,ChunkVersion myLastMod) {
+string Chunk::genID() const {
+ return genID(_manager->getns(), _min);
+}
- to.append( "_id" , genID( _manager->getns() , _min ) );
+string Chunk::genID(const string& ns, const BSONObj& o) {
+ StringBuilder buf;
+ buf << ns << "-";
- if ( myLastMod.isSet() ) {
- myLastMod.addToBSON(to, ChunkType::DEPRECATED_lastmod());
- }
- else if ( _lastmod.isSet() ) {
- _lastmod.addToBSON(to, ChunkType::DEPRECATED_lastmod());
- }
- else {
- verify(0);
- }
-
- to << ChunkType::ns(_manager->getns());
- to << ChunkType::min(_min);
- to << ChunkType::max(_max);
- to << ChunkType::shard(_shardId);
+ BSONObjIterator i(o);
+ while (i.more()) {
+ BSONElement e = i.next();
+ buf << e.fieldName() << "_" << e.toString(false, true);
}
- string Chunk::genID() const {
- return genID(_manager->getns(), _min);
+ return buf.str();
+}
+
+string Chunk::toString() const {
+ stringstream ss;
+ ss << ChunkType::ns() << ": " << _manager->getns() << ", " << ChunkType::shard() << ": "
+ << _shardId << ", " << ChunkType::DEPRECATED_lastmod() << ": " << _lastmod.toString() << ", "
+ << ChunkType::min() << ": " << _min << ", " << ChunkType::max() << ": " << _max;
+ return ss.str();
+}
+
+void Chunk::markAsJumbo() const {
+ // set this first
+ // even if we can't set it in the db
+ // at least this mongos won't try and keep moving
+ _jumbo = true;
+
+ Status result = grid.catalogManager()->update(ChunkType::ConfigNS,
+ BSON(ChunkType::name(genID())),
+ BSON("$set" << BSON(ChunkType::jumbo(true))),
+ false, // upsert
+ false, // multi
+ NULL);
+ if (!result.isOK()) {
+ warning() << "couldn't set jumbo for chunk: " << genID() << result.reason();
}
-
- string Chunk::genID( const string& ns , const BSONObj& o ) {
- StringBuilder buf;
- buf << ns << "-";
-
- BSONObjIterator i(o);
- while ( i.more() ) {
- BSONElement e = i.next();
- buf << e.fieldName() << "_" << e.toString(false, true);
- }
-
- return buf.str();
- }
-
- string Chunk::toString() const {
- stringstream ss;
- ss << ChunkType::ns() << ": " << _manager->getns() << ", "
- << ChunkType::shard() << ": " << _shardId << ", "
- << ChunkType::DEPRECATED_lastmod() << ": " << _lastmod.toString() << ", "
- << ChunkType::min() << ": " << _min << ", "
- << ChunkType::max() << ": " << _max;
- return ss.str();
- }
-
- void Chunk::markAsJumbo() const {
- // set this first
- // even if we can't set it in the db
- // at least this mongos won't try and keep moving
- _jumbo = true;
-
- Status result = grid.catalogManager()->update(ChunkType::ConfigNS,
- BSON(ChunkType::name(genID())),
- BSON("$set" << BSON(ChunkType::jumbo(true))),
- false, // upsert
- false, // multi
- NULL);
- if (!result.isOK()) {
- warning() << "couldn't set jumbo for chunk: " << genID() << result.reason();
- }
+}
+
+void Chunk::refreshChunkSize() {
+ auto chunkSizeSettingsResult =
+ grid.catalogManager()->getGlobalSettings(SettingsType::ChunkSizeDocKey);
+ if (!chunkSizeSettingsResult.isOK()) {
+ log() << chunkSizeSettingsResult.getStatus();
+ return;
}
+ SettingsType chunkSizeSettings = chunkSizeSettingsResult.getValue();
+ int csize = chunkSizeSettings.getChunkSizeMB();
- void Chunk::refreshChunkSize() {
- auto chunkSizeSettingsResult =
- grid.catalogManager()->getGlobalSettings(SettingsType::ChunkSizeDocKey);
- if (!chunkSizeSettingsResult.isOK()) {
- log() << chunkSizeSettingsResult.getStatus();
- return;
- }
- SettingsType chunkSizeSettings = chunkSizeSettingsResult.getValue();
- int csize = chunkSizeSettings.getChunkSizeMB();
-
- LOG(1) << "Refreshing MaxChunkSize: " << csize << "MB";
-
- if (csize != Chunk::MaxChunkSize/(1024*1024)) {
- log() << "MaxChunkSize changing from " << Chunk::MaxChunkSize/(1024*1024) << "MB"
- << " to " << csize << "MB";
- }
+ LOG(1) << "Refreshing MaxChunkSize: " << csize << "MB";
- if ( !setMaxChunkSizeSizeMB( csize ) ) {
- warning() << "invalid MaxChunkSize: " << csize;
- }
+ if (csize != Chunk::MaxChunkSize / (1024 * 1024)) {
+ log() << "MaxChunkSize changing from " << Chunk::MaxChunkSize / (1024 * 1024) << "MB"
+ << " to " << csize << "MB";
}
- bool Chunk::setMaxChunkSizeSizeMB( int newMaxChunkSize ) {
- if ( newMaxChunkSize < 1 )
- return false;
- if ( newMaxChunkSize > 1024 )
- return false;
- MaxChunkSize = newMaxChunkSize * 1024 * 1024;
- return true;
+ if (!setMaxChunkSizeSizeMB(csize)) {
+ warning() << "invalid MaxChunkSize: " << csize;
}
-
-} // namespace mongo
+}
+
+bool Chunk::setMaxChunkSizeSizeMB(int newMaxChunkSize) {
+ if (newMaxChunkSize < 1)
+ return false;
+ if (newMaxChunkSize > 1024)
+ return false;
+ MaxChunkSize = newMaxChunkSize * 1024 * 1024;
+ return true;
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/chunk.h b/src/mongo/s/chunk.h
index b90da174961..d4fd7857d30 100644
--- a/src/mongo/s/chunk.h
+++ b/src/mongo/s/chunk.h
@@ -33,251 +33,271 @@
namespace mongo {
- class ChunkManager;
- struct WriteConcernOptions;
+class ChunkManager;
+struct WriteConcernOptions;
+
+/**
+ config.chunks
+ { ns : "alleyinsider.fs.chunks" , min : {} , max : {} , server : "localhost:30001" }
+
+ x is in a shard iff
+ min <= x < max
+ */
+class Chunk {
+ MONGO_DISALLOW_COPYING(Chunk);
+
+public:
+ enum SplitPointMode {
+ // Determines the split points that will make the current chunk smaller than
+ // the current chunk size setting. Gives empty result if chunk is not big enough.
+ normal,
+
+ // Will get a split which approximately splits the chunk into 2 halves,
+ // regardless of the size of the chunk.
+ atMedian,
+
+ // Behaves like normal, with additional special heuristics for "top chunks"
+ // (the 1 or 2 chunks in the extreme ends of the chunk key space).
+ autoSplitInternal
+ };
+
+ Chunk(const ChunkManager* info, BSONObj from);
+ Chunk(const ChunkManager* info,
+ const BSONObj& min,
+ const BSONObj& max,
+ const ShardId& shardId,
+ ChunkVersion lastmod = ChunkVersion());
+
+ //
+ // serialization support
+ //
+
+ void serialize(BSONObjBuilder& to, ChunkVersion myLastMod = ChunkVersion(0, 0, OID()));
+
+ //
+ // chunk boundary support
+ //
+
+ const BSONObj& getMin() const {
+ return _min;
+ }
+ const BSONObj& getMax() const {
+ return _max;
+ }
+
+ // Returns true if this chunk contains the given shard key, and false otherwise
+ //
+ // Note: this function takes an extracted *key*, not an original document
+ // (the point may be computed by, say, hashing a given field or projecting
+ // to a subset of fields).
+ bool containsKey(const BSONObj& shardKey) const;
+
+ std::string genID() const;
+ static std::string genID(const std::string& ns, const BSONObj& min);
+
+ //
+ // chunk version support
+ //
+
+ void appendShortVersion(const char* name, BSONObjBuilder& b) const;
+
+ ChunkVersion getLastmod() const {
+ return _lastmod;
+ }
+ void setLastmod(ChunkVersion v) {
+ _lastmod = v;
+ }
+
+ //
+ // split support
+ //
+
+ long long getBytesWritten() const {
+ return _dataWritten;
+ }
+ // Const since _dataWritten is mutable and a heuristic
+ // TODO: Split data tracking and chunk information
+ void setBytesWritten(long long bytesWritten) const {
+ _dataWritten = bytesWritten;
+ }
/**
- config.chunks
- { ns : "alleyinsider.fs.chunks" , min : {} , max : {} , server : "localhost:30001" }
+ * if the amount of data written nears the max size of a shard
+ * then we check the real size, and if its too big, we split
+ * @return if something was split
+ */
+ bool splitIfShould(long dataWritten) const;
- x is in a shard iff
- min <= x < max
+ /**
+ * Splits this chunk at a non-specificed split key to be chosen by the mongod holding this chunk.
+ *
+ * @param mode
+ * @param res the object containing details about the split execution
+ * @param resultingSplits the number of resulting split points. Set to NULL to ignore.
+ *
+ * @throws UserException
*/
- class Chunk {
- MONGO_DISALLOW_COPYING(Chunk);
- public:
- enum SplitPointMode {
- // Determines the split points that will make the current chunk smaller than
- // the current chunk size setting. Gives empty result if chunk is not big enough.
- normal,
-
- // Will get a split which approximately splits the chunk into 2 halves,
- // regardless of the size of the chunk.
- atMedian,
-
- // Behaves like normal, with additional special heuristics for "top chunks"
- // (the 1 or 2 chunks in the extreme ends of the chunk key space).
- autoSplitInternal
- };
-
- Chunk( const ChunkManager * info , BSONObj from);
- Chunk( const ChunkManager * info ,
- const BSONObj& min,
- const BSONObj& max,
- const ShardId& shardId,
- ChunkVersion lastmod = ChunkVersion() );
-
- //
- // serialization support
- //
-
- void serialize(BSONObjBuilder& to, ChunkVersion myLastMod = ChunkVersion(0, 0, OID()));
-
- //
- // chunk boundary support
- //
-
- const BSONObj& getMin() const { return _min; }
- const BSONObj& getMax() const { return _max; }
-
- // Returns true if this chunk contains the given shard key, and false otherwise
- //
- // Note: this function takes an extracted *key*, not an original document
- // (the point may be computed by, say, hashing a given field or projecting
- // to a subset of fields).
- bool containsKey( const BSONObj& shardKey ) const;
-
- std::string genID() const;
- static std::string genID( const std::string& ns , const BSONObj& min );
-
- //
- // chunk version support
- //
-
- void appendShortVersion( const char * name , BSONObjBuilder& b ) const;
-
- ChunkVersion getLastmod() const { return _lastmod; }
- void setLastmod( ChunkVersion v ) { _lastmod = v; }
-
- //
- // split support
- //
-
- long long getBytesWritten() const { return _dataWritten; }
- // Const since _dataWritten is mutable and a heuristic
- // TODO: Split data tracking and chunk information
- void setBytesWritten(long long bytesWritten) const { _dataWritten = bytesWritten; }
-
- /**
- * if the amount of data written nears the max size of a shard
- * then we check the real size, and if its too big, we split
- * @return if something was split
- */
- bool splitIfShould( long dataWritten ) const;
-
- /**
- * Splits this chunk at a non-specificed split key to be chosen by the mongod holding this chunk.
- *
- * @param mode
- * @param res the object containing details about the split execution
- * @param resultingSplits the number of resulting split points. Set to NULL to ignore.
- *
- * @throws UserException
- */
- Status split(SplitPointMode mode,
- size_t* resultingSplits,
- BSONObj* res) const;
-
- /**
- * Splits this chunk at the given key (or keys)
- *
- * @param splitPoints the vector of keys that should be used to divide this chunk
- * @param res the object containing details about the split execution
- *
- * @throws UserException
- */
- Status multiSplit(const std::vector<BSONObj>& splitPoints, BSONObj* res) const;
-
- /**
- * Asks the mongod holding this chunk to find a key that approximately divides this chunk in two
- *
- * @param medianKey the key that divides this chunk, if there is one, or empty
- */
- void pickMedianKey( BSONObj& medianKey ) const;
-
- /**
- * @param splitPoints vector to be filled in
- * @param chunkSize chunk size to target in bytes
- * @param maxPoints limits the number of split points that are needed, zero is max (optional)
- * @param maxObjs limits the number of objects in each chunk, zero is as max (optional)
- */
- void pickSplitVector(std::vector<BSONObj>& splitPoints,
- long long chunkSize,
- int maxPoints = 0,
- int maxObjs = 0) const;
-
- //
- // migration support
- //
-
- /**
- * Issues a migrate request for this chunk
- *
- * @param to shard to move this chunk to
- * @param chunSize maximum number of bytes beyond which the migrate should no go trhough
- * @param writeConcern detailed write concern. NULL means the default write concern.
- * @param waitForDelete whether chunk move should wait for cleanup or return immediately
- * @param maxTimeMS max time for the migrate request
- * @param res the object containing details about the migrate execution
- * @return true if move was successful
- */
- bool moveAndCommit(const ShardId& to,
- long long chunkSize,
- const WriteConcernOptions* writeConcern,
- bool waitForDelete,
- int maxTimeMS,
- BSONObj& res) const;
-
- /**
- * @return size of shard in bytes
- * talks to mongod to do this
- */
- long getPhysicalSize() const;
-
- /**
- * marks this chunk as a jumbo chunk
- * that means the chunk will be inelligble for migrates
- */
- void markAsJumbo() const;
-
- bool isJumbo() const { return _jumbo; }
-
- /**
- * Attempt to refresh maximum chunk size from config.
- */
- static void refreshChunkSize();
-
- /**
- * sets MaxChunkSize
- * 1 <= newMaxChunkSize <= 1024
- * @return true if newMaxChunkSize is valid and was set
- */
- static bool setMaxChunkSizeSizeMB( int newMaxChunkSize );
-
- //
- // public constants
- //
-
- static long long MaxChunkSize;
- static int MaxObjectPerChunk;
- static bool ShouldAutoSplit;
-
- //
- // accessors and helpers
- //
-
- std::string toString() const;
-
- friend std::ostream& operator << (std::ostream& out, const Chunk& c) { return (out << c.toString()); }
-
- // chunk equality is determined by comparing the min and max bounds of the chunk
- bool operator==(const Chunk& s) const;
- bool operator!=(const Chunk& s) const { return ! ( *this == s ); }
-
- ShardId getShardId() const { return _shardId; }
- const ChunkManager* getManager() const { return _manager; }
-
- private:
-
- /**
- * Returns the connection string for the shard on which this chunk lives on.
- */
- const ConnectionString& _getShardConnectionString() const;
-
- // if min/max key is pos/neg infinity
- bool _minIsInf() const;
- bool _maxIsInf() const;
-
- // The chunk manager, which owns this chunk. Not owned by the chunk.
- const ChunkManager* _manager;
-
- BSONObj _min;
- BSONObj _max;
- ShardId _shardId;
- ChunkVersion _lastmod;
- mutable bool _jumbo;
-
- // transient stuff
-
- mutable long long _dataWritten;
-
- // methods, etc..
-
- /**
- * Returns the split point that will result in one of the chunk having exactly one
- * document. Also returns an empty document if the split point cannot be determined.
- *
- * @param doSplitAtLower determines which side of the split will have exactly one document.
- * True means that the split point chosen will be closer to the lower bound.
- *
- * Warning: this assumes that the shard key is not "special"- that is, the shardKeyPattern
- * is simply an ordered list of ascending/descending field names. Examples:
- * {a : 1, b : -1} is not special. {a : "hashed"} is.
- */
- BSONObj _getExtremeKey(bool doSplitAtLower) const;
-
- /**
- * Determines the appropriate split points for this chunk.
- *
- * @param atMedian perform a single split at the middle of this chunk.
- * @param splitPoints out parameter containing the chosen split points. Can be empty.
- */
- void determineSplitPoints(bool atMedian, std::vector<BSONObj>* splitPoints) const;
-
- /** initializes _dataWritten with a random value so that a mongos restart wouldn't cause delay in splitting */
- static int mkDataWritten();
- };
+ Status split(SplitPointMode mode, size_t* resultingSplits, BSONObj* res) const;
+
+ /**
+ * Splits this chunk at the given key (or keys)
+ *
+ * @param splitPoints the vector of keys that should be used to divide this chunk
+ * @param res the object containing details about the split execution
+ *
+ * @throws UserException
+ */
+ Status multiSplit(const std::vector<BSONObj>& splitPoints, BSONObj* res) const;
+
+ /**
+ * Asks the mongod holding this chunk to find a key that approximately divides this chunk in two
+ *
+ * @param medianKey the key that divides this chunk, if there is one, or empty
+ */
+ void pickMedianKey(BSONObj& medianKey) const;
+
+ /**
+ * @param splitPoints vector to be filled in
+ * @param chunkSize chunk size to target in bytes
+ * @param maxPoints limits the number of split points that are needed, zero is max (optional)
+ * @param maxObjs limits the number of objects in each chunk, zero is as max (optional)
+ */
+ void pickSplitVector(std::vector<BSONObj>& splitPoints,
+ long long chunkSize,
+ int maxPoints = 0,
+ int maxObjs = 0) const;
+
+ //
+ // migration support
+ //
+
+ /**
+ * Issues a migrate request for this chunk
+ *
+ * @param to shard to move this chunk to
+ * @param chunSize maximum number of bytes beyond which the migrate should no go trhough
+ * @param writeConcern detailed write concern. NULL means the default write concern.
+ * @param waitForDelete whether chunk move should wait for cleanup or return immediately
+ * @param maxTimeMS max time for the migrate request
+ * @param res the object containing details about the migrate execution
+ * @return true if move was successful
+ */
+ bool moveAndCommit(const ShardId& to,
+ long long chunkSize,
+ const WriteConcernOptions* writeConcern,
+ bool waitForDelete,
+ int maxTimeMS,
+ BSONObj& res) const;
+
+ /**
+ * @return size of shard in bytes
+ * talks to mongod to do this
+ */
+ long getPhysicalSize() const;
+
+ /**
+ * marks this chunk as a jumbo chunk
+ * that means the chunk will be inelligble for migrates
+ */
+ void markAsJumbo() const;
+
+ bool isJumbo() const {
+ return _jumbo;
+ }
+
+ /**
+ * Attempt to refresh maximum chunk size from config.
+ */
+ static void refreshChunkSize();
+
+ /**
+ * sets MaxChunkSize
+ * 1 <= newMaxChunkSize <= 1024
+ * @return true if newMaxChunkSize is valid and was set
+ */
+ static bool setMaxChunkSizeSizeMB(int newMaxChunkSize);
+
+ //
+ // public constants
+ //
+
+ static long long MaxChunkSize;
+ static int MaxObjectPerChunk;
+ static bool ShouldAutoSplit;
+
+ //
+ // accessors and helpers
+ //
+
+ std::string toString() const;
+
+ friend std::ostream& operator<<(std::ostream& out, const Chunk& c) {
+ return (out << c.toString());
+ }
+
+ // chunk equality is determined by comparing the min and max bounds of the chunk
+ bool operator==(const Chunk& s) const;
+ bool operator!=(const Chunk& s) const {
+ return !(*this == s);
+ }
+
+ ShardId getShardId() const {
+ return _shardId;
+ }
+ const ChunkManager* getManager() const {
+ return _manager;
+ }
+
+private:
+ /**
+ * Returns the connection string for the shard on which this chunk lives on.
+ */
+ const ConnectionString& _getShardConnectionString() const;
+
+ // if min/max key is pos/neg infinity
+ bool _minIsInf() const;
+ bool _maxIsInf() const;
+
+ // The chunk manager, which owns this chunk. Not owned by the chunk.
+ const ChunkManager* _manager;
+
+ BSONObj _min;
+ BSONObj _max;
+ ShardId _shardId;
+ ChunkVersion _lastmod;
+ mutable bool _jumbo;
+
+ // transient stuff
+
+ mutable long long _dataWritten;
+
+ // methods, etc..
+
+ /**
+ * Returns the split point that will result in one of the chunk having exactly one
+ * document. Also returns an empty document if the split point cannot be determined.
+ *
+ * @param doSplitAtLower determines which side of the split will have exactly one document.
+ * True means that the split point chosen will be closer to the lower bound.
+ *
+ * Warning: this assumes that the shard key is not "special"- that is, the shardKeyPattern
+ * is simply an ordered list of ascending/descending field names. Examples:
+ * {a : 1, b : -1} is not special. {a : "hashed"} is.
+ */
+ BSONObj _getExtremeKey(bool doSplitAtLower) const;
+
+ /**
+ * Determines the appropriate split points for this chunk.
+ *
+ * @param atMedian perform a single split at the middle of this chunk.
+ * @param splitPoints out parameter containing the chosen split points. Can be empty.
+ */
+ void determineSplitPoints(bool atMedian, std::vector<BSONObj>* splitPoints) const;
+
+ /** initializes _dataWritten with a random value so that a mongos restart wouldn't cause delay in splitting */
+ static int mkDataWritten();
+};
- typedef std::shared_ptr<const Chunk> ChunkPtr;
+typedef std::shared_ptr<const Chunk> ChunkPtr;
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/chunk_diff.cpp b/src/mongo/s/chunk_diff.cpp
index 488ea85728b..b270a54d9d3 100644
--- a/src/mongo/s/chunk_diff.cpp
+++ b/src/mongo/s/chunk_diff.cpp
@@ -42,223 +42,215 @@
namespace mongo {
- template <class ValType, class ShardType>
- ConfigDiffTracker<ValType, ShardType>::ConfigDiffTracker() {
- _ns.clear();
- _currMap = NULL;
- _maxVersion = NULL;
- _maxShardVersions = NULL;
- _validDiffs = 0;
- }
+template <class ValType, class ShardType>
+ConfigDiffTracker<ValType, ShardType>::ConfigDiffTracker() {
+ _ns.clear();
+ _currMap = NULL;
+ _maxVersion = NULL;
+ _maxShardVersions = NULL;
+ _validDiffs = 0;
+}
+
+template <class ValType, class ShardType>
+ConfigDiffTracker<ValType, ShardType>::~ConfigDiffTracker() = default;
+
+template <class ValType, class ShardType>
+void ConfigDiffTracker<ValType, ShardType>::attach(const std::string& ns,
+ RangeMap& currMap,
+ ChunkVersion& maxVersion,
+ MaxChunkVersionMap& maxShardVersions) {
+ _ns = ns;
+ _currMap = &currMap;
+ _maxVersion = &maxVersion;
+ _maxShardVersions = &maxShardVersions;
+ _validDiffs = 0;
+}
+
+template <class ValType, class ShardType>
+bool ConfigDiffTracker<ValType, ShardType>::isOverlapping(const BSONObj& min, const BSONObj& max) {
+ RangeOverlap overlap = overlappingRange(min, max);
+
+ return overlap.first != overlap.second;
+}
+
+template <class ValType, class ShardType>
+void ConfigDiffTracker<ValType, ShardType>::removeOverlapping(const BSONObj& min,
+ const BSONObj& max) {
+ _assertAttached();
- template <class ValType, class ShardType>
- ConfigDiffTracker<ValType, ShardType>::~ConfigDiffTracker() = default;
-
- template <class ValType, class ShardType>
- void ConfigDiffTracker<ValType, ShardType>::attach(const std::string& ns,
- RangeMap& currMap,
- ChunkVersion& maxVersion,
- MaxChunkVersionMap& maxShardVersions) {
- _ns = ns;
- _currMap = &currMap;
- _maxVersion = &maxVersion;
- _maxShardVersions = &maxShardVersions;
- _validDiffs = 0;
- }
+ RangeOverlap overlap = overlappingRange(min, max);
- template <class ValType, class ShardType>
- bool ConfigDiffTracker<ValType, ShardType>::isOverlapping(const BSONObj& min,
- const BSONObj& max) {
- RangeOverlap overlap = overlappingRange(min, max);
+ _currMap->erase(overlap.first, overlap.second);
+}
- return overlap.first != overlap.second;
- }
+template <class ValType, class ShardType>
+typename ConfigDiffTracker<ValType, ShardType>::RangeOverlap
+ConfigDiffTracker<ValType, ShardType>::overlappingRange(const BSONObj& min, const BSONObj& max) {
+ _assertAttached();
+
+ typename RangeMap::iterator low;
+ typename RangeMap::iterator high;
- template <class ValType, class ShardType>
- void ConfigDiffTracker<ValType, ShardType>::removeOverlapping(const BSONObj& min,
- const BSONObj& max) {
- _assertAttached();
+ if (isMinKeyIndexed()) {
+ // Returns the first chunk with a min key that is >= min - implies the
+ // previous chunk cannot overlap min
+ low = _currMap->lower_bound(min);
- RangeOverlap overlap = overlappingRange(min, max);
+ // Returns the first chunk with a min key that is >= max - implies the
+ // chunk does not overlap max
+ high = _currMap->lower_bound(max);
+ } else {
+ // Returns the first chunk with a max key that is > min - implies that
+ // the chunk overlaps min
+ low = _currMap->upper_bound(min);
- _currMap->erase(overlap.first, overlap.second);
+ // Returns the first chunk with a max key that is > max - implies that
+ // the next chunk cannot not overlap max
+ high = _currMap->upper_bound(max);
}
- template <class ValType, class ShardType>
- typename ConfigDiffTracker<ValType, ShardType>::RangeOverlap
- ConfigDiffTracker<ValType, ShardType>::overlappingRange(const BSONObj& min,
- const BSONObj& max) {
- _assertAttached();
+ return RangeOverlap(low, high);
+}
- typename RangeMap::iterator low;
- typename RangeMap::iterator high;
+template <class ValType, class ShardType>
+int ConfigDiffTracker<ValType, ShardType>::calculateConfigDiff(CatalogManager* catalogManager) {
+ _assertAttached();
- if (isMinKeyIndexed()) {
- // Returns the first chunk with a min key that is >= min - implies the
- // previous chunk cannot overlap min
- low = _currMap->lower_bound(min);
+ // Get the diff query required
+ Query diffQuery = configDiffQuery();
- // Returns the first chunk with a min key that is >= max - implies the
- // chunk does not overlap max
- high = _currMap->lower_bound(max);
- }
- else {
- // Returns the first chunk with a max key that is > min - implies that
- // the chunk overlaps min
- low = _currMap->upper_bound(min);
-
- // Returns the first chunk with a max key that is > max - implies that
- // the next chunk cannot not overlap max
- high = _currMap->upper_bound(max);
- }
+ try {
+ std::vector<ChunkType> chunks;
+ uassertStatusOK(catalogManager->getChunks(diffQuery, 0, &chunks));
- return RangeOverlap(low, high);
+ return calculateConfigDiff(chunks);
+ } catch (DBException& e) {
+ // Should only happen on connection errors
+ e.addContext(str::stream() << "could not calculate config difference for ns " << _ns);
+ throw;
}
+}
+
+template <class ValType, class ShardType>
+int ConfigDiffTracker<ValType, ShardType>::calculateConfigDiff(
+ const std::vector<ChunkType>& chunks) {
+ _assertAttached();
+
+ // Apply the chunk changes to the ranges and versions
+ //
+ // Overall idea here is to work in two steps :
+ // 1. For all the new chunks we find, increment the maximum version per-shard and
+ // per-collection, and remove any conflicting chunks from the ranges.
+ // 2. For all the new chunks we're interested in (all of them for mongos, just chunks on
+ // the shard for mongod) add them to the ranges.
- template<class ValType, class ShardType>
- int ConfigDiffTracker<ValType, ShardType>::calculateConfigDiff(
- CatalogManager* catalogManager) {
- _assertAttached();
+ std::vector<ChunkType> newTracked;
- // Get the diff query required
- Query diffQuery = configDiffQuery();
+ // Store epoch now so it doesn't change when we change max
+ OID currEpoch = _maxVersion->epoch();
- try {
- std::vector<ChunkType> chunks;
- uassertStatusOK(catalogManager->getChunks(diffQuery, 0, &chunks));
+ _validDiffs = 0;
- return calculateConfigDiff(chunks);
+ for (const ChunkType& chunk : chunks) {
+ ChunkVersion chunkVersion =
+ ChunkVersion::fromBSON(chunk.toBSON(), ChunkType::DEPRECATED_lastmod());
+
+ if (!chunkVersion.isSet() || !chunkVersion.hasEqualEpoch(currEpoch)) {
+ warning() << "got invalid chunk version " << chunkVersion << " in document "
+ << chunk.toString() << " when trying to load differing chunks at version "
+ << ChunkVersion(
+ _maxVersion->majorVersion(), _maxVersion->minorVersion(), currEpoch);
+
+ // Don't keep loading, since we know we'll be broken here
+ return -1;
}
- catch (DBException& e) {
- // Should only happen on connection errors
- e.addContext(str::stream() << "could not calculate config difference for ns " << _ns);
- throw;
+
+ _validDiffs++;
+
+ // Get max changed version and chunk version
+ if (chunkVersion > *_maxVersion) {
+ *_maxVersion = chunkVersion;
}
- }
- template<class ValType, class ShardType>
- int ConfigDiffTracker<ValType, ShardType>::calculateConfigDiff(
- const std::vector<ChunkType>& chunks) {
- _assertAttached();
+ // Chunk version changes
+ ShardType shard = shardFor(chunk.getShard());
- // Apply the chunk changes to the ranges and versions
- //
- // Overall idea here is to work in two steps :
- // 1. For all the new chunks we find, increment the maximum version per-shard and
- // per-collection, and remove any conflicting chunks from the ranges.
- // 2. For all the new chunks we're interested in (all of them for mongos, just chunks on
- // the shard for mongod) add them to the ranges.
-
- std::vector<ChunkType> newTracked;
-
- // Store epoch now so it doesn't change when we change max
- OID currEpoch = _maxVersion->epoch();
-
- _validDiffs = 0;
-
- for (const ChunkType& chunk : chunks) {
- ChunkVersion chunkVersion = ChunkVersion::fromBSON(chunk.toBSON(),
- ChunkType::DEPRECATED_lastmod());
-
- if (!chunkVersion.isSet() || !chunkVersion.hasEqualEpoch(currEpoch)) {
- warning() << "got invalid chunk version " << chunkVersion
- << " in document " << chunk.toString()
- << " when trying to load differing chunks at version "
- << ChunkVersion(_maxVersion->majorVersion(),
- _maxVersion->minorVersion(),
- currEpoch);
-
- // Don't keep loading, since we know we'll be broken here
- return -1;
- }
-
- _validDiffs++;
-
- // Get max changed version and chunk version
- if (chunkVersion > *_maxVersion) {
- *_maxVersion = chunkVersion;
- }
-
- // Chunk version changes
- ShardType shard = shardFor(chunk.getShard());
-
- typename MaxChunkVersionMap::const_iterator shardVersionIt = _maxShardVersions->find(shard);
- if (shardVersionIt == _maxShardVersions->end() ||
- shardVersionIt->second < chunkVersion) {
- (*_maxShardVersions)[shard] = chunkVersion;
- }
-
- // See if we need to remove any chunks we are currently tracking because of this
- // chunk's changes
- removeOverlapping(chunk.getMin(), chunk.getMax());
-
- // Figure out which of the new chunks we need to track
- // Important - we need to actually own this doc, in case the cursor decides to getMore
- // or unbuffer.
- if (isTracked(chunk)) {
- newTracked.push_back(chunk);
- }
+ typename MaxChunkVersionMap::const_iterator shardVersionIt = _maxShardVersions->find(shard);
+ if (shardVersionIt == _maxShardVersions->end() || shardVersionIt->second < chunkVersion) {
+ (*_maxShardVersions)[shard] = chunkVersion;
}
- LOG(3) << "found " << _validDiffs << " new chunks for collection " << _ns
- << " (tracking " << newTracked.size() << "), new version is " << *_maxVersion;
+ // See if we need to remove any chunks we are currently tracking because of this
+ // chunk's changes
+ removeOverlapping(chunk.getMin(), chunk.getMax());
- for (const ChunkType& chunk : newTracked) {
- // Invariant enforced by sharding - it's possible to read inconsistent state due to
- // getMore and yielding, so we want to detect it as early as possible.
- //
- // TODO: This checks for overlap, we also should check for holes here iff we're
- // tracking all chunks.
- if (isOverlapping(chunk.getMin(), chunk.getMax())) {
- return -1;
- }
+ // Figure out which of the new chunks we need to track
+ // Important - we need to actually own this doc, in case the cursor decides to getMore
+ // or unbuffer.
+ if (isTracked(chunk)) {
+ newTracked.push_back(chunk);
+ }
+ }
+
+ LOG(3) << "found " << _validDiffs << " new chunks for collection " << _ns << " (tracking "
+ << newTracked.size() << "), new version is " << *_maxVersion;
- _currMap->insert(rangeFor(chunk));
+ for (const ChunkType& chunk : newTracked) {
+ // Invariant enforced by sharding - it's possible to read inconsistent state due to
+ // getMore and yielding, so we want to detect it as early as possible.
+ //
+ // TODO: This checks for overlap, we also should check for holes here iff we're
+ // tracking all chunks.
+ if (isOverlapping(chunk.getMin(), chunk.getMax())) {
+ return -1;
}
- return _validDiffs;
+ _currMap->insert(rangeFor(chunk));
}
- template<class ValType, class ShardType>
- Query ConfigDiffTracker<ValType, ShardType>::configDiffQuery() const {
- _assertAttached();
+ return _validDiffs;
+}
- // Basic idea behind the query is to find all the chunks $gte the current max version.
- // Currently, any splits and merges will increment the current max version.
- BSONObjBuilder queryB;
- queryB.append(ChunkType::ns(), _ns);
+template <class ValType, class ShardType>
+Query ConfigDiffTracker<ValType, ShardType>::configDiffQuery() const {
+ _assertAttached();
- {
- BSONObjBuilder tsBuilder(queryB.subobjStart(ChunkType::DEPRECATED_lastmod()));
- tsBuilder.appendTimestamp("$gte", _maxVersion->toLong());
- tsBuilder.done();
- }
+ // Basic idea behind the query is to find all the chunks $gte the current max version.
+ // Currently, any splits and merges will increment the current max version.
+ BSONObjBuilder queryB;
+ queryB.append(ChunkType::ns(), _ns);
- // NOTE: IT IS IMPORTANT FOR CONSISTENCY THAT WE SORT BY ASC VERSION, IN ORDER TO HANDLE
- // CURSOR YIELDING BETWEEN CHUNKS BEING MIGRATED.
- //
- // This ensures that changes to chunk version (which will always be higher) will always
- // come *after* our current position in the chunk cursor.
+ {
+ BSONObjBuilder tsBuilder(queryB.subobjStart(ChunkType::DEPRECATED_lastmod()));
+ tsBuilder.appendTimestamp("$gte", _maxVersion->toLong());
+ tsBuilder.done();
+ }
- Query queryObj(queryB.obj());
- queryObj.sort(BSON("lastmod" << 1));
+ // NOTE: IT IS IMPORTANT FOR CONSISTENCY THAT WE SORT BY ASC VERSION, IN ORDER TO HANDLE
+ // CURSOR YIELDING BETWEEN CHUNKS BEING MIGRATED.
+ //
+ // This ensures that changes to chunk version (which will always be higher) will always
+ // come *after* our current position in the chunk cursor.
- LOG(2) << "major version query from " << *_maxVersion
- << " and over " << _maxShardVersions->size() << " shards is " << queryObj;
+ Query queryObj(queryB.obj());
+ queryObj.sort(BSON("lastmod" << 1));
- return queryObj;
- }
+ LOG(2) << "major version query from " << *_maxVersion << " and over "
+ << _maxShardVersions->size() << " shards is " << queryObj;
- template <class ValType, class ShardType>
- void ConfigDiffTracker<ValType, ShardType>::_assertAttached() const {
- invariant(_currMap);
- invariant(_maxVersion);
- invariant(_maxShardVersions);
- }
+ return queryObj;
+}
+
+template <class ValType, class ShardType>
+void ConfigDiffTracker<ValType, ShardType>::_assertAttached() const {
+ invariant(_currMap);
+ invariant(_maxVersion);
+ invariant(_maxShardVersions);
+}
- // Ensures that these instances of the template are compiled
- template class ConfigDiffTracker<BSONObj, std::string>;
- template class ConfigDiffTracker<std::shared_ptr<Chunk>, std::string>;
+// Ensures that these instances of the template are compiled
+template class ConfigDiffTracker<BSONObj, std::string>;
+template class ConfigDiffTracker<std::shared_ptr<Chunk>, std::string>;
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/chunk_diff.h b/src/mongo/s/chunk_diff.h
index 9a67583a53b..00d713addd5 100644
--- a/src/mongo/s/chunk_diff.h
+++ b/src/mongo/s/chunk_diff.h
@@ -35,104 +35,108 @@
namespace mongo {
- class ChunkType;
- struct ChunkVersion;
- class CatalogManager;
- class Query;
+class ChunkType;
+struct ChunkVersion;
+class CatalogManager;
+class Query;
- /**
- * This class manages and applies diffs from partial config server data reloads. Because the
- * config data can be large, we want to update it in small parts, not all-at-once. Once a
- * ConfigDiffTracker is created, the current config data is *attached* to it, and it is then
- * able to modify the data.
- *
- * The current form is templated b/c the overall algorithm is identical between mongos and
- * mongod, but the actual chunk maps used differ in implementation. We don't want to copy the
- * implementation, because the logic is identical, or the chunk data, because that would be
- * slow for big clusters, so this is the alternative for now.
- *
- * TODO: Standardize between mongos and mongod and convert template parameters to types.
- */
- template <class ValType, class ShardType>
- class ConfigDiffTracker {
- public:
- // Stores ranges indexed by max or min key
- typedef typename std::map<BSONObj, ValType, BSONObjCmp> RangeMap;
+/**
+ * This class manages and applies diffs from partial config server data reloads. Because the
+ * config data can be large, we want to update it in small parts, not all-at-once. Once a
+ * ConfigDiffTracker is created, the current config data is *attached* to it, and it is then
+ * able to modify the data.
+ *
+ * The current form is templated b/c the overall algorithm is identical between mongos and
+ * mongod, but the actual chunk maps used differ in implementation. We don't want to copy the
+ * implementation, because the logic is identical, or the chunk data, because that would be
+ * slow for big clusters, so this is the alternative for now.
+ *
+ * TODO: Standardize between mongos and mongod and convert template parameters to types.
+ */
+template <class ValType, class ShardType>
+class ConfigDiffTracker {
+public:
+ // Stores ranges indexed by max or min key
+ typedef typename std::map<BSONObj, ValType, BSONObjCmp> RangeMap;
- // Pair of iterators defining a subset of ranges
- typedef typename std::pair<typename RangeMap::iterator,
- typename RangeMap::iterator> RangeOverlap;
+ // Pair of iterators defining a subset of ranges
+ typedef
+ typename std::pair<typename RangeMap::iterator, typename RangeMap::iterator> RangeOverlap;
- // Map of shard identifiers to the maximum chunk version on that shard
- typedef typename std::map<ShardType, ChunkVersion> MaxChunkVersionMap;
+ // Map of shard identifiers to the maximum chunk version on that shard
+ typedef typename std::map<ShardType, ChunkVersion> MaxChunkVersionMap;
- ConfigDiffTracker();
- virtual ~ConfigDiffTracker();
+ ConfigDiffTracker();
+ virtual ~ConfigDiffTracker();
- /**
- * The tracker attaches to a set of ranges with versions, and uses a config server
- * connection to update these. Because the set of ranges and versions may be large, they
- * aren't owned by the tracker, they're just passed in and updated. Therefore they must all
- * stay in scope while the tracker is working.
- *
- * TODO: Make a standard VersionedRange to encapsulate this info in both mongod and mongos?
- */
- void attach(const std::string& ns,
- RangeMap& currMap,
- ChunkVersion& maxVersion,
- MaxChunkVersionMap& maxShardVersions);
+ /**
+ * The tracker attaches to a set of ranges with versions, and uses a config server
+ * connection to update these. Because the set of ranges and versions may be large, they
+ * aren't owned by the tracker, they're just passed in and updated. Therefore they must all
+ * stay in scope while the tracker is working.
+ *
+ * TODO: Make a standard VersionedRange to encapsulate this info in both mongod and mongos?
+ */
+ void attach(const std::string& ns,
+ RangeMap& currMap,
+ ChunkVersion& maxVersion,
+ MaxChunkVersionMap& maxShardVersions);
- // Call after load for more information
- int numValidDiffs() const { return _validDiffs; }
+ // Call after load for more information
+ int numValidDiffs() const {
+ return _validDiffs;
+ }
- // Whether or not a range exists in the min/max region
- bool isOverlapping(const BSONObj& min, const BSONObj& max);
+ // Whether or not a range exists in the min/max region
+ bool isOverlapping(const BSONObj& min, const BSONObj& max);
- // Removes all ranges in the region from min/max
- void removeOverlapping(const BSONObj& min, const BSONObj& max);
+ // Removes all ranges in the region from min/max
+ void removeOverlapping(const BSONObj& min, const BSONObj& max);
- // Returns a subset of ranges overlapping the region min/max
- RangeOverlap overlappingRange(const BSONObj& min, const BSONObj& max);
+ // Returns a subset of ranges overlapping the region min/max
+ RangeOverlap overlappingRange(const BSONObj& min, const BSONObj& max);
- // Finds and applies the changes to a collection from the config servers via
- // the catalog manager.
- // Also includes minor version changes for particular major-version chunks if explicitly
- // specified.
- // Returns the number of diffs processed, or -1 if the diffs were inconsistent
- // Throws a DBException on connection errors
- int calculateConfigDiff(CatalogManager* catalogManager);
+ // Finds and applies the changes to a collection from the config servers via
+ // the catalog manager.
+ // Also includes minor version changes for particular major-version chunks if explicitly
+ // specified.
+ // Returns the number of diffs processed, or -1 if the diffs were inconsistent
+ // Throws a DBException on connection errors
+ int calculateConfigDiff(CatalogManager* catalogManager);
- // Applies changes to the config data from a vector of chunks passed in
- // Returns the number of diffs processed, or -1 if the diffs were inconsistent
- // Throws a DBException on connection errors
- int calculateConfigDiff(const std::vector<ChunkType>& chunks);
+ // Applies changes to the config data from a vector of chunks passed in
+ // Returns the number of diffs processed, or -1 if the diffs were inconsistent
+ // Throws a DBException on connection errors
+ int calculateConfigDiff(const std::vector<ChunkType>& chunks);
- // Returns the query needed to find new changes to a collection from the config server
- // Needed only if a custom connection is required to the config server
- Query configDiffQuery() const;
+ // Returns the query needed to find new changes to a collection from the config server
+ // Needed only if a custom connection is required to the config server
+ Query configDiffQuery() const;
- protected:
- // Determines which chunks are actually being remembered by our RangeMap
- virtual bool isTracked(const ChunkType& chunk) const = 0;
+protected:
+ // Determines which chunks are actually being remembered by our RangeMap
+ virtual bool isTracked(const ChunkType& chunk) const = 0;
- // Whether or not our RangeMap uses min or max keys
- virtual bool isMinKeyIndexed() const { return true; }
+ // Whether or not our RangeMap uses min or max keys
+ virtual bool isMinKeyIndexed() const {
+ return true;
+ }
- virtual std::pair<BSONObj, ValType> rangeFor(const ChunkType& chunk) const = 0;
+ virtual std::pair<BSONObj, ValType> rangeFor(const ChunkType& chunk) const = 0;
- virtual ShardType shardFor(const std::string& name) const = 0;
+ virtual ShardType shardFor(const std::string& name) const = 0;
- private:
- void _assertAttached() const;
+private:
+ void _assertAttached() const;
- std::string _ns;
- RangeMap* _currMap;
- ChunkVersion* _maxVersion;
- MaxChunkVersionMap* _maxShardVersions;
+ std::string _ns;
+ RangeMap* _currMap;
+ ChunkVersion* _maxVersion;
+ MaxChunkVersionMap* _maxShardVersions;
- // Store for later use
- int _validDiffs;
- };
+ // Store for later use
+ int _validDiffs;
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/chunk_diff_test.cpp b/src/mongo/s/chunk_diff_test.cpp
index d63cb236fee..067b80ac0f9 100644
--- a/src/mongo/s/chunk_diff_test.cpp
+++ b/src/mongo/s/chunk_diff_test.cpp
@@ -37,38 +37,41 @@
namespace {
- using mongo::BSONObj;
- using mongo::ChunkType;
- using mongo::ConfigDiffTracker;
- using std::string;
- using std::pair;
- using std::make_pair;
+using mongo::BSONObj;
+using mongo::ChunkType;
+using mongo::ConfigDiffTracker;
+using std::string;
+using std::pair;
+using std::make_pair;
- // XXX
- // We'd move ChunkDiffUnitTest here
- // We can check the queries it generates.
- // We can check if is populating the attaching structures properly
- //
+// XXX
+// We'd move ChunkDiffUnitTest here
+// We can check the queries it generates.
+// We can check if is populating the attaching structures properly
+//
- // The default pass-through adapter for using config diffs.
- class DefaultDiffAdapter : public ConfigDiffTracker<BSONObj,string> {
- public:
+// The default pass-through adapter for using config diffs.
+class DefaultDiffAdapter : public ConfigDiffTracker<BSONObj, string> {
+public:
+ DefaultDiffAdapter() {}
+ virtual ~DefaultDiffAdapter() {}
- DefaultDiffAdapter() {}
- virtual ~DefaultDiffAdapter() {}
-
- virtual bool isTracked(const ChunkType& chunk) const { return true; }
-
- virtual pair<BSONObj,BSONObj> rangeFor(const ChunkType& chunk) const {
- return make_pair(chunk.getMin(), chunk.getMax());
- }
+ virtual bool isTracked(const ChunkType& chunk) const {
+ return true;
+ }
- virtual string shardFor(const string& name) const { return name; }
- };
+ virtual pair<BSONObj, BSONObj> rangeFor(const ChunkType& chunk) const {
+ return make_pair(chunk.getMin(), chunk.getMax());
+ }
- TEST(Basics, Simple) {
- DefaultDiffAdapter differ;
- ASSERT_TRUE(true);
+ virtual string shardFor(const string& name) const {
+ return name;
}
+};
+
+TEST(Basics, Simple) {
+ DefaultDiffAdapter differ;
+ ASSERT_TRUE(true);
+}
-} // unnamed namespace
+} // unnamed namespace
diff --git a/src/mongo/s/chunk_manager.cpp b/src/mongo/s/chunk_manager.cpp
index bcf7db273fa..73c1461c92f 100644
--- a/src/mongo/s/chunk_manager.cpp
+++ b/src/mongo/s/chunk_manager.cpp
@@ -55,752 +55,745 @@
namespace mongo {
- using std::shared_ptr;
+using std::shared_ptr;
- using std::make_pair;
- using std::map;
- using std::max;
- using std::pair;
- using std::set;
- using std::string;
- using std::vector;
+using std::make_pair;
+using std::map;
+using std::max;
+using std::pair;
+using std::set;
+using std::string;
+using std::vector;
namespace {
- /**
- * This is an adapter so we can use config diffs - mongos and mongod do them slightly
- * differently
- *
- * The mongos adapter here tracks all shards, and stores ranges by (max, Chunk) in the map.
- */
- class CMConfigDiffTracker : public ConfigDiffTracker<shared_ptr<Chunk>, string> {
- public:
- CMConfigDiffTracker(ChunkManager* manager) : _manager(manager) { }
-
- bool isTracked(const ChunkType& chunk) const final {
- // Mongos tracks all shards
- return true;
- }
+/**
+ * This is an adapter so we can use config diffs - mongos and mongod do them slightly
+ * differently
+ *
+ * The mongos adapter here tracks all shards, and stores ranges by (max, Chunk) in the map.
+ */
+class CMConfigDiffTracker : public ConfigDiffTracker<shared_ptr<Chunk>, string> {
+public:
+ CMConfigDiffTracker(ChunkManager* manager) : _manager(manager) {}
- bool isMinKeyIndexed() const final { return false; }
+ bool isTracked(const ChunkType& chunk) const final {
+ // Mongos tracks all shards
+ return true;
+ }
- pair<BSONObj, shared_ptr<Chunk>> rangeFor(const ChunkType& chunk) const final {
- shared_ptr<Chunk> c(new Chunk(_manager, chunk.toBSON()));
- return make_pair(chunk.getMax(), c);
- }
+ bool isMinKeyIndexed() const final {
+ return false;
+ }
- string shardFor(const string& hostName) const final {
- const auto shard = grid.shardRegistry()->getShard(hostName);
- return shard->getId();
- }
+ pair<BSONObj, shared_ptr<Chunk>> rangeFor(const ChunkType& chunk) const final {
+ shared_ptr<Chunk> c(new Chunk(_manager, chunk.toBSON()));
+ return make_pair(chunk.getMax(), c);
+ }
- private:
- ChunkManager* const _manager;
- };
+ string shardFor(const string& hostName) const final {
+ const auto shard = grid.shardRegistry()->getShard(hostName);
+ return shard->getId();
+ }
+private:
+ ChunkManager* const _manager;
+};
- bool allOfType(BSONType type, const BSONObj& o) {
- BSONObjIterator it(o);
- while(it.more()) {
- if (it.next().type() != type) {
- return false;
- }
+
+bool allOfType(BSONType type, const BSONObj& o) {
+ BSONObjIterator it(o);
+ while (it.more()) {
+ if (it.next().type() != type) {
+ return false;
}
+ }
+ return true;
+}
+
+bool isChunkMapValid(const ChunkMap& chunkMap) {
+#define ENSURE(x) \
+ do { \
+ if (!(x)) { \
+ log() << "ChunkManager::_isValid failed: " #x; \
+ return false; \
+ } \
+ } while (0)
+
+ if (chunkMap.empty()) {
return true;
}
- bool isChunkMapValid(const ChunkMap& chunkMap) {
-#define ENSURE(x) do { if(!(x)) { log() << "ChunkManager::_isValid failed: " #x; return false; } } while(0)
+ // Check endpoints
+ ENSURE(allOfType(MinKey, chunkMap.begin()->second->getMin()));
+ ENSURE(allOfType(MaxKey, boost::prior(chunkMap.end())->second->getMax()));
- if (chunkMap.empty()) {
- return true;
- }
-
- // Check endpoints
- ENSURE(allOfType(MinKey, chunkMap.begin()->second->getMin()));
- ENSURE(allOfType(MaxKey, boost::prior(chunkMap.end())->second->getMax()));
+ // Make sure there are no gaps or overlaps
+ for (ChunkMap::const_iterator it = boost::next(chunkMap.begin()), end = chunkMap.end();
+ it != end;
+ ++it) {
+ ChunkMap::const_iterator last = boost::prior(it);
- // Make sure there are no gaps or overlaps
- for (ChunkMap::const_iterator it = boost::next(chunkMap.begin()), end = chunkMap.end(); it != end; ++it) {
- ChunkMap::const_iterator last = boost::prior(it);
-
- if (!(it->second->getMin() == last->second->getMax())) {
- log() << last->second->toString();
- log() << it->second->toString();
- log() << it->second->getMin();
- log() << last->second->getMax();
- }
-
- ENSURE(it->second->getMin() == last->second->getMax());
+ if (!(it->second->getMin() == last->second->getMax())) {
+ log() << last->second->toString();
+ log() << it->second->toString();
+ log() << it->second->getMin();
+ log() << last->second->getMax();
}
- return true;
-
-#undef ENSURE
+ ENSURE(it->second->getMin() == last->second->getMax());
}
-} // namespace
+ return true;
- AtomicUInt32 ChunkManager::NextSequenceNumber(1U);
-
- ChunkManager::ChunkManager(const string& ns, const ShardKeyPattern& pattern, bool unique)
- : _ns( ns ),
- _keyPattern( pattern.getKeyPattern() ),
- _unique( unique ),
- _sequenceNumber(NextSequenceNumber.addAndFetch(1)),
- _chunkRanges() {
+#undef ENSURE
+}
+
+} // namespace
+
+AtomicUInt32 ChunkManager::NextSequenceNumber(1U);
+
+ChunkManager::ChunkManager(const string& ns, const ShardKeyPattern& pattern, bool unique)
+ : _ns(ns),
+ _keyPattern(pattern.getKeyPattern()),
+ _unique(unique),
+ _sequenceNumber(NextSequenceNumber.addAndFetch(1)),
+ _chunkRanges() {}
+
+ChunkManager::ChunkManager(const CollectionType& coll)
+ : _ns(coll.getNs()),
+ _keyPattern(coll.getKeyPattern()),
+ _unique(coll.getUnique()),
+ _sequenceNumber(NextSequenceNumber.addAndFetch(1)),
+ _chunkRanges() {
+ _version = ChunkVersion::fromBSON(coll.toBSON());
+}
+
+void ChunkManager::loadExistingRanges(const ChunkManager* oldManager) {
+ int tries = 3;
+
+ while (tries--) {
+ ChunkMap chunkMap;
+ set<ShardId> shardIds;
+ ShardVersionMap shardVersions;
+
+ Timer t;
+
+ bool success = _load(chunkMap, shardIds, &shardVersions, oldManager);
+ if (success) {
+ log() << "ChunkManager: time to load chunks for " << _ns << ": " << t.millis() << "ms"
+ << " sequenceNumber: " << _sequenceNumber << " version: " << _version.toString()
+ << " based on: "
+ << (oldManager ? oldManager->getVersion().toString() : "(empty)");
+
+ // TODO: Merge into diff code above, so we validate in one place
+ if (isChunkMapValid(chunkMap)) {
+ _chunkMap.swap(chunkMap);
+ _shardIds.swap(shardIds);
+ _shardVersions.swap(shardVersions);
+ _chunkRanges.reloadAll(_chunkMap);
+
+ return;
+ }
+ }
- }
+ if (_chunkMap.size() < 10) {
+ _printChunks();
+ }
- ChunkManager::ChunkManager(const CollectionType& coll)
- : _ns(coll.getNs()),
- _keyPattern(coll.getKeyPattern()),
- _unique(coll.getUnique()),
- _sequenceNumber(NextSequenceNumber.addAndFetch(1)),
- _chunkRanges() {
+ warning() << "ChunkManager loaded an invalid config for " << _ns << ", trying again";
- _version = ChunkVersion::fromBSON(coll.toBSON());
+ sleepmillis(10 * (3 - tries));
}
- void ChunkManager::loadExistingRanges(const ChunkManager* oldManager) {
- int tries = 3;
+ // This will abort construction so we should never have a reference to an invalid config
+ msgasserted(13282,
+ str::stream() << "Couldn't load a valid config for " << _ns
+ << " after 3 attempts. Please try again.");
+}
- while (tries--) {
- ChunkMap chunkMap;
- set<ShardId> shardIds;
- ShardVersionMap shardVersions;
+bool ChunkManager::_load(ChunkMap& chunkMap,
+ set<ShardId>& shardIds,
+ ShardVersionMap* shardVersions,
+ const ChunkManager* oldManager) {
+ // Reset the max version, but not the epoch, when we aren't loading from the oldManager
+ _version = ChunkVersion(0, 0, _version.epoch());
- Timer t;
+ // If we have a previous version of the ChunkManager to work from, use that info to reduce
+ // our config query
+ if (oldManager && oldManager->getVersion().isSet()) {
+ // Get the old max version
+ _version = oldManager->getVersion();
- bool success = _load(chunkMap, shardIds, &shardVersions, oldManager);
- if (success) {
- log() << "ChunkManager: time to load chunks for " << _ns << ": "
- << t.millis() << "ms"
- << " sequenceNumber: " << _sequenceNumber
- << " version: " << _version.toString()
- << " based on: "
- << (oldManager ? oldManager->getVersion().toString() : "(empty)");
+ // Load a copy of the old versions
+ *shardVersions = oldManager->_shardVersions;
- // TODO: Merge into diff code above, so we validate in one place
- if (isChunkMapValid(chunkMap)) {
- _chunkMap.swap(chunkMap);
- _shardIds.swap(shardIds);
- _shardVersions.swap(shardVersions);
- _chunkRanges.reloadAll(_chunkMap);
-
- return;
- }
- }
+ // Load a copy of the chunk map, replacing the chunk manager with our own
+ const ChunkMap& oldChunkMap = oldManager->getChunkMap();
- if (_chunkMap.size() < 10) {
- _printChunks();
- }
+ // Could be v.expensive
+ // TODO: If chunks were immutable and didn't reference the manager, we could do more
+ // interesting things here
+ for (const auto& oldChunkMapEntry : oldChunkMap) {
+ shared_ptr<Chunk> oldC = oldChunkMapEntry.second;
+ shared_ptr<Chunk> newC(new Chunk(
+ this, oldC->getMin(), oldC->getMax(), oldC->getShardId(), oldC->getLastmod()));
- warning() << "ChunkManager loaded an invalid config for " << _ns << ", trying again";
+ newC->setBytesWritten(oldC->getBytesWritten());
- sleepmillis(10 * (3 - tries));
+ chunkMap.insert(make_pair(oldC->getMax(), newC));
}
- // This will abort construction so we should never have a reference to an invalid config
- msgasserted(13282, str::stream() << "Couldn't load a valid config for " << _ns
- << " after 3 attempts. Please try again.");
+ LOG(2) << "loading chunk manager for collection " << _ns
+ << " using old chunk manager w/ version " << _version.toString() << " and "
+ << oldChunkMap.size() << " chunks";
}
- bool ChunkManager::_load(ChunkMap& chunkMap,
- set<ShardId>& shardIds,
- ShardVersionMap* shardVersions,
- const ChunkManager* oldManager) {
-
- // Reset the max version, but not the epoch, when we aren't loading from the oldManager
- _version = ChunkVersion(0, 0, _version.epoch());
-
- // If we have a previous version of the ChunkManager to work from, use that info to reduce
- // our config query
- if (oldManager && oldManager->getVersion().isSet()) {
- // Get the old max version
- _version = oldManager->getVersion();
-
- // Load a copy of the old versions
- *shardVersions = oldManager->_shardVersions;
-
- // Load a copy of the chunk map, replacing the chunk manager with our own
- const ChunkMap& oldChunkMap = oldManager->getChunkMap();
-
- // Could be v.expensive
- // TODO: If chunks were immutable and didn't reference the manager, we could do more
- // interesting things here
- for(const auto& oldChunkMapEntry : oldChunkMap){
- shared_ptr<Chunk> oldC = oldChunkMapEntry.second;
- shared_ptr<Chunk> newC(new Chunk(this,
- oldC->getMin(),
- oldC->getMax(),
- oldC->getShardId(),
- oldC->getLastmod()));
-
- newC->setBytesWritten(oldC->getBytesWritten());
-
- chunkMap.insert(make_pair(oldC->getMax(), newC));
+ // Attach a diff tracker for the versioned chunk data
+ CMConfigDiffTracker differ(this);
+ differ.attach(_ns, chunkMap, _version, *shardVersions);
+
+ // Diff tracker should *always* find at least one chunk if collection exists
+ int diffsApplied = differ.calculateConfigDiff(grid.catalogManager());
+ if (diffsApplied > 0) {
+ LOG(2) << "loaded " << diffsApplied << " chunks into new chunk manager for " << _ns
+ << " with version " << _version;
+
+ // Add all existing shards we find to the shards set
+ for (ShardVersionMap::iterator it = shardVersions->begin(); it != shardVersions->end();) {
+ shared_ptr<Shard> shard = grid.shardRegistry()->getShard(it->first);
+ if (shard) {
+ shardIds.insert(it->first);
+ ++it;
+ } else {
+ shardVersions->erase(it++);
}
-
- LOG(2) << "loading chunk manager for collection " << _ns
- << " using old chunk manager w/ version " << _version.toString()
- << " and " << oldChunkMap.size() << " chunks";
}
- // Attach a diff tracker for the versioned chunk data
- CMConfigDiffTracker differ(this);
- differ.attach(_ns, chunkMap, _version, *shardVersions);
-
- // Diff tracker should *always* find at least one chunk if collection exists
- int diffsApplied = differ.calculateConfigDiff(grid.catalogManager());
- if (diffsApplied > 0) {
- LOG(2) << "loaded " << diffsApplied << " chunks into new chunk manager for " << _ns
- << " with version " << _version;
-
- // Add all existing shards we find to the shards set
- for (ShardVersionMap::iterator it = shardVersions->begin();
- it != shardVersions->end(); ) {
-
- shared_ptr<Shard> shard = grid.shardRegistry()->getShard(it->first);
- if (shard) {
- shardIds.insert(it->first);
- ++it;
- }
- else {
- shardVersions->erase(it++);
- }
- }
+ return true;
+ } else if (diffsApplied == 0) {
+ // No chunks were found for the ns
+ warning() << "no chunks found when reloading " << _ns << ", previous version was "
+ << _version;
- return true;
- }
- else if (diffsApplied == 0) {
- // No chunks were found for the ns
- warning() << "no chunks found when reloading " << _ns
- << ", previous version was " << _version;
+ // Set all our data to empty
+ chunkMap.clear();
+ shardVersions->clear();
- // Set all our data to empty
- chunkMap.clear();
- shardVersions->clear();
+ _version = ChunkVersion(0, 0, OID());
- _version = ChunkVersion(0, 0, OID());
+ return true;
+ } else { // diffsApplied < 0
- return true;
+ bool allInconsistent = (differ.numValidDiffs() == 0);
+ if (allInconsistent) {
+ // All versions are different, this can be normal
+ warning() << "major change in chunk information found when reloading " << _ns
+ << ", previous version was " << _version;
+ } else {
+ // Inconsistent load halfway through (due to yielding cursor during load)
+ // should be rare
+ warning() << "inconsistent chunks found when reloading " << _ns
+ << ", previous version was " << _version << ", this should be rare";
}
- else { // diffsApplied < 0
- bool allInconsistent = (differ.numValidDiffs() == 0);
- if (allInconsistent) {
- // All versions are different, this can be normal
- warning() << "major change in chunk information found when reloading "
- << _ns << ", previous version was " << _version;
- }
- else {
- // Inconsistent load halfway through (due to yielding cursor during load)
- // should be rare
- warning() << "inconsistent chunks found when reloading " << _ns
- << ", previous version was " << _version << ", this should be rare";
- }
-
- // Set all our data to empty to be extra safe
- chunkMap.clear();
- shardVersions->clear();
+ // Set all our data to empty to be extra safe
+ chunkMap.clear();
+ shardVersions->clear();
- _version = ChunkVersion(0, 0, OID());
+ _version = ChunkVersion(0, 0, OID());
- return allInconsistent;
- }
+ return allInconsistent;
}
+}
+
+shared_ptr<ChunkManager> ChunkManager::reload(bool force) const {
+ const NamespaceString nss(_ns);
+ auto status = grid.catalogCache()->getDatabase(nss.db().toString());
+ shared_ptr<DBConfig> config = uassertStatusOK(status);
- shared_ptr<ChunkManager> ChunkManager::reload(bool force) const {
- const NamespaceString nss(_ns);
- auto status = grid.catalogCache()->getDatabase(nss.db().toString());
- shared_ptr<DBConfig> config = uassertStatusOK(status);
+ return config->getChunkManager(getns(), force);
+}
- return config->getChunkManager(getns(), force);
+void ChunkManager::_printChunks() const {
+ for (ChunkMap::const_iterator it = _chunkMap.begin(), end = _chunkMap.end(); it != end; ++it) {
+ log() << *it->second;
}
+}
+
+void ChunkManager::calcInitSplitsAndShards(const ShardId& primaryShardId,
+ const vector<BSONObj>* initPoints,
+ const set<ShardId>* initShardIds,
+ vector<BSONObj>* splitPoints,
+ vector<ShardId>* shardIds) const {
+ verify(_chunkMap.size() == 0);
+
+ unsigned long long numObjects = 0;
+ Chunk c(this,
+ _keyPattern.getKeyPattern().globalMin(),
+ _keyPattern.getKeyPattern().globalMax(),
+ primaryShardId);
+
+ if (!initPoints || !initPoints->size()) {
+ // discover split points
+ {
+ const auto primaryShard = grid.shardRegistry()->getShard(primaryShardId);
+ // get stats to see if there is any data
+ ScopedDbConnection shardConn(primaryShard->getConnString());
- void ChunkManager::_printChunks() const {
- for (ChunkMap::const_iterator it=_chunkMap.begin(), end=_chunkMap.end(); it != end; ++it) {
- log() << *it->second ;
+ numObjects = shardConn->count(getns());
+ shardConn.done();
}
- }
- void ChunkManager::calcInitSplitsAndShards( const ShardId& primaryShardId,
- const vector<BSONObj>* initPoints,
- const set<ShardId>* initShardIds,
- vector<BSONObj>* splitPoints,
- vector<ShardId>* shardIds ) const
- {
- verify( _chunkMap.size() == 0 );
-
- unsigned long long numObjects = 0;
- Chunk c(this,
- _keyPattern.getKeyPattern().globalMin(),
- _keyPattern.getKeyPattern().globalMax(),
- primaryShardId);
-
- if ( !initPoints || !initPoints->size() ) {
- // discover split points
- {
- const auto primaryShard = grid.shardRegistry()->getShard(primaryShardId);
- // get stats to see if there is any data
- ScopedDbConnection shardConn(primaryShard->getConnString());
-
- numObjects = shardConn->count( getns() );
- shardConn.done();
- }
+ if (numObjects > 0)
+ c.pickSplitVector(*splitPoints, Chunk::MaxChunkSize);
- if ( numObjects > 0 )
- c.pickSplitVector( *splitPoints , Chunk::MaxChunkSize );
+ // since docs alread exists, must use primary shard
+ shardIds->push_back(primaryShardId);
+ } else {
+ // make sure points are unique and ordered
+ set<BSONObj> orderedPts;
+ for (unsigned i = 0; i < initPoints->size(); ++i) {
+ BSONObj pt = (*initPoints)[i];
+ orderedPts.insert(pt);
+ }
+ for (set<BSONObj>::iterator it = orderedPts.begin(); it != orderedPts.end(); ++it) {
+ splitPoints->push_back(*it);
+ }
- // since docs alread exists, must use primary shard
+ if (!initShardIds || !initShardIds->size()) {
+ // If not specified, only use the primary shard (note that it's not safe for mongos
+ // to put initial chunks on other shards without the primary mongod knowing).
shardIds->push_back(primaryShardId);
} else {
- // make sure points are unique and ordered
- set<BSONObj> orderedPts;
- for ( unsigned i = 0; i < initPoints->size(); ++i ) {
- BSONObj pt = (*initPoints)[i];
- orderedPts.insert( pt );
- }
- for ( set<BSONObj>::iterator it = orderedPts.begin(); it != orderedPts.end(); ++it ) {
- splitPoints->push_back( *it );
- }
-
- if ( !initShardIds || !initShardIds->size() ) {
- // If not specified, only use the primary shard (note that it's not safe for mongos
- // to put initial chunks on other shards without the primary mongod knowing).
- shardIds->push_back(primaryShardId);
- } else {
- std::copy(initShardIds->begin() , initShardIds->end() , std::back_inserter(*shardIds));
- }
+ std::copy(initShardIds->begin(), initShardIds->end(), std::back_inserter(*shardIds));
}
}
+}
- void ChunkManager::createFirstChunks(const ShardId& primaryShardId,
- const vector<BSONObj>* initPoints,
- const set<ShardId>* initShardIds)
- {
- // TODO distlock?
- // TODO: Race condition if we shard the collection and insert data while we split across
- // the non-primary shard.
+void ChunkManager::createFirstChunks(const ShardId& primaryShardId,
+ const vector<BSONObj>* initPoints,
+ const set<ShardId>* initShardIds) {
+ // TODO distlock?
+ // TODO: Race condition if we shard the collection and insert data while we split across
+ // the non-primary shard.
- vector<BSONObj> splitPoints;
- vector<ShardId> shardIds;
- calcInitSplitsAndShards(primaryShardId, initPoints, initShardIds, &splitPoints, &shardIds);
+ vector<BSONObj> splitPoints;
+ vector<ShardId> shardIds;
+ calcInitSplitsAndShards(primaryShardId, initPoints, initShardIds, &splitPoints, &shardIds);
- // this is the first chunk; start the versioning from scratch
- ChunkVersion version;
- version.incEpoch();
- version.incMajor();
-
- log() << "going to create " << splitPoints.size() + 1 << " chunk(s) for: " << _ns
- << " using new epoch " << version.epoch() ;
+ // this is the first chunk; start the versioning from scratch
+ ChunkVersion version;
+ version.incEpoch();
+ version.incMajor();
- for ( unsigned i=0; i<=splitPoints.size(); i++ ) {
- BSONObj min = i == 0 ? _keyPattern.getKeyPattern().globalMin() : splitPoints[i-1];
- BSONObj max = i < splitPoints.size() ?
- splitPoints[i] : _keyPattern.getKeyPattern().globalMax();
+ log() << "going to create " << splitPoints.size() + 1 << " chunk(s) for: " << _ns
+ << " using new epoch " << version.epoch();
- Chunk temp( this , min , max , shardIds[ i % shardIds.size() ], version );
+ for (unsigned i = 0; i <= splitPoints.size(); i++) {
+ BSONObj min = i == 0 ? _keyPattern.getKeyPattern().globalMin() : splitPoints[i - 1];
+ BSONObj max =
+ i < splitPoints.size() ? splitPoints[i] : _keyPattern.getKeyPattern().globalMax();
- BSONObjBuilder chunkBuilder;
- temp.serialize(chunkBuilder);
+ Chunk temp(this, min, max, shardIds[i % shardIds.size()], version);
- BSONObj chunkObj = chunkBuilder.obj();
+ BSONObjBuilder chunkBuilder;
+ temp.serialize(chunkBuilder);
- Status result = grid.catalogManager()->update(ChunkType::ConfigNS,
- BSON(ChunkType::name(temp.genID())),
- chunkObj,
- true,
- false,
- NULL);
+ BSONObj chunkObj = chunkBuilder.obj();
- version.incMinor();
+ Status result = grid.catalogManager()->update(
+ ChunkType::ConfigNS, BSON(ChunkType::name(temp.genID())), chunkObj, true, false, NULL);
- if (!result.isOK()) {
- string ss = str::stream() << "creating first chunks failed. result: "
- << result.reason();
- error() << ss;
- msgasserted(15903, ss);
- }
- }
+ version.incMinor();
- _version = ChunkVersion(0, 0, version.epoch());
+ if (!result.isOK()) {
+ string ss = str::stream()
+ << "creating first chunks failed. result: " << result.reason();
+ error() << ss;
+ msgasserted(15903, ss);
+ }
}
- ChunkPtr ChunkManager::findIntersectingChunk( const BSONObj& shardKey ) const {
- {
- BSONObj chunkMin;
- ChunkPtr chunk;
- {
- ChunkMap::const_iterator it = _chunkMap.upper_bound( shardKey );
- if (it != _chunkMap.end()) {
- chunkMin = it->first;
- chunk = it->second;
- }
- }
+ _version = ChunkVersion(0, 0, version.epoch());
+}
- if ( chunk ) {
- if ( chunk->containsKey( shardKey ) ){
- return chunk;
- }
-
- log() << chunkMin;
- log() << *chunk;
- log() << shardKey;
-
- reload();
- msgasserted(13141, "Chunk map pointed to incorrect chunk");
+ChunkPtr ChunkManager::findIntersectingChunk(const BSONObj& shardKey) const {
+ {
+ BSONObj chunkMin;
+ ChunkPtr chunk;
+ {
+ ChunkMap::const_iterator it = _chunkMap.upper_bound(shardKey);
+ if (it != _chunkMap.end()) {
+ chunkMin = it->first;
+ chunk = it->second;
}
}
- msgasserted( 8070 ,
- str::stream() << "couldn't find a chunk intersecting: " << shardKey
- << " for ns: " << _ns
- << " at version: " << _version.toString()
- << ", number of chunks: " << _chunkMap.size() );
- }
-
- void ChunkManager::getShardIdsForQuery(set<ShardId>& shardIds, const BSONObj& query) const {
- CanonicalQuery* canonicalQuery = NULL;
- Status status = CanonicalQuery::canonicalize(
- _ns,
- query,
- &canonicalQuery,
- WhereCallbackNoop());
-
- std::unique_ptr<CanonicalQuery> canonicalQueryPtr(canonicalQuery);
-
- uassert(status.code(), status.reason(), status.isOK());
-
- // Query validation
- if (QueryPlannerCommon::hasNode(canonicalQuery->root(), MatchExpression::GEO_NEAR)) {
- uassert(13501, "use geoNear command rather than $near query", false);
- }
-
- // Transforms query into bounds for each field in the shard key
- // for example :
- // Key { a: 1, b: 1 },
- // Query { a : { $gte : 1, $lt : 2 },
- // b : { $gte : 3, $lt : 4 } }
- // => Bounds { a : [1, 2), b : [3, 4) }
- IndexBounds bounds = getIndexBoundsForQuery(_keyPattern.toBSON(), canonicalQuery);
-
- // Transforms bounds for each shard key field into full shard key ranges
- // for example :
- // Key { a : 1, b : 1 }
- // Bounds { a : [1, 2), b : [3, 4) }
- // => Ranges { a : 1, b : 3 } => { a : 2, b : 4 }
- BoundList ranges = _keyPattern.flattenBounds(bounds);
-
- for (BoundList::const_iterator it = ranges.begin(); it != ranges.end();
- ++it) {
-
- getShardIdsForRange(shardIds, it->first /*min*/, it->second /*max*/);
+ if (chunk) {
+ if (chunk->containsKey(shardKey)) {
+ return chunk;
+ }
- // once we know we need to visit all shards no need to keep looping
- if(shardIds.size() == _shardIds.size()) break;
- }
+ log() << chunkMin;
+ log() << *chunk;
+ log() << shardKey;
- // SERVER-4914 Some clients of getShardIdsForQuery() assume at least one shard will be
- // returned. For now, we satisfy that assumption by adding a shard with no matches rather
- // than return an empty set of shards.
- if (shardIds.empty()) {
- massert( 16068, "no chunk ranges available", !_chunkRanges.ranges().empty() );
- shardIds.insert(_chunkRanges.ranges().begin()->second->getShardId());
+ reload();
+ msgasserted(13141, "Chunk map pointed to incorrect chunk");
}
}
- void ChunkManager::getShardIdsForRange(set<ShardId>& shardIds,
- const BSONObj& min,
- const BSONObj& max) const {
-
- ChunkRangeMap::const_iterator it = _chunkRanges.upper_bound(min);
- ChunkRangeMap::const_iterator end = _chunkRanges.upper_bound(max);
+ msgasserted(8070,
+ str::stream() << "couldn't find a chunk intersecting: " << shardKey
+ << " for ns: " << _ns << " at version: " << _version.toString()
+ << ", number of chunks: " << _chunkMap.size());
+}
- massert(13507,
- str::stream() << "no chunks found between bounds " << min << " and " << max,
- it != _chunkRanges.ranges().end());
+void ChunkManager::getShardIdsForQuery(set<ShardId>& shardIds, const BSONObj& query) const {
+ CanonicalQuery* canonicalQuery = NULL;
+ Status status = CanonicalQuery::canonicalize(_ns, query, &canonicalQuery, WhereCallbackNoop());
- if( end != _chunkRanges.ranges().end() ) ++end;
+ std::unique_ptr<CanonicalQuery> canonicalQueryPtr(canonicalQuery);
- for( ; it != end; ++it ){
- shardIds.insert(it->second->getShardId());
+ uassert(status.code(), status.reason(), status.isOK());
- // once we know we need to visit all shards no need to keep looping
- if (shardIds.size() == _shardIds.size()) break;
- }
+ // Query validation
+ if (QueryPlannerCommon::hasNode(canonicalQuery->root(), MatchExpression::GEO_NEAR)) {
+ uassert(13501, "use geoNear command rather than $near query", false);
}
- void ChunkManager::getAllShardIds(set<ShardId>* all) const {
- dassert(all);
+ // Transforms query into bounds for each field in the shard key
+ // for example :
+ // Key { a: 1, b: 1 },
+ // Query { a : { $gte : 1, $lt : 2 },
+ // b : { $gte : 3, $lt : 4 } }
+ // => Bounds { a : [1, 2), b : [3, 4) }
+ IndexBounds bounds = getIndexBoundsForQuery(_keyPattern.toBSON(), canonicalQuery);
+
+ // Transforms bounds for each shard key field into full shard key ranges
+ // for example :
+ // Key { a : 1, b : 1 }
+ // Bounds { a : [1, 2), b : [3, 4) }
+ // => Ranges { a : 1, b : 3 } => { a : 2, b : 4 }
+ BoundList ranges = _keyPattern.flattenBounds(bounds);
+
+ for (BoundList::const_iterator it = ranges.begin(); it != ranges.end(); ++it) {
+ getShardIdsForRange(shardIds, it->first /*min*/, it->second /*max*/);
+
+ // once we know we need to visit all shards no need to keep looping
+ if (shardIds.size() == _shardIds.size())
+ break;
+ }
- all->insert(_shardIds.begin(), _shardIds.end());
+ // SERVER-4914 Some clients of getShardIdsForQuery() assume at least one shard will be
+ // returned. For now, we satisfy that assumption by adding a shard with no matches rather
+ // than return an empty set of shards.
+ if (shardIds.empty()) {
+ massert(16068, "no chunk ranges available", !_chunkRanges.ranges().empty());
+ shardIds.insert(_chunkRanges.ranges().begin()->second->getShardId());
}
+}
- IndexBounds ChunkManager::getIndexBoundsForQuery(const BSONObj& key, const CanonicalQuery* canonicalQuery) {
- // $text is not allowed in planning since we don't have text index on mongos.
- //
- // TODO: Treat $text query as a no-op in planning. So with shard key {a: 1},
- // the query { a: 2, $text: { ... } } will only target to {a: 2}.
- if (QueryPlannerCommon::hasNode(canonicalQuery->root(), MatchExpression::TEXT)) {
- IndexBounds bounds;
- IndexBoundsBuilder::allValuesBounds(key, &bounds); // [minKey, maxKey]
- return bounds;
- }
+void ChunkManager::getShardIdsForRange(set<ShardId>& shardIds,
+ const BSONObj& min,
+ const BSONObj& max) const {
+ ChunkRangeMap::const_iterator it = _chunkRanges.upper_bound(min);
+ ChunkRangeMap::const_iterator end = _chunkRanges.upper_bound(max);
- // Consider shard key as an index
- string accessMethod = IndexNames::findPluginName(key);
- dassert(accessMethod == IndexNames::BTREE || accessMethod == IndexNames::HASHED);
+ massert(13507,
+ str::stream() << "no chunks found between bounds " << min << " and " << max,
+ it != _chunkRanges.ranges().end());
- // Use query framework to generate index bounds
- QueryPlannerParams plannerParams;
- // Must use "shard key" index
- plannerParams.options = QueryPlannerParams::NO_TABLE_SCAN;
- IndexEntry indexEntry(key, accessMethod, false /* multiKey */, false /* sparse */,
- false /* unique */, "shardkey", NULL /* filterExpr */, BSONObj());
- plannerParams.indices.push_back(indexEntry);
+ if (end != _chunkRanges.ranges().end())
+ ++end;
- OwnedPointerVector<QuerySolution> solutions;
- Status status = QueryPlanner::plan(*canonicalQuery, plannerParams, &solutions.mutableVector());
- uassert(status.code(), status.reason(), status.isOK());
+ for (; it != end; ++it) {
+ shardIds.insert(it->second->getShardId());
- IndexBounds bounds;
+ // once we know we need to visit all shards no need to keep looping
+ if (shardIds.size() == _shardIds.size())
+ break;
+ }
+}
- for (vector<QuerySolution*>::const_iterator it = solutions.begin();
- bounds.size() == 0 && it != solutions.end(); it++) {
- // Try next solution if we failed to generate index bounds, i.e. bounds.size() == 0
- bounds = collapseQuerySolution((*it)->root.get());
- }
+void ChunkManager::getAllShardIds(set<ShardId>* all) const {
+ dassert(all);
- if (bounds.size() == 0) {
- // We cannot plan the query without collection scan, so target to all shards.
- IndexBoundsBuilder::allValuesBounds(key, &bounds); // [minKey, maxKey]
- }
+ all->insert(_shardIds.begin(), _shardIds.end());
+}
+
+IndexBounds ChunkManager::getIndexBoundsForQuery(const BSONObj& key,
+ const CanonicalQuery* canonicalQuery) {
+ // $text is not allowed in planning since we don't have text index on mongos.
+ //
+ // TODO: Treat $text query as a no-op in planning. So with shard key {a: 1},
+ // the query { a: 2, $text: { ... } } will only target to {a: 2}.
+ if (QueryPlannerCommon::hasNode(canonicalQuery->root(), MatchExpression::TEXT)) {
+ IndexBounds bounds;
+ IndexBoundsBuilder::allValuesBounds(key, &bounds); // [minKey, maxKey]
return bounds;
}
- IndexBounds ChunkManager::collapseQuerySolution( const QuerySolutionNode* node ) {
- if (node->children.size() == 0) {
- invariant(node->getType() == STAGE_IXSCAN);
+ // Consider shard key as an index
+ string accessMethod = IndexNames::findPluginName(key);
+ dassert(accessMethod == IndexNames::BTREE || accessMethod == IndexNames::HASHED);
+
+ // Use query framework to generate index bounds
+ QueryPlannerParams plannerParams;
+ // Must use "shard key" index
+ plannerParams.options = QueryPlannerParams::NO_TABLE_SCAN;
+ IndexEntry indexEntry(key,
+ accessMethod,
+ false /* multiKey */,
+ false /* sparse */,
+ false /* unique */,
+ "shardkey",
+ NULL /* filterExpr */,
+ BSONObj());
+ plannerParams.indices.push_back(indexEntry);
+
+ OwnedPointerVector<QuerySolution> solutions;
+ Status status = QueryPlanner::plan(*canonicalQuery, plannerParams, &solutions.mutableVector());
+ uassert(status.code(), status.reason(), status.isOK());
+
+ IndexBounds bounds;
+
+ for (vector<QuerySolution*>::const_iterator it = solutions.begin();
+ bounds.size() == 0 && it != solutions.end();
+ it++) {
+ // Try next solution if we failed to generate index bounds, i.e. bounds.size() == 0
+ bounds = collapseQuerySolution((*it)->root.get());
+ }
- const IndexScanNode* ixNode = static_cast<const IndexScanNode*>( node );
- return ixNode->bounds;
- }
+ if (bounds.size() == 0) {
+ // We cannot plan the query without collection scan, so target to all shards.
+ IndexBoundsBuilder::allValuesBounds(key, &bounds); // [minKey, maxKey]
+ }
+ return bounds;
+}
- if (node->children.size() == 1) {
- // e.g. FETCH -> IXSCAN
- return collapseQuerySolution( node->children.front() );
- }
+IndexBounds ChunkManager::collapseQuerySolution(const QuerySolutionNode* node) {
+ if (node->children.size() == 0) {
+ invariant(node->getType() == STAGE_IXSCAN);
- // children.size() > 1, assert it's OR / SORT_MERGE.
- if ( node->getType() != STAGE_OR && node->getType() != STAGE_SORT_MERGE ) {
- // Unexpected node. We should never reach here.
- error() << "could not generate index bounds on query solution tree: " << node->toString();
- dassert(false); // We'd like to know this error in testing.
+ const IndexScanNode* ixNode = static_cast<const IndexScanNode*>(node);
+ return ixNode->bounds;
+ }
- // Bail out with all shards in production, since this isn't a fatal error.
- return IndexBounds();
- }
+ if (node->children.size() == 1) {
+ // e.g. FETCH -> IXSCAN
+ return collapseQuerySolution(node->children.front());
+ }
- IndexBounds bounds;
- for ( vector<QuerySolutionNode*>::const_iterator it = node->children.begin();
- it != node->children.end(); it++ )
- {
- // The first branch under OR
- if ( it == node->children.begin() ) {
- invariant(bounds.size() == 0);
- bounds = collapseQuerySolution( *it );
- if (bounds.size() == 0) { // Got unexpected node in query solution tree
- return IndexBounds();
- }
- continue;
- }
+ // children.size() > 1, assert it's OR / SORT_MERGE.
+ if (node->getType() != STAGE_OR && node->getType() != STAGE_SORT_MERGE) {
+ // Unexpected node. We should never reach here.
+ error() << "could not generate index bounds on query solution tree: " << node->toString();
+ dassert(false); // We'd like to know this error in testing.
- IndexBounds childBounds = collapseQuerySolution( *it );
- if (childBounds.size() == 0) { // Got unexpected node in query solution tree
- return IndexBounds();
- }
+ // Bail out with all shards in production, since this isn't a fatal error.
+ return IndexBounds();
+ }
- invariant(childBounds.size() == bounds.size());
- for ( size_t i = 0; i < bounds.size(); i++ ) {
- bounds.fields[i].intervals.insert( bounds.fields[i].intervals.end(),
- childBounds.fields[i].intervals.begin(),
- childBounds.fields[i].intervals.end() );
+ IndexBounds bounds;
+ for (vector<QuerySolutionNode*>::const_iterator it = node->children.begin();
+ it != node->children.end();
+ it++) {
+ // The first branch under OR
+ if (it == node->children.begin()) {
+ invariant(bounds.size() == 0);
+ bounds = collapseQuerySolution(*it);
+ if (bounds.size() == 0) { // Got unexpected node in query solution tree
+ return IndexBounds();
}
+ continue;
}
- for ( size_t i = 0; i < bounds.size(); i++ ) {
- IndexBoundsBuilder::unionize( &bounds.fields[i] );
+ IndexBounds childBounds = collapseQuerySolution(*it);
+ if (childBounds.size() == 0) { // Got unexpected node in query solution tree
+ return IndexBounds();
}
- return bounds;
- }
-
- bool ChunkManager::compatibleWith(const ChunkManager& other, const string& shardName) const {
- // Return true if the shard version is the same in the two chunk managers
- // TODO: This doesn't need to be so strong, just major vs
- return other.getVersion(shardName).equals(getVersion(shardName));
+ invariant(childBounds.size() == bounds.size());
+ for (size_t i = 0; i < bounds.size(); i++) {
+ bounds.fields[i].intervals.insert(bounds.fields[i].intervals.end(),
+ childBounds.fields[i].intervals.begin(),
+ childBounds.fields[i].intervals.end());
+ }
}
- ChunkVersion ChunkManager::getVersion(const std::string& shardName) const {
- ShardVersionMap::const_iterator i = _shardVersions.find(shardName);
- if ( i == _shardVersions.end() ) {
- // Shards without explicitly tracked shard versions (meaning they have
- // no chunks) always have a version of (0, 0, epoch). Note this is
- // *different* from the dropped chunk version of (0, 0, OID(000...)).
- // See s/chunk_version.h.
- return ChunkVersion( 0, 0, _version.epoch() );
- }
- return i->second;
+ for (size_t i = 0; i < bounds.size(); i++) {
+ IndexBoundsBuilder::unionize(&bounds.fields[i]);
}
- ChunkVersion ChunkManager::getVersion() const {
- return _version;
+ return bounds;
+}
+
+bool ChunkManager::compatibleWith(const ChunkManager& other, const string& shardName) const {
+ // Return true if the shard version is the same in the two chunk managers
+ // TODO: This doesn't need to be so strong, just major vs
+ return other.getVersion(shardName).equals(getVersion(shardName));
+}
+
+ChunkVersion ChunkManager::getVersion(const std::string& shardName) const {
+ ShardVersionMap::const_iterator i = _shardVersions.find(shardName);
+ if (i == _shardVersions.end()) {
+ // Shards without explicitly tracked shard versions (meaning they have
+ // no chunks) always have a version of (0, 0, epoch). Note this is
+ // *different* from the dropped chunk version of (0, 0, OID(000...)).
+ // See s/chunk_version.h.
+ return ChunkVersion(0, 0, _version.epoch());
}
+ return i->second;
+}
- string ChunkManager::toString() const {
- StringBuilder sb;
- sb << "ChunkManager: " << _ns << " key:" << _keyPattern.toString() << '\n';
+ChunkVersion ChunkManager::getVersion() const {
+ return _version;
+}
- for (ChunkMap::const_iterator i = _chunkMap.begin(); i != _chunkMap.end(); ++i) {
- sb << "\t" << i->second->toString() << '\n';
- }
+string ChunkManager::toString() const {
+ StringBuilder sb;
+ sb << "ChunkManager: " << _ns << " key:" << _keyPattern.toString() << '\n';
- return sb.str();
+ for (ChunkMap::const_iterator i = _chunkMap.begin(); i != _chunkMap.end(); ++i) {
+ sb << "\t" << i->second->toString() << '\n';
}
+ return sb.str();
+}
- ChunkRange::ChunkRange(ChunkMap::const_iterator begin, const ChunkMap::const_iterator end)
- : _manager(begin->second->getManager()),
- _shardId(begin->second->getShardId()),
- _min(begin->second->getMin()),
- _max(boost::prior(end)->second->getMax()) {
- invariant(begin != end);
+ChunkRange::ChunkRange(ChunkMap::const_iterator begin, const ChunkMap::const_iterator end)
+ : _manager(begin->second->getManager()),
+ _shardId(begin->second->getShardId()),
+ _min(begin->second->getMin()),
+ _max(boost::prior(end)->second->getMax()) {
+ invariant(begin != end);
- DEV while (begin != end) {
- dassert(begin->second->getManager() == _manager);
- dassert(begin->second->getShardId() == _shardId);
- ++begin;
- }
- }
-
- ChunkRange::ChunkRange(const ChunkRange& min, const ChunkRange& max)
- : _manager(min.getManager()),
- _shardId(min.getShardId()),
- _min(min.getMin()),
- _max(max.getMax()) {
-
- invariant(min.getShardId() == max.getShardId());
- invariant(min.getManager() == max.getManager());
- invariant(min.getMax() == max.getMin());
+ DEV while (begin != end) {
+ dassert(begin->second->getManager() == _manager);
+ dassert(begin->second->getShardId() == _shardId);
+ ++begin;
}
+}
- string ChunkRange::toString() const {
- StringBuilder sb;
- sb << "ChunkRange(min=" << _min << ", max=" << _max
- << ", shard=" << _shardId << ")";
+ChunkRange::ChunkRange(const ChunkRange& min, const ChunkRange& max)
+ : _manager(min.getManager()),
+ _shardId(min.getShardId()),
+ _min(min.getMin()),
+ _max(max.getMax()) {
+ invariant(min.getShardId() == max.getShardId());
+ invariant(min.getManager() == max.getManager());
+ invariant(min.getMax() == max.getMin());
+}
- return sb.str();
- }
+string ChunkRange::toString() const {
+ StringBuilder sb;
+ sb << "ChunkRange(min=" << _min << ", max=" << _max << ", shard=" << _shardId << ")";
+ return sb.str();
+}
- void ChunkRangeManager::assertValid() const {
- if (_ranges.empty())
- return;
- try {
- // No Nulls
- for (ChunkRangeMap::const_iterator it=_ranges.begin(), end=_ranges.end(); it != end; ++it) {
- verify(it->second);
- }
+void ChunkRangeManager::assertValid() const {
+ if (_ranges.empty())
+ return;
- // Check endpoints
- verify(allOfType(MinKey, _ranges.begin()->second->getMin()));
- verify(allOfType(MaxKey, boost::prior(_ranges.end())->second->getMax()));
+ try {
+ // No Nulls
+ for (ChunkRangeMap::const_iterator it = _ranges.begin(), end = _ranges.end(); it != end;
+ ++it) {
+ verify(it->second);
+ }
- // Make sure there are no gaps or overlaps
- for (ChunkRangeMap::const_iterator it=boost::next(_ranges.begin()), end=_ranges.end(); it != end; ++it) {
- ChunkRangeMap::const_iterator last = boost::prior(it);
- verify(it->second->getMin() == last->second->getMax());
- }
+ // Check endpoints
+ verify(allOfType(MinKey, _ranges.begin()->second->getMin()));
+ verify(allOfType(MaxKey, boost::prior(_ranges.end())->second->getMax()));
- // Check Map keys
- for (ChunkRangeMap::const_iterator it=_ranges.begin(), end=_ranges.end(); it != end; ++it) {
- verify(it->first == it->second->getMax());
- }
+ // Make sure there are no gaps or overlaps
+ for (ChunkRangeMap::const_iterator it = boost::next(_ranges.begin()), end = _ranges.end();
+ it != end;
+ ++it) {
+ ChunkRangeMap::const_iterator last = boost::prior(it);
+ verify(it->second->getMin() == last->second->getMax());
+ }
- // Make sure we match the original chunks
- const ChunkMap chunks = _ranges.begin()->second->getManager()->_chunkMap;
- for ( ChunkMap::const_iterator i=chunks.begin(); i!=chunks.end(); ++i ) {
- const ChunkPtr chunk = i->second;
+ // Check Map keys
+ for (ChunkRangeMap::const_iterator it = _ranges.begin(), end = _ranges.end(); it != end;
+ ++it) {
+ verify(it->first == it->second->getMax());
+ }
- ChunkRangeMap::const_iterator min = _ranges.upper_bound(chunk->getMin());
- ChunkRangeMap::const_iterator max = _ranges.lower_bound(chunk->getMax());
+ // Make sure we match the original chunks
+ const ChunkMap chunks = _ranges.begin()->second->getManager()->_chunkMap;
+ for (ChunkMap::const_iterator i = chunks.begin(); i != chunks.end(); ++i) {
+ const ChunkPtr chunk = i->second;
- verify(min != _ranges.end());
- verify(max != _ranges.end());
- verify(min == max);
- verify(min->second->getShardId() == chunk->getShardId());
- verify(min->second->containsKey( chunk->getMin() ));
- verify(min->second->containsKey( chunk->getMax() ) || (min->second->getMax() == chunk->getMax()));
- }
+ ChunkRangeMap::const_iterator min = _ranges.upper_bound(chunk->getMin());
+ ChunkRangeMap::const_iterator max = _ranges.lower_bound(chunk->getMax());
+ verify(min != _ranges.end());
+ verify(max != _ranges.end());
+ verify(min == max);
+ verify(min->second->getShardId() == chunk->getShardId());
+ verify(min->second->containsKey(chunk->getMin()));
+ verify(min->second->containsKey(chunk->getMax()) ||
+ (min->second->getMax() == chunk->getMax()));
}
- catch (...) {
- error() << "\t invalid ChunkRangeMap! printing ranges:";
- for (ChunkRangeMap::const_iterator it = _ranges.begin(), end = _ranges.end(); it != end; ++it) {
- log() << it->first << ": " << it->second->toString();
- }
+ } catch (...) {
+ error() << "\t invalid ChunkRangeMap! printing ranges:";
- throw;
+ for (ChunkRangeMap::const_iterator it = _ranges.begin(), end = _ranges.end(); it != end;
+ ++it) {
+ log() << it->first << ": " << it->second->toString();
}
+
+ throw;
}
+}
- void ChunkRangeManager::reloadAll(const ChunkMap& chunks) {
- _ranges.clear();
- _insertRange(chunks.begin(), chunks.end());
+void ChunkRangeManager::reloadAll(const ChunkMap& chunks) {
+ _ranges.clear();
+ _insertRange(chunks.begin(), chunks.end());
- DEV assertValid();
- }
+ DEV assertValid();
+}
- void ChunkRangeManager::_insertRange(ChunkMap::const_iterator begin, const ChunkMap::const_iterator end) {
- while (begin != end) {
- ChunkMap::const_iterator first = begin;
- ShardId shardId = first->second->getShardId();
- while (begin != end && (begin->second->getShardId() == shardId))
- ++begin;
+void ChunkRangeManager::_insertRange(ChunkMap::const_iterator begin,
+ const ChunkMap::const_iterator end) {
+ while (begin != end) {
+ ChunkMap::const_iterator first = begin;
+ ShardId shardId = first->second->getShardId();
+ while (begin != end && (begin->second->getShardId() == shardId))
+ ++begin;
- shared_ptr<ChunkRange> cr (new ChunkRange(first, begin));
- _ranges[cr->getMax()] = cr;
- }
+ shared_ptr<ChunkRange> cr(new ChunkRange(first, begin));
+ _ranges[cr->getMax()] = cr;
}
+}
- int ChunkManager::getCurrentDesiredChunkSize() const {
- // split faster in early chunks helps spread out an initial load better
- const int minChunkSize = 1 << 20; // 1 MBytes
+int ChunkManager::getCurrentDesiredChunkSize() const {
+ // split faster in early chunks helps spread out an initial load better
+ const int minChunkSize = 1 << 20; // 1 MBytes
- int splitThreshold = Chunk::MaxChunkSize;
+ int splitThreshold = Chunk::MaxChunkSize;
- int nc = numChunks();
-
- if ( nc <= 1 ) {
- return 1024;
- }
- else if ( nc < 3 ) {
- return minChunkSize / 2;
- }
- else if ( nc < 10 ) {
- splitThreshold = max( splitThreshold / 4 , minChunkSize );
- }
- else if ( nc < 20 ) {
- splitThreshold = max( splitThreshold / 2 , minChunkSize );
- }
+ int nc = numChunks();
- return splitThreshold;
+ if (nc <= 1) {
+ return 1024;
+ } else if (nc < 3) {
+ return minChunkSize / 2;
+ } else if (nc < 10) {
+ splitThreshold = max(splitThreshold / 4, minChunkSize);
+ } else if (nc < 20) {
+ splitThreshold = max(splitThreshold / 2, minChunkSize);
}
-} // namespace mongo
+ return splitThreshold;
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/chunk_manager.h b/src/mongo/s/chunk_manager.h
index e3b61a30b62..c2c7a48286d 100644
--- a/src/mongo/s/chunk_manager.h
+++ b/src/mongo/s/chunk_manager.h
@@ -38,240 +38,270 @@
namespace mongo {
- class CanonicalQuery;
- class ChunkManager;
- class CollectionType;
- struct QuerySolutionNode;
-
- typedef std::shared_ptr<ChunkManager> ChunkManagerPtr;
-
- // The key for the map is max for each Chunk or ChunkRange
- typedef std::map<BSONObj, std::shared_ptr<Chunk>, BSONObjCmp> ChunkMap;
-
- class ChunkRange {
- public:
- ChunkRange(ChunkMap::const_iterator begin, const ChunkMap::const_iterator end);
-
- // Merge min and max (must be adjacent ranges)
- ChunkRange(const ChunkRange& min, const ChunkRange& max);
-
- const ChunkManager* getManager() const { return _manager; }
- ShardId getShardId() const { return _shardId; }
-
- const BSONObj& getMin() const { return _min; }
- const BSONObj& getMax() const { return _max; }
-
- // clones of Chunk methods
- // Returns true if this ChunkRange contains the given shard key, and false otherwise
- //
- // Note: this function takes an extracted *key*, not an original document
- // (the point may be computed by, say, hashing a given field or projecting
- // to a subset of fields).
- bool containsKey( const BSONObj& shardKey ) const;
-
- std::string toString() const;
-
- private:
- const ChunkManager* _manager;
- const ShardId _shardId;
- const BSONObj _min;
- const BSONObj _max;
- };
-
- typedef std::map<BSONObj, std::shared_ptr<ChunkRange>, BSONObjCmp> ChunkRangeMap;
-
-
- class ChunkRangeManager {
+class CanonicalQuery;
+class ChunkManager;
+class CollectionType;
+struct QuerySolutionNode;
+
+typedef std::shared_ptr<ChunkManager> ChunkManagerPtr;
+
+// The key for the map is max for each Chunk or ChunkRange
+typedef std::map<BSONObj, std::shared_ptr<Chunk>, BSONObjCmp> ChunkMap;
+
+class ChunkRange {
+public:
+ ChunkRange(ChunkMap::const_iterator begin, const ChunkMap::const_iterator end);
+
+ // Merge min and max (must be adjacent ranges)
+ ChunkRange(const ChunkRange& min, const ChunkRange& max);
+
+ const ChunkManager* getManager() const {
+ return _manager;
+ }
+ ShardId getShardId() const {
+ return _shardId;
+ }
+
+ const BSONObj& getMin() const {
+ return _min;
+ }
+ const BSONObj& getMax() const {
+ return _max;
+ }
+
+ // clones of Chunk methods
+ // Returns true if this ChunkRange contains the given shard key, and false otherwise
+ //
+ // Note: this function takes an extracted *key*, not an original document
+ // (the point may be computed by, say, hashing a given field or projecting
+ // to a subset of fields).
+ bool containsKey(const BSONObj& shardKey) const;
+
+ std::string toString() const;
+
+private:
+ const ChunkManager* _manager;
+ const ShardId _shardId;
+ const BSONObj _min;
+ const BSONObj _max;
+};
+
+typedef std::map<BSONObj, std::shared_ptr<ChunkRange>, BSONObjCmp> ChunkRangeMap;
+
+
+class ChunkRangeManager {
+public:
+ const ChunkRangeMap& ranges() const {
+ return _ranges;
+ }
+
+ void clear() {
+ _ranges.clear();
+ }
+
+ void reloadAll(const ChunkMap& chunks);
+
+ // Slow operation -- wrap with DEV
+ void assertValid() const;
+
+ ChunkRangeMap::const_iterator upper_bound(const BSONObj& o) const {
+ return _ranges.upper_bound(o);
+ }
+ ChunkRangeMap::const_iterator lower_bound(const BSONObj& o) const {
+ return _ranges.lower_bound(o);
+ }
+
+private:
+ // assumes nothing in this range exists in _ranges
+ void _insertRange(ChunkMap::const_iterator begin, const ChunkMap::const_iterator end);
+
+ ChunkRangeMap _ranges;
+};
+
+
+/* config.sharding
+ { ns: 'alleyinsider.fs.chunks' ,
+ key: { ts : 1 } ,
+ shards: [ { min: 1, max: 100, server: a } , { min: 101, max: 200 , server : b } ]
+ }
+*/
+class ChunkManager {
+public:
+ typedef std::map<std::string, ChunkVersion> ShardVersionMap;
+
+ // Loads a new chunk manager from a collection document
+ explicit ChunkManager(const CollectionType& coll);
+
+ // Creates an empty chunk manager for the namespace
+ ChunkManager(const std::string& ns, const ShardKeyPattern& pattern, bool unique);
+
+ const std::string& getns() const {
+ return _ns;
+ }
+ const ShardKeyPattern& getShardKeyPattern() const {
+ return _keyPattern;
+ }
+ bool isUnique() const {
+ return _unique;
+ }
+
+ /**
+ * this is just an increasing number of how many ChunkManagers we have so we know if something has been updated
+ */
+ unsigned long long getSequenceNumber() const {
+ return _sequenceNumber;
+ }
+
+ //
+ // After constructor is invoked, we need to call loadExistingRanges. If this is a new
+ // sharded collection, we can call createFirstChunks first.
+ //
+
+ // Creates new chunks based on info in chunk manager
+ void createFirstChunks(const ShardId& primaryShardId,
+ const std::vector<BSONObj>* initPoints,
+ const std::set<ShardId>* initShardIds);
+
+ // Loads existing ranges based on info in chunk manager
+ void loadExistingRanges(const ChunkManager* oldManager);
+
+
+ // Helpers for load
+ void calcInitSplitsAndShards(const ShardId& primaryShardId,
+ const std::vector<BSONObj>* initPoints,
+ const std::set<ShardId>* initShardIds,
+ std::vector<BSONObj>* splitPoints,
+ std::vector<ShardId>* shardIds) const;
+
+ //
+ // Methods to use once loaded / created
+ //
+
+ int numChunks() const {
+ return _chunkMap.size();
+ }
+
+ /**
+ * Given a key that has been extracted from a document, returns the
+ * chunk that contains that key.
+ *
+ * For instance, to locate the chunk for document {a : "foo" , b : "bar"}
+ * when the shard key is {a : "hashed"}, you can call
+ * findIntersectingChunk() on {a : hash("foo") }
+ */
+ ChunkPtr findIntersectingChunk(const BSONObj& shardKey) const;
+
+ void getShardIdsForQuery(std::set<ShardId>& shardIds, const BSONObj& query) const;
+ void getAllShardIds(std::set<ShardId>* all) const;
+ /** @param shardIds set to the shard ids for shards
+ * covered by the interval [min, max], see SERVER-4791
+ */
+ void getShardIdsForRange(std::set<ShardId>& shardIds,
+ const BSONObj& min,
+ const BSONObj& max) const;
+
+ // Transforms query into bounds for each field in the shard key
+ // for example :
+ // Key { a: 1, b: 1 },
+ // Query { a : { $gte : 1, $lt : 2 },
+ // b : { $gte : 3, $lt : 4 } }
+ // => Bounds { a : [1, 2), b : [3, 4) }
+ static IndexBounds getIndexBoundsForQuery(const BSONObj& key,
+ const CanonicalQuery* canonicalQuery);
+
+ // Collapse query solution tree.
+ //
+ // If it has OR node, the result could be a superset of the index bounds generated.
+ // Since to give a single IndexBounds, this gives the union of bounds on each field.
+ // for example:
+ // OR: { a: (0, 1), b: (0, 1) },
+ // { a: (2, 3), b: (2, 3) }
+ // => { a: (0, 1), (2, 3), b: (0, 1), (2, 3) }
+ static IndexBounds collapseQuerySolution(const QuerySolutionNode* node);
+
+ const ChunkMap& getChunkMap() const {
+ return _chunkMap;
+ }
+
+ /**
+ * Returns true if, for this shard, the chunks are identical in both chunk managers
+ */
+ bool compatibleWith(const ChunkManager& other, const std::string& shard) const;
+
+ std::string toString() const;
+
+ ChunkVersion getVersion(const std::string& shardName) const;
+ ChunkVersion getVersion() const;
+
+ void _printChunks() const;
+
+ int getCurrentDesiredChunkSize() const;
+
+ std::shared_ptr<ChunkManager> reload(bool force = true) const; // doesn't modify self!
+
+private:
+ // returns true if load was consistent
+ bool _load(ChunkMap& chunks,
+ std::set<ShardId>& shardIds,
+ ShardVersionMap* shardVersions,
+ const ChunkManager* oldManager);
+
+
+ // All members should be const for thread-safety
+ const std::string _ns;
+ const ShardKeyPattern _keyPattern;
+ const bool _unique;
+
+ // The shard versioning mechanism hinges on keeping track of the number of times we reload
+ // ChunkManagers. Increasing this number here will prompt checkShardVersion to refresh the
+ // connection-level versions to the most up to date value.
+ const unsigned long long _sequenceNumber;
+
+ ChunkMap _chunkMap;
+ ChunkRangeManager _chunkRanges;
+
+ std::set<ShardId> _shardIds;
+
+ // Max known version per shard
+ ShardVersionMap _shardVersions;
+
+ // Max version across all chunks
+ ChunkVersion _version;
+
+ //
+ // Split Heuristic info
+ //
+ class SplitHeuristics {
public:
- const ChunkRangeMap& ranges() const { return _ranges; }
-
- void clear() { _ranges.clear(); }
-
- void reloadAll(const ChunkMap& chunks);
-
- // Slow operation -- wrap with DEV
- void assertValid() const;
-
- ChunkRangeMap::const_iterator upper_bound(const BSONObj& o) const { return _ranges.upper_bound(o); }
- ChunkRangeMap::const_iterator lower_bound(const BSONObj& o) const { return _ranges.lower_bound(o); }
-
- private:
- // assumes nothing in this range exists in _ranges
- void _insertRange(ChunkMap::const_iterator begin, const ChunkMap::const_iterator end);
-
- ChunkRangeMap _ranges;
+ SplitHeuristics() : _splitTickets(maxParallelSplits) {}
+
+ TicketHolder _splitTickets;
+
+ // Test whether we should split once data * splitTestFactor > chunkSize (approximately)
+ static const int splitTestFactor = 5;
+ // Maximum number of parallel threads requesting a split
+ static const int maxParallelSplits = 5;
+
+ // The idea here is that we're over-aggressive on split testing by a factor of
+ // splitTestFactor, so we can safely wait until we get to splitTestFactor invalid splits
+ // before changing. Unfortunately, we also potentially over-request the splits by a
+ // factor of maxParallelSplits, but since the factors are identical it works out
+ // (for now) for parallel or sequential oversplitting.
+ // TODO: Make splitting a separate thread with notifications?
+ static const int staleMinorReloadThreshold = maxParallelSplits;
};
+ mutable SplitHeuristics _splitHeuristics;
- /* config.sharding
- { ns: 'alleyinsider.fs.chunks' ,
- key: { ts : 1 } ,
- shards: [ { min: 1, max: 100, server: a } , { min: 101, max: 200 , server : b } ]
- }
- */
- class ChunkManager {
- public:
- typedef std::map<std::string, ChunkVersion> ShardVersionMap;
-
- // Loads a new chunk manager from a collection document
- explicit ChunkManager(const CollectionType& coll);
-
- // Creates an empty chunk manager for the namespace
- ChunkManager( const std::string& ns, const ShardKeyPattern& pattern, bool unique );
-
- const std::string& getns() const { return _ns; }
- const ShardKeyPattern& getShardKeyPattern() const { return _keyPattern; }
- bool isUnique() const { return _unique; }
-
- /**
- * this is just an increasing number of how many ChunkManagers we have so we know if something has been updated
- */
- unsigned long long getSequenceNumber() const { return _sequenceNumber; }
-
- //
- // After constructor is invoked, we need to call loadExistingRanges. If this is a new
- // sharded collection, we can call createFirstChunks first.
- //
-
- // Creates new chunks based on info in chunk manager
- void createFirstChunks(const ShardId& primaryShardId,
- const std::vector<BSONObj>* initPoints,
- const std::set<ShardId>* initShardIds);
+ //
+ // End split heuristics
+ //
- // Loads existing ranges based on info in chunk manager
- void loadExistingRanges(const ChunkManager* oldManager);
-
-
- // Helpers for load
- void calcInitSplitsAndShards(const ShardId& primaryShardId,
- const std::vector<BSONObj>* initPoints,
- const std::set<ShardId>* initShardIds,
- std::vector<BSONObj>* splitPoints,
- std::vector<ShardId>* shardIds) const;
-
- //
- // Methods to use once loaded / created
- //
-
- int numChunks() const { return _chunkMap.size(); }
-
- /**
- * Given a key that has been extracted from a document, returns the
- * chunk that contains that key.
- *
- * For instance, to locate the chunk for document {a : "foo" , b : "bar"}
- * when the shard key is {a : "hashed"}, you can call
- * findIntersectingChunk() on {a : hash("foo") }
- */
- ChunkPtr findIntersectingChunk( const BSONObj& shardKey ) const;
-
- void getShardIdsForQuery(std::set<ShardId>& shardIds, const BSONObj& query) const;
- void getAllShardIds(std::set<ShardId>* all) const;
- /** @param shardIds set to the shard ids for shards
- * covered by the interval [min, max], see SERVER-4791
- */
- void getShardIdsForRange(std::set<ShardId>& shardIds, const BSONObj& min, const BSONObj& max) const;
-
- // Transforms query into bounds for each field in the shard key
- // for example :
- // Key { a: 1, b: 1 },
- // Query { a : { $gte : 1, $lt : 2 },
- // b : { $gte : 3, $lt : 4 } }
- // => Bounds { a : [1, 2), b : [3, 4) }
- static IndexBounds getIndexBoundsForQuery(const BSONObj& key, const CanonicalQuery* canonicalQuery);
-
- // Collapse query solution tree.
- //
- // If it has OR node, the result could be a superset of the index bounds generated.
- // Since to give a single IndexBounds, this gives the union of bounds on each field.
- // for example:
- // OR: { a: (0, 1), b: (0, 1) },
- // { a: (2, 3), b: (2, 3) }
- // => { a: (0, 1), (2, 3), b: (0, 1), (2, 3) }
- static IndexBounds collapseQuerySolution( const QuerySolutionNode* node );
-
- const ChunkMap& getChunkMap() const { return _chunkMap; }
-
- /**
- * Returns true if, for this shard, the chunks are identical in both chunk managers
- */
- bool compatibleWith(const ChunkManager& other, const std::string& shard) const;
-
- std::string toString() const;
-
- ChunkVersion getVersion(const std::string& shardName) const;
- ChunkVersion getVersion() const;
-
- void _printChunks() const;
-
- int getCurrentDesiredChunkSize() const;
-
- std::shared_ptr<ChunkManager> reload(bool force = true) const; // doesn't modify self!
-
- private:
- // returns true if load was consistent
- bool _load(ChunkMap& chunks,
- std::set<ShardId>& shardIds,
- ShardVersionMap* shardVersions,
- const ChunkManager* oldManager);
-
-
- // All members should be const for thread-safety
- const std::string _ns;
- const ShardKeyPattern _keyPattern;
- const bool _unique;
-
- // The shard versioning mechanism hinges on keeping track of the number of times we reload
- // ChunkManagers. Increasing this number here will prompt checkShardVersion to refresh the
- // connection-level versions to the most up to date value.
- const unsigned long long _sequenceNumber;
-
- ChunkMap _chunkMap;
- ChunkRangeManager _chunkRanges;
-
- std::set<ShardId> _shardIds;
-
- // Max known version per shard
- ShardVersionMap _shardVersions;
-
- // Max version across all chunks
- ChunkVersion _version;
-
- //
- // Split Heuristic info
- //
- class SplitHeuristics {
- public:
- SplitHeuristics() : _splitTickets(maxParallelSplits) {
- }
-
- TicketHolder _splitTickets;
-
- // Test whether we should split once data * splitTestFactor > chunkSize (approximately)
- static const int splitTestFactor = 5;
- // Maximum number of parallel threads requesting a split
- static const int maxParallelSplits = 5;
-
- // The idea here is that we're over-aggressive on split testing by a factor of
- // splitTestFactor, so we can safely wait until we get to splitTestFactor invalid splits
- // before changing. Unfortunately, we also potentially over-request the splits by a
- // factor of maxParallelSplits, but since the factors are identical it works out
- // (for now) for parallel or sequential oversplitting.
- // TODO: Make splitting a separate thread with notifications?
- static const int staleMinorReloadThreshold = maxParallelSplits;
- };
-
- mutable SplitHeuristics _splitHeuristics;
-
- //
- // End split heuristics
- //
-
- friend class Chunk;
- friend class ChunkRangeManager; // only needed for CRM::assertValid()
- static AtomicUInt32 NextSequenceNumber;
+ friend class Chunk;
+ friend class ChunkRangeManager; // only needed for CRM::assertValid()
+ static AtomicUInt32 NextSequenceNumber;
- friend class TestableChunkManager;
- };
+ friend class TestableChunkManager;
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/chunk_manager_targeter.cpp b/src/mongo/s/chunk_manager_targeter.cpp
index bac563fc027..446d4a5fafd 100644
--- a/src/mongo/s/chunk_manager_targeter.cpp
+++ b/src/mongo/s/chunk_manager_targeter.cpp
@@ -43,738 +43,678 @@
namespace mongo {
- using std::shared_ptr;
- using mongoutils::str::stream;
- using std::map;
- using std::set;
- using std::string;
- using std::vector;
+using std::shared_ptr;
+using mongoutils::str::stream;
+using std::map;
+using std::set;
+using std::string;
+using std::vector;
namespace {
- enum UpdateType {
- UpdateType_Replacement, UpdateType_OpStyle, UpdateType_Unknown
- };
-
- enum CompareResult {
- CompareResult_Unknown, CompareResult_GTE, CompareResult_LT
- };
-
- const ShardKeyPattern virtualIdShardKey(BSON("_id" << 1));
-
- // To match legacy reload behavior, we have to backoff on config reload per-thread
- // TODO: Centralize this behavior better by refactoring config reload in mongos
- boost::thread_specific_ptr<Backoff> perThreadBackoff;
- const int maxWaitMillis = 500;
-
- /**
- * There are two styles of update expressions:
- *
- * Replacement style: coll.update({ x : 1 }, { y : 2 })
- * OpStyle: coll.update({ x : 1 }, { $set : { y : 2 } })
- */
- UpdateType getUpdateExprType(const BSONObj& updateExpr) {
- // Empty update is replacement-style, by default
- if (updateExpr.isEmpty()) {
- return UpdateType_Replacement;
- }
+enum UpdateType { UpdateType_Replacement, UpdateType_OpStyle, UpdateType_Unknown };
- UpdateType updateType = UpdateType_Unknown;
+enum CompareResult { CompareResult_Unknown, CompareResult_GTE, CompareResult_LT };
- BSONObjIterator it(updateExpr);
- while (it.more()) {
- BSONElement next = it.next();
+const ShardKeyPattern virtualIdShardKey(BSON("_id" << 1));
- if (next.fieldName()[0] == '$') {
- if (updateType == UpdateType_Unknown) {
- updateType = UpdateType_OpStyle;
- }
- else if (updateType == UpdateType_Replacement) {
- return UpdateType_Unknown;
- }
- }
- else {
- if (updateType == UpdateType_Unknown) {
- updateType = UpdateType_Replacement;
- }
- else if (updateType == UpdateType_OpStyle) {
- return UpdateType_Unknown;
- }
- }
- }
+// To match legacy reload behavior, we have to backoff on config reload per-thread
+// TODO: Centralize this behavior better by refactoring config reload in mongos
+boost::thread_specific_ptr<Backoff> perThreadBackoff;
+const int maxWaitMillis = 500;
- return updateType;
- }
-
- /**
- * This returns "does the query have an _id field" and "is the _id field querying for a direct
- * value like _id : 3 and not _id : { $gt : 3 }"
- *
- * Ex: { _id : 1 } => true
- * { foo : <anything>, _id : 1 } => true
- * { _id : { $lt : 30 } } => false
- * { foo : <anything> } => false
- */
- bool isExactIdQuery(const BSONObj& query) {
- StatusWith<BSONObj> status = virtualIdShardKey.extractShardKeyFromQuery(query);
- if (!status.isOK()) {
- return false;
- }
-
- return !status.getValue()["_id"].eoo();
+/**
+ * There are two styles of update expressions:
+ *
+ * Replacement style: coll.update({ x : 1 }, { y : 2 })
+ * OpStyle: coll.update({ x : 1 }, { $set : { y : 2 } })
+ */
+UpdateType getUpdateExprType(const BSONObj& updateExpr) {
+ // Empty update is replacement-style, by default
+ if (updateExpr.isEmpty()) {
+ return UpdateType_Replacement;
}
- void refreshBackoff() {
- if (!perThreadBackoff.get()) {
- perThreadBackoff.reset(new Backoff(maxWaitMillis, maxWaitMillis * 2));
- }
-
- perThreadBackoff.get()->nextSleepMillis();
- }
+ UpdateType updateType = UpdateType_Unknown;
+ BSONObjIterator it(updateExpr);
+ while (it.more()) {
+ BSONElement next = it.next();
- //
- // Utilities to compare shard versions
- //
-
- /**
- * Returns the relationship of two shard versions. Shard versions of a collection that has not
- * been dropped and recreated and where there is at least one chunk on a shard are comparable,
- * otherwise the result is ambiguous.
- */
- CompareResult compareShardVersions(const ChunkVersion& shardVersionA,
- const ChunkVersion& shardVersionB) {
-
- // Collection may have been dropped
- if (!shardVersionA.hasEqualEpoch(shardVersionB)) {
- return CompareResult_Unknown;
- }
-
- // Zero shard versions are only comparable to themselves
- if (!shardVersionA.isSet() || !shardVersionB.isSet()) {
- // If both are zero...
- if (!shardVersionA.isSet() && !shardVersionB.isSet()) {
- return CompareResult_GTE;
+ if (next.fieldName()[0] == '$') {
+ if (updateType == UpdateType_Unknown) {
+ updateType = UpdateType_OpStyle;
+ } else if (updateType == UpdateType_Replacement) {
+ return UpdateType_Unknown;
+ }
+ } else {
+ if (updateType == UpdateType_Unknown) {
+ updateType = UpdateType_Replacement;
+ } else if (updateType == UpdateType_OpStyle) {
+ return UpdateType_Unknown;
}
-
- return CompareResult_Unknown;
}
+ }
- if (shardVersionA < shardVersionB) {
- return CompareResult_LT;
- }
+ return updateType;
+}
- else return CompareResult_GTE;
+/**
+ * This returns "does the query have an _id field" and "is the _id field querying for a direct
+ * value like _id : 3 and not _id : { $gt : 3 }"
+ *
+ * Ex: { _id : 1 } => true
+ * { foo : <anything>, _id : 1 } => true
+ * { _id : { $lt : 30 } } => false
+ * { foo : <anything> } => false
+ */
+bool isExactIdQuery(const BSONObj& query) {
+ StatusWith<BSONObj> status = virtualIdShardKey.extractShardKeyFromQuery(query);
+ if (!status.isOK()) {
+ return false;
}
- ChunkVersion getShardVersion(StringData shardName,
- const ChunkManager* manager,
- const Shard* primary) {
+ return !status.getValue()["_id"].eoo();
+}
- dassert(!(manager && primary));
- dassert(manager || primary);
+void refreshBackoff() {
+ if (!perThreadBackoff.get()) {
+ perThreadBackoff.reset(new Backoff(maxWaitMillis, maxWaitMillis * 2));
+ }
- if (primary) {
- return ChunkVersion::UNSHARDED();
- }
+ perThreadBackoff.get()->nextSleepMillis();
+}
- return manager->getVersion(shardName.toString());
- }
-
- /**
- * Returns the relationship between two maps of shard versions. As above, these maps are often
- * comparable when the collection has not been dropped and there is at least one chunk on the
- * shards. If any versions in the maps are not comparable, the result is _Unknown.
- *
- * If any versions in the first map (cached) are _LT the versions in the second map (remote),
- * the first (cached) versions are _LT the second (remote) versions.
- *
- * Note that the signature here is weird since our cached map of chunk versions is stored in a
- * ChunkManager or is implicit in the primary shard of the collection.
- */
- CompareResult compareAllShardVersions(const ChunkManager* cachedChunkManager,
- const Shard* cachedPrimary,
- const map<string, ChunkVersion>& remoteShardVersions) {
-
- CompareResult finalResult = CompareResult_GTE;
-
- for (map<string, ChunkVersion>::const_iterator it = remoteShardVersions.begin();
- it != remoteShardVersions.end();
- ++it) {
-
- // Get the remote and cached version for the next shard
- const string& shardName = it->first;
- const ChunkVersion& remoteShardVersion = it->second;
-
- ChunkVersion cachedShardVersion;
-
- try {
- // Throws b/c shard constructor throws
- cachedShardVersion = getShardVersion(shardName,
- cachedChunkManager,
- cachedPrimary);
- }
- catch (const DBException& ex) {
- warning() << "could not lookup shard " << shardName
- << " in local cache, shard metadata may have changed"
- << " or be unavailable" << causedBy(ex);
- return CompareResult_Unknown;
- }
+//
+// Utilities to compare shard versions
+//
- // Compare the remote and cached versions
- CompareResult result = compareShardVersions(cachedShardVersion, remoteShardVersion);
-
- if (result == CompareResult_Unknown) return result;
- if (result == CompareResult_LT) finalResult = CompareResult_LT;
+/**
+ * Returns the relationship of two shard versions. Shard versions of a collection that has not
+ * been dropped and recreated and where there is at least one chunk on a shard are comparable,
+ * otherwise the result is ambiguous.
+ */
+CompareResult compareShardVersions(const ChunkVersion& shardVersionA,
+ const ChunkVersion& shardVersionB) {
+ // Collection may have been dropped
+ if (!shardVersionA.hasEqualEpoch(shardVersionB)) {
+ return CompareResult_Unknown;
+ }
- // Note that we keep going after _LT b/c there could be more _Unknowns.
+ // Zero shard versions are only comparable to themselves
+ if (!shardVersionA.isSet() || !shardVersionB.isSet()) {
+ // If both are zero...
+ if (!shardVersionA.isSet() && !shardVersionB.isSet()) {
+ return CompareResult_GTE;
}
- return finalResult;
+ return CompareResult_Unknown;
}
- /**
- * Whether or not the manager/primary pair is different from the other manager/primary pair.
- */
- bool isMetadataDifferent(const ChunkManagerPtr& managerA,
- const ShardPtr& primaryA,
- const ChunkManagerPtr& managerB,
- const ShardPtr& primaryB) {
+ if (shardVersionA < shardVersionB) {
+ return CompareResult_LT;
+ }
- if ((managerA && !managerB) || (!managerA && managerB) || (primaryA && !primaryB) || (!primaryA && primaryB)) return true;
+ else
+ return CompareResult_GTE;
+}
- if (managerA) {
- return !managerA->getVersion().isStrictlyEqualTo(managerB->getVersion());
- }
+ChunkVersion getShardVersion(StringData shardName,
+ const ChunkManager* manager,
+ const Shard* primary) {
+ dassert(!(manager && primary));
+ dassert(manager || primary);
- dassert(NULL != primaryA.get());
- return primaryA->getId() != primaryB->getId();
+ if (primary) {
+ return ChunkVersion::UNSHARDED();
}
- /**
- * Whether or not the manager/primary pair was changed or refreshed from a previous version
- * of the metadata.
- */
- bool wasMetadataRefreshed(const ChunkManagerPtr& managerA,
- const ShardPtr& primaryA,
- const ChunkManagerPtr& managerB,
- const ShardPtr& primaryB) {
+ return manager->getVersion(shardName.toString());
+}
- if (isMetadataDifferent(managerA, primaryA, managerB, primaryB))
- return true;
+/**
+ * Returns the relationship between two maps of shard versions. As above, these maps are often
+ * comparable when the collection has not been dropped and there is at least one chunk on the
+ * shards. If any versions in the maps are not comparable, the result is _Unknown.
+ *
+ * If any versions in the first map (cached) are _LT the versions in the second map (remote),
+ * the first (cached) versions are _LT the second (remote) versions.
+ *
+ * Note that the signature here is weird since our cached map of chunk versions is stored in a
+ * ChunkManager or is implicit in the primary shard of the collection.
+ */
+CompareResult compareAllShardVersions(const ChunkManager* cachedChunkManager,
+ const Shard* cachedPrimary,
+ const map<string, ChunkVersion>& remoteShardVersions) {
+ CompareResult finalResult = CompareResult_GTE;
+
+ for (map<string, ChunkVersion>::const_iterator it = remoteShardVersions.begin();
+ it != remoteShardVersions.end();
+ ++it) {
+ // Get the remote and cached version for the next shard
+ const string& shardName = it->first;
+ const ChunkVersion& remoteShardVersion = it->second;
+
+ ChunkVersion cachedShardVersion;
+
+ try {
+ // Throws b/c shard constructor throws
+ cachedShardVersion = getShardVersion(shardName, cachedChunkManager, cachedPrimary);
+ } catch (const DBException& ex) {
+ warning() << "could not lookup shard " << shardName
+ << " in local cache, shard metadata may have changed"
+ << " or be unavailable" << causedBy(ex);
- if (managerA) {
- dassert(managerB.get()); // otherwise metadata would be different
- return managerA->getSequenceNumber() != managerB->getSequenceNumber();
+ return CompareResult_Unknown;
}
- return false;
- }
-
-} // namespace
+ // Compare the remote and cached versions
+ CompareResult result = compareShardVersions(cachedShardVersion, remoteShardVersion);
- ChunkManagerTargeter::ChunkManagerTargeter(const NamespaceString& nss)
- : _nss(nss),
- _needsTargetingRefresh(false) {
+ if (result == CompareResult_Unknown)
+ return result;
+ if (result == CompareResult_LT)
+ finalResult = CompareResult_LT;
+ // Note that we keep going after _LT b/c there could be more _Unknowns.
}
- Status ChunkManagerTargeter::init() {
- auto status = grid.implicitCreateDb(_nss.db().toString());
- if (!status.isOK()) {
- return status.getStatus();
- }
-
- shared_ptr<DBConfig> config = status.getValue();
- config->getChunkManagerOrPrimary(_nss.ns(), _manager, _primary);
-
- return Status::OK();
- }
+ return finalResult;
+}
- const NamespaceString& ChunkManagerTargeter::getNS() const {
- return _nss;
+/**
+ * Whether or not the manager/primary pair is different from the other manager/primary pair.
+ */
+bool isMetadataDifferent(const ChunkManagerPtr& managerA,
+ const ShardPtr& primaryA,
+ const ChunkManagerPtr& managerB,
+ const ShardPtr& primaryB) {
+ if ((managerA && !managerB) || (!managerA && managerB) || (primaryA && !primaryB) ||
+ (!primaryA && primaryB))
+ return true;
+
+ if (managerA) {
+ return !managerA->getVersion().isStrictlyEqualTo(managerB->getVersion());
}
- Status ChunkManagerTargeter::targetInsert( const BSONObj& doc,
- ShardEndpoint** endpoint ) const {
+ dassert(NULL != primaryA.get());
+ return primaryA->getId() != primaryB->getId();
+}
- BSONObj shardKey;
+/**
+* Whether or not the manager/primary pair was changed or refreshed from a previous version
+* of the metadata.
+*/
+bool wasMetadataRefreshed(const ChunkManagerPtr& managerA,
+ const ShardPtr& primaryA,
+ const ChunkManagerPtr& managerB,
+ const ShardPtr& primaryB) {
+ if (isMetadataDifferent(managerA, primaryA, managerB, primaryB))
+ return true;
+
+ if (managerA) {
+ dassert(managerB.get()); // otherwise metadata would be different
+ return managerA->getSequenceNumber() != managerB->getSequenceNumber();
+ }
- if ( _manager ) {
+ return false;
+}
- //
- // Sharded collections have the following requirements for targeting:
- //
- // Inserts must contain the exact shard key.
- //
+} // namespace
- shardKey = _manager->getShardKeyPattern().extractShardKeyFromDoc(doc);
+ChunkManagerTargeter::ChunkManagerTargeter(const NamespaceString& nss)
+ : _nss(nss), _needsTargetingRefresh(false) {}
- // Check shard key exists
- if (shardKey.isEmpty()) {
- return Status(ErrorCodes::ShardKeyNotFound,
- stream() << "document " << doc
- << " does not contain shard key for pattern "
- << _manager->getShardKeyPattern().toString());
- }
-
- // Check shard key size on insert
- Status status = ShardKeyPattern::checkShardKeySize(shardKey);
- if (!status.isOK())
- return status;
- }
+Status ChunkManagerTargeter::init() {
+ auto status = grid.implicitCreateDb(_nss.db().toString());
+ if (!status.isOK()) {
+ return status.getStatus();
+ }
- // Target the shard key or database primary
- if (!shardKey.isEmpty()) {
- return targetShardKey(shardKey, doc.objsize(), endpoint);
- }
- else {
+ shared_ptr<DBConfig> config = status.getValue();
+ config->getChunkManagerOrPrimary(_nss.ns(), _manager, _primary);
- if (!_primary) {
- return Status(ErrorCodes::NamespaceNotFound,
- str::stream() << "could not target insert in collection "
- << getNS().ns() << "; no metadata found");
- }
+ return Status::OK();
+}
- *endpoint = new ShardEndpoint(_primary->getId(), ChunkVersion::UNSHARDED());
- return Status::OK();
- }
- }
+const NamespaceString& ChunkManagerTargeter::getNS() const {
+ return _nss;
+}
- Status ChunkManagerTargeter::targetUpdate( const BatchedUpdateDocument& updateDoc,
- vector<ShardEndpoint*>* endpoints ) const {
+Status ChunkManagerTargeter::targetInsert(const BSONObj& doc, ShardEndpoint** endpoint) const {
+ BSONObj shardKey;
+ if (_manager) {
//
- // Update targeting may use either the query or the update. This is to support save-style
- // updates, of the form:
- //
- // coll.update({ _id : xxx }, { _id : xxx, shardKey : 1, foo : bar }, { upsert : true })
- //
- // Because drivers do not know the shard key, they can't pull the shard key automatically
- // into the query doc, and to correctly support upsert we must target a single shard.
+ // Sharded collections have the following requirements for targeting:
//
- // The rule is simple - If the update is replacement style (no '$set'), we target using the
- // update. If the update is replacement style, we target using the query.
+ // Inserts must contain the exact shard key.
//
- // If we have the exact shard key in either the query or replacement doc, we target using
- // that extracted key.
- //
-
- BSONObj query = updateDoc.getQuery();
- BSONObj updateExpr = updateDoc.getUpdateExpr();
- UpdateType updateType = getUpdateExprType( updateDoc.getUpdateExpr() );
+ shardKey = _manager->getShardKeyPattern().extractShardKeyFromDoc(doc);
- if ( updateType == UpdateType_Unknown ) {
- return Status( ErrorCodes::UnsupportedFormat,
- stream() << "update document " << updateExpr
- << " has mixed $operator and non-$operator style fields" );
+ // Check shard key exists
+ if (shardKey.isEmpty()) {
+ return Status(ErrorCodes::ShardKeyNotFound,
+ stream() << "document " << doc
+ << " does not contain shard key for pattern "
+ << _manager->getShardKeyPattern().toString());
}
- BSONObj shardKey;
-
- if ( _manager ) {
-
- //
- // Sharded collections have the following futher requirements for targeting:
- //
- // Upserts must be targeted exactly by shard key.
- // Non-multi updates must be targeted exactly by shard key *or* exact _id.
- //
-
- // Get the shard key
- if (updateType == UpdateType_OpStyle) {
-
- // Target using the query
- StatusWith<BSONObj> status =
- _manager->getShardKeyPattern().extractShardKeyFromQuery(query);
-
- // Bad query
- if (!status.isOK())
- return status.getStatus();
-
- shardKey = status.getValue();
- }
- else {
- // Target using the replacement document
- shardKey = _manager->getShardKeyPattern().extractShardKeyFromDoc(updateExpr);
- }
+ // Check shard key size on insert
+ Status status = ShardKeyPattern::checkShardKeySize(shardKey);
+ if (!status.isOK())
+ return status;
+ }
- //
- // Extra sharded update validation
- //
+ // Target the shard key or database primary
+ if (!shardKey.isEmpty()) {
+ return targetShardKey(shardKey, doc.objsize(), endpoint);
+ } else {
+ if (!_primary) {
+ return Status(ErrorCodes::NamespaceNotFound,
+ str::stream() << "could not target insert in collection " << getNS().ns()
+ << "; no metadata found");
+ }
- if (updateDoc.getUpsert()) {
+ *endpoint = new ShardEndpoint(_primary->getId(), ChunkVersion::UNSHARDED());
+ return Status::OK();
+ }
+}
- // Sharded upserts *always* need to be exactly targeted by shard key
- if (shardKey.isEmpty()) {
- return Status(ErrorCodes::ShardKeyNotFound,
- stream() << "upsert " << updateDoc.toBSON()
- << " does not contain shard key for pattern "
- << _manager->getShardKeyPattern().toString());
- }
+Status ChunkManagerTargeter::targetUpdate(const BatchedUpdateDocument& updateDoc,
+ vector<ShardEndpoint*>* endpoints) const {
+ //
+ // Update targeting may use either the query or the update. This is to support save-style
+ // updates, of the form:
+ //
+ // coll.update({ _id : xxx }, { _id : xxx, shardKey : 1, foo : bar }, { upsert : true })
+ //
+ // Because drivers do not know the shard key, they can't pull the shard key automatically
+ // into the query doc, and to correctly support upsert we must target a single shard.
+ //
+ // The rule is simple - If the update is replacement style (no '$set'), we target using the
+ // update. If the update is replacement style, we target using the query.
+ //
+ // If we have the exact shard key in either the query or replacement doc, we target using
+ // that extracted key.
+ //
- // Also check shard key size on upsert
- Status status = ShardKeyPattern::checkShardKeySize(shardKey);
- if (!status.isOK())
- return status;
- }
+ BSONObj query = updateDoc.getQuery();
+ BSONObj updateExpr = updateDoc.getUpdateExpr();
- // Validate that single (non-multi) sharded updates are targeted by shard key or _id
- if (!updateDoc.getMulti() && shardKey.isEmpty()
- && !isExactIdQuery(updateDoc.getQuery())) {
- return Status(ErrorCodes::ShardKeyNotFound,
- stream() << "update " << updateDoc.toBSON()
- << " does not contain _id or shard key for pattern "
- << _manager->getShardKeyPattern().toString());
- }
- }
+ UpdateType updateType = getUpdateExprType(updateDoc.getUpdateExpr());
- // Target the shard key, query, or replacement doc
- if (!shardKey.isEmpty()) {
- // We can't rely on our query targeting to be exact
- ShardEndpoint* endpoint = NULL;
- Status result = targetShardKey(shardKey,
- (query.objsize() + updateExpr.objsize()),
- &endpoint);
- endpoints->push_back(endpoint);
- return result;
- }
- else if (updateType == UpdateType_OpStyle) {
- return targetQuery(query, endpoints);
- }
- else {
- return targetDoc(updateExpr, endpoints);
- }
+ if (updateType == UpdateType_Unknown) {
+ return Status(ErrorCodes::UnsupportedFormat,
+ stream() << "update document " << updateExpr
+ << " has mixed $operator and non-$operator style fields");
}
- Status ChunkManagerTargeter::targetDelete( const BatchedDeleteDocument& deleteDoc,
- vector<ShardEndpoint*>* endpoints ) const {
-
- BSONObj shardKey;
-
- if ( _manager ) {
+ BSONObj shardKey;
- //
- // Sharded collections have the following further requirements for targeting:
- //
- // Limit-1 deletes must be targeted exactly by shard key *or* exact _id
- //
+ if (_manager) {
+ //
+ // Sharded collections have the following futher requirements for targeting:
+ //
+ // Upserts must be targeted exactly by shard key.
+ // Non-multi updates must be targeted exactly by shard key *or* exact _id.
+ //
- // Get the shard key
+ // Get the shard key
+ if (updateType == UpdateType_OpStyle) {
+ // Target using the query
StatusWith<BSONObj> status =
- _manager->getShardKeyPattern().extractShardKeyFromQuery(deleteDoc.getQuery());
+ _manager->getShardKeyPattern().extractShardKeyFromQuery(query);
// Bad query
if (!status.isOK())
return status.getStatus();
shardKey = status.getValue();
+ } else {
+ // Target using the replacement document
+ shardKey = _manager->getShardKeyPattern().extractShardKeyFromDoc(updateExpr);
+ }
- // Validate that single (limit-1) sharded deletes are targeted by shard key or _id
- if (deleteDoc.getLimit() == 1 && shardKey.isEmpty()
- && !isExactIdQuery(deleteDoc.getQuery())) {
+ //
+ // Extra sharded update validation
+ //
+
+ if (updateDoc.getUpsert()) {
+ // Sharded upserts *always* need to be exactly targeted by shard key
+ if (shardKey.isEmpty()) {
return Status(ErrorCodes::ShardKeyNotFound,
- stream() << "delete " << deleteDoc.toBSON()
- << " does not contain _id or shard key for pattern "
+ stream() << "upsert " << updateDoc.toBSON()
+ << " does not contain shard key for pattern "
<< _manager->getShardKeyPattern().toString());
}
- }
- // Target the shard key or delete query
- if (!shardKey.isEmpty()) {
- // We can't rely on our query targeting to be exact
- ShardEndpoint* endpoint = NULL;
- Status result = targetShardKey(shardKey, 0, &endpoint);
- endpoints->push_back(endpoint);
- return result;
+ // Also check shard key size on upsert
+ Status status = ShardKeyPattern::checkShardKeySize(shardKey);
+ if (!status.isOK())
+ return status;
}
- else {
- return targetQuery(deleteDoc.getQuery(), endpoints);
+
+ // Validate that single (non-multi) sharded updates are targeted by shard key or _id
+ if (!updateDoc.getMulti() && shardKey.isEmpty() && !isExactIdQuery(updateDoc.getQuery())) {
+ return Status(ErrorCodes::ShardKeyNotFound,
+ stream() << "update " << updateDoc.toBSON()
+ << " does not contain _id or shard key for pattern "
+ << _manager->getShardKeyPattern().toString());
}
}
- Status ChunkManagerTargeter::targetDoc(const BSONObj& doc,
- vector<ShardEndpoint*>* endpoints) const {
- // NOTE: This is weird and fragile, but it's the way our language works right now -
- // documents are either A) invalid or B) valid equality queries over themselves.
- return targetQuery(doc, endpoints);
+ // Target the shard key, query, or replacement doc
+ if (!shardKey.isEmpty()) {
+ // We can't rely on our query targeting to be exact
+ ShardEndpoint* endpoint = NULL;
+ Status result =
+ targetShardKey(shardKey, (query.objsize() + updateExpr.objsize()), &endpoint);
+ endpoints->push_back(endpoint);
+ return result;
+ } else if (updateType == UpdateType_OpStyle) {
+ return targetQuery(query, endpoints);
+ } else {
+ return targetDoc(updateExpr, endpoints);
}
+}
- Status ChunkManagerTargeter::targetQuery( const BSONObj& query,
- vector<ShardEndpoint*>* endpoints ) const {
-
- if ( !_primary && !_manager ) {
- return Status(ErrorCodes::NamespaceNotFound,
- stream() << "could not target query in "
- << getNS().ns() << "; no metadata found");
- }
-
- set<ShardId> shardIds;
- if ( _manager ) {
- try {
- _manager->getShardIdsForQuery(shardIds, query);
- } catch ( const DBException& ex ) {
- return ex.toStatus();
- }
- }
- else {
- shardIds.insert(_primary->getId());
- }
+Status ChunkManagerTargeter::targetDelete(const BatchedDeleteDocument& deleteDoc,
+ vector<ShardEndpoint*>* endpoints) const {
+ BSONObj shardKey;
- for (const ShardId& shardId : shardIds) {
- endpoints->push_back(new ShardEndpoint(shardId,
- _manager ? _manager->getVersion(shardId) :
- ChunkVersion::UNSHARDED()));
- }
+ if (_manager) {
+ //
+ // Sharded collections have the following further requirements for targeting:
+ //
+ // Limit-1 deletes must be targeted exactly by shard key *or* exact _id
+ //
- return Status::OK();
- }
+ // Get the shard key
+ StatusWith<BSONObj> status =
+ _manager->getShardKeyPattern().extractShardKeyFromQuery(deleteDoc.getQuery());
- Status ChunkManagerTargeter::targetShardKey(const BSONObj& shardKey,
- long long estDataSize,
- ShardEndpoint** endpoint) const {
- invariant(NULL != _manager);
+ // Bad query
+ if (!status.isOK())
+ return status.getStatus();
- ChunkPtr chunk = _manager->findIntersectingChunk(shardKey);
+ shardKey = status.getValue();
- // Track autosplit stats for sharded collections
- // Note: this is only best effort accounting and is not accurate.
- if (estDataSize > 0) {
- _stats.chunkSizeDelta[chunk->getMin()] += estDataSize;
+ // Validate that single (limit-1) sharded deletes are targeted by shard key or _id
+ if (deleteDoc.getLimit() == 1 && shardKey.isEmpty() &&
+ !isExactIdQuery(deleteDoc.getQuery())) {
+ return Status(ErrorCodes::ShardKeyNotFound,
+ stream() << "delete " << deleteDoc.toBSON()
+ << " does not contain _id or shard key for pattern "
+ << _manager->getShardKeyPattern().toString());
}
+ }
- *endpoint = new ShardEndpoint(chunk->getShardId(),
- _manager->getVersion(chunk->getShardId()));
+ // Target the shard key or delete query
+ if (!shardKey.isEmpty()) {
+ // We can't rely on our query targeting to be exact
+ ShardEndpoint* endpoint = NULL;
+ Status result = targetShardKey(shardKey, 0, &endpoint);
+ endpoints->push_back(endpoint);
+ return result;
+ } else {
+ return targetQuery(deleteDoc.getQuery(), endpoints);
+ }
+}
+
+Status ChunkManagerTargeter::targetDoc(const BSONObj& doc,
+ vector<ShardEndpoint*>* endpoints) const {
+ // NOTE: This is weird and fragile, but it's the way our language works right now -
+ // documents are either A) invalid or B) valid equality queries over themselves.
+ return targetQuery(doc, endpoints);
+}
+
+Status ChunkManagerTargeter::targetQuery(const BSONObj& query,
+ vector<ShardEndpoint*>* endpoints) const {
+ if (!_primary && !_manager) {
+ return Status(ErrorCodes::NamespaceNotFound,
+ stream() << "could not target query in " << getNS().ns()
+ << "; no metadata found");
+ }
- return Status::OK();
+ set<ShardId> shardIds;
+ if (_manager) {
+ try {
+ _manager->getShardIdsForQuery(shardIds, query);
+ } catch (const DBException& ex) {
+ return ex.toStatus();
+ }
+ } else {
+ shardIds.insert(_primary->getId());
}
- Status ChunkManagerTargeter::targetCollection( vector<ShardEndpoint*>* endpoints ) const {
+ for (const ShardId& shardId : shardIds) {
+ endpoints->push_back(new ShardEndpoint(
+ shardId, _manager ? _manager->getVersion(shardId) : ChunkVersion::UNSHARDED()));
+ }
- if ( !_primary && !_manager ) {
- return Status( ErrorCodes::NamespaceNotFound,
- str::stream() << "could not target full range of "
- << getNS().ns()
- << "; metadata not found" );
- }
+ return Status::OK();
+}
- set<ShardId> shardIds;
- if ( _manager ) {
- _manager->getAllShardIds(&shardIds);
- }
- else {
- shardIds.insert(_primary->getId());
- }
+Status ChunkManagerTargeter::targetShardKey(const BSONObj& shardKey,
+ long long estDataSize,
+ ShardEndpoint** endpoint) const {
+ invariant(NULL != _manager);
- for (const ShardId& shardId : shardIds) {
- endpoints->push_back(new ShardEndpoint(shardId,
- _manager ? _manager->getVersion(shardId) :
- ChunkVersion::UNSHARDED()));
- }
+ ChunkPtr chunk = _manager->findIntersectingChunk(shardKey);
- return Status::OK();
+ // Track autosplit stats for sharded collections
+ // Note: this is only best effort accounting and is not accurate.
+ if (estDataSize > 0) {
+ _stats.chunkSizeDelta[chunk->getMin()] += estDataSize;
}
- Status ChunkManagerTargeter::targetAllShards( vector<ShardEndpoint*>* endpoints ) const {
+ *endpoint = new ShardEndpoint(chunk->getShardId(), _manager->getVersion(chunk->getShardId()));
- if ( !_primary && !_manager ) {
- return Status( ErrorCodes::NamespaceNotFound,
- str::stream() << "could not target every shard with versions for "
- << getNS().ns()
- << "; metadata not found" );
- }
-
- vector<ShardId> shardIds;
- grid.shardRegistry()->getAllShardIds(&shardIds);
+ return Status::OK();
+}
- for (const ShardId& shardId : shardIds) {
- endpoints->push_back(new ShardEndpoint(shardId,
- _manager ?
- _manager->getVersion(shardId) :
- ChunkVersion::UNSHARDED()));
- }
+Status ChunkManagerTargeter::targetCollection(vector<ShardEndpoint*>* endpoints) const {
+ if (!_primary && !_manager) {
+ return Status(ErrorCodes::NamespaceNotFound,
+ str::stream() << "could not target full range of " << getNS().ns()
+ << "; metadata not found");
+ }
- return Status::OK();
+ set<ShardId> shardIds;
+ if (_manager) {
+ _manager->getAllShardIds(&shardIds);
+ } else {
+ shardIds.insert(_primary->getId());
}
- void ChunkManagerTargeter::noteStaleResponse( const ShardEndpoint& endpoint,
- const BSONObj& staleInfo ) {
- dassert( !_needsTargetingRefresh );
+ for (const ShardId& shardId : shardIds) {
+ endpoints->push_back(new ShardEndpoint(
+ shardId, _manager ? _manager->getVersion(shardId) : ChunkVersion::UNSHARDED()));
+ }
- ChunkVersion remoteShardVersion;
- if ( staleInfo["vWanted"].eoo() ) {
- // If we don't have a vWanted sent, assume the version is higher than our current
- // version.
- remoteShardVersion =
- getShardVersion(endpoint.shardName, _manager.get(), _primary.get());
- remoteShardVersion.incMajor();
- }
- else {
- remoteShardVersion = ChunkVersion::fromBSON( staleInfo, "vWanted" );
- }
+ return Status::OK();
+}
- ShardVersionMap::iterator it = _remoteShardVersions.find( endpoint.shardName );
- if ( it == _remoteShardVersions.end() ) {
- _remoteShardVersions.insert( make_pair( endpoint.shardName, remoteShardVersion ) );
- }
- else {
- ChunkVersion& previouslyNotedVersion = it->second;
- if ( previouslyNotedVersion.hasEqualEpoch( remoteShardVersion )) {
- if ( previouslyNotedVersion.isOlderThan( remoteShardVersion )) {
- remoteShardVersion.cloneTo( &previouslyNotedVersion );
- }
- }
- else {
- // Epoch changed midway while applying the batch so set the version to
- // something unique and non-existent to force a reload when
- // refreshIsNeeded is called.
- ChunkVersion::IGNORED().cloneTo( &previouslyNotedVersion );
- }
- }
+Status ChunkManagerTargeter::targetAllShards(vector<ShardEndpoint*>* endpoints) const {
+ if (!_primary && !_manager) {
+ return Status(ErrorCodes::NamespaceNotFound,
+ str::stream() << "could not target every shard with versions for "
+ << getNS().ns() << "; metadata not found");
}
- void ChunkManagerTargeter::noteCouldNotTarget() {
- dassert( _remoteShardVersions.empty() );
- _needsTargetingRefresh = true;
- }
+ vector<ShardId> shardIds;
+ grid.shardRegistry()->getAllShardIds(&shardIds);
- const TargeterStats* ChunkManagerTargeter::getStats() const {
- return &_stats;
+ for (const ShardId& shardId : shardIds) {
+ endpoints->push_back(new ShardEndpoint(
+ shardId, _manager ? _manager->getVersion(shardId) : ChunkVersion::UNSHARDED()));
}
- Status ChunkManagerTargeter::refreshIfNeeded( bool *wasChanged ) {
+ return Status::OK();
+}
+
+void ChunkManagerTargeter::noteStaleResponse(const ShardEndpoint& endpoint,
+ const BSONObj& staleInfo) {
+ dassert(!_needsTargetingRefresh);
+
+ ChunkVersion remoteShardVersion;
+ if (staleInfo["vWanted"].eoo()) {
+ // If we don't have a vWanted sent, assume the version is higher than our current
+ // version.
+ remoteShardVersion = getShardVersion(endpoint.shardName, _manager.get(), _primary.get());
+ remoteShardVersion.incMajor();
+ } else {
+ remoteShardVersion = ChunkVersion::fromBSON(staleInfo, "vWanted");
+ }
- bool dummy;
- if (!wasChanged) {
- wasChanged = &dummy;
+ ShardVersionMap::iterator it = _remoteShardVersions.find(endpoint.shardName);
+ if (it == _remoteShardVersions.end()) {
+ _remoteShardVersions.insert(make_pair(endpoint.shardName, remoteShardVersion));
+ } else {
+ ChunkVersion& previouslyNotedVersion = it->second;
+ if (previouslyNotedVersion.hasEqualEpoch(remoteShardVersion)) {
+ if (previouslyNotedVersion.isOlderThan(remoteShardVersion)) {
+ remoteShardVersion.cloneTo(&previouslyNotedVersion);
+ }
+ } else {
+ // Epoch changed midway while applying the batch so set the version to
+ // something unique and non-existent to force a reload when
+ // refreshIsNeeded is called.
+ ChunkVersion::IGNORED().cloneTo(&previouslyNotedVersion);
}
+ }
+}
- *wasChanged = false;
+void ChunkManagerTargeter::noteCouldNotTarget() {
+ dassert(_remoteShardVersions.empty());
+ _needsTargetingRefresh = true;
+}
- //
- // Did we have any stale config or targeting errors at all?
- //
+const TargeterStats* ChunkManagerTargeter::getStats() const {
+ return &_stats;
+}
- if (!_needsTargetingRefresh && _remoteShardVersions.empty()) {
- return Status::OK();
- }
+Status ChunkManagerTargeter::refreshIfNeeded(bool* wasChanged) {
+ bool dummy;
+ if (!wasChanged) {
+ wasChanged = &dummy;
+ }
- //
- // Get the latest metadata information from the cache if there were issues
- //
+ *wasChanged = false;
- ChunkManagerPtr lastManager = _manager;
- ShardPtr lastPrimary = _primary;
+ //
+ // Did we have any stale config or targeting errors at all?
+ //
- auto status = grid.implicitCreateDb(_nss.db().toString());
- if (!status.isOK()) {
- return status.getStatus();
- }
+ if (!_needsTargetingRefresh && _remoteShardVersions.empty()) {
+ return Status::OK();
+ }
- shared_ptr<DBConfig> config = status.getValue();
- config->getChunkManagerOrPrimary( _nss.ns(), _manager, _primary );
+ //
+ // Get the latest metadata information from the cache if there were issues
+ //
- // We now have the latest metadata from the cache.
+ ChunkManagerPtr lastManager = _manager;
+ ShardPtr lastPrimary = _primary;
- //
- // See if and how we need to do a remote refresh.
- // Either we couldn't target at all, or we have stale versions, but not both.
- //
+ auto status = grid.implicitCreateDb(_nss.db().toString());
+ if (!status.isOK()) {
+ return status.getStatus();
+ }
- dassert( !( _needsTargetingRefresh && !_remoteShardVersions.empty() ) );
+ shared_ptr<DBConfig> config = status.getValue();
+ config->getChunkManagerOrPrimary(_nss.ns(), _manager, _primary);
- if ( _needsTargetingRefresh ) {
+ // We now have the latest metadata from the cache.
- // Reset the field
- _needsTargetingRefresh = false;
+ //
+ // See if and how we need to do a remote refresh.
+ // Either we couldn't target at all, or we have stale versions, but not both.
+ //
- // If we couldn't target, we might need to refresh if we haven't remotely refreshed the
- // metadata since we last got it from the cache.
+ dassert(!(_needsTargetingRefresh && !_remoteShardVersions.empty()));
- bool alreadyRefreshed = wasMetadataRefreshed( lastManager,
- lastPrimary,
- _manager,
- _primary );
+ if (_needsTargetingRefresh) {
+ // Reset the field
+ _needsTargetingRefresh = false;
- // If didn't already refresh the targeting information, refresh it
- if ( !alreadyRefreshed ) {
- // To match previous behavior, we just need an incremental refresh here
- return refreshNow( RefreshType_RefreshChunkManager );
- }
+ // If we couldn't target, we might need to refresh if we haven't remotely refreshed the
+ // metadata since we last got it from the cache.
- *wasChanged = isMetadataDifferent( lastManager, lastPrimary, _manager, _primary );
- return Status::OK();
- }
- else if ( !_remoteShardVersions.empty() ) {
+ bool alreadyRefreshed = wasMetadataRefreshed(lastManager, lastPrimary, _manager, _primary);
- // If we got stale shard versions from remote shards, we may need to refresh
- // NOTE: Not sure yet if this can happen simultaneously with targeting issues
+ // If didn't already refresh the targeting information, refresh it
+ if (!alreadyRefreshed) {
+ // To match previous behavior, we just need an incremental refresh here
+ return refreshNow(RefreshType_RefreshChunkManager);
+ }
- CompareResult result = compareAllShardVersions(_manager.get(),
- _primary.get(),
- _remoteShardVersions);
- // Reset the versions
- _remoteShardVersions.clear();
+ *wasChanged = isMetadataDifferent(lastManager, lastPrimary, _manager, _primary);
+ return Status::OK();
+ } else if (!_remoteShardVersions.empty()) {
+ // If we got stale shard versions from remote shards, we may need to refresh
+ // NOTE: Not sure yet if this can happen simultaneously with targeting issues
- if ( result == CompareResult_Unknown ) {
- // Our current shard versions aren't all comparable to the old versions, maybe drop
- return refreshNow( RefreshType_ReloadDatabase );
- }
- else if ( result == CompareResult_LT ) {
- // Our current shard versions are less than the remote versions, but no drop
- return refreshNow( RefreshType_RefreshChunkManager );
- }
+ CompareResult result =
+ compareAllShardVersions(_manager.get(), _primary.get(), _remoteShardVersions);
+ // Reset the versions
+ _remoteShardVersions.clear();
- *wasChanged = isMetadataDifferent( lastManager, lastPrimary, _manager, _primary );
- return Status::OK();
+ if (result == CompareResult_Unknown) {
+ // Our current shard versions aren't all comparable to the old versions, maybe drop
+ return refreshNow(RefreshType_ReloadDatabase);
+ } else if (result == CompareResult_LT) {
+ // Our current shard versions are less than the remote versions, but no drop
+ return refreshNow(RefreshType_RefreshChunkManager);
}
- // unreachable
- dassert( false );
+ *wasChanged = isMetadataDifferent(lastManager, lastPrimary, _manager, _primary);
return Status::OK();
}
- Status ChunkManagerTargeter::refreshNow( RefreshType refreshType ) {
- auto status = grid.implicitCreateDb(_nss.db().toString());
- if (!status.isOK()) {
- return status.getStatus();
- }
+ // unreachable
+ dassert(false);
+ return Status::OK();
+}
- shared_ptr<DBConfig> config = status.getValue();
+Status ChunkManagerTargeter::refreshNow(RefreshType refreshType) {
+ auto status = grid.implicitCreateDb(_nss.db().toString());
+ if (!status.isOK()) {
+ return status.getStatus();
+ }
- // Try not to spam the configs
- refreshBackoff();
+ shared_ptr<DBConfig> config = status.getValue();
- // TODO: Improve synchronization and make more explicit
- if ( refreshType == RefreshType_RefreshChunkManager ) {
- try {
- // Forces a remote check of the collection info, synchronization between threads
- // happens internally.
- config->getChunkManagerIfExists( _nss.ns(), true );
- }
- catch ( const DBException& ex ) {
- return Status( ErrorCodes::UnknownError, ex.toString() );
- }
- config->getChunkManagerOrPrimary( _nss.ns(), _manager, _primary );
- }
- else if ( refreshType == RefreshType_ReloadDatabase ) {
- try {
- // Dumps the db info, reloads it all, synchronization between threads happens
- // internally.
- config->reload();
- config->getChunkManagerIfExists( _nss.ns(), true, true );
- }
- catch ( const DBException& ex ) {
- return Status( ErrorCodes::UnknownError, ex.toString() );
- }
+ // Try not to spam the configs
+ refreshBackoff();
- config->getChunkManagerOrPrimary( _nss.ns(), _manager, _primary );
+ // TODO: Improve synchronization and make more explicit
+ if (refreshType == RefreshType_RefreshChunkManager) {
+ try {
+ // Forces a remote check of the collection info, synchronization between threads
+ // happens internally.
+ config->getChunkManagerIfExists(_nss.ns(), true);
+ } catch (const DBException& ex) {
+ return Status(ErrorCodes::UnknownError, ex.toString());
+ }
+ config->getChunkManagerOrPrimary(_nss.ns(), _manager, _primary);
+ } else if (refreshType == RefreshType_ReloadDatabase) {
+ try {
+ // Dumps the db info, reloads it all, synchronization between threads happens
+ // internally.
+ config->reload();
+ config->getChunkManagerIfExists(_nss.ns(), true, true);
+ } catch (const DBException& ex) {
+ return Status(ErrorCodes::UnknownError, ex.toString());
}
- return Status::OK();
+ config->getChunkManagerOrPrimary(_nss.ns(), _manager, _primary);
}
-} // namespace mongo
+ return Status::OK();
+}
+} // namespace mongo
diff --git a/src/mongo/s/chunk_manager_targeter.h b/src/mongo/s/chunk_manager_targeter.h
index 3c5b9d68a09..e7c754448c8 100644
--- a/src/mongo/s/chunk_manager_targeter.h
+++ b/src/mongo/s/chunk_manager_targeter.h
@@ -36,130 +36,130 @@
namespace mongo {
- class ChunkManager;
- struct ChunkVersion;
- class Shard;
-
- struct TargeterStats {
- // Map of chunk shard minKey -> approximate delta. This is used for deciding
- // whether a chunk might need splitting or not.
- std::map<BSONObj, int> chunkSizeDelta;
- };
+class ChunkManager;
+struct ChunkVersion;
+class Shard;
+
+struct TargeterStats {
+ // Map of chunk shard minKey -> approximate delta. This is used for deciding
+ // whether a chunk might need splitting or not.
+ std::map<BSONObj, int> chunkSizeDelta;
+};
+
+/**
+ * NSTargeter based on a ChunkManager implementation. Wraps all exception codepaths and
+ * returns DatabaseNotFound statuses on applicable failures.
+ *
+ * Must be initialized before use, and initialization may fail.
+ */
+class ChunkManagerTargeter : public NSTargeter {
+public:
+ ChunkManagerTargeter(const NamespaceString& nss);
+
+ /**
+ * Initializes the ChunkManagerTargeter with the latest targeting information for the
+ * namespace. May need to block and load information from a remote config server.
+ *
+ * Returns !OK if the information could not be initialized.
+ */
+ Status init();
+
+ const NamespaceString& getNS() const;
+
+ // Returns ShardKeyNotFound if document does not have a full shard key.
+ Status targetInsert(const BSONObj& doc, ShardEndpoint** endpoint) const;
+
+ // Returns ShardKeyNotFound if the update can't be targeted without a shard key.
+ Status targetUpdate(const BatchedUpdateDocument& updateDoc,
+ std::vector<ShardEndpoint*>* endpoints) const;
+
+ // Returns ShardKeyNotFound if the delete can't be targeted without a shard key.
+ Status targetDelete(const BatchedDeleteDocument& deleteDoc,
+ std::vector<ShardEndpoint*>* endpoints) const;
+
+ Status targetCollection(std::vector<ShardEndpoint*>* endpoints) const;
+
+ Status targetAllShards(std::vector<ShardEndpoint*>* endpoints) const;
+
+ void noteStaleResponse(const ShardEndpoint& endpoint, const BSONObj& staleInfo);
+
+ void noteCouldNotTarget();
/**
- * NSTargeter based on a ChunkManager implementation. Wraps all exception codepaths and
- * returns DatabaseNotFound statuses on applicable failures.
+ * Replaces the targeting information with the latest information from the cache. If this
+ * information is stale WRT the noted stale responses or a remote refresh is needed due
+ * to a targeting failure, will contact the config servers to reload the metadata.
+ *
+ * Reports wasChanged = true if the metadata is different after this reload.
*
- * Must be initialized before use, and initialization may fail.
+ * Also see NSTargeter::refreshIfNeeded().
+ */
+ Status refreshIfNeeded(bool* wasChanged);
+
+ /**
+ * Returns the stats. Note that the returned stats object is still owned by this targeter.
*/
- class ChunkManagerTargeter : public NSTargeter {
- public:
- ChunkManagerTargeter(const NamespaceString& nss);
-
- /**
- * Initializes the ChunkManagerTargeter with the latest targeting information for the
- * namespace. May need to block and load information from a remote config server.
- *
- * Returns !OK if the information could not be initialized.
- */
- Status init();
-
- const NamespaceString& getNS() const;
-
- // Returns ShardKeyNotFound if document does not have a full shard key.
- Status targetInsert( const BSONObj& doc, ShardEndpoint** endpoint ) const;
-
- // Returns ShardKeyNotFound if the update can't be targeted without a shard key.
- Status targetUpdate( const BatchedUpdateDocument& updateDoc,
- std::vector<ShardEndpoint*>* endpoints ) const;
-
- // Returns ShardKeyNotFound if the delete can't be targeted without a shard key.
- Status targetDelete( const BatchedDeleteDocument& deleteDoc,
- std::vector<ShardEndpoint*>* endpoints ) const;
-
- Status targetCollection( std::vector<ShardEndpoint*>* endpoints ) const;
-
- Status targetAllShards( std::vector<ShardEndpoint*>* endpoints ) const;
-
- void noteStaleResponse( const ShardEndpoint& endpoint, const BSONObj& staleInfo );
-
- void noteCouldNotTarget();
-
- /**
- * Replaces the targeting information with the latest information from the cache. If this
- * information is stale WRT the noted stale responses or a remote refresh is needed due
- * to a targeting failure, will contact the config servers to reload the metadata.
- *
- * Reports wasChanged = true if the metadata is different after this reload.
- *
- * Also see NSTargeter::refreshIfNeeded().
- */
- Status refreshIfNeeded( bool* wasChanged );
-
- /**
- * Returns the stats. Note that the returned stats object is still owned by this targeter.
- */
- const TargeterStats* getStats() const;
-
- private:
- // Different ways we can refresh metadata
- enum RefreshType {
- // No refresh is needed
- RefreshType_None,
- // The version has gone up, but the collection hasn't been dropped
- RefreshType_RefreshChunkManager,
- // The collection may have been dropped, so we need to reload the db
- RefreshType_ReloadDatabase
- };
-
- typedef std::map<std::string, ChunkVersion> ShardVersionMap;
-
-
- /**
- * Performs an actual refresh from the config server.
- */
- Status refreshNow( RefreshType refreshType );
-
- /**
- * Returns a vector of ShardEndpoints where a document might need to be placed.
- *
- * Returns !OK with message if replacement could not be targeted
- */
- Status targetDoc(const BSONObj& doc, std::vector<ShardEndpoint*>* endpoints) const;
-
- /**
- * Returns a vector of ShardEndpoints for a potentially multi-shard query.
- *
- * Returns !OK with message if query could not be targeted.
- */
- Status targetQuery( const BSONObj& query, std::vector<ShardEndpoint*>* endpoints ) const;
-
- /**
- * Returns a ShardEndpoint for an exact shard key query.
- *
- * Also has the side effect of updating the chunks stats with an estimate of the amount of
- * data targeted at this shard key.
- */
- Status targetShardKey(const BSONObj& doc,
- long long estDataSize,
- ShardEndpoint** endpoint) const;
-
- // Full namespace of the collection for this targeter
- const NamespaceString _nss;
-
- // Stores whether we need to check the remote server on refresh
- bool _needsTargetingRefresh;
-
- // Represents only the view and not really part of the targeter state.
- mutable TargeterStats _stats;
-
- // Zero or one of these are filled at all times
- // If sharded, _manager, if unsharded, _primary, on error, neither
- std::shared_ptr<ChunkManager> _manager;
- std::shared_ptr<Shard> _primary;
-
- // Map of shard->remote shard version reported from stale errors
- ShardVersionMap _remoteShardVersions;
+ const TargeterStats* getStats() const;
+
+private:
+ // Different ways we can refresh metadata
+ enum RefreshType {
+ // No refresh is needed
+ RefreshType_None,
+ // The version has gone up, but the collection hasn't been dropped
+ RefreshType_RefreshChunkManager,
+ // The collection may have been dropped, so we need to reload the db
+ RefreshType_ReloadDatabase
};
-} // namespace mongo
+ typedef std::map<std::string, ChunkVersion> ShardVersionMap;
+
+
+ /**
+ * Performs an actual refresh from the config server.
+ */
+ Status refreshNow(RefreshType refreshType);
+
+ /**
+ * Returns a vector of ShardEndpoints where a document might need to be placed.
+ *
+ * Returns !OK with message if replacement could not be targeted
+ */
+ Status targetDoc(const BSONObj& doc, std::vector<ShardEndpoint*>* endpoints) const;
+
+ /**
+ * Returns a vector of ShardEndpoints for a potentially multi-shard query.
+ *
+ * Returns !OK with message if query could not be targeted.
+ */
+ Status targetQuery(const BSONObj& query, std::vector<ShardEndpoint*>* endpoints) const;
+
+ /**
+ * Returns a ShardEndpoint for an exact shard key query.
+ *
+ * Also has the side effect of updating the chunks stats with an estimate of the amount of
+ * data targeted at this shard key.
+ */
+ Status targetShardKey(const BSONObj& doc,
+ long long estDataSize,
+ ShardEndpoint** endpoint) const;
+
+ // Full namespace of the collection for this targeter
+ const NamespaceString _nss;
+
+ // Stores whether we need to check the remote server on refresh
+ bool _needsTargetingRefresh;
+
+ // Represents only the view and not really part of the targeter state.
+ mutable TargeterStats _stats;
+
+ // Zero or one of these are filled at all times
+ // If sharded, _manager, if unsharded, _primary, on error, neither
+ std::shared_ptr<ChunkManager> _manager;
+ std::shared_ptr<Shard> _primary;
+
+ // Map of shard->remote shard version reported from stale errors
+ ShardVersionMap _remoteShardVersions;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/chunk_manager_targeter_test.cpp b/src/mongo/s/chunk_manager_targeter_test.cpp
index 85e674fd1c1..4f43580d40c 100644
--- a/src/mongo/s/chunk_manager_targeter_test.cpp
+++ b/src/mongo/s/chunk_manager_targeter_test.cpp
@@ -40,496 +40,475 @@
namespace {
- using namespace mongo;
-
- using std::unique_ptr;
- using std::make_pair;
-
- /**
- * ChunkManager targeting test
- *
- * TODO:
- * Pull the implementation out of chunk.cpp
- */
-
- // Utility function to create a CanonicalQuery
- CanonicalQuery* canonicalize(const char* queryStr) {
- BSONObj queryObj = fromjson(queryStr);
- CanonicalQuery* cq;
- Status result = CanonicalQuery::canonicalize(
- "test.foo", queryObj, &cq, WhereCallbackNoop());
- ASSERT_OK(result);
- return cq;
- }
-
- void checkIndexBoundsWithKey(const char* keyStr,
- const char* queryStr,
- const IndexBounds& expectedBounds) {
-
- unique_ptr<CanonicalQuery> query(canonicalize(queryStr));
- ASSERT(query.get() != NULL);
-
- BSONObj key = fromjson(keyStr);
-
- IndexBounds indexBounds = ChunkManager::getIndexBoundsForQuery(key, query.get());
- ASSERT_EQUALS(indexBounds.size(), expectedBounds.size());
- for (size_t i = 0; i < indexBounds.size(); i++) {
- const OrderedIntervalList& oil = indexBounds.fields[i];
- const OrderedIntervalList& expectedOil = expectedBounds.fields[i];
- ASSERT_EQUALS(oil.intervals.size(), expectedOil.intervals.size());
- for (size_t i = 0; i < oil.intervals.size(); i++) {
- if (Interval::INTERVAL_EQUALS != oil.intervals[i].compare(expectedOil.intervals[i])) {
- log() << oil.intervals[i] << " != " << expectedOil.intervals[i];
- }
- ASSERT_EQUALS(Interval::INTERVAL_EQUALS, oil.intervals[i].compare(expectedOil.intervals[i]));
- }
- }
- }
-
- // Assume shard key is { a: 1 }
- void checkIndexBounds(const char* queryStr, const OrderedIntervalList& expectedOil) {
- unique_ptr<CanonicalQuery> query(canonicalize(queryStr));
- ASSERT(query.get() != NULL);
-
- BSONObj key = fromjson("{a: 1}");
+using namespace mongo;
- IndexBounds indexBounds = ChunkManager::getIndexBoundsForQuery(key, query.get());
- ASSERT_EQUALS(indexBounds.size(), 1U);
- const OrderedIntervalList& oil = indexBounds.fields.front();
+using std::unique_ptr;
+using std::make_pair;
- if (oil.intervals.size() != expectedOil.intervals.size()) {
- for (size_t i = 0; i < oil.intervals.size(); i++) {
- log() << oil.intervals[i];
- }
- }
+/**
+ * ChunkManager targeting test
+ *
+ * TODO:
+ * Pull the implementation out of chunk.cpp
+ */
+// Utility function to create a CanonicalQuery
+CanonicalQuery* canonicalize(const char* queryStr) {
+ BSONObj queryObj = fromjson(queryStr);
+ CanonicalQuery* cq;
+ Status result = CanonicalQuery::canonicalize("test.foo", queryObj, &cq, WhereCallbackNoop());
+ ASSERT_OK(result);
+ return cq;
+}
+
+void checkIndexBoundsWithKey(const char* keyStr,
+ const char* queryStr,
+ const IndexBounds& expectedBounds) {
+ unique_ptr<CanonicalQuery> query(canonicalize(queryStr));
+ ASSERT(query.get() != NULL);
+
+ BSONObj key = fromjson(keyStr);
+
+ IndexBounds indexBounds = ChunkManager::getIndexBoundsForQuery(key, query.get());
+ ASSERT_EQUALS(indexBounds.size(), expectedBounds.size());
+ for (size_t i = 0; i < indexBounds.size(); i++) {
+ const OrderedIntervalList& oil = indexBounds.fields[i];
+ const OrderedIntervalList& expectedOil = expectedBounds.fields[i];
ASSERT_EQUALS(oil.intervals.size(), expectedOil.intervals.size());
for (size_t i = 0; i < oil.intervals.size(); i++) {
- ASSERT_EQUALS(Interval::INTERVAL_EQUALS, oil.intervals[i].compare(expectedOil.intervals[i]));
+ if (Interval::INTERVAL_EQUALS != oil.intervals[i].compare(expectedOil.intervals[i])) {
+ log() << oil.intervals[i] << " != " << expectedOil.intervals[i];
+ }
+ ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
+ oil.intervals[i].compare(expectedOil.intervals[i]));
}
}
+}
- const double INF = std::numeric_limits<double>::infinity();
+// Assume shard key is { a: 1 }
+void checkIndexBounds(const char* queryStr, const OrderedIntervalList& expectedOil) {
+ unique_ptr<CanonicalQuery> query(canonicalize(queryStr));
+ ASSERT(query.get() != NULL);
- // { a: 2 } -> a: [2, 2]
- TEST(CMCollapseTreeTest, Basic) {
- OrderedIntervalList expected;
- expected.intervals.push_back(Interval(BSON("" << 2 << "" << 2), true, true));
- checkIndexBounds("{a: 2}", expected);
- }
+ BSONObj key = fromjson("{a: 1}");
- // { b: 2 } -> a: [MinKey, MaxKey]
- TEST(CMCollapseTreeTest, AllValue) {
- OrderedIntervalList expected;
- BSONObjBuilder builder;
- builder.appendMinKey("");
- builder.appendMaxKey("");
- expected.intervals.push_back(Interval(builder.obj(), true, true));
- checkIndexBounds("{b: 2}", expected);
- }
+ IndexBounds indexBounds = ChunkManager::getIndexBoundsForQuery(key, query.get());
+ ASSERT_EQUALS(indexBounds.size(), 1U);
+ const OrderedIntervalList& oil = indexBounds.fields.front();
- // { 'a' : { '$not' : { '$gt' : 1 } } } -> a: [MinKey, 1.0], (inf.0, MaxKey]
- TEST(CMCollapseTreeTest, NegativeGT) {
- OrderedIntervalList expected;
- {
- BSONObjBuilder builder;
- builder.appendMinKey("");
- builder.appendNumber("", 1.0);
- expected.intervals.push_back(Interval(builder.obj(), true, true));
- }
- {
- BSONObjBuilder builder;
- builder.append("", std::numeric_limits<double>::infinity());
- builder.appendMaxKey("");
- expected.intervals.push_back(Interval(builder.obj(), false, true));
+ if (oil.intervals.size() != expectedOil.intervals.size()) {
+ for (size_t i = 0; i < oil.intervals.size(); i++) {
+ log() << oil.intervals[i];
}
- checkIndexBounds("{ 'a' : { '$not' : { '$gt' : 1 } } }", expected);
- }
-
- // {$or: [{a: 20}, {$and: [{a:1}, {b:7}]}]} -> a: [1.0, 1.0], [20.0, 20.0]
- TEST(CMCollapseTreeTest, OrWithAndChild) {
- OrderedIntervalList expected;
- expected.intervals.push_back(Interval(BSON("" << 1.0 << "" << 1.0), true, true));
- expected.intervals.push_back(Interval(BSON("" << 20.0 << "" << 20.0), true, true));
- checkIndexBounds("{$or: [{a: 20}, {$and: [{a:1}, {b:7}]}]}", expected);
- }
-
- // {a:20, $or: [{b:1}, {c:7}]} -> a: [20.0, 20.0]
- TEST(CMCollapseTreeTest, AndWithUnindexedOrChild) {
- // Logic rewrite could give a tree with root OR.
- OrderedIntervalList expected;
- expected.intervals.push_back(Interval(BSON("" << 20.0 << "" << 20.0), true, true));
- checkIndexBounds("{a:20, $or: [{b:1}, {c:7}]}", expected);
- }
-
- // {$or: [{a:{$gt:2,$lt:10}}, {a:{$gt:0,$lt:5}}]} -> a: (0.0, 10.0)
- TEST(CMCollapseTreeTest, OrOfAnd) {
- OrderedIntervalList expected;
- expected.intervals.push_back(Interval(BSON("" << 0.0 << "" << 10.0), false, false));
- checkIndexBounds("{$or: [{a:{$gt:2,$lt:10}}, {a:{$gt:0,$lt:5}}]}", expected);
- }
-
- // {$or: [{a:{$gt:2,$lt:10}}, {a:{$gt:0,$lt:15}}, {a:{$gt:20}}]}
- // -> a: (0.0, 15.0), (20.0, inf.0]
- TEST(CMCollapseTreeTest, OrOfAnd2) {
- OrderedIntervalList expected;
- expected.intervals.push_back(Interval(BSON("" << 0.0 << "" << 15.0), false, false));
- expected.intervals.push_back(Interval(BSON("" << 20.0 << "" << INF), false, true));
- checkIndexBounds("{$or: [{a:{$gt:2,$lt:10}}, {a:{$gt:0,$lt:15}}, {a:{$gt:20}}]}", expected);
- }
-
- // "{$or: [{a:{$gt:1,$lt:5},b:6}, {a:3,b:{$gt:0,$lt:10}}]}" -> a: (1.0, 5.0)
- TEST(CMCollapseTreeTest, OrOfAnd3) {
- OrderedIntervalList expected;
- expected.intervals.push_back(Interval(BSON("" << 1.0 << "" << 5.0), false, false));
- checkIndexBounds("{$or: [{a:{$gt:1,$lt:5},b:6}, {a:3,b:{$gt:0,$lt:10}}]}", expected);
- }
-
- //
- // Compound shard key
- //
-
- // "{$or: [{a:{$gt:1,$lt:5}, b:{$gt:0,$lt:3}, c:6}, "
- // "{a:3, b:{$gt:1,$lt:2}, c:{$gt:0,$lt:10}}]}",
- // -> a: (1.0, 5.0), b: (0.0, 3.0)
- TEST(CMCollapseTreeTest, OrOfAnd4) {
- IndexBounds expectedBounds;
- expectedBounds.fields.push_back(OrderedIntervalList());
- expectedBounds.fields.push_back(OrderedIntervalList());
-
- expectedBounds.fields[0].intervals.push_back(
- Interval(BSON("" << 1.0 << "" << 5.0), false, false));
- expectedBounds.fields[1].intervals.push_back(
- Interval(BSON("" << 0.0 << "" << 3.0), false, false));
-
- checkIndexBoundsWithKey(
- "{a: 1, b: 1}", // shard key
- "{$or: [{a:{$gt:1,$lt:5}, b:{$gt:0,$lt:3}, c:6}, "
- "{a:3, b:{$gt:1,$lt:2}, c:{$gt:0,$lt:10}}]}",
- expectedBounds);
- }
-
- // "{$or: [{a:{$gt:1,$lt:5}, c:6}, "
- // "{a:3, b:{$gt:1,$lt:2}, c:{$gt:0,$lt:10}}]}"));
- // ->
- TEST(CMCollapseTreeTest, OrOfAnd5) {
- IndexBounds expectedBounds;
- expectedBounds.fields.push_back(OrderedIntervalList());
- expectedBounds.fields.push_back(OrderedIntervalList());
-
- expectedBounds.fields[0].intervals.push_back(
- Interval(BSON("" << 1.0 << "" << 5.0), false, false));
- BSONObjBuilder builder;
- builder.appendMinKey("");
- builder.appendMaxKey("");
- expectedBounds.fields[1].intervals.push_back(
- Interval(builder.obj(), true, true));
-
- checkIndexBoundsWithKey(
- "{a: 1, b: 1}", // shard key
- "{$or: [{a:{$gt:1,$lt:5}, c:6}, "
- "{a:3, b:{$gt:1,$lt:2}, c:{$gt:0,$lt:10}}]}",
- expectedBounds);
- }
-
- // {$or: [{a:{$in:[1]},b:{$in:[1]}}, {a:{$in:[1,5]},b:{$in:[1,5]}}]}
- // -> a: [1], [5]; b: [1], [5]
- TEST(CMCollapseTreeTest, OrOfAnd6) {
- IndexBounds expectedBounds;
- expectedBounds.fields.push_back(OrderedIntervalList());
- expectedBounds.fields.push_back(OrderedIntervalList());
-
- // a: [1], [5]
- expectedBounds.fields[0].intervals.push_back(
- Interval(BSON("" << 1.0 << "" << 1.0), true, true));
- expectedBounds.fields[0].intervals.push_back(
- Interval(BSON("" << 5.0 << "" << 5.0), true, true));
-
- // b: [1], [5]
- expectedBounds.fields[1].intervals.push_back(
- Interval(BSON("" << 1.0 << "" << 1.0), true, true));
- expectedBounds.fields[1].intervals.push_back(
- Interval(BSON("" << 5.0 << "" << 5.0), true, true));
-
- checkIndexBoundsWithKey(
- "{a: 1, b: 1}", // shard key
- "{$or: [{a:{$in:[1]},b:{$in:[1]}}, {a:{$in:[1,5]},b:{$in:[1,5]}}]}",
- expectedBounds);
- }
-
- //
- // Array operators
- //
-
- // {a : {$elemMatch: {b:1}}} -> a.b: [MinKey, MaxKey]
- // Shard key doesn't allow multikey, but query on array should succeed without error.
- TEST(CMCollapseTreeTest, ElemMatchOneField) {
- IndexBounds expectedBounds;
- expectedBounds.fields.push_back(OrderedIntervalList());
- OrderedIntervalList& oil = expectedBounds.fields.front();
- oil.intervals.push_back(Interval(BSON("" << 1 << "" << 1), true, true));
- checkIndexBoundsWithKey("{'a.b': 1}", "{a : {$elemMatch: {b:1}}}", expectedBounds);
- }
-
- // {foo: {$all: [ {$elemMatch: {a:1, b:1}}, {$elemMatch: {a:2, b:2}}]}}
- // -> foo.a: [1, 1]
- // Or -> foo.a: [2, 2]
- TEST(CMCollapseTreeTest, BasicAllElemMatch) {
- Interval expectedInterval(BSON("" << 1 << "" << 1), true, true);
-
- const char* queryStr = "{foo: {$all: [ {$elemMatch: {a:1, b:1}} ]}}";
- unique_ptr<CanonicalQuery> query(canonicalize(queryStr));
- ASSERT(query.get() != NULL);
-
- BSONObj key = fromjson("{'foo.a': 1}");
-
- IndexBounds indexBounds = ChunkManager::getIndexBoundsForQuery(key, query.get());
- ASSERT_EQUALS(indexBounds.size(), 1U);
- const OrderedIntervalList& oil = indexBounds.fields.front();
- ASSERT_EQUALS(oil.intervals.size(), 1U);
- const Interval& interval = oil.intervals.front();
-
- // Choose one of the two possible solutions.
- // Two solutions differ only by assignment of index tags.
- ASSERT(Interval::INTERVAL_EQUALS == interval.compare(expectedInterval));
- }
-
- // {a : [1, 2, 3]} -> a: [1, 1], [[1, 2, 3], [1, 2, 3]]
- TEST(CMCollapseTreeTest, ArrayEquality) {
- OrderedIntervalList expected;
- expected.intervals.push_back(Interval(BSON("" << 1 << "" << 1), true, true));
- BSONArray array(BSON_ARRAY(1 << 2 << 3));
-
- Interval interval(BSON("" << array << "" << array), true, true);
- expected.intervals.push_back(interval);
- checkIndexBounds("{a : [1, 2, 3]}", expected);
}
-
- //
- // Features: Regex, $where, $text, hashed key
- //
-
- // { a: /abc/ } -> a: ["", {}), [/abc/, /abc/]
- TEST(CMCollapseTreeTest, Regex) {
- OrderedIntervalList expected;
- expected.intervals.push_back(
- Interval(BSON("" << "" << "" << BSONObj()), true, false));
- BSONObjBuilder builder;
- builder.appendRegex("", "abc");
- builder.appendRegex("", "abc");
- expected.intervals.push_back(Interval(builder.obj(), true, true));
- checkIndexBounds("{ a: /abc/ }", expected);
+ ASSERT_EQUALS(oil.intervals.size(), expectedOil.intervals.size());
+ for (size_t i = 0; i < oil.intervals.size(); i++) {
+ ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
+ oil.intervals[i].compare(expectedOil.intervals[i]));
}
-
- // {$where: 'this.credits == this.debits' }
- TEST(CMCollapseTreeTest, Where) {
- OrderedIntervalList expected;
+}
+
+const double INF = std::numeric_limits<double>::infinity();
+
+// { a: 2 } -> a: [2, 2]
+TEST(CMCollapseTreeTest, Basic) {
+ OrderedIntervalList expected;
+ expected.intervals.push_back(Interval(BSON("" << 2 << "" << 2), true, true));
+ checkIndexBounds("{a: 2}", expected);
+}
+
+// { b: 2 } -> a: [MinKey, MaxKey]
+TEST(CMCollapseTreeTest, AllValue) {
+ OrderedIntervalList expected;
+ BSONObjBuilder builder;
+ builder.appendMinKey("");
+ builder.appendMaxKey("");
+ expected.intervals.push_back(Interval(builder.obj(), true, true));
+ checkIndexBounds("{b: 2}", expected);
+}
+
+// { 'a' : { '$not' : { '$gt' : 1 } } } -> a: [MinKey, 1.0], (inf.0, MaxKey]
+TEST(CMCollapseTreeTest, NegativeGT) {
+ OrderedIntervalList expected;
+ {
BSONObjBuilder builder;
builder.appendMinKey("");
- builder.appendMaxKey("");
+ builder.appendNumber("", 1.0);
expected.intervals.push_back(Interval(builder.obj(), true, true));
- checkIndexBounds("{$where: 'this.credits == this.debits' }", expected);
- }
-
- // { $text: { $search: "coffee -cake" } }
- TEST(CMCollapseTreeTest, Text) {
- OrderedIntervalList expected;
- BSONObjBuilder builder;
- builder.appendMinKey("");
- builder.appendMaxKey("");
- expected.intervals.push_back(Interval(builder.obj(), true, true));
- checkIndexBounds("{ $text: { $search: 'coffee -cake' } }", expected);
- }
-
- // { a: 2, $text: { $search: "leche", $language: "es" } }
- TEST(CMCollapseTreeTest, TextWithQuery) {
- OrderedIntervalList expected;
- BSONObjBuilder builder;
- builder.appendMinKey("");
- builder.appendMaxKey("");
- expected.intervals.push_back(Interval(builder.obj(), true, true));
- checkIndexBounds("{ a: 2, $text: { $search: 'leche', $language: 'es' } }", expected);
- }
-
- // { a: 0 } -> hashed a: [hash(0), hash(0)]
- TEST(CMCollapseTreeTest, HashedSinglePoint) {
- const char* queryStr = "{ a: 0 }";
- unique_ptr<CanonicalQuery> query(canonicalize(queryStr));
- ASSERT(query.get() != NULL);
-
- BSONObj key = fromjson("{a: 'hashed'}");
-
- IndexBounds indexBounds = ChunkManager::getIndexBoundsForQuery(key, query.get());
- ASSERT_EQUALS(indexBounds.size(), 1U);
- const OrderedIntervalList& oil = indexBounds.fields.front();
- ASSERT_EQUALS(oil.intervals.size(), 1U);
- const Interval& interval = oil.intervals.front();
- ASSERT(interval.isPoint());
- }
-
- // { a: { $lt: 2, $gt: 1} } -> hashed a: [Minkey, Maxkey]
- TEST(CMCollapseTreeTest, HashedRange) {
- IndexBounds expectedBounds;
- expectedBounds.fields.push_back(OrderedIntervalList());
- OrderedIntervalList& expectedOil = expectedBounds.fields.front();
- BSONObjBuilder builder;
- builder.appendMinKey("");
- builder.appendMaxKey("");
- expectedOil.intervals.push_back(Interval(builder.obj(), true, true));
- checkIndexBoundsWithKey("{a: 'hashed'}", "{ a: { $lt: 2, $gt: 1} }", expectedBounds);
}
-
- // { a: /abc/ } -> hashed a: [Minkey, Maxkey]
- TEST(CMCollapseTreeTest, HashedRegex) {
- IndexBounds expectedBounds;
- expectedBounds.fields.push_back(OrderedIntervalList());
- OrderedIntervalList& expectedOil = expectedBounds.fields.front();
+ {
BSONObjBuilder builder;
- builder.appendMinKey("");
+ builder.append("", std::numeric_limits<double>::infinity());
builder.appendMaxKey("");
- expectedOil.intervals.push_back(Interval(builder.obj(), true, true));
-
- checkIndexBoundsWithKey("{a: 'hashed'}", "{ a: /abc/ }", expectedBounds);
+ expected.intervals.push_back(Interval(builder.obj(), false, true));
}
+ checkIndexBounds("{ 'a' : { '$not' : { '$gt' : 1 } } }", expected);
+}
+
+// {$or: [{a: 20}, {$and: [{a:1}, {b:7}]}]} -> a: [1.0, 1.0], [20.0, 20.0]
+TEST(CMCollapseTreeTest, OrWithAndChild) {
+ OrderedIntervalList expected;
+ expected.intervals.push_back(Interval(BSON("" << 1.0 << "" << 1.0), true, true));
+ expected.intervals.push_back(Interval(BSON("" << 20.0 << "" << 20.0), true, true));
+ checkIndexBounds("{$or: [{a: 20}, {$and: [{a:1}, {b:7}]}]}", expected);
+}
+
+// {a:20, $or: [{b:1}, {c:7}]} -> a: [20.0, 20.0]
+TEST(CMCollapseTreeTest, AndWithUnindexedOrChild) {
+ // Logic rewrite could give a tree with root OR.
+ OrderedIntervalList expected;
+ expected.intervals.push_back(Interval(BSON("" << 20.0 << "" << 20.0), true, true));
+ checkIndexBounds("{a:20, $or: [{b:1}, {c:7}]}", expected);
+}
+
+// {$or: [{a:{$gt:2,$lt:10}}, {a:{$gt:0,$lt:5}}]} -> a: (0.0, 10.0)
+TEST(CMCollapseTreeTest, OrOfAnd) {
+ OrderedIntervalList expected;
+ expected.intervals.push_back(Interval(BSON("" << 0.0 << "" << 10.0), false, false));
+ checkIndexBounds("{$or: [{a:{$gt:2,$lt:10}}, {a:{$gt:0,$lt:5}}]}", expected);
+}
+
+// {$or: [{a:{$gt:2,$lt:10}}, {a:{$gt:0,$lt:15}}, {a:{$gt:20}}]}
+// -> a: (0.0, 15.0), (20.0, inf.0]
+TEST(CMCollapseTreeTest, OrOfAnd2) {
+ OrderedIntervalList expected;
+ expected.intervals.push_back(Interval(BSON("" << 0.0 << "" << 15.0), false, false));
+ expected.intervals.push_back(Interval(BSON("" << 20.0 << "" << INF), false, true));
+ checkIndexBounds("{$or: [{a:{$gt:2,$lt:10}}, {a:{$gt:0,$lt:15}}, {a:{$gt:20}}]}", expected);
+}
+
+// "{$or: [{a:{$gt:1,$lt:5},b:6}, {a:3,b:{$gt:0,$lt:10}}]}" -> a: (1.0, 5.0)
+TEST(CMCollapseTreeTest, OrOfAnd3) {
+ OrderedIntervalList expected;
+ expected.intervals.push_back(Interval(BSON("" << 1.0 << "" << 5.0), false, false));
+ checkIndexBounds("{$or: [{a:{$gt:1,$lt:5},b:6}, {a:3,b:{$gt:0,$lt:10}}]}", expected);
+}
+
+//
+// Compound shard key
+//
+
+// "{$or: [{a:{$gt:1,$lt:5}, b:{$gt:0,$lt:3}, c:6}, "
+// "{a:3, b:{$gt:1,$lt:2}, c:{$gt:0,$lt:10}}]}",
+// -> a: (1.0, 5.0), b: (0.0, 3.0)
+TEST(CMCollapseTreeTest, OrOfAnd4) {
+ IndexBounds expectedBounds;
+ expectedBounds.fields.push_back(OrderedIntervalList());
+ expectedBounds.fields.push_back(OrderedIntervalList());
+
+ expectedBounds.fields[0].intervals.push_back(
+ Interval(BSON("" << 1.0 << "" << 5.0), false, false));
+ expectedBounds.fields[1].intervals.push_back(
+ Interval(BSON("" << 0.0 << "" << 3.0), false, false));
+
+ checkIndexBoundsWithKey("{a: 1, b: 1}", // shard key
+ "{$or: [{a:{$gt:1,$lt:5}, b:{$gt:0,$lt:3}, c:6}, "
+ "{a:3, b:{$gt:1,$lt:2}, c:{$gt:0,$lt:10}}]}",
+ expectedBounds);
+}
+
+// "{$or: [{a:{$gt:1,$lt:5}, c:6}, "
+// "{a:3, b:{$gt:1,$lt:2}, c:{$gt:0,$lt:10}}]}"));
+// ->
+TEST(CMCollapseTreeTest, OrOfAnd5) {
+ IndexBounds expectedBounds;
+ expectedBounds.fields.push_back(OrderedIntervalList());
+ expectedBounds.fields.push_back(OrderedIntervalList());
+
+ expectedBounds.fields[0].intervals.push_back(
+ Interval(BSON("" << 1.0 << "" << 5.0), false, false));
+ BSONObjBuilder builder;
+ builder.appendMinKey("");
+ builder.appendMaxKey("");
+ expectedBounds.fields[1].intervals.push_back(Interval(builder.obj(), true, true));
+
+ checkIndexBoundsWithKey("{a: 1, b: 1}", // shard key
+ "{$or: [{a:{$gt:1,$lt:5}, c:6}, "
+ "{a:3, b:{$gt:1,$lt:2}, c:{$gt:0,$lt:10}}]}",
+ expectedBounds);
+}
+
+// {$or: [{a:{$in:[1]},b:{$in:[1]}}, {a:{$in:[1,5]},b:{$in:[1,5]}}]}
+// -> a: [1], [5]; b: [1], [5]
+TEST(CMCollapseTreeTest, OrOfAnd6) {
+ IndexBounds expectedBounds;
+ expectedBounds.fields.push_back(OrderedIntervalList());
+ expectedBounds.fields.push_back(OrderedIntervalList());
+
+ // a: [1], [5]
+ expectedBounds.fields[0].intervals.push_back(
+ Interval(BSON("" << 1.0 << "" << 1.0), true, true));
+ expectedBounds.fields[0].intervals.push_back(
+ Interval(BSON("" << 5.0 << "" << 5.0), true, true));
+
+ // b: [1], [5]
+ expectedBounds.fields[1].intervals.push_back(
+ Interval(BSON("" << 1.0 << "" << 1.0), true, true));
+ expectedBounds.fields[1].intervals.push_back(
+ Interval(BSON("" << 5.0 << "" << 5.0), true, true));
+
+ checkIndexBoundsWithKey("{a: 1, b: 1}", // shard key
+ "{$or: [{a:{$in:[1]},b:{$in:[1]}}, {a:{$in:[1,5]},b:{$in:[1,5]}}]}",
+ expectedBounds);
+}
+
+//
+// Array operators
+//
+
+// {a : {$elemMatch: {b:1}}} -> a.b: [MinKey, MaxKey]
+// Shard key doesn't allow multikey, but query on array should succeed without error.
+TEST(CMCollapseTreeTest, ElemMatchOneField) {
+ IndexBounds expectedBounds;
+ expectedBounds.fields.push_back(OrderedIntervalList());
+ OrderedIntervalList& oil = expectedBounds.fields.front();
+ oil.intervals.push_back(Interval(BSON("" << 1 << "" << 1), true, true));
+ checkIndexBoundsWithKey("{'a.b': 1}", "{a : {$elemMatch: {b:1}}}", expectedBounds);
+}
+
+// {foo: {$all: [ {$elemMatch: {a:1, b:1}}, {$elemMatch: {a:2, b:2}}]}}
+// -> foo.a: [1, 1]
+// Or -> foo.a: [2, 2]
+TEST(CMCollapseTreeTest, BasicAllElemMatch) {
+ Interval expectedInterval(BSON("" << 1 << "" << 1), true, true);
+
+ const char* queryStr = "{foo: {$all: [ {$elemMatch: {a:1, b:1}} ]}}";
+ unique_ptr<CanonicalQuery> query(canonicalize(queryStr));
+ ASSERT(query.get() != NULL);
+
+ BSONObj key = fromjson("{'foo.a': 1}");
+
+ IndexBounds indexBounds = ChunkManager::getIndexBoundsForQuery(key, query.get());
+ ASSERT_EQUALS(indexBounds.size(), 1U);
+ const OrderedIntervalList& oil = indexBounds.fields.front();
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ const Interval& interval = oil.intervals.front();
+
+ // Choose one of the two possible solutions.
+ // Two solutions differ only by assignment of index tags.
+ ASSERT(Interval::INTERVAL_EQUALS == interval.compare(expectedInterval));
+}
+
+// {a : [1, 2, 3]} -> a: [1, 1], [[1, 2, 3], [1, 2, 3]]
+TEST(CMCollapseTreeTest, ArrayEquality) {
+ OrderedIntervalList expected;
+ expected.intervals.push_back(Interval(BSON("" << 1 << "" << 1), true, true));
+ BSONArray array(BSON_ARRAY(1 << 2 << 3));
+
+ Interval interval(BSON("" << array << "" << array), true, true);
+ expected.intervals.push_back(interval);
+ checkIndexBounds("{a : [1, 2, 3]}", expected);
+}
+
+
+//
+// Features: Regex, $where, $text, hashed key
+//
+
+// { a: /abc/ } -> a: ["", {}), [/abc/, /abc/]
+TEST(CMCollapseTreeTest, Regex) {
+ OrderedIntervalList expected;
+ expected.intervals.push_back(Interval(BSON(""
+ << ""
+ << "" << BSONObj()),
+ true,
+ false));
+ BSONObjBuilder builder;
+ builder.appendRegex("", "abc");
+ builder.appendRegex("", "abc");
+ expected.intervals.push_back(Interval(builder.obj(), true, true));
+ checkIndexBounds("{ a: /abc/ }", expected);
+}
+
+// {$where: 'this.credits == this.debits' }
+TEST(CMCollapseTreeTest, Where) {
+ OrderedIntervalList expected;
+ BSONObjBuilder builder;
+ builder.appendMinKey("");
+ builder.appendMaxKey("");
+ expected.intervals.push_back(Interval(builder.obj(), true, true));
+ checkIndexBounds("{$where: 'this.credits == this.debits' }", expected);
+}
+
+// { $text: { $search: "coffee -cake" } }
+TEST(CMCollapseTreeTest, Text) {
+ OrderedIntervalList expected;
+ BSONObjBuilder builder;
+ builder.appendMinKey("");
+ builder.appendMaxKey("");
+ expected.intervals.push_back(Interval(builder.obj(), true, true));
+ checkIndexBounds("{ $text: { $search: 'coffee -cake' } }", expected);
+}
+
+// { a: 2, $text: { $search: "leche", $language: "es" } }
+TEST(CMCollapseTreeTest, TextWithQuery) {
+ OrderedIntervalList expected;
+ BSONObjBuilder builder;
+ builder.appendMinKey("");
+ builder.appendMaxKey("");
+ expected.intervals.push_back(Interval(builder.obj(), true, true));
+ checkIndexBounds("{ a: 2, $text: { $search: 'leche', $language: 'es' } }", expected);
+}
+
+// { a: 0 } -> hashed a: [hash(0), hash(0)]
+TEST(CMCollapseTreeTest, HashedSinglePoint) {
+ const char* queryStr = "{ a: 0 }";
+ unique_ptr<CanonicalQuery> query(canonicalize(queryStr));
+ ASSERT(query.get() != NULL);
+
+ BSONObj key = fromjson("{a: 'hashed'}");
+
+ IndexBounds indexBounds = ChunkManager::getIndexBoundsForQuery(key, query.get());
+ ASSERT_EQUALS(indexBounds.size(), 1U);
+ const OrderedIntervalList& oil = indexBounds.fields.front();
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ const Interval& interval = oil.intervals.front();
+ ASSERT(interval.isPoint());
+}
+
+// { a: { $lt: 2, $gt: 1} } -> hashed a: [Minkey, Maxkey]
+TEST(CMCollapseTreeTest, HashedRange) {
+ IndexBounds expectedBounds;
+ expectedBounds.fields.push_back(OrderedIntervalList());
+ OrderedIntervalList& expectedOil = expectedBounds.fields.front();
+ BSONObjBuilder builder;
+ builder.appendMinKey("");
+ builder.appendMaxKey("");
+ expectedOil.intervals.push_back(Interval(builder.obj(), true, true));
+ checkIndexBoundsWithKey("{a: 'hashed'}", "{ a: { $lt: 2, $gt: 1} }", expectedBounds);
+}
+
+// { a: /abc/ } -> hashed a: [Minkey, Maxkey]
+TEST(CMCollapseTreeTest, HashedRegex) {
+ IndexBounds expectedBounds;
+ expectedBounds.fields.push_back(OrderedIntervalList());
+ OrderedIntervalList& expectedOil = expectedBounds.fields.front();
+ BSONObjBuilder builder;
+ builder.appendMinKey("");
+ builder.appendMaxKey("");
+ expectedOil.intervals.push_back(Interval(builder.obj(), true, true));
+
+ checkIndexBoundsWithKey("{a: 'hashed'}", "{ a: /abc/ }", expectedBounds);
+}
- /**
- * KeyPattern key bounds generation test
- */
-
- void CheckBoundList(const BoundList& list, const BoundList& expected) {
- ASSERT_EQUALS(list.size(), expected.size());
- for (size_t i = 0; i < list.size(); i++) {
- ASSERT_EQUALS(list[i].first.woCompare(expected[i].first), 0);
- ASSERT_EQUALS(list[i].second.woCompare(expected[i].second), 0);
- }
- }
-
- // Key { a: 1 }, Bounds a: [0]
- // => { a: 0 } -> { a: 0 }
- TEST(CMKeyBoundsTest, Basic) {
- IndexBounds indexBounds;
- indexBounds.fields.push_back(OrderedIntervalList());
- indexBounds.fields.front().intervals.push_back(
- Interval(BSON("" << 0 << "" << 0), true, true));
-
- BoundList expectedList;
- expectedList.push_back(make_pair(fromjson("{a: 0}"), fromjson("{a: 0}")));
-
- ShardKeyPattern skeyPattern(fromjson("{a: 1}"));
- BoundList list = skeyPattern.flattenBounds(indexBounds);
- CheckBoundList(list, expectedList);
- }
-
- // Key { a: 1 }, Bounds a: [2, 3)
- // => { a: 2 } -> { a: 3 } // bound inclusion is ignored.
- TEST(CMKeyBoundsTest, SingleInterval) {
- IndexBounds indexBounds;
- indexBounds.fields.push_back(OrderedIntervalList());
- indexBounds.fields.front().intervals.push_back(
- Interval(BSON("" << 2 << "" << 3), true, false));
-
- BoundList expectedList;
- expectedList.push_back(make_pair(fromjson("{a: 2}"), fromjson("{a: 3}")));
-
- ShardKeyPattern skeyPattern(fromjson("{a: 1}"));
- BoundList list = skeyPattern.flattenBounds(indexBounds);
- CheckBoundList(list, expectedList);
- }
-
- // Key { a: 1, b: 1, c: 1 }, Bounds a: [2, 3), b: [2, 3), c: [2: 3)
- // => { a: 2, b: 2, c: 2 } -> { a: 3, b: 3, c: 3 }
- TEST(CMKeyBoundsTest, MultiIntervals) {
- IndexBounds indexBounds;
- indexBounds.fields.push_back(OrderedIntervalList());
- indexBounds.fields.push_back(OrderedIntervalList());
- indexBounds.fields.push_back(OrderedIntervalList());
- indexBounds.fields[0].intervals.push_back(
- Interval(BSON("" << 2 << "" << 3), true, false));
- indexBounds.fields[1].intervals.push_back(
- Interval(BSON("" << 2 << "" << 3), true, false));
- indexBounds.fields[2].intervals.push_back(
- Interval(BSON("" << 2 << "" << 3), true, false));
-
- BoundList expectedList;
- expectedList.push_back(make_pair(
- fromjson("{ a: 2, b: 2, c: 2 }"),
- fromjson("{ a: 3, b: 3, c: 3 }")));
-
- ShardKeyPattern skeyPattern(fromjson("{a: 1, b: 1, c: 1}"));
- BoundList list = skeyPattern.flattenBounds(indexBounds);
- CheckBoundList(list, expectedList);
- }
-
- // Key { a: 1, b: 1, c: 1 }, Bounds a: [0, 0], b: { $in: [4, 5, 6] }, c: [2: 3)
- // => { a: 0, b: 4, c: 2 } -> { a: 0, b: 4, c: 3 }
- // { a: 0, b: 5, c: 2 } -> { a: 0, b: 5, c: 3 }
- // { a: 0, b: 6, c: 2 } -> { a: 0, b: 6, c: 3 }
- TEST(CMKeyBoundsTest, IntervalExpansion) {
- IndexBounds indexBounds;
- indexBounds.fields.push_back(OrderedIntervalList());
- indexBounds.fields.push_back(OrderedIntervalList());
- indexBounds.fields.push_back(OrderedIntervalList());
-
- indexBounds.fields[0].intervals.push_back(
- Interval(BSON("" << 0 << "" << 0), true, true));
-
- indexBounds.fields[1].intervals.push_back(
- Interval(BSON("" << 4 << "" << 4), true, true));
- indexBounds.fields[1].intervals.push_back(
- Interval(BSON("" << 5 << "" << 5), true, true));
- indexBounds.fields[1].intervals.push_back(
- Interval(BSON("" << 6 << "" << 6), true, true));
-
- indexBounds.fields[2].intervals.push_back(
- Interval(BSON("" << 2 << "" << 3), true, false));
-
- BoundList expectedList;
- expectedList.push_back(make_pair(
- fromjson("{ a: 0, b: 4, c: 2 }"),
- fromjson("{ a: 0, b: 4, c: 3 }")));
- expectedList.push_back(make_pair(
- fromjson("{ a: 0, b: 5, c: 2 }"),
- fromjson("{ a: 0, b: 5, c: 3 }")));
- expectedList.push_back(make_pair(
- fromjson("{ a: 0, b: 6, c: 2 }"),
- fromjson("{ a: 0, b: 6, c: 3 }")));
-
- ShardKeyPattern skeyPattern(fromjson("{a: 1, b: 1, c: 1}"));
- BoundList list = skeyPattern.flattenBounds(indexBounds);
- CheckBoundList(list, expectedList);
- }
+/**
+ * KeyPattern key bounds generation test
+ */
- // Key { a: 1, b: 1, c: 1 }, Bounds a: [0, 1], b: { $in: [4, 5, 6] }, c: [2: 3)
- // => { a: 0, b: 4, c: 2 } -> { a: 1, b: 6, c: 3 }
- // Since field "a" is not a point, expasion after "a" is not allowed.
- TEST(CMKeyBoundsTest, NonPointIntervalExpasion) {
- IndexBounds indexBounds;
- indexBounds.fields.push_back(OrderedIntervalList());
- indexBounds.fields.push_back(OrderedIntervalList());
- indexBounds.fields.push_back(OrderedIntervalList());
-
- indexBounds.fields[0].intervals.push_back(
- Interval(BSON("" << 0 << "" << 1), true, true));
-
- indexBounds.fields[1].intervals.push_back(
- Interval(BSON("" << 4 << "" << 4), true, true));
- indexBounds.fields[1].intervals.push_back(
- Interval(BSON("" << 5 << "" << 5), true, true));
- indexBounds.fields[1].intervals.push_back(
- Interval(BSON("" << 6 << "" << 6), true, true));
-
- indexBounds.fields[2].intervals.push_back(
- Interval(BSON("" << 2 << "" << 3), true, false));
-
- BoundList expectedList;
- expectedList.push_back(make_pair(
- fromjson("{ a: 0, b: 4, c: 2 }"),
- fromjson("{ a: 1, b: 6, c: 3 }")));
-
- ShardKeyPattern skeyPattern(fromjson("{a: 1, b: 1, c: 1}"));
- BoundList list = skeyPattern.flattenBounds(indexBounds);
- CheckBoundList(list, expectedList);
+void CheckBoundList(const BoundList& list, const BoundList& expected) {
+ ASSERT_EQUALS(list.size(), expected.size());
+ for (size_t i = 0; i < list.size(); i++) {
+ ASSERT_EQUALS(list[i].first.woCompare(expected[i].first), 0);
+ ASSERT_EQUALS(list[i].second.woCompare(expected[i].second), 0);
}
-
-} // namespace
+}
+
+// Key { a: 1 }, Bounds a: [0]
+// => { a: 0 } -> { a: 0 }
+TEST(CMKeyBoundsTest, Basic) {
+ IndexBounds indexBounds;
+ indexBounds.fields.push_back(OrderedIntervalList());
+ indexBounds.fields.front().intervals.push_back(Interval(BSON("" << 0 << "" << 0), true, true));
+
+ BoundList expectedList;
+ expectedList.push_back(make_pair(fromjson("{a: 0}"), fromjson("{a: 0}")));
+
+ ShardKeyPattern skeyPattern(fromjson("{a: 1}"));
+ BoundList list = skeyPattern.flattenBounds(indexBounds);
+ CheckBoundList(list, expectedList);
+}
+
+// Key { a: 1 }, Bounds a: [2, 3)
+// => { a: 2 } -> { a: 3 } // bound inclusion is ignored.
+TEST(CMKeyBoundsTest, SingleInterval) {
+ IndexBounds indexBounds;
+ indexBounds.fields.push_back(OrderedIntervalList());
+ indexBounds.fields.front().intervals.push_back(Interval(BSON("" << 2 << "" << 3), true, false));
+
+ BoundList expectedList;
+ expectedList.push_back(make_pair(fromjson("{a: 2}"), fromjson("{a: 3}")));
+
+ ShardKeyPattern skeyPattern(fromjson("{a: 1}"));
+ BoundList list = skeyPattern.flattenBounds(indexBounds);
+ CheckBoundList(list, expectedList);
+}
+
+// Key { a: 1, b: 1, c: 1 }, Bounds a: [2, 3), b: [2, 3), c: [2: 3)
+// => { a: 2, b: 2, c: 2 } -> { a: 3, b: 3, c: 3 }
+TEST(CMKeyBoundsTest, MultiIntervals) {
+ IndexBounds indexBounds;
+ indexBounds.fields.push_back(OrderedIntervalList());
+ indexBounds.fields.push_back(OrderedIntervalList());
+ indexBounds.fields.push_back(OrderedIntervalList());
+ indexBounds.fields[0].intervals.push_back(Interval(BSON("" << 2 << "" << 3), true, false));
+ indexBounds.fields[1].intervals.push_back(Interval(BSON("" << 2 << "" << 3), true, false));
+ indexBounds.fields[2].intervals.push_back(Interval(BSON("" << 2 << "" << 3), true, false));
+
+ BoundList expectedList;
+ expectedList.push_back(
+ make_pair(fromjson("{ a: 2, b: 2, c: 2 }"), fromjson("{ a: 3, b: 3, c: 3 }")));
+
+ ShardKeyPattern skeyPattern(fromjson("{a: 1, b: 1, c: 1}"));
+ BoundList list = skeyPattern.flattenBounds(indexBounds);
+ CheckBoundList(list, expectedList);
+}
+
+// Key { a: 1, b: 1, c: 1 }, Bounds a: [0, 0], b: { $in: [4, 5, 6] }, c: [2: 3)
+// => { a: 0, b: 4, c: 2 } -> { a: 0, b: 4, c: 3 }
+// { a: 0, b: 5, c: 2 } -> { a: 0, b: 5, c: 3 }
+// { a: 0, b: 6, c: 2 } -> { a: 0, b: 6, c: 3 }
+TEST(CMKeyBoundsTest, IntervalExpansion) {
+ IndexBounds indexBounds;
+ indexBounds.fields.push_back(OrderedIntervalList());
+ indexBounds.fields.push_back(OrderedIntervalList());
+ indexBounds.fields.push_back(OrderedIntervalList());
+
+ indexBounds.fields[0].intervals.push_back(Interval(BSON("" << 0 << "" << 0), true, true));
+
+ indexBounds.fields[1].intervals.push_back(Interval(BSON("" << 4 << "" << 4), true, true));
+ indexBounds.fields[1].intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true));
+ indexBounds.fields[1].intervals.push_back(Interval(BSON("" << 6 << "" << 6), true, true));
+
+ indexBounds.fields[2].intervals.push_back(Interval(BSON("" << 2 << "" << 3), true, false));
+
+ BoundList expectedList;
+ expectedList.push_back(
+ make_pair(fromjson("{ a: 0, b: 4, c: 2 }"), fromjson("{ a: 0, b: 4, c: 3 }")));
+ expectedList.push_back(
+ make_pair(fromjson("{ a: 0, b: 5, c: 2 }"), fromjson("{ a: 0, b: 5, c: 3 }")));
+ expectedList.push_back(
+ make_pair(fromjson("{ a: 0, b: 6, c: 2 }"), fromjson("{ a: 0, b: 6, c: 3 }")));
+
+ ShardKeyPattern skeyPattern(fromjson("{a: 1, b: 1, c: 1}"));
+ BoundList list = skeyPattern.flattenBounds(indexBounds);
+ CheckBoundList(list, expectedList);
+}
+
+// Key { a: 1, b: 1, c: 1 }, Bounds a: [0, 1], b: { $in: [4, 5, 6] }, c: [2: 3)
+// => { a: 0, b: 4, c: 2 } -> { a: 1, b: 6, c: 3 }
+// Since field "a" is not a point, expasion after "a" is not allowed.
+TEST(CMKeyBoundsTest, NonPointIntervalExpasion) {
+ IndexBounds indexBounds;
+ indexBounds.fields.push_back(OrderedIntervalList());
+ indexBounds.fields.push_back(OrderedIntervalList());
+ indexBounds.fields.push_back(OrderedIntervalList());
+
+ indexBounds.fields[0].intervals.push_back(Interval(BSON("" << 0 << "" << 1), true, true));
+
+ indexBounds.fields[1].intervals.push_back(Interval(BSON("" << 4 << "" << 4), true, true));
+ indexBounds.fields[1].intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true));
+ indexBounds.fields[1].intervals.push_back(Interval(BSON("" << 6 << "" << 6), true, true));
+
+ indexBounds.fields[2].intervals.push_back(Interval(BSON("" << 2 << "" << 3), true, false));
+
+ BoundList expectedList;
+ expectedList.push_back(
+ make_pair(fromjson("{ a: 0, b: 4, c: 2 }"), fromjson("{ a: 1, b: 6, c: 3 }")));
+
+ ShardKeyPattern skeyPattern(fromjson("{a: 1, b: 1, c: 1}"));
+ BoundList list = skeyPattern.flattenBounds(indexBounds);
+ CheckBoundList(list, expectedList);
+}
+
+} // namespace
diff --git a/src/mongo/s/chunk_version.h b/src/mongo/s/chunk_version.h
index 41b9e03bae2..fef1a581909 100644
--- a/src/mongo/s/chunk_version.h
+++ b/src/mongo/s/chunk_version.h
@@ -32,428 +32,423 @@
namespace mongo {
- /**
- * ChunkVersions consist of a major/minor version scoped to a version epoch
- *
- * Version configurations (format: major version, epoch):
- *
- * 1. (0, 0) - collection is dropped.
- * 2. (0, n), n > 0 - applicable only to shardVersion; shard has no chunk.
- * 3. (n, 0), n > 0 - invalid configuration.
- * 4. (n, m), n > 0, m > 0 - normal sharded collection version.
- *
- * TODO: This is a "manual type" but, even so, still needs to comform to what's
- * expected from types.
- */
- struct ChunkVersion {
-
- union {
- struct {
- int _minor;
- int _major;
- };
- unsigned long long _combined;
+/**
+ * ChunkVersions consist of a major/minor version scoped to a version epoch
+ *
+ * Version configurations (format: major version, epoch):
+ *
+ * 1. (0, 0) - collection is dropped.
+ * 2. (0, n), n > 0 - applicable only to shardVersion; shard has no chunk.
+ * 3. (n, 0), n > 0 - invalid configuration.
+ * 4. (n, m), n > 0, m > 0 - normal sharded collection version.
+ *
+ * TODO: This is a "manual type" but, even so, still needs to comform to what's
+ * expected from types.
+ */
+struct ChunkVersion {
+ union {
+ struct {
+ int _minor;
+ int _major;
};
- OID _epoch;
-
- ChunkVersion() : _minor(0), _major(0), _epoch(OID()) {}
-
- //
- // Constructors shouldn't have default parameters here, since it's vital we track from
- // here on the epochs of versions, even if not used.
- //
+ unsigned long long _combined;
+ };
+ OID _epoch;
- ChunkVersion( int major, int minor, const OID& epoch )
- : _minor(minor),_major(major), _epoch(epoch) {
- }
+ ChunkVersion() : _minor(0), _major(0), _epoch(OID()) {}
- static ChunkVersion DROPPED() {
- return ChunkVersion( 0, 0, OID() ); // dropped OID is zero time, zero machineId/inc
- }
+ //
+ // Constructors shouldn't have default parameters here, since it's vital we track from
+ // here on the epochs of versions, even if not used.
+ //
- static ChunkVersion UNSHARDED() {
- // TODO: Distinguish between these cases
- return DROPPED();
- }
+ ChunkVersion(int major, int minor, const OID& epoch)
+ : _minor(minor), _major(major), _epoch(epoch) {}
- static ChunkVersion IGNORED() {
- ChunkVersion version = ChunkVersion();
- version._epoch.init(Date_t(), true); // ignored OID is zero time, max machineId/inc
- return version;
- }
+ static ChunkVersion DROPPED() {
+ return ChunkVersion(0, 0, OID()); // dropped OID is zero time, zero machineId/inc
+ }
- static ChunkVersion fromDeprecatedLong(unsigned long long num, const OID& epoch) {
- ChunkVersion version(0, 0, epoch);
- version._combined = num;
- return version;
- }
+ static ChunkVersion UNSHARDED() {
+ // TODO: Distinguish between these cases
+ return DROPPED();
+ }
- static bool isIgnoredVersion( const ChunkVersion& version ) {
- return version.majorVersion() == 0 && version.minorVersion() == 0
- && version.epoch() == IGNORED().epoch();
- }
+ static ChunkVersion IGNORED() {
+ ChunkVersion version = ChunkVersion();
+ version._epoch.init(Date_t(), true); // ignored OID is zero time, max machineId/inc
+ return version;
+ }
- void incMajor() {
- _major++;
- _minor = 0;
- }
+ static ChunkVersion fromDeprecatedLong(unsigned long long num, const OID& epoch) {
+ ChunkVersion version(0, 0, epoch);
+ version._combined = num;
+ return version;
+ }
- void incMinor() {
- _minor++;
- }
+ static bool isIgnoredVersion(const ChunkVersion& version) {
+ return version.majorVersion() == 0 && version.minorVersion() == 0 &&
+ version.epoch() == IGNORED().epoch();
+ }
- // Incrementing an epoch creates a new, randomly generated identifier
- void incEpoch() {
- _epoch = OID::gen();
- _major = 0;
- _minor = 0;
- }
+ void incMajor() {
+ _major++;
+ _minor = 0;
+ }
- // Note: this shouldn't be used as a substitute for version except in specific cases -
- // epochs make versions more complex
- unsigned long long toLong() const {
- return _combined;
- }
+ void incMinor() {
+ _minor++;
+ }
- bool isSet() const {
- return _combined > 0;
- }
+ // Incrementing an epoch creates a new, randomly generated identifier
+ void incEpoch() {
+ _epoch = OID::gen();
+ _major = 0;
+ _minor = 0;
+ }
- bool isEpochSet() const {
- return _epoch.isSet();
- }
+ // Note: this shouldn't be used as a substitute for version except in specific cases -
+ // epochs make versions more complex
+ unsigned long long toLong() const {
+ return _combined;
+ }
- std::string toString() const {
- std::stringstream ss;
- // Similar to month/day/year. For the most part when debugging, we care about major
- // so it's first
- ss << _major << "|" << _minor << "||" << _epoch;
- return ss.str();
- }
+ bool isSet() const {
+ return _combined > 0;
+ }
- int majorVersion() const { return _major; }
- int minorVersion() const { return _minor; }
- OID epoch() const { return _epoch; }
+ bool isEpochSet() const {
+ return _epoch.isSet();
+ }
- //
- // Explicit comparison operators - versions with epochs have non-trivial comparisons.
- // > < operators do not check epoch cases. Generally if using == we need to handle
- // more complex cases.
- //
+ std::string toString() const {
+ std::stringstream ss;
+ // Similar to month/day/year. For the most part when debugging, we care about major
+ // so it's first
+ ss << _major << "|" << _minor << "||" << _epoch;
+ return ss.str();
+ }
- bool operator>( const ChunkVersion& otherVersion ) const {
- return this->_combined > otherVersion._combined;
- }
+ int majorVersion() const {
+ return _major;
+ }
+ int minorVersion() const {
+ return _minor;
+ }
+ OID epoch() const {
+ return _epoch;
+ }
- bool operator>=( const ChunkVersion& otherVersion ) const {
- return this->_combined >= otherVersion._combined;
- }
+ //
+ // Explicit comparison operators - versions with epochs have non-trivial comparisons.
+ // > < operators do not check epoch cases. Generally if using == we need to handle
+ // more complex cases.
+ //
- bool operator<( const ChunkVersion& otherVersion ) const {
- return this->_combined < otherVersion._combined;
- }
+ bool operator>(const ChunkVersion& otherVersion) const {
+ return this->_combined > otherVersion._combined;
+ }
- bool operator<=( const ChunkVersion& otherVersion ) const {
- return this->_combined <= otherVersion._combined;
- }
+ bool operator>=(const ChunkVersion& otherVersion) const {
+ return this->_combined >= otherVersion._combined;
+ }
- //
- // Equivalence comparison types.
- //
+ bool operator<(const ChunkVersion& otherVersion) const {
+ return this->_combined < otherVersion._combined;
+ }
- // Can we write to this data and not have a problem?
- bool isWriteCompatibleWith( const ChunkVersion& otherVersion ) const {
- if( ! hasEqualEpoch( otherVersion ) ) return false;
- return otherVersion._major == _major;
- }
+ bool operator<=(const ChunkVersion& otherVersion) const {
+ return this->_combined <= otherVersion._combined;
+ }
- // Is this the same version?
- bool equals( const ChunkVersion& otherVersion ) const {
- if( ! hasEqualEpoch( otherVersion ) ) return false;
- return otherVersion._combined == _combined;
- }
+ //
+ // Equivalence comparison types.
+ //
- /**
- * Returns true if the otherVersion is the same as this version and enforces strict epoch
- * checking (empty epochs are not wildcards).
- */
- bool isStrictlyEqualTo( const ChunkVersion& otherVersion ) const {
- if ( otherVersion._epoch != _epoch )
- return false;
- return otherVersion._combined == _combined;
- }
+ // Can we write to this data and not have a problem?
+ bool isWriteCompatibleWith(const ChunkVersion& otherVersion) const {
+ if (!hasEqualEpoch(otherVersion))
+ return false;
+ return otherVersion._major == _major;
+ }
- /**
- * Returns true if this version is (strictly) in the same epoch as the other version and
- * this version is older. Returns false if we're not sure because the epochs are different
- * or if this version is newer.
- */
- bool isOlderThan( const ChunkVersion& otherVersion ) const {
- if ( otherVersion._epoch != _epoch )
- return false;
-
- if ( _major != otherVersion._major )
- return _major < otherVersion._major;
-
- return _minor < otherVersion._minor;
- }
+ // Is this the same version?
+ bool equals(const ChunkVersion& otherVersion) const {
+ if (!hasEqualEpoch(otherVersion))
+ return false;
+ return otherVersion._combined == _combined;
+ }
- // Is this in the same epoch?
- bool hasEqualEpoch( const ChunkVersion& otherVersion ) const {
- return hasEqualEpoch( otherVersion._epoch );
- }
+ /**
+ * Returns true if the otherVersion is the same as this version and enforces strict epoch
+ * checking (empty epochs are not wildcards).
+ */
+ bool isStrictlyEqualTo(const ChunkVersion& otherVersion) const {
+ if (otherVersion._epoch != _epoch)
+ return false;
+ return otherVersion._combined == _combined;
+ }
- bool hasEqualEpoch( const OID& otherEpoch ) const {
- return _epoch == otherEpoch;
- }
+ /**
+ * Returns true if this version is (strictly) in the same epoch as the other version and
+ * this version is older. Returns false if we're not sure because the epochs are different
+ * or if this version is newer.
+ */
+ bool isOlderThan(const ChunkVersion& otherVersion) const {
+ if (otherVersion._epoch != _epoch)
+ return false;
- //
- // BSON input/output
- //
- // The idea here is to make the BSON input style very flexible right now, so we
- // can then tighten it up in the next version. We can accept either a BSONObject field
- // with version and epoch, or version and epoch in different fields (either is optional).
- // In this case, epoch always is stored in a field name of the version field name + "Epoch"
- //
-
- //
- // { version : <TS> } and { version : [<TS>,<OID>] } format
- //
-
- static bool canParseBSON( const BSONElement& el, const std::string& prefix="" ){
- bool canParse;
- fromBSON( el, prefix, &canParse );
- return canParse;
- }
+ if (_major != otherVersion._major)
+ return _major < otherVersion._major;
- static ChunkVersion fromBSON( const BSONElement& el, const std::string& prefix="" ){
- bool canParse;
- return fromBSON( el, prefix, &canParse );
- }
+ return _minor < otherVersion._minor;
+ }
- static ChunkVersion fromBSON( const BSONElement& el,
- const std::string& prefix,
- bool* canParse )
- {
- *canParse = true;
+ // Is this in the same epoch?
+ bool hasEqualEpoch(const ChunkVersion& otherVersion) const {
+ return hasEqualEpoch(otherVersion._epoch);
+ }
- int type = el.type();
+ bool hasEqualEpoch(const OID& otherEpoch) const {
+ return _epoch == otherEpoch;
+ }
- if( type == Array ){
- return fromBSON( BSONArray( el.Obj() ), canParse );
- }
+ //
+ // BSON input/output
+ //
+ // The idea here is to make the BSON input style very flexible right now, so we
+ // can then tighten it up in the next version. We can accept either a BSONObject field
+ // with version and epoch, or version and epoch in different fields (either is optional).
+ // In this case, epoch always is stored in a field name of the version field name + "Epoch"
+ //
+
+ //
+ // { version : <TS> } and { version : [<TS>,<OID>] } format
+ //
+
+ static bool canParseBSON(const BSONElement& el, const std::string& prefix = "") {
+ bool canParse;
+ fromBSON(el, prefix, &canParse);
+ return canParse;
+ }
- if( type == jstOID ){
- return ChunkVersion( 0, 0, el.OID() );
- }
+ static ChunkVersion fromBSON(const BSONElement& el, const std::string& prefix = "") {
+ bool canParse;
+ return fromBSON(el, prefix, &canParse);
+ }
- if( type == bsonTimestamp || type == Date ){
- return fromDeprecatedLong( el._numberLong(), OID() );
- }
+ static ChunkVersion fromBSON(const BSONElement& el, const std::string& prefix, bool* canParse) {
+ *canParse = true;
- *canParse = false;
+ int type = el.type();
- return ChunkVersion( 0, 0, OID() );
+ if (type == Array) {
+ return fromBSON(BSONArray(el.Obj()), canParse);
}
- //
- // { version : <TS>, versionEpoch : <OID> } object format
- //
-
- static bool canParseBSON( const BSONObj& obj, const std::string& prefix="" ){
- bool canParse;
- fromBSON( obj, prefix, &canParse );
- return canParse;
+ if (type == jstOID) {
+ return ChunkVersion(0, 0, el.OID());
}
- static ChunkVersion fromBSON( const BSONObj& obj, const std::string& prefix="" ){
- bool canParse;
- return fromBSON( obj, prefix, &canParse );
+ if (type == bsonTimestamp || type == Date) {
+ return fromDeprecatedLong(el._numberLong(), OID());
}
- static ChunkVersion fromBSON( const BSONObj& obj,
- const std::string& prefixIn,
- bool* canParse )
- {
- *canParse = true;
+ *canParse = false;
- std::string prefix = prefixIn;
- // "version" doesn't have a "cluster constanst" because that field is never
- // written to the config.
- if( prefixIn == "" && ! obj[ "version" ].eoo() ){
- prefix = (std::string)"version";
- }
- // TODO: use ChunkType::DEPRECATED_lastmod()
- // NOTE: type_chunk.h includes this file
- else if( prefixIn == "" && ! obj["lastmod"].eoo() ){
- prefix = (std::string)"lastmod";
- }
+ return ChunkVersion(0, 0, OID());
+ }
- ChunkVersion version = fromBSON( obj[ prefix ], prefixIn, canParse );
+ //
+ // { version : <TS>, versionEpoch : <OID> } object format
+ //
- if( obj[ prefix + "Epoch" ].type() == jstOID ){
- version._epoch = obj[ prefix + "Epoch" ].OID();
- *canParse = true;
- }
+ static bool canParseBSON(const BSONObj& obj, const std::string& prefix = "") {
+ bool canParse;
+ fromBSON(obj, prefix, &canParse);
+ return canParse;
+ }
- return version;
- }
+ static ChunkVersion fromBSON(const BSONObj& obj, const std::string& prefix = "") {
+ bool canParse;
+ return fromBSON(obj, prefix, &canParse);
+ }
- //
- // { version : [<TS>, <OID>] } format
- //
+ static ChunkVersion fromBSON(const BSONObj& obj, const std::string& prefixIn, bool* canParse) {
+ *canParse = true;
- static bool canParseBSON( const BSONArray& arr ){
- bool canParse;
- fromBSON( arr, &canParse );
- return canParse;
+ std::string prefix = prefixIn;
+ // "version" doesn't have a "cluster constanst" because that field is never
+ // written to the config.
+ if (prefixIn == "" && !obj["version"].eoo()) {
+ prefix = (std::string) "version";
+ }
+ // TODO: use ChunkType::DEPRECATED_lastmod()
+ // NOTE: type_chunk.h includes this file
+ else if (prefixIn == "" && !obj["lastmod"].eoo()) {
+ prefix = (std::string) "lastmod";
}
- static ChunkVersion fromBSON( const BSONArray& arr ){
- bool canParse;
- return fromBSON( arr, &canParse );
+ ChunkVersion version = fromBSON(obj[prefix], prefixIn, canParse);
+
+ if (obj[prefix + "Epoch"].type() == jstOID) {
+ version._epoch = obj[prefix + "Epoch"].OID();
+ *canParse = true;
}
- static ChunkVersion fromBSON( const BSONArray& arr,
- bool* canParse )
- {
- *canParse = false;
+ return version;
+ }
- ChunkVersion version;
+ //
+ // { version : [<TS>, <OID>] } format
+ //
- BSONObjIterator it( arr );
- if( ! it.more() ) return version;
+ static bool canParseBSON(const BSONArray& arr) {
+ bool canParse;
+ fromBSON(arr, &canParse);
+ return canParse;
+ }
- version = fromBSON( it.next(), "", canParse );
- if( ! (*canParse) ) return version;
+ static ChunkVersion fromBSON(const BSONArray& arr) {
+ bool canParse;
+ return fromBSON(arr, &canParse);
+ }
- *canParse = true;
+ static ChunkVersion fromBSON(const BSONArray& arr, bool* canParse) {
+ *canParse = false;
- if( ! it.more() ) return version;
- BSONElement next = it.next();
- if( next.type() != jstOID ) return version;
+ ChunkVersion version;
- version._epoch = next.OID();
+ BSONObjIterator it(arr);
+ if (!it.more())
+ return version;
+ version = fromBSON(it.next(), "", canParse);
+ if (!(*canParse))
return version;
- }
- enum VersionChoice {
- VersionChoice_Local,
- VersionChoice_Remote,
- VersionChoice_Unknown
- };
+ *canParse = true;
- /**
- * Compares a remotely-loaded version 'remoteVersion' to the latest local version of a
- * collection, 'localVersion', and returns the newest.
- *
- * Because it isn't clear during epoch changes which epoch is newer, the local version
- * before the reload occurred, 'prevLocalVersion', is used to determine whether the remote
- * epoch is definitely newer, or we're not sure.
- */
- static VersionChoice chooseNewestVersion( ChunkVersion prevLocalVersion,
- ChunkVersion localVersion,
- ChunkVersion remoteVersion )
- {
- OID prevEpoch = prevLocalVersion.epoch();
- OID localEpoch = localVersion.epoch();
- OID remoteEpoch = remoteVersion.epoch();
-
- // Everything changed in-flight, so we need to try again
- if ( prevEpoch != localEpoch && localEpoch != remoteEpoch ) {
- return VersionChoice_Unknown;
- }
+ if (!it.more())
+ return version;
+ BSONElement next = it.next();
+ if (next.type() != jstOID)
+ return version;
- // We're in the same (zero) epoch as the latest metadata, nothing to do
- if ( localEpoch == remoteEpoch && !remoteEpoch.isSet() ) {
- return VersionChoice_Local;
- }
+ version._epoch = next.OID();
- // We're in the same (non-zero) epoch as the latest metadata, so increment the version
- if ( localEpoch == remoteEpoch && remoteEpoch.isSet() ) {
+ return version;
+ }
- // Use the newer version if possible
- if ( localVersion < remoteVersion ) {
- return VersionChoice_Remote;
- }
- else {
- return VersionChoice_Local;
- }
- }
+ enum VersionChoice { VersionChoice_Local, VersionChoice_Remote, VersionChoice_Unknown };
- // We're now sure we're installing a new epoch and the epoch didn't change during reload
- dassert( prevEpoch == localEpoch && localEpoch != remoteEpoch );
- return VersionChoice_Remote;
+ /**
+ * Compares a remotely-loaded version 'remoteVersion' to the latest local version of a
+ * collection, 'localVersion', and returns the newest.
+ *
+ * Because it isn't clear during epoch changes which epoch is newer, the local version
+ * before the reload occurred, 'prevLocalVersion', is used to determine whether the remote
+ * epoch is definitely newer, or we're not sure.
+ */
+ static VersionChoice chooseNewestVersion(ChunkVersion prevLocalVersion,
+ ChunkVersion localVersion,
+ ChunkVersion remoteVersion) {
+ OID prevEpoch = prevLocalVersion.epoch();
+ OID localEpoch = localVersion.epoch();
+ OID remoteEpoch = remoteVersion.epoch();
+
+ // Everything changed in-flight, so we need to try again
+ if (prevEpoch != localEpoch && localEpoch != remoteEpoch) {
+ return VersionChoice_Unknown;
}
- //
- // Currently our BSON output is to two different fields, to cleanly work with older
- // versions that know nothing about epochs.
- //
+ // We're in the same (zero) epoch as the latest metadata, nothing to do
+ if (localEpoch == remoteEpoch && !remoteEpoch.isSet()) {
+ return VersionChoice_Local;
+ }
- BSONObj toBSONWithPrefix( const std::string& prefixIn ) const {
- BSONObjBuilder b;
+ // We're in the same (non-zero) epoch as the latest metadata, so increment the version
+ if (localEpoch == remoteEpoch && remoteEpoch.isSet()) {
+ // Use the newer version if possible
+ if (localVersion < remoteVersion) {
+ return VersionChoice_Remote;
+ } else {
+ return VersionChoice_Local;
+ }
+ }
- std::string prefix = prefixIn;
- if( prefix == "" ) prefix = "version";
+ // We're now sure we're installing a new epoch and the epoch didn't change during reload
+ dassert(prevEpoch == localEpoch && localEpoch != remoteEpoch);
+ return VersionChoice_Remote;
+ }
- b.appendTimestamp( prefix, _combined );
- b.append( prefix + "Epoch", _epoch );
- return b.obj();
- }
+ //
+ // Currently our BSON output is to two different fields, to cleanly work with older
+ // versions that know nothing about epochs.
+ //
- void addToBSON( BSONObjBuilder& b, const std::string& prefix="" ) const {
- b.appendElements( toBSONWithPrefix( prefix ) );
- }
+ BSONObj toBSONWithPrefix(const std::string& prefixIn) const {
+ BSONObjBuilder b;
- void addEpochToBSON( BSONObjBuilder& b, const std::string& prefix="" ) const {
- b.append( prefix + "Epoch", _epoch );
- }
+ std::string prefix = prefixIn;
+ if (prefix == "")
+ prefix = "version";
- BSONObj toBSON() const {
- // ChunkVersion wants to be an array.
- BSONArrayBuilder b;
- b.appendTimestamp(_combined);
- b.append(_epoch);
- return b.arr();
- }
+ b.appendTimestamp(prefix, _combined);
+ b.append(prefix + "Epoch", _epoch);
+ return b.obj();
+ }
- bool parseBSON(const BSONObj& source, std::string* errMsg) {
- // ChunkVersion wants to be an array.
- BSONArray arrSource = static_cast<BSONArray>(source);
+ void addToBSON(BSONObjBuilder& b, const std::string& prefix = "") const {
+ b.appendElements(toBSONWithPrefix(prefix));
+ }
- bool canParse;
- ChunkVersion version = fromBSON(arrSource, &canParse);
- if (!canParse) {
- *errMsg = "Could not parse version structure";
- return false;
- }
+ void addEpochToBSON(BSONObjBuilder& b, const std::string& prefix = "") const {
+ b.append(prefix + "Epoch", _epoch);
+ }
- _minor = version._minor;
- _major = version._major;
- _epoch = version._epoch;
- return true;
- }
+ BSONObj toBSON() const {
+ // ChunkVersion wants to be an array.
+ BSONArrayBuilder b;
+ b.appendTimestamp(_combined);
+ b.append(_epoch);
+ return b.arr();
+ }
- void clear() {
- _minor = 0;
- _major = 0;
- _epoch = OID();
- }
+ bool parseBSON(const BSONObj& source, std::string* errMsg) {
+ // ChunkVersion wants to be an array.
+ BSONArray arrSource = static_cast<BSONArray>(source);
- void cloneTo(ChunkVersion* other) const {
- other->clear();
- other->_minor = _minor;
- other->_major = _major;
- other->_epoch = _epoch;
+ bool canParse;
+ ChunkVersion version = fromBSON(arrSource, &canParse);
+ if (!canParse) {
+ *errMsg = "Could not parse version structure";
+ return false;
}
- };
+ _minor = version._minor;
+ _major = version._major;
+ _epoch = version._epoch;
+ return true;
+ }
- inline std::ostream& operator<<( std::ostream &s , const ChunkVersion& v) {
- s << v.toString();
- return s;
+ void clear() {
+ _minor = 0;
+ _major = 0;
+ _epoch = OID();
}
-} // namespace mongo
+ void cloneTo(ChunkVersion* other) const {
+ other->clear();
+ other->_minor = _minor;
+ other->_major = _major;
+ other->_epoch = _epoch;
+ }
+};
+
+inline std::ostream& operator<<(std::ostream& s, const ChunkVersion& v) {
+ s << v.toString();
+ return s;
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/chunk_version_test.cpp b/src/mongo/s/chunk_version_test.cpp
index 07f2b54f883..f660d3f104d 100644
--- a/src/mongo/s/chunk_version_test.cpp
+++ b/src/mongo/s/chunk_version_test.cpp
@@ -35,94 +35,89 @@
namespace mongo {
namespace {
- /**
- * Tests parsing of BSON for versions. In version 2.2, this parsing is meant to be very
- * flexible so different formats can be tried and enforced later.
- *
- * Formats are:
- *
- * A) { vFieldName : <TSTYPE>, [ vFieldNameEpoch : <OID> ], ... }
- * B) { fieldName : [ <TSTYPE>, <OID> ], ... }
- *
- * vFieldName is a specifyable name - usually "version" (default) or "lastmod". <TSTYPE> is a
- * type convertible to Timestamp, ideally Timestamp but also numeric.
- * <OID> is a value of type OID.
- *
- */
- TEST(Compatibility, LegacyFormatA) {
- BSONObjBuilder versionObjB;
- versionObjB.appendTimestamp( "testVersion",
- ChunkVersion( 1, 1, OID() ).toLong() );
- versionObjB.append( "testVersionEpoch", OID::gen() );
- BSONObj versionObj = versionObjB.obj();
-
- ChunkVersion parsed =
- ChunkVersion::fromBSON( versionObj[ "testVersion" ] );
-
- ASSERT( ChunkVersion::canParseBSON( versionObj[ "testVersion" ] ) );
- ASSERT( parsed.majorVersion() == 1 );
- ASSERT( parsed.minorVersion() == 1 );
- ASSERT( ! parsed.epoch().isSet() );
-
- parsed = ChunkVersion::fromBSON( versionObj, "testVersion" );
-
- ASSERT( ChunkVersion::canParseBSON( versionObj, "testVersion" ) );
- ASSERT( parsed.majorVersion() == 1 );
- ASSERT( parsed.minorVersion() == 1 );
- ASSERT( parsed.epoch().isSet() );
- }
-
- TEST(Compatibility, SubArrayFormatB) {
- BSONObjBuilder tsObjB;
- tsObjB.appendTimestamp( "ts", ChunkVersion( 1, 1, OID() ).toLong() );
- BSONObj tsObj = tsObjB.obj();
-
- BSONObjBuilder versionObjB;
- BSONArrayBuilder subArrB( versionObjB.subarrayStart( "testVersion" ) );
- // Append this weird way so we're sure we get a timestamp type
- subArrB.append( tsObj.firstElement() );
- subArrB.append( OID::gen() );
- subArrB.done();
- BSONObj versionObj = versionObjB.obj();
-
- ChunkVersion parsed =
- ChunkVersion::fromBSON( versionObj[ "testVersion" ] );
-
- ASSERT( ChunkVersion::canParseBSON( versionObj[ "testVersion" ] ) );
- ASSERT( ChunkVersion::canParseBSON( BSONArray( versionObj[ "testVersion" ].Obj() ) ) );
- ASSERT( parsed.majorVersion() == 1 );
- ASSERT( parsed.minorVersion() == 1 );
- ASSERT( parsed.epoch().isSet() );
- }
-
- TEST(Comparison, StrictEqual) {
-
- OID epoch = OID::gen();
-
- ASSERT( ChunkVersion( 3, 1, epoch ).isStrictlyEqualTo( ChunkVersion( 3, 1, epoch ) ) );
- ASSERT( !ChunkVersion( 3, 1, epoch ).isStrictlyEqualTo( ChunkVersion( 3, 1, OID() ) ) );
- ASSERT( !ChunkVersion( 3, 1, OID() ).isStrictlyEqualTo( ChunkVersion( 3, 1, epoch ) ) );
- ASSERT( ChunkVersion( 3, 1, OID() ).isStrictlyEqualTo( ChunkVersion( 3, 1, OID() ) ) );
- ASSERT( !ChunkVersion( 4, 2, epoch ).isStrictlyEqualTo( ChunkVersion( 4, 1, epoch ) ) );
- }
-
- TEST(Comparison, OlderThan) {
-
- OID epoch = OID::gen();
-
- ASSERT( ChunkVersion( 3, 1, epoch ).isOlderThan( ChunkVersion( 4, 1, epoch ) ) );
- ASSERT( !ChunkVersion( 4, 1, epoch ).isOlderThan( ChunkVersion( 3, 1, epoch ) ) );
-
- ASSERT( ChunkVersion( 3, 1, epoch ).isOlderThan( ChunkVersion( 3, 2, epoch ) ) );
- ASSERT( !ChunkVersion( 3, 2, epoch ).isOlderThan( ChunkVersion( 3, 1, epoch ) ) );
-
- ASSERT( !ChunkVersion( 3, 1, epoch ).isOlderThan( ChunkVersion( 4, 1, OID() ) ) );
- ASSERT( !ChunkVersion( 4, 1, OID() ).isOlderThan( ChunkVersion( 3, 1, epoch ) ) );
-
- ASSERT( ChunkVersion( 3, 2, epoch ).isOlderThan( ChunkVersion( 4, 1, epoch ) ) );
-
- ASSERT( !ChunkVersion( 3, 1, epoch ).isOlderThan( ChunkVersion( 3, 1, epoch ) ) );
- }
-
-} // unnamed namespace
-} // namespace mongo
+/**
+ * Tests parsing of BSON for versions. In version 2.2, this parsing is meant to be very
+ * flexible so different formats can be tried and enforced later.
+ *
+ * Formats are:
+ *
+ * A) { vFieldName : <TSTYPE>, [ vFieldNameEpoch : <OID> ], ... }
+ * B) { fieldName : [ <TSTYPE>, <OID> ], ... }
+ *
+ * vFieldName is a specifyable name - usually "version" (default) or "lastmod". <TSTYPE> is a
+ * type convertible to Timestamp, ideally Timestamp but also numeric.
+ * <OID> is a value of type OID.
+ *
+ */
+TEST(Compatibility, LegacyFormatA) {
+ BSONObjBuilder versionObjB;
+ versionObjB.appendTimestamp("testVersion", ChunkVersion(1, 1, OID()).toLong());
+ versionObjB.append("testVersionEpoch", OID::gen());
+ BSONObj versionObj = versionObjB.obj();
+
+ ChunkVersion parsed = ChunkVersion::fromBSON(versionObj["testVersion"]);
+
+ ASSERT(ChunkVersion::canParseBSON(versionObj["testVersion"]));
+ ASSERT(parsed.majorVersion() == 1);
+ ASSERT(parsed.minorVersion() == 1);
+ ASSERT(!parsed.epoch().isSet());
+
+ parsed = ChunkVersion::fromBSON(versionObj, "testVersion");
+
+ ASSERT(ChunkVersion::canParseBSON(versionObj, "testVersion"));
+ ASSERT(parsed.majorVersion() == 1);
+ ASSERT(parsed.minorVersion() == 1);
+ ASSERT(parsed.epoch().isSet());
+}
+
+TEST(Compatibility, SubArrayFormatB) {
+ BSONObjBuilder tsObjB;
+ tsObjB.appendTimestamp("ts", ChunkVersion(1, 1, OID()).toLong());
+ BSONObj tsObj = tsObjB.obj();
+
+ BSONObjBuilder versionObjB;
+ BSONArrayBuilder subArrB(versionObjB.subarrayStart("testVersion"));
+ // Append this weird way so we're sure we get a timestamp type
+ subArrB.append(tsObj.firstElement());
+ subArrB.append(OID::gen());
+ subArrB.done();
+ BSONObj versionObj = versionObjB.obj();
+
+ ChunkVersion parsed = ChunkVersion::fromBSON(versionObj["testVersion"]);
+
+ ASSERT(ChunkVersion::canParseBSON(versionObj["testVersion"]));
+ ASSERT(ChunkVersion::canParseBSON(BSONArray(versionObj["testVersion"].Obj())));
+ ASSERT(parsed.majorVersion() == 1);
+ ASSERT(parsed.minorVersion() == 1);
+ ASSERT(parsed.epoch().isSet());
+}
+
+TEST(Comparison, StrictEqual) {
+ OID epoch = OID::gen();
+
+ ASSERT(ChunkVersion(3, 1, epoch).isStrictlyEqualTo(ChunkVersion(3, 1, epoch)));
+ ASSERT(!ChunkVersion(3, 1, epoch).isStrictlyEqualTo(ChunkVersion(3, 1, OID())));
+ ASSERT(!ChunkVersion(3, 1, OID()).isStrictlyEqualTo(ChunkVersion(3, 1, epoch)));
+ ASSERT(ChunkVersion(3, 1, OID()).isStrictlyEqualTo(ChunkVersion(3, 1, OID())));
+ ASSERT(!ChunkVersion(4, 2, epoch).isStrictlyEqualTo(ChunkVersion(4, 1, epoch)));
+}
+
+TEST(Comparison, OlderThan) {
+ OID epoch = OID::gen();
+
+ ASSERT(ChunkVersion(3, 1, epoch).isOlderThan(ChunkVersion(4, 1, epoch)));
+ ASSERT(!ChunkVersion(4, 1, epoch).isOlderThan(ChunkVersion(3, 1, epoch)));
+
+ ASSERT(ChunkVersion(3, 1, epoch).isOlderThan(ChunkVersion(3, 2, epoch)));
+ ASSERT(!ChunkVersion(3, 2, epoch).isOlderThan(ChunkVersion(3, 1, epoch)));
+
+ ASSERT(!ChunkVersion(3, 1, epoch).isOlderThan(ChunkVersion(4, 1, OID())));
+ ASSERT(!ChunkVersion(4, 1, OID()).isOlderThan(ChunkVersion(3, 1, epoch)));
+
+ ASSERT(ChunkVersion(3, 2, epoch).isOlderThan(ChunkVersion(4, 1, epoch)));
+
+ ASSERT(!ChunkVersion(3, 1, epoch).isOlderThan(ChunkVersion(3, 1, epoch)));
+}
+
+} // unnamed namespace
+} // namespace mongo
diff --git a/src/mongo/s/client/dbclient_multi_command.cpp b/src/mongo/s/client/dbclient_multi_command.cpp
index 3d7202f72ac..6fc6048d663 100644
--- a/src/mongo/s/client/dbclient_multi_command.cpp
+++ b/src/mongo/s/client/dbclient_multi_command.cpp
@@ -42,208 +42,201 @@
namespace mongo {
- using std::unique_ptr;
- using std::deque;
- using std::string;
-
- DBClientMultiCommand::PendingCommand::PendingCommand( const ConnectionString& endpoint,
- StringData dbName,
- const BSONObj& cmdObj ) :
- endpoint( endpoint ),
- dbName( dbName.toString() ),
- cmdObj( cmdObj ),
- conn( NULL ),
- status( Status::OK() ) {
- }
-
- void DBClientMultiCommand::addCommand( const ConnectionString& endpoint,
- StringData dbName,
- const BSONSerializable& request ) {
- PendingCommand* command = new PendingCommand( endpoint, dbName, request.toBSON() );
- _pendingCommands.push_back( command );
- }
-
- namespace {
+using std::unique_ptr;
+using std::deque;
+using std::string;
+
+DBClientMultiCommand::PendingCommand::PendingCommand(const ConnectionString& endpoint,
+ StringData dbName,
+ const BSONObj& cmdObj)
+ : endpoint(endpoint),
+ dbName(dbName.toString()),
+ cmdObj(cmdObj),
+ conn(NULL),
+ status(Status::OK()) {}
+
+void DBClientMultiCommand::addCommand(const ConnectionString& endpoint,
+ StringData dbName,
+ const BSONSerializable& request) {
+ PendingCommand* command = new PendingCommand(endpoint, dbName, request.toBSON());
+ _pendingCommands.push_back(command);
+}
- //
- // For sanity checks of batch write operations
- //
+namespace {
+
+//
+// For sanity checks of batch write operations
+//
+
+BatchedCommandRequest::BatchType getBatchWriteType(const BSONObj& cmdObj) {
+ string cmdName = cmdObj.firstElement().fieldName();
+ if (cmdName == "insert")
+ return BatchedCommandRequest::BatchType_Insert;
+ if (cmdName == "update")
+ return BatchedCommandRequest::BatchType_Update;
+ if (cmdName == "delete")
+ return BatchedCommandRequest::BatchType_Delete;
+ return BatchedCommandRequest::BatchType_Unknown;
+}
- BatchedCommandRequest::BatchType getBatchWriteType( const BSONObj& cmdObj ) {
- string cmdName = cmdObj.firstElement().fieldName();
- if ( cmdName == "insert" ) return BatchedCommandRequest::BatchType_Insert;
- if ( cmdName == "update" ) return BatchedCommandRequest::BatchType_Update;
- if ( cmdName == "delete" ) return BatchedCommandRequest::BatchType_Delete;
- return BatchedCommandRequest::BatchType_Unknown;
- }
+bool isBatchWriteCommand(const BSONObj& cmdObj) {
+ return getBatchWriteType(cmdObj) != BatchedCommandRequest::BatchType_Unknown;
+}
- bool isBatchWriteCommand( const BSONObj& cmdObj ) {
- return getBatchWriteType( cmdObj ) != BatchedCommandRequest::BatchType_Unknown;
- }
+bool hasBatchWriteFeature(DBClientBase* conn) {
+ return conn->getMinWireVersion() <= BATCH_COMMANDS &&
+ conn->getMaxWireVersion() >= BATCH_COMMANDS;
+}
+}
- bool hasBatchWriteFeature( DBClientBase* conn ) {
- return conn->getMinWireVersion() <= BATCH_COMMANDS
- && conn->getMaxWireVersion() >= BATCH_COMMANDS;
- }
+// THROWS
+static void sayAsCmd(DBClientBase* conn, StringData dbName, const BSONObj& cmdObj) {
+ auto requestBuilder =
+ rpc::makeRequestBuilder(conn->getClientRPCProtocols(), conn->getServerRPCProtocols());
+ BSONObj upconvertedCmd;
+ BSONObj upconvertedMetadata;
+
+ // Previous implementation had hardcoded flags of 0 - more specifically, writes
+ // are never secondaryOk.
+ std::tie(upconvertedCmd, upconvertedMetadata) =
+ uassertStatusOK(rpc::upconvertRequestMetadata(cmdObj, 0));
+
+ BSONObjBuilder metadataBob;
+ metadataBob.appendElements(upconvertedMetadata);
+ if (conn->getRequestMetadataWriter()) {
+ conn->getRequestMetadataWriter()(&metadataBob);
}
- // THROWS
- static void sayAsCmd( DBClientBase* conn, StringData dbName, const BSONObj& cmdObj ) {
- auto requestBuilder = rpc::makeRequestBuilder(conn->getClientRPCProtocols(),
- conn->getServerRPCProtocols());
- BSONObj upconvertedCmd;
- BSONObj upconvertedMetadata;
-
- // Previous implementation had hardcoded flags of 0 - more specifically, writes
- // are never secondaryOk.
- std::tie(upconvertedCmd, upconvertedMetadata) = uassertStatusOK(
- rpc::upconvertRequestMetadata(cmdObj, 0)
- );
-
- BSONObjBuilder metadataBob;
- metadataBob.appendElements(upconvertedMetadata);
- if (conn->getRequestMetadataWriter()) {
- conn->getRequestMetadataWriter()(&metadataBob);
- }
+ requestBuilder->setDatabase(dbName);
+ requestBuilder->setCommandName(upconvertedCmd.firstElementFieldName());
+ requestBuilder->setMetadata(metadataBob.done());
+ requestBuilder->setCommandArgs(upconvertedCmd);
+ // Send our command
+ conn->say(*requestBuilder->done());
+}
- requestBuilder->setDatabase(dbName);
- requestBuilder->setCommandName(upconvertedCmd.firstElementFieldName());
- requestBuilder->setMetadata(metadataBob.done());
- requestBuilder->setCommandArgs(upconvertedCmd);
- // Send our command
- conn->say(*requestBuilder->done());
+// THROWS
+static void recvAsCmd(DBClientBase* conn, Message* toRecv, BSONObj* result) {
+ if (!conn->recv(*toRecv)) {
+ // Confusingly, socket exceptions here are written to the log, not thrown.
+ uasserted(17255,
+ "error receiving write command response, "
+ "possible socket exception - see logs");
}
- // THROWS
- static void recvAsCmd( DBClientBase* conn, Message* toRecv, BSONObj* result ) {
-
- if ( !conn->recv( *toRecv ) ) {
- // Confusingly, socket exceptions here are written to the log, not thrown.
- uasserted( 17255, "error receiving write command response, "
- "possible socket exception - see logs" );
- }
-
- auto reply = rpc::makeReply(toRecv);
-
- if (conn->getReplyMetadataReader()) {
- conn->getReplyMetadataReader()(reply->getMetadata(), conn->getServerAddress());
- }
+ auto reply = rpc::makeReply(toRecv);
- *result = reply->getCommandReply();
+ if (conn->getReplyMetadataReader()) {
+ conn->getReplyMetadataReader()(reply->getMetadata(), conn->getServerAddress());
}
- void DBClientMultiCommand::sendAll() {
-
- for ( deque<PendingCommand*>::iterator it = _pendingCommands.begin();
- it != _pendingCommands.end(); ++it ) {
-
- PendingCommand* command = *it;
- dassert( NULL == command->conn );
-
- try {
- dassert( command->endpoint.type() == ConnectionString::MASTER ||
- command->endpoint.type() == ConnectionString::CUSTOM );
-
- // TODO: Fix the pool up to take millis directly
- int timeoutSecs = _timeoutMillis / 1000;
- command->conn = shardConnectionPool.get( command->endpoint, timeoutSecs );
+ *result = reply->getCommandReply();
+}
- // Sanity check if we're sending a batch write that we're talking to a new-enough
- // server.
- massert(28563, str::stream() << "cannot send batch write operation to server "
- << command->conn->toString(),
- !isBatchWriteCommand(command->cmdObj) ||
- hasBatchWriteFeature(command->conn));
+void DBClientMultiCommand::sendAll() {
+ for (deque<PendingCommand*>::iterator it = _pendingCommands.begin();
+ it != _pendingCommands.end();
+ ++it) {
+ PendingCommand* command = *it;
+ dassert(NULL == command->conn);
- sayAsCmd( command->conn, command->dbName, command->cmdObj );
- }
- catch ( const DBException& ex ) {
- command->status = ex.toStatus();
-
- if ( NULL != command->conn ) {
-
- // Confusingly, the pool needs to know about failed connections so that it can
- // invalidate other connections which might be bad. But if the connection
- // doesn't seem bad, don't send it back, because we don't want to reuse it.
- if ( !command->conn->isFailed() ) {
- delete command->conn;
- }
- else {
- shardConnectionPool.release( command->endpoint.toString(), command->conn );
- }
-
- command->conn = NULL;
+ try {
+ dassert(command->endpoint.type() == ConnectionString::MASTER ||
+ command->endpoint.type() == ConnectionString::CUSTOM);
+
+ // TODO: Fix the pool up to take millis directly
+ int timeoutSecs = _timeoutMillis / 1000;
+ command->conn = shardConnectionPool.get(command->endpoint, timeoutSecs);
+
+ // Sanity check if we're sending a batch write that we're talking to a new-enough
+ // server.
+ massert(28563,
+ str::stream() << "cannot send batch write operation to server "
+ << command->conn->toString(),
+ !isBatchWriteCommand(command->cmdObj) || hasBatchWriteFeature(command->conn));
+
+ sayAsCmd(command->conn, command->dbName, command->cmdObj);
+ } catch (const DBException& ex) {
+ command->status = ex.toStatus();
+
+ if (NULL != command->conn) {
+ // Confusingly, the pool needs to know about failed connections so that it can
+ // invalidate other connections which might be bad. But if the connection
+ // doesn't seem bad, don't send it back, because we don't want to reuse it.
+ if (!command->conn->isFailed()) {
+ delete command->conn;
+ } else {
+ shardConnectionPool.release(command->endpoint.toString(), command->conn);
}
+
+ command->conn = NULL;
}
}
}
+}
- int DBClientMultiCommand::numPending() const {
- return static_cast<int>( _pendingCommands.size() );
- }
-
- Status DBClientMultiCommand::recvAny( ConnectionString* endpoint, BSONSerializable* response ) {
-
- unique_ptr<PendingCommand> command( _pendingCommands.front() );
- _pendingCommands.pop_front();
+int DBClientMultiCommand::numPending() const {
+ return static_cast<int>(_pendingCommands.size());
+}
- *endpoint = command->endpoint;
- if ( !command->status.isOK() ) return command->status;
+Status DBClientMultiCommand::recvAny(ConnectionString* endpoint, BSONSerializable* response) {
+ unique_ptr<PendingCommand> command(_pendingCommands.front());
+ _pendingCommands.pop_front();
- dassert( NULL != command->conn );
+ *endpoint = command->endpoint;
+ if (!command->status.isOK())
+ return command->status;
- try {
+ dassert(NULL != command->conn);
- // Holds the data and BSONObj for the command result
- Message toRecv;
- BSONObj result;
+ try {
+ // Holds the data and BSONObj for the command result
+ Message toRecv;
+ BSONObj result;
- recvAsCmd( command->conn, &toRecv, &result );
+ recvAsCmd(command->conn, &toRecv, &result);
- shardConnectionPool.release( command->endpoint.toString(), command->conn );
- command->conn = NULL;
+ shardConnectionPool.release(command->endpoint.toString(), command->conn);
+ command->conn = NULL;
- string errMsg;
- if ( !response->parseBSON( result, &errMsg ) || !response->isValid( &errMsg ) ) {
- return Status( ErrorCodes::FailedToParse, errMsg );
- }
+ string errMsg;
+ if (!response->parseBSON(result, &errMsg) || !response->isValid(&errMsg)) {
+ return Status(ErrorCodes::FailedToParse, errMsg);
}
- catch ( const DBException& ex ) {
-
- // Confusingly, the pool needs to know about failed connections so that it can
- // invalidate other connections which might be bad. But if the connection doesn't seem
- // bad, don't send it back, because we don't want to reuse it.
- if ( !command->conn->isFailed() ) {
- delete command->conn;
- }
- else {
- shardConnectionPool.release( command->endpoint.toString(), command->conn );
- }
- command->conn = NULL;
-
- return ex.toStatus();
+ } catch (const DBException& ex) {
+ // Confusingly, the pool needs to know about failed connections so that it can
+ // invalidate other connections which might be bad. But if the connection doesn't seem
+ // bad, don't send it back, because we don't want to reuse it.
+ if (!command->conn->isFailed()) {
+ delete command->conn;
+ } else {
+ shardConnectionPool.release(command->endpoint.toString(), command->conn);
}
+ command->conn = NULL;
- return Status::OK();
+ return ex.toStatus();
}
- DBClientMultiCommand::~DBClientMultiCommand() {
-
- // Cleanup anything outstanding, do *not* return stuff to the pool, that might error
- for ( deque<PendingCommand*>::iterator it = _pendingCommands.begin();
- it != _pendingCommands.end(); ++it ) {
-
- PendingCommand* command = *it;
-
- if ( NULL != command->conn ) delete command->conn;
- delete command;
- command = NULL;
- }
+ return Status::OK();
+}
- _pendingCommands.clear();
+DBClientMultiCommand::~DBClientMultiCommand() {
+ // Cleanup anything outstanding, do *not* return stuff to the pool, that might error
+ for (deque<PendingCommand*>::iterator it = _pendingCommands.begin();
+ it != _pendingCommands.end();
+ ++it) {
+ PendingCommand* command = *it;
+
+ if (NULL != command->conn)
+ delete command->conn;
+ delete command;
+ command = NULL;
}
- void DBClientMultiCommand::setTimeoutMillis( int milliSecs ) {
- _timeoutMillis = milliSecs;
- }
+ _pendingCommands.clear();
+}
+
+void DBClientMultiCommand::setTimeoutMillis(int milliSecs) {
+ _timeoutMillis = milliSecs;
+}
}
diff --git a/src/mongo/s/client/dbclient_multi_command.h b/src/mongo/s/client/dbclient_multi_command.h
index 6cffa079fe7..0615fbfce66 100644
--- a/src/mongo/s/client/dbclient_multi_command.h
+++ b/src/mongo/s/client/dbclient_multi_command.h
@@ -35,55 +35,49 @@
namespace mongo {
- /**
- * A DBClientMultiCommand uses the client driver (DBClientConnections) to send and recv
- * commands to different hosts in parallel.
- *
- * See MultiCommandDispatch for more details.
- */
- class DBClientMultiCommand : public MultiCommandDispatch {
- public:
-
- DBClientMultiCommand() : _timeoutMillis( 0 ) {}
-
- ~DBClientMultiCommand();
-
- void addCommand( const ConnectionString& endpoint,
- StringData dbName,
- const BSONSerializable& request );
-
- void sendAll();
+/**
+ * A DBClientMultiCommand uses the client driver (DBClientConnections) to send and recv
+ * commands to different hosts in parallel.
+ *
+ * See MultiCommandDispatch for more details.
+ */
+class DBClientMultiCommand : public MultiCommandDispatch {
+public:
+ DBClientMultiCommand() : _timeoutMillis(0) {}
- int numPending() const;
+ ~DBClientMultiCommand();
- Status recvAny( ConnectionString* endpoint, BSONSerializable* response );
+ void addCommand(const ConnectionString& endpoint,
+ StringData dbName,
+ const BSONSerializable& request);
- void setTimeoutMillis( int milliSecs );
+ void sendAll();
- private:
+ int numPending() const;
- // All info associated with an pre- or in-flight command
- struct PendingCommand {
+ Status recvAny(ConnectionString* endpoint, BSONSerializable* response);
- PendingCommand( const ConnectionString& endpoint,
- StringData dbName,
- const BSONObj& cmdObj );
+ void setTimeoutMillis(int milliSecs);
- // What to send
- const ConnectionString endpoint;
- const std::string dbName;
- const BSONObj cmdObj;
+private:
+ // All info associated with an pre- or in-flight command
+ struct PendingCommand {
+ PendingCommand(const ConnectionString& endpoint, StringData dbName, const BSONObj& cmdObj);
- // Where to send it
- DBClientBase* conn;
+ // What to send
+ const ConnectionString endpoint;
+ const std::string dbName;
+ const BSONObj cmdObj;
- // If anything goes wrong
- Status status;
- };
+ // Where to send it
+ DBClientBase* conn;
- typedef std::deque<PendingCommand*> PendingQueue;
- PendingQueue _pendingCommands;
- int _timeoutMillis;
+ // If anything goes wrong
+ Status status;
};
+ typedef std::deque<PendingCommand*> PendingQueue;
+ PendingQueue _pendingCommands;
+ int _timeoutMillis;
+};
}
diff --git a/src/mongo/s/client/mock_multi_write_command.h b/src/mongo/s/client/mock_multi_write_command.h
index cefdd00752c..5491c0b44f1 100644
--- a/src/mongo/s/client/mock_multi_write_command.h
+++ b/src/mongo/s/client/mock_multi_write_command.h
@@ -37,136 +37,127 @@
namespace mongo {
- /**
- * A ConnectionString endpoint registered with some kind of error, to simulate returning when
- * the endpoint is used.
- */
- struct MockWriteResult {
-
- MockWriteResult( const ConnectionString& endpoint, const WriteErrorDetail& error ) :
- endpoint( endpoint ) {
+/**
+ * A ConnectionString endpoint registered with some kind of error, to simulate returning when
+ * the endpoint is used.
+ */
+struct MockWriteResult {
+ MockWriteResult(const ConnectionString& endpoint, const WriteErrorDetail& error)
+ : endpoint(endpoint) {
+ WriteErrorDetail* errorCopy = new WriteErrorDetail;
+ error.cloneTo(errorCopy);
+ errorCopy->setIndex(0);
+ response.setOk(true);
+ response.setN(0);
+ response.addToErrDetails(errorCopy);
+ }
+
+ MockWriteResult(const ConnectionString& endpoint, const WriteErrorDetail& error, int copies)
+ : endpoint(endpoint) {
+ response.setOk(true);
+ response.setN(0);
+
+ for (int i = 0; i < copies; ++i) {
WriteErrorDetail* errorCopy = new WriteErrorDetail;
- error.cloneTo( errorCopy );
- errorCopy->setIndex( 0 );
- response.setOk(true);
- response.setN(0);
- response.addToErrDetails( errorCopy );
+ error.cloneTo(errorCopy);
+ errorCopy->setIndex(i);
+ response.addToErrDetails(errorCopy);
}
+ }
- MockWriteResult( const ConnectionString& endpoint,
- const WriteErrorDetail& error,
- int copies ) :
- endpoint( endpoint ) {
-
- response.setOk( true );
- response.setN( 0 );
-
- for ( int i = 0; i < copies; ++i ) {
- WriteErrorDetail* errorCopy = new WriteErrorDetail;
- error.cloneTo( errorCopy );
- errorCopy->setIndex( i );
- response.addToErrDetails( errorCopy );
- }
- }
+ MockWriteResult(const ConnectionString& endpoint, const BatchedCommandResponse& response)
+ : endpoint(endpoint) {
+ response.cloneTo(&this->response);
+ }
- MockWriteResult( const ConnectionString& endpoint, const BatchedCommandResponse& response ) :
- endpoint( endpoint ) {
- response.cloneTo( &this->response );
- }
+ const ConnectionString endpoint;
+ BatchedCommandResponse response;
+};
- const ConnectionString endpoint;
- BatchedCommandResponse response;
- };
+/**
+ * Implementation of the MultiCommandDispatch interface which allows registering a number of
+ * endpoints on which errors are returned. Note that *only* BatchedCommandResponses are
+ * supported here.
+ *
+ * The first matching MockEndpoint for a request in the MockEndpoint* vector is used for one
+ * request, then removed. This allows simulating retryable errors where a second request
+ * succeeds or has a different error reported.
+ *
+ * If an endpoint isn't registered with a MockEndpoint, just returns BatchedCommandResponses
+ * with ok : true.
+ */
+class MockMultiWriteCommand : public MultiCommandDispatch {
+public:
+ void init(const std::vector<MockWriteResult*> mockEndpoints) {
+ ASSERT(!mockEndpoints.empty());
+ _mockEndpoints.mutableVector().insert(
+ _mockEndpoints.mutableVector().end(), mockEndpoints.begin(), mockEndpoints.end());
+ }
+
+ void addCommand(const ConnectionString& endpoint,
+ StringData dbName,
+ const BSONSerializable& request) {
+ _pending.push_back(endpoint);
+ }
+
+ void sendAll() {
+ // No-op
+ }
+
+ int numPending() const {
+ return static_cast<int>(_pending.size());
+ }
/**
- * Implementation of the MultiCommandDispatch interface which allows registering a number of
- * endpoints on which errors are returned. Note that *only* BatchedCommandResponses are
- * supported here.
- *
- * The first matching MockEndpoint for a request in the MockEndpoint* vector is used for one
- * request, then removed. This allows simulating retryable errors where a second request
- * succeeds or has a different error reported.
- *
- * If an endpoint isn't registered with a MockEndpoint, just returns BatchedCommandResponses
- * with ok : true.
+ * Returns an error response if the next pending endpoint returned has a corresponding
+ * MockEndpoint.
*/
- class MockMultiWriteCommand : public MultiCommandDispatch {
- public:
-
- void init( const std::vector<MockWriteResult*> mockEndpoints ) {
- ASSERT( !mockEndpoints.empty() );
- _mockEndpoints.mutableVector().insert( _mockEndpoints.mutableVector().end(),
- mockEndpoints.begin(),
- mockEndpoints.end() );
- }
-
- void addCommand( const ConnectionString& endpoint,
- StringData dbName,
- const BSONSerializable& request ) {
- _pending.push_back( endpoint );
+ Status recvAny(ConnectionString* endpoint, BSONSerializable* response) {
+ BatchedCommandResponse* batchResponse = //
+ static_cast<BatchedCommandResponse*>(response);
+
+ *endpoint = _pending.front();
+ MockWriteResult* mockResponse = releaseByHost(_pending.front());
+ _pending.pop_front();
+
+ if (NULL == mockResponse) {
+ batchResponse->setOk(true);
+ batchResponse->setN(0); // TODO: Make this accurate
+ } else {
+ mockResponse->response.cloneTo(batchResponse);
+ delete mockResponse;
}
- void sendAll() {
- // No-op
- }
-
- int numPending() const {
- return static_cast<int>( _pending.size() );
- }
-
- /**
- * Returns an error response if the next pending endpoint returned has a corresponding
- * MockEndpoint.
- */
- Status recvAny( ConnectionString* endpoint, BSONSerializable* response ) {
-
- BatchedCommandResponse* batchResponse = //
- static_cast<BatchedCommandResponse*>( response );
-
- *endpoint = _pending.front();
- MockWriteResult* mockResponse = releaseByHost( _pending.front() );
- _pending.pop_front();
-
- if ( NULL == mockResponse ) {
- batchResponse->setOk( true );
- batchResponse->setN( 0 ); // TODO: Make this accurate
- }
- else {
- mockResponse->response.cloneTo( batchResponse );
- delete mockResponse;
+ ASSERT(batchResponse->isValid(NULL));
+ return Status::OK();
+ }
+
+ const std::vector<MockWriteResult*>& getEndpoints() const {
+ return _mockEndpoints.vector();
+ }
+
+private:
+ // Find a MockEndpoint* by host, and release it so we don't see it again
+ MockWriteResult* releaseByHost(const ConnectionString& endpoint) {
+ std::vector<MockWriteResult*>& endpoints = _mockEndpoints.mutableVector();
+
+ for (std::vector<MockWriteResult*>::iterator it = endpoints.begin(); it != endpoints.end();
+ ++it) {
+ MockWriteResult* storedEndpoint = *it;
+ if (storedEndpoint->endpoint.toString().compare(endpoint.toString()) == 0) {
+ endpoints.erase(it);
+ return storedEndpoint;
}
-
- ASSERT( batchResponse->isValid( NULL ) );
- return Status::OK();
- }
-
- const std::vector<MockWriteResult*>& getEndpoints() const {
- return _mockEndpoints.vector();
}
- private:
-
- // Find a MockEndpoint* by host, and release it so we don't see it again
- MockWriteResult* releaseByHost( const ConnectionString& endpoint ) {
- std::vector<MockWriteResult*>& endpoints = _mockEndpoints.mutableVector();
-
- for ( std::vector<MockWriteResult*>::iterator it = endpoints.begin();
- it != endpoints.end(); ++it ) {
- MockWriteResult* storedEndpoint = *it;
- if ( storedEndpoint->endpoint.toString().compare( endpoint.toString() ) == 0 ) {
- endpoints.erase( it );
- return storedEndpoint;
- }
- }
-
- return NULL;
- }
+ return NULL;
+ }
- // Manually-stored ranges
- OwnedPointerVector<MockWriteResult> _mockEndpoints;
+ // Manually-stored ranges
+ OwnedPointerVector<MockWriteResult> _mockEndpoints;
- std::deque<ConnectionString> _pending;
- };
+ std::deque<ConnectionString> _pending;
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/client/multi_command_dispatch.h b/src/mongo/s/client/multi_command_dispatch.h
index f9470091637..c16baec3f55 100644
--- a/src/mongo/s/client/multi_command_dispatch.h
+++ b/src/mongo/s/client/multi_command_dispatch.h
@@ -35,53 +35,50 @@
namespace mongo {
+/**
+ * A MultiCommandDispatch is a send/recv operation for multiple commands at once.
+ *
+ * The commands are first registered with an endpoint and serializable request. Commands are
+ * sent out without waiting for responses, and then responses are read later one-at-a-time.
+ *
+ * If context must be tracked alongside these requests, it can be associated with the endpoint
+ * object.
+ */
+class MultiCommandDispatch {
+public:
+ virtual ~MultiCommandDispatch() {}
+
/**
- * A MultiCommandDispatch is a send/recv operation for multiple commands at once.
+ * Adds a command to this multi-command dispatch. Commands are registered with a
+ * ConnectionString endpoint and a serializable request.
*
- * The commands are first registered with an endpoint and serializable request. Commands are
- * sent out without waiting for responses, and then responses are read later one-at-a-time.
- *
- * If context must be tracked alongside these requests, it can be associated with the endpoint
- * object.
+ * Commands are not sent immediately, they are sent on sendAll.
*/
- class MultiCommandDispatch {
- public:
-
- virtual ~MultiCommandDispatch() {
- }
-
- /**
- * Adds a command to this multi-command dispatch. Commands are registered with a
- * ConnectionString endpoint and a serializable request.
- *
- * Commands are not sent immediately, they are sent on sendAll.
- */
- virtual void addCommand( const ConnectionString& endpoint,
- StringData dbName,
- const BSONSerializable& request ) = 0;
-
- /**
- * Sends all the commands in this dispatch to their endpoints, in undefined order and
- * without waiting for responses. May block on full send queue (though this should be
- * rare).
- *
- * Any error which occurs during sendAll will be reported on recvAny, *does not throw.*
- */
- virtual void sendAll() = 0;
+ virtual void addCommand(const ConnectionString& endpoint,
+ StringData dbName,
+ const BSONSerializable& request) = 0;
- /**
- * Returns the number of sent requests that are still waiting to be recv'd.
- */
- virtual int numPending() const = 0;
+ /**
+ * Sends all the commands in this dispatch to their endpoints, in undefined order and
+ * without waiting for responses. May block on full send queue (though this should be
+ * rare).
+ *
+ * Any error which occurs during sendAll will be reported on recvAny, *does not throw.*
+ */
+ virtual void sendAll() = 0;
- /**
- * Blocks until a command response has come back. Any outstanding command response may be
- * returned with associated endpoint.
- *
- * Returns !OK on send/recv/parse failure, otherwise command-level errors are returned in
- * the response object itself.
- */
- virtual Status recvAny( ConnectionString* endpoint, BSONSerializable* response ) = 0;
- };
+ /**
+ * Returns the number of sent requests that are still waiting to be recv'd.
+ */
+ virtual int numPending() const = 0;
+ /**
+ * Blocks until a command response has come back. Any outstanding command response may be
+ * returned with associated endpoint.
+ *
+ * Returns !OK on send/recv/parse failure, otherwise command-level errors are returned in
+ * the response object itself.
+ */
+ virtual Status recvAny(ConnectionString* endpoint, BSONSerializable* response) = 0;
+};
}
diff --git a/src/mongo/s/client/multi_host_query.cpp b/src/mongo/s/client/multi_host_query.cpp
index 2fcb5906bf4..bfe9d36f55d 100644
--- a/src/mongo/s/client/multi_host_query.cpp
+++ b/src/mongo/s/client/multi_host_query.cpp
@@ -34,375 +34,339 @@
namespace mongo {
- using std::shared_ptr;
- using std::make_pair;
- using std::string;
- using std::vector;
+using std::shared_ptr;
+using std::make_pair;
+using std::string;
+using std::vector;
- typedef stdx::unique_lock<stdx::mutex> boost_unique_lock;
+typedef stdx::unique_lock<stdx::mutex> boost_unique_lock;
- HostThreadPool::HostThreadPool(int poolSize, bool scopeAllWork) :
- _scopeAllWork(scopeAllWork), _context(new PoolContext) {
+HostThreadPool::HostThreadPool(int poolSize, bool scopeAllWork)
+ : _scopeAllWork(scopeAllWork), _context(new PoolContext) {
+ // All threads start as active to avoid races detecting idleness on thread startup -
+ // the pool isn't idle until all threads have started waiting.
+ _context->numActiveWorkers = poolSize;
- // All threads start as active to avoid races detecting idleness on thread startup -
- // the pool isn't idle until all threads have started waiting.
- _context->numActiveWorkers = poolSize;
-
- for (int i = 0; i < poolSize; ++i) {
-
- //
- // Each thread keeps a shared context allowing them to synchronize even if this
- // dispatching pool has already been disposed.
- //
-
- _threads.push_back(new stdx::thread(stdx::bind(&HostThreadPool::doWork, _context)));
- }
- }
+ for (int i = 0; i < poolSize; ++i) {
+ //
+ // Each thread keeps a shared context allowing them to synchronize even if this
+ // dispatching pool has already been disposed.
+ //
- void HostThreadPool::schedule(Callback callback) {
- boost_unique_lock lk(_context->mutex);
- _context->scheduled.push_back(callback);
- _context->workScheduledCV.notify_one();
+ _threads.push_back(new stdx::thread(stdx::bind(&HostThreadPool::doWork, _context)));
}
+}
- void HostThreadPool::doWork(std::shared_ptr<PoolContext> context) {
-
- while (true) {
-
- Callback callback;
-
- {
- boost_unique_lock lk(context->mutex);
-
- --context->numActiveWorkers;
- if (context->numActiveWorkers == 0)
- context->isIdleCV.notify_all();
-
- // Wait for work or until we're finished
- while (context->isPoolActive && context->scheduled.empty()) {
- context->workScheduledCV.wait(lk);
- }
+void HostThreadPool::schedule(Callback callback) {
+ boost_unique_lock lk(_context->mutex);
+ _context->scheduled.push_back(callback);
+ _context->workScheduledCV.notify_one();
+}
- //
- // Either the pool is no longer active, or the queue has some work we should do
- //
+void HostThreadPool::doWork(std::shared_ptr<PoolContext> context) {
+ while (true) {
+ Callback callback;
- if (!context->isPoolActive)
- return;
+ {
+ boost_unique_lock lk(context->mutex);
- invariant( !context->scheduled.empty() );
- callback = context->scheduled.front();
- context->scheduled.pop_front();
+ --context->numActiveWorkers;
+ if (context->numActiveWorkers == 0)
+ context->isIdleCV.notify_all();
- ++context->numActiveWorkers;
+ // Wait for work or until we're finished
+ while (context->isPoolActive && context->scheduled.empty()) {
+ context->workScheduledCV.wait(lk);
}
- callback();
- }
- }
-
- void HostThreadPool::waitUntilIdle() {
- boost_unique_lock lk(_context->mutex);
- while (_context->numActiveWorkers > 0) {
- _context->isIdleCV.wait(lk);
- }
- }
+ //
+ // Either the pool is no longer active, or the queue has some work we should do
+ //
- HostThreadPool::~HostThreadPool() {
+ if (!context->isPoolActive)
+ return;
- // Boost can throw on notify(), join(), detach()
+ invariant(!context->scheduled.empty());
+ callback = context->scheduled.front();
+ context->scheduled.pop_front();
- {
- boost_unique_lock lk(_context->mutex);
- _context->isPoolActive = false;
- _context->scheduled.clear();
+ ++context->numActiveWorkers;
}
- DESTRUCTOR_GUARD( _context->workScheduledCV.notify_all(); )
-
- for (vector<stdx::thread*>::iterator it = _threads.begin(); it != _threads.end(); ++it) {
-
- if (_scopeAllWork) {
- DESTRUCTOR_GUARD( ( *it )->join(); )
- }
- else {
- DESTRUCTOR_GUARD( ( *it )->detach(); )
- }
+ callback();
+ }
+}
- delete *it;
- }
+void HostThreadPool::waitUntilIdle() {
+ boost_unique_lock lk(_context->mutex);
+ while (_context->numActiveWorkers > 0) {
+ _context->isIdleCV.wait(lk);
}
+}
- HostThreadPools::HostThreadPools(int poolSize, bool scopeAllWork) :
- _poolSize(poolSize), _scopeAllWork(scopeAllWork) {
+HostThreadPool::~HostThreadPool() {
+ // Boost can throw on notify(), join(), detach()
+
+ {
+ boost_unique_lock lk(_context->mutex);
+ _context->isPoolActive = false;
+ _context->scheduled.clear();
}
- void HostThreadPools::schedule(const ConnectionString& host,
- HostThreadPool::Callback callback) {
- boost_unique_lock lk(_mutex);
+ DESTRUCTOR_GUARD(_context->workScheduledCV.notify_all();)
- HostPoolMap::iterator seenIt = _pools.find(host);
- if (seenIt == _pools.end()) {
- seenIt = _pools.insert(make_pair(host, new HostThreadPool(_poolSize, _scopeAllWork)))
- .first;
+ for (vector<stdx::thread*>::iterator it = _threads.begin(); it != _threads.end(); ++it) {
+ if (_scopeAllWork) {
+ DESTRUCTOR_GUARD((*it)->join();)
+ } else {
+ DESTRUCTOR_GUARD((*it)->detach();)
}
- seenIt->second->schedule(callback);
+ delete *it;
}
+}
- void HostThreadPools::waitUntilIdle(const ConnectionString& host) {
-
- // Note that this prevents the creation of any new pools - it is only intended to be used
- // for testing.
-
- boost_unique_lock lk(_mutex);
+HostThreadPools::HostThreadPools(int poolSize, bool scopeAllWork)
+ : _poolSize(poolSize), _scopeAllWork(scopeAllWork) {}
- HostPoolMap::iterator seenIt = _pools.find(host);
- if (seenIt == _pools.end())
- return;
+void HostThreadPools::schedule(const ConnectionString& host, HostThreadPool::Callback callback) {
+ boost_unique_lock lk(_mutex);
- seenIt->second->waitUntilIdle();
+ HostPoolMap::iterator seenIt = _pools.find(host);
+ if (seenIt == _pools.end()) {
+ seenIt = _pools.insert(make_pair(host, new HostThreadPool(_poolSize, _scopeAllWork))).first;
}
- HostThreadPools::~HostThreadPools() {
+ seenIt->second->schedule(callback);
+}
- boost_unique_lock lk(_mutex);
- for (HostPoolMap::iterator it = _pools.begin(); it != _pools.end(); ++it) {
- delete it->second;
- }
- }
+void HostThreadPools::waitUntilIdle(const ConnectionString& host) {
+ // Note that this prevents the creation of any new pools - it is only intended to be used
+ // for testing.
- MultiHostQueryOp::MultiHostQueryOp(SystemEnv* systemEnv, HostThreadPools* hostThreads) :
- _systemEnv(systemEnv), _hostThreads(hostThreads) {
- }
+ boost_unique_lock lk(_mutex);
- StatusWith<DBClientCursor*> MultiHostQueryOp::queryAny(const vector<ConnectionString>& hosts,
- const QuerySpec& query,
- int timeoutMillis) {
-
- Date_t nowMillis = _systemEnv->currentTimeMillis();
- Date_t timeoutAtMillis = nowMillis + Milliseconds(timeoutMillis);
+ HostPoolMap::iterator seenIt = _pools.find(host);
+ if (seenIt == _pools.end())
+ return;
- // Send out all queries
- scheduleQuery(hosts, query, timeoutAtMillis);
+ seenIt->second->waitUntilIdle();
+}
- // Wait for them to come back
- return waitForNextResult(timeoutAtMillis);
+HostThreadPools::~HostThreadPools() {
+ boost_unique_lock lk(_mutex);
+ for (HostPoolMap::iterator it = _pools.begin(); it != _pools.end(); ++it) {
+ delete it->second;
}
+}
- void MultiHostQueryOp::scheduleQuery(const vector<ConnectionString>& hosts,
- const QuerySpec& query,
- Date_t timeoutAtMillis) {
-
- invariant( _pending.empty() );
+MultiHostQueryOp::MultiHostQueryOp(SystemEnv* systemEnv, HostThreadPools* hostThreads)
+ : _systemEnv(systemEnv), _hostThreads(hostThreads) {}
- for (vector<ConnectionString>::const_iterator it = hosts.begin(); it != hosts.end(); ++it) {
+StatusWith<DBClientCursor*> MultiHostQueryOp::queryAny(const vector<ConnectionString>& hosts,
+ const QuerySpec& query,
+ int timeoutMillis) {
+ Date_t nowMillis = _systemEnv->currentTimeMillis();
+ Date_t timeoutAtMillis = nowMillis + Milliseconds(timeoutMillis);
- const ConnectionString& host = *it;
+ // Send out all queries
+ scheduleQuery(hosts, query, timeoutAtMillis);
- shared_ptr<PendingQueryContext> pendingOp(new PendingQueryContext(host,
- query,
- timeoutAtMillis,
- this));
+ // Wait for them to come back
+ return waitForNextResult(timeoutAtMillis);
+}
- _pending.insert(make_pair(host, pendingOp));
+void MultiHostQueryOp::scheduleQuery(const vector<ConnectionString>& hosts,
+ const QuerySpec& query,
+ Date_t timeoutAtMillis) {
+ invariant(_pending.empty());
- HostThreadPool::Callback callback =
- stdx::bind(&MultiHostQueryOp::PendingQueryContext::doBlockingQuery, pendingOp);
+ for (vector<ConnectionString>::const_iterator it = hosts.begin(); it != hosts.end(); ++it) {
+ const ConnectionString& host = *it;
- _hostThreads->schedule(host, callback);
- }
- }
+ shared_ptr<PendingQueryContext> pendingOp(
+ new PendingQueryContext(host, query, timeoutAtMillis, this));
- StatusWith<DBClientCursor*> MultiHostQueryOp::waitForNextResult(Date_t timeoutAtMillis) {
+ _pending.insert(make_pair(host, pendingOp));
- StatusWith<DBClientCursor*> nextResult( NULL);
+ HostThreadPool::Callback callback =
+ stdx::bind(&MultiHostQueryOp::PendingQueryContext::doBlockingQuery, pendingOp);
- boost_unique_lock lk(_resultsMutex);
- while (!releaseResult_inlock(&nextResult)) {
+ _hostThreads->schedule(host, callback);
+ }
+}
- Date_t nowMillis = _systemEnv->currentTimeMillis();
+StatusWith<DBClientCursor*> MultiHostQueryOp::waitForNextResult(Date_t timeoutAtMillis) {
+ StatusWith<DBClientCursor*> nextResult(NULL);
- if (nowMillis >= timeoutAtMillis) {
- nextResult = StatusWith<DBClientCursor*>(combineErrorResults_inlock());
- break;
- }
+ boost_unique_lock lk(_resultsMutex);
+ while (!releaseResult_inlock(&nextResult)) {
+ Date_t nowMillis = _systemEnv->currentTimeMillis();
- _nextResultCV.wait_for(lk, timeoutAtMillis - nowMillis);
+ if (nowMillis >= timeoutAtMillis) {
+ nextResult = StatusWith<DBClientCursor*>(combineErrorResults_inlock());
+ break;
}
- dassert( !nextResult.isOK() || nextResult.getValue() );
- return nextResult;
+ _nextResultCV.wait_for(lk, timeoutAtMillis - nowMillis);
}
- void MultiHostQueryOp::noteResult(const ConnectionString& host,
- StatusWith<DBClientCursor*> result) {
-
- boost_unique_lock lk(_resultsMutex);
- dassert( _results.find( host ) == _results.end() );
- _results.insert(make_pair(host, result));
+ dassert(!nextResult.isOK() || nextResult.getValue());
+ return nextResult;
+}
- _nextResultCV.notify_one();
- }
+void MultiHostQueryOp::noteResult(const ConnectionString& host,
+ StatusWith<DBClientCursor*> result) {
+ boost_unique_lock lk(_resultsMutex);
+ dassert(_results.find(host) == _results.end());
+ _results.insert(make_pair(host, result));
- /**
- * The results in the result map have four states:
- * Nonexistent - query result still pending
- * Status::OK w/ pointer - successful query result, not yet released to user
- * Status::OK w/ NULL pointer - successful query result, user consumed the result
- * Status::Not OK - error during query
- *
- * This function returns true and the next result to release to the user (or an error
- * if there can be no successful results to release) or false to indicate the user
- * should keep waiting.
- */
- bool MultiHostQueryOp::releaseResult_inlock(StatusWith<DBClientCursor*>* nextResult) {
-
- int numErrors = 0;
- int numReleased = 0;
- for (ResultMap::iterator it = _results.begin(); it != _results.end(); ++it) {
-
- StatusWith<DBClientCursor*>& result = it->second;
-
- if (result.isOK() && result.getValue() != NULL) {
- *nextResult = result;
- it->second = StatusWith<DBClientCursor*>( NULL);
- return true;
- }
- else if (result.isOK()) {
- ++numReleased;
- }
- else {
- ++numErrors;
- }
- }
+ _nextResultCV.notify_one();
+}
- if (numErrors + numReleased == static_cast<int>(_pending.size())) {
- *nextResult = StatusWith<DBClientCursor*>(combineErrorResults_inlock());
+/**
+ * The results in the result map have four states:
+ * Nonexistent - query result still pending
+ * Status::OK w/ pointer - successful query result, not yet released to user
+ * Status::OK w/ NULL pointer - successful query result, user consumed the result
+ * Status::Not OK - error during query
+ *
+ * This function returns true and the next result to release to the user (or an error
+ * if there can be no successful results to release) or false to indicate the user
+ * should keep waiting.
+ */
+bool MultiHostQueryOp::releaseResult_inlock(StatusWith<DBClientCursor*>* nextResult) {
+ int numErrors = 0;
+ int numReleased = 0;
+ for (ResultMap::iterator it = _results.begin(); it != _results.end(); ++it) {
+ StatusWith<DBClientCursor*>& result = it->second;
+
+ if (result.isOK() && result.getValue() != NULL) {
+ *nextResult = result;
+ it->second = StatusWith<DBClientCursor*>(NULL);
return true;
+ } else if (result.isOK()) {
+ ++numReleased;
+ } else {
+ ++numErrors;
}
-
- return false;
}
- /**
- * Goes through the set of results and combines all non-OK results into a single Status.
- * If a single error is found, just returns that error.
- * If no non-OK results are found, assumes the cause is a timeout.
- */
- Status MultiHostQueryOp::combineErrorResults_inlock() {
-
- ErrorCodes::Error code = ErrorCodes::OK;
- StringBuilder errMsg;
- // Whether we should include human-readable codes in the msg - we don't need them if we're
- // not aggregating multiple statuses together
- bool includeHRCodes = false;
-
- for (ResultMap::const_iterator it = _results.begin(); it != _results.end(); ++it) {
-
- const StatusWith<DBClientCursor*>& result = it->second;
-
- if (!result.isOK()) {
-
- if (code == ErrorCodes::OK) {
- code = result.getStatus().code();
- }
- else {
+ if (numErrors + numReleased == static_cast<int>(_pending.size())) {
+ *nextResult = StatusWith<DBClientCursor*>(combineErrorResults_inlock());
+ return true;
+ }
- if (!includeHRCodes) {
- includeHRCodes = true;
- // Fixup the single error message to include a code
- errMsg.reset();
- errMsg.append(Status(code, errMsg.str()).toString());
- }
+ return false;
+}
- code = ErrorCodes::MultipleErrorsOccurred;
- errMsg.append(" :: and :: ");
+/**
+ * Goes through the set of results and combines all non-OK results into a single Status.
+ * If a single error is found, just returns that error.
+ * If no non-OK results are found, assumes the cause is a timeout.
+ */
+Status MultiHostQueryOp::combineErrorResults_inlock() {
+ ErrorCodes::Error code = ErrorCodes::OK;
+ StringBuilder errMsg;
+ // Whether we should include human-readable codes in the msg - we don't need them if we're
+ // not aggregating multiple statuses together
+ bool includeHRCodes = false;
+
+ for (ResultMap::const_iterator it = _results.begin(); it != _results.end(); ++it) {
+ const StatusWith<DBClientCursor*>& result = it->second;
+
+ if (!result.isOK()) {
+ if (code == ErrorCodes::OK) {
+ code = result.getStatus().code();
+ } else {
+ if (!includeHRCodes) {
+ includeHRCodes = true;
+ // Fixup the single error message to include a code
+ errMsg.reset();
+ errMsg.append(Status(code, errMsg.str()).toString());
}
- errMsg.append(
- includeHRCodes ? result.getStatus().toString() : result.getStatus().reason());
- errMsg.append(string(", host ") + it->first.toString());
+ code = ErrorCodes::MultipleErrorsOccurred;
+ errMsg.append(" :: and :: ");
}
- }
- if (code == ErrorCodes::OK) {
- return Status(ErrorCodes::NetworkTimeout, "no results were returned in time");
+ errMsg.append(includeHRCodes ? result.getStatus().toString()
+ : result.getStatus().reason());
+ errMsg.append(string(", host ") + it->first.toString());
}
-
- return Status(code, errMsg.str());
}
- MultiHostQueryOp::PendingQueryContext::PendingQueryContext(const ConnectionString& host,
- const QuerySpec& query,
- const Date_t timeoutAtMillis,
- MultiHostQueryOp* parentOp) :
- host(host), query(query), timeoutAtMillis(timeoutAtMillis), parentOp(parentOp) {
+ if (code == ErrorCodes::OK) {
+ return Status(ErrorCodes::NetworkTimeout, "no results were returned in time");
}
- void MultiHostQueryOp::PendingQueryContext::doBlockingQuery() {
-
- // This *NEEDS* to be around for as long as we're doing queries - i.e. as long as the
- // HostThreadPools is.
- MultiHostQueryOp::SystemEnv* systemEnv;
+ return Status(code, errMsg.str());
+}
- // Extract means of doing query from the parent op
- {
- boost_unique_lock lk(parentMutex);
+MultiHostQueryOp::PendingQueryContext::PendingQueryContext(const ConnectionString& host,
+ const QuerySpec& query,
+ const Date_t timeoutAtMillis,
+ MultiHostQueryOp* parentOp)
+ : host(host), query(query), timeoutAtMillis(timeoutAtMillis), parentOp(parentOp) {}
- if (!parentOp)
- return;
+void MultiHostQueryOp::PendingQueryContext::doBlockingQuery() {
+ // This *NEEDS* to be around for as long as we're doing queries - i.e. as long as the
+ // HostThreadPools is.
+ MultiHostQueryOp::SystemEnv* systemEnv;
- systemEnv = parentOp->_systemEnv;
- }
+ // Extract means of doing query from the parent op
+ {
+ boost_unique_lock lk(parentMutex);
- // Make sure we're not timed out
- Date_t nowMillis = systemEnv->currentTimeMillis();
- if (nowMillis >= timeoutAtMillis)
+ if (!parentOp)
return;
- // Do query
- StatusWith<DBClientCursor*> result = systemEnv->doBlockingQuery(host, query);
-
- // Push results back to parent op if it's still around
- {
- boost_unique_lock lk(parentMutex);
-
- if (parentOp)
- parentOp->noteResult(host, result);
- else if(result.isOK())
- delete result.getValue();
- }
+ systemEnv = parentOp->_systemEnv;
}
- MultiHostQueryOp::~MultiHostQueryOp() {
+ // Make sure we're not timed out
+ Date_t nowMillis = systemEnv->currentTimeMillis();
+ if (nowMillis >= timeoutAtMillis)
+ return;
- //
- // Orphan all outstanding query contexts that haven't reported back - these will be gc'd
- // once all scheduled query callbacks are finished.
- //
+ // Do query
+ StatusWith<DBClientCursor*> result = systemEnv->doBlockingQuery(host, query);
- for (PendingMap::iterator it = _pending.begin(); it != _pending.end(); ++it) {
+ // Push results back to parent op if it's still around
+ {
+ boost_unique_lock lk(parentMutex);
- shared_ptr<PendingQueryContext>& pendingContext = it->second;
+ if (parentOp)
+ parentOp->noteResult(host, result);
+ else if (result.isOK())
+ delete result.getValue();
+ }
+}
- boost_unique_lock lk(pendingContext->parentMutex);
- pendingContext->parentOp = NULL;
- }
+MultiHostQueryOp::~MultiHostQueryOp() {
+ //
+ // Orphan all outstanding query contexts that haven't reported back - these will be gc'd
+ // once all scheduled query callbacks are finished.
+ //
- //
- // Nobody else should be modifying _results now - callbacks don't have access to this op,
- // and other clients should know the op is going out of scope
- //
+ for (PendingMap::iterator it = _pending.begin(); it != _pending.end(); ++it) {
+ shared_ptr<PendingQueryContext>& pendingContext = it->second;
- for (ResultMap::iterator it = _results.begin(); it != _results.end(); ++it) {
+ boost_unique_lock lk(pendingContext->parentMutex);
+ pendingContext->parentOp = NULL;
+ }
- StatusWith<DBClientCursor*>& result = it->second;
+ //
+ // Nobody else should be modifying _results now - callbacks don't have access to this op,
+ // and other clients should know the op is going out of scope
+ //
- if (result.isOK()) {
- delete result.getValue();
- }
+ for (ResultMap::iterator it = _results.begin(); it != _results.end(); ++it) {
+ StatusWith<DBClientCursor*>& result = it->second;
+
+ if (result.isOK()) {
+ delete result.getValue();
}
}
-
+}
}
diff --git a/src/mongo/s/client/multi_host_query.h b/src/mongo/s/client/multi_host_query.h
index 7b69ecc25f5..61bdf27bf4f 100644
--- a/src/mongo/s/client/multi_host_query.h
+++ b/src/mongo/s/client/multi_host_query.h
@@ -39,290 +39,280 @@
namespace mongo {
- //
- // NOTE TO DEVS
- // This is probably not what we want long-term - think very carefully before letting any of the
- // functionality below escape this file.
- //
+//
+// NOTE TO DEVS
+// This is probably not what we want long-term - think very carefully before letting any of the
+// functionality below escape this file.
+//
+
+class HostThreadPools;
+class HostThreadPool;
+
+/**
+ * A MultiHostQueryOp manages a query operation across multiple hosts. Supports returning
+ * immediately when any host has results or when all hosts have (connectivity) errors.
+ *
+ * The QueryOp itself dispatches work to the thread pool, and does not wait for all work to be
+ * complete before destruction. This class is not intended to be used by multiple clients at
+ * once without external synchronization (for now).
+ *
+ * Cannot be reused once all query results and errors have been returned.
+ */
+class MultiHostQueryOp {
+ MONGO_DISALLOW_COPYING(MultiHostQueryOp);
- class HostThreadPools;
- class HostThreadPool;
+public:
+ /**
+ * Network and time services interface
+ */
+ class SystemEnv;
/**
- * A MultiHostQueryOp manages a query operation across multiple hosts. Supports returning
- * immediately when any host has results or when all hosts have (connectivity) errors.
+ * Constructs a MultiHostQueryOp. Allows running a query across multiple hosts with a
+ * blocking interface. The lifetime of this class can be shorter than the lifetime of the
+ * queries sent via queryAny, freeing up the caller to do further work when any host is fast
+ * to respond.
*
- * The QueryOp itself dispatches work to the thread pool, and does not wait for all work to be
- * complete before destruction. This class is not intended to be used by multiple clients at
- * once without external synchronization (for now).
+ * The systemEnv and hostThreads must remain in scope while the query op remains in scope.
*
- * Cannot be reused once all query results and errors have been returned.
+ * NOTE: SystemEnv* MUST remain valid for as long as hostThreads remains valid, since this
+ * operation may schedule background queries but fall out of scope while one of those
+ * background queries is still in-progress.
*/
- class MultiHostQueryOp {
- MONGO_DISALLOW_COPYING(MultiHostQueryOp);
- public:
-
- /**
- * Network and time services interface
- */
- class SystemEnv;
-
- /**
- * Constructs a MultiHostQueryOp. Allows running a query across multiple hosts with a
- * blocking interface. The lifetime of this class can be shorter than the lifetime of the
- * queries sent via queryAny, freeing up the caller to do further work when any host is fast
- * to respond.
- *
- * The systemEnv and hostThreads must remain in scope while the query op remains in scope.
- *
- * NOTE: SystemEnv* MUST remain valid for as long as hostThreads remains valid, since this
- * operation may schedule background queries but fall out of scope while one of those
- * background queries is still in-progress.
- */
- MultiHostQueryOp(SystemEnv* systemEnv, HostThreadPools* hostThreads);
-
- ~MultiHostQueryOp();
-
- /**
- * Blocks for a query to be run on any of the hosts, and returns the fastest result as soon
- * as it becomes available. This function may only be executed once.
- *
- * If one or more hosts have an error sending/recv'ing the query, the error or composite
- * error is returned if no other hosts are responsive after the timeout period. Note that
- * this does not apply to errors successfully returned from remote hosts - this is a
- * successful query with an error.
- *
- * Caller owns the returned result if OK.
- */
- StatusWith<DBClientCursor*> queryAny(const std::vector<ConnectionString>& hosts,
- const QuerySpec& query,
- int timeoutMillis);
-
- //
- // Below is exposed for testing *only*
- //
-
- /**
- * Schedules the query work on each of the hosts using the thread pool, with a timeout
- * indicating how long the work is useful for. Can be called only once.
- */
- void scheduleQuery(const std::vector<ConnectionString>& hosts,
- const QuerySpec& query,
- Date_t timeoutAtMillis);
-
- /**
- * Blocks and waits for the next successful query result or any errors once the timeout is
- * reached.
- * Can be called multiple times until results from all hosts are returned or !OK.
- */
- StatusWith<DBClientCursor*> waitForNextResult(Date_t timeoutAtMillis);
-
- private:
-
- /**
- * Data required to execute a query operation by a callback on an arbitrary thread.
- * Information from the dispatching parent op may not be available if the parent is no
- * longer in scope.
- */
- struct PendingQueryContext {
-
- PendingQueryContext(const ConnectionString& host,
- const QuerySpec& query,
- const Date_t timeoutAtMillis,
- MultiHostQueryOp* parentOp);
-
- void doBlockingQuery();
-
- const ConnectionString host;
- const QuerySpec query;
- const Date_t timeoutAtMillis;
-
- // Must be held to access the parent pointer below
- stdx::mutex parentMutex;
- // Set and unset by the parent operation on scheduling and destruction
- MultiHostQueryOp* parentOp;
- };
-
- /**
- * Called by a scheduled query (generally on a different thread from the waiting client)
- * when a result is ready from a particular host.
- */
- void noteResult(const ConnectionString& host, StatusWith<DBClientCursor*> result);
-
- /**
- * Helper to check if any result is ready and extract that result
- * Synchronized by _resultsMutex
- */
- bool releaseResult_inlock(StatusWith<DBClientCursor*>* nextResult);
-
- /**
- * Helper to return an error status from zero or more results
- * Synchronized by _resultsMutex
- */
- Status combineErrorResults_inlock();
-
- // Not owned here
- SystemEnv* _systemEnv;
-
- // Not owned here
- HostThreadPools* _hostThreads;
-
- // Outstanding requests
- typedef std::map<ConnectionString, std::shared_ptr<PendingQueryContext> > PendingMap;
- PendingMap _pending;
+ MultiHostQueryOp(SystemEnv* systemEnv, HostThreadPools* hostThreads);
- // Synchronizes below
- stdx::mutex _resultsMutex;
+ ~MultiHostQueryOp();
- // Current results recv'd
- typedef std::map<ConnectionString, StatusWith<DBClientCursor*> > ResultMap;
- ResultMap _results;
+ /**
+ * Blocks for a query to be run on any of the hosts, and returns the fastest result as soon
+ * as it becomes available. This function may only be executed once.
+ *
+ * If one or more hosts have an error sending/recv'ing the query, the error or composite
+ * error is returned if no other hosts are responsive after the timeout period. Note that
+ * this does not apply to errors successfully returned from remote hosts - this is a
+ * successful query with an error.
+ *
+ * Caller owns the returned result if OK.
+ */
+ StatusWith<DBClientCursor*> queryAny(const std::vector<ConnectionString>& hosts,
+ const QuerySpec& query,
+ int timeoutMillis);
- stdx::condition_variable _nextResultCV;
- };
+ //
+ // Below is exposed for testing *only*
+ //
/**
- * Provides network and time services to allow unit testing of MultiHostQueryOp.
+ * Schedules the query work on each of the hosts using the thread pool, with a timeout
+ * indicating how long the work is useful for. Can be called only once.
*/
- class MultiHostQueryOp::SystemEnv {
- public:
-
- virtual ~SystemEnv() {
- }
-
- /**
- * Returns the current time in milliseconds
- */
- virtual Date_t currentTimeMillis() = 0;
-
- /**
- * Executes a query against a given host. No timeout hint is given, but the query should
- * not block forever.
- * Note that no guarantees are given as to the state of the connection used after this
- * returns, so the cursor must be self-contained.
- *
- * Caller owns any resulting cursor.
- */
- virtual StatusWith<DBClientCursor*> doBlockingQuery(const ConnectionString& host,
- const QuerySpec& query) = 0;
- };
+ void scheduleQuery(const std::vector<ConnectionString>& hosts,
+ const QuerySpec& query,
+ Date_t timeoutAtMillis);
/**
- * Object which encapsulates a thread pool per host, and allows scheduling operations against
- * each of these hosts.
- *
- * Optionally supports not waiting for blocked threads before destruction.
- *
- * Thin wrapper for multiple hosts around HostThreadPool.
+ * Blocks and waits for the next successful query result or any errors once the timeout is
+ * reached.
+ * Can be called multiple times until results from all hosts are returned or !OK.
+ */
+ StatusWith<DBClientCursor*> waitForNextResult(Date_t timeoutAtMillis);
+
+private:
+ /**
+ * Data required to execute a query operation by a callback on an arbitrary thread.
+ * Information from the dispatching parent op may not be available if the parent is no
+ * longer in scope.
*/
- class HostThreadPools {
- MONGO_DISALLOW_COPYING(HostThreadPools);
- public:
-
- typedef stdx::function<void(void)> Callback;
-
- /**
- * Construct a HostThreadPools object, which lazily constructs thread pools per-host of the
- * specified size.
- *
- * @param scopeAllWork true if the pool should wait for all work to be finished before
- * going out of scope
- */
- HostThreadPools(int poolSize, bool scopeAllWork);
- ~HostThreadPools();
-
- /**
- * Schedules some work in the form of a callback for the pool of a particular host.
- */
- void schedule(const ConnectionString& host, Callback callback);
-
- /**
- * Blocks until pool is idle for a particular host.
- * For testing.
- */
- void waitUntilIdle(const ConnectionString& host);
-
- private:
-
- const int _poolSize;
- const bool _scopeAllWork;
-
- stdx::mutex _mutex;
- typedef std::map<ConnectionString, HostThreadPool*> HostPoolMap;
- HostPoolMap _pools;
+ struct PendingQueryContext {
+ PendingQueryContext(const ConnectionString& host,
+ const QuerySpec& query,
+ const Date_t timeoutAtMillis,
+ MultiHostQueryOp* parentOp);
+
+ void doBlockingQuery();
+
+ const ConnectionString host;
+ const QuerySpec query;
+ const Date_t timeoutAtMillis;
+
+ // Must be held to access the parent pointer below
+ stdx::mutex parentMutex;
+ // Set and unset by the parent operation on scheduling and destruction
+ MultiHostQueryOp* parentOp;
};
/**
- * EXPOSED FOR TESTING ONLY.
+ * Called by a scheduled query (generally on a different thread from the waiting client)
+ * when a result is ready from a particular host.
+ */
+ void noteResult(const ConnectionString& host, StatusWith<DBClientCursor*> result);
+
+ /**
+ * Helper to check if any result is ready and extract that result
+ * Synchronized by _resultsMutex
+ */
+ bool releaseResult_inlock(StatusWith<DBClientCursor*>* nextResult);
+
+ /**
+ * Helper to return an error status from zero or more results
+ * Synchronized by _resultsMutex
+ */
+ Status combineErrorResults_inlock();
+
+ // Not owned here
+ SystemEnv* _systemEnv;
+
+ // Not owned here
+ HostThreadPools* _hostThreads;
+
+ // Outstanding requests
+ typedef std::map<ConnectionString, std::shared_ptr<PendingQueryContext>> PendingMap;
+ PendingMap _pending;
+
+ // Synchronizes below
+ stdx::mutex _resultsMutex;
+
+ // Current results recv'd
+ typedef std::map<ConnectionString, StatusWith<DBClientCursor*>> ResultMap;
+ ResultMap _results;
+
+ stdx::condition_variable _nextResultCV;
+};
+
+/**
+ * Provides network and time services to allow unit testing of MultiHostQueryOp.
+ */
+class MultiHostQueryOp::SystemEnv {
+public:
+ virtual ~SystemEnv() {}
+
+ /**
+ * Returns the current time in milliseconds
+ */
+ virtual Date_t currentTimeMillis() = 0;
+
+ /**
+ * Executes a query against a given host. No timeout hint is given, but the query should
+ * not block forever.
+ * Note that no guarantees are given as to the state of the connection used after this
+ * returns, so the cursor must be self-contained.
*
- * Thread pool allowing work to be scheduled against various hosts.
- * Generic interface, but should not be used outside of this class.
+ * Caller owns any resulting cursor.
*/
- class HostThreadPool {
- public:
+ virtual StatusWith<DBClientCursor*> doBlockingQuery(const ConnectionString& host,
+ const QuerySpec& query) = 0;
+};
- typedef stdx::function<void(void)> Callback;
+/**
+ * Object which encapsulates a thread pool per host, and allows scheduling operations against
+ * each of these hosts.
+ *
+ * Optionally supports not waiting for blocked threads before destruction.
+ *
+ * Thin wrapper for multiple hosts around HostThreadPool.
+ */
+class HostThreadPools {
+ MONGO_DISALLOW_COPYING(HostThreadPools);
- /**
- * Constructs a thread pool of a given size.
- *
- * Parameter scopeAllWork indicates whether the pool should wait for all work to be finished
- * before going out of scope.
- */
- HostThreadPool(int poolSize, bool scopeAllWork);
+public:
+ typedef stdx::function<void(void)> Callback;
- ~HostThreadPool();
+ /**
+ * Construct a HostThreadPools object, which lazily constructs thread pools per-host of the
+ * specified size.
+ *
+ * @param scopeAllWork true if the pool should wait for all work to be finished before
+ * going out of scope
+ */
+ HostThreadPools(int poolSize, bool scopeAllWork);
+ ~HostThreadPools();
- /**
- * Schedules some work in the form of a callback to be done ASAP.
- */
- void schedule(Callback callback);
+ /**
+ * Schedules some work in the form of a callback for the pool of a particular host.
+ */
+ void schedule(const ConnectionString& host, Callback callback);
+
+ /**
+ * Blocks until pool is idle for a particular host.
+ * For testing.
+ */
+ void waitUntilIdle(const ConnectionString& host);
- /**
- * Blocks until all threads are idle.
- */
- void waitUntilIdle();
+private:
+ const int _poolSize;
+ const bool _scopeAllWork;
- private:
+ stdx::mutex _mutex;
+ typedef std::map<ConnectionString, HostThreadPool*> HostPoolMap;
+ HostPoolMap _pools;
+};
- /**
- * Synchronized work and activity information shared between the pool and the individual
- * worker threads.
- * This information must be shared, since if !scopeAllWork the parent pool is allowed to
- * fall out of scope before the child thread completes.
- */
- struct PoolContext {
+/**
+ * EXPOSED FOR TESTING ONLY.
+ *
+ * Thread pool allowing work to be scheduled against various hosts.
+ * Generic interface, but should not be used outside of this class.
+ */
+class HostThreadPool {
+public:
+ typedef stdx::function<void(void)> Callback;
- PoolContext() :
- numActiveWorkers(0), isPoolActive(true) {
- }
+ /**
+ * Constructs a thread pool of a given size.
+ *
+ * Parameter scopeAllWork indicates whether the pool should wait for all work to be finished
+ * before going out of scope.
+ */
+ HostThreadPool(int poolSize, bool scopeAllWork);
- // Synchronizes below
- stdx::mutex mutex;
+ ~HostThreadPool();
- // The scheduled work
- std::deque<Callback> scheduled;
- stdx::condition_variable workScheduledCV;
+ /**
+ * Schedules some work in the form of a callback to be done ASAP.
+ */
+ void schedule(Callback callback);
- // How many workers are currently active
- int numActiveWorkers;
- stdx::condition_variable isIdleCV;
+ /**
+ * Blocks until all threads are idle.
+ */
+ void waitUntilIdle();
- // Whether the pool has been disposed of
- bool isPoolActive;
- };
+private:
+ /**
+ * Synchronized work and activity information shared between the pool and the individual
+ * worker threads.
+ * This information must be shared, since if !scopeAllWork the parent pool is allowed to
+ * fall out of scope before the child thread completes.
+ */
+ struct PoolContext {
+ PoolContext() : numActiveWorkers(0), isPoolActive(true) {}
- /**
- * Worker loop run by each thread.
- */
- static void doWork(std::shared_ptr<PoolContext> context);
+ // Synchronizes below
+ stdx::mutex mutex;
- const bool _scopeAllWork;
+ // The scheduled work
+ std::deque<Callback> scheduled;
+ stdx::condition_variable workScheduledCV;
- // For now, only modified in the constructor and destructor, but non-const
- std::vector<stdx::thread*> _threads;
+ // How many workers are currently active
+ int numActiveWorkers;
+ stdx::condition_variable isIdleCV;
- // Shared work and worker activity information
- std::shared_ptr<PoolContext> _context;
+ // Whether the pool has been disposed of
+ bool isPoolActive;
};
+
+ /**
+ * Worker loop run by each thread.
+ */
+ static void doWork(std::shared_ptr<PoolContext> context);
+
+ const bool _scopeAllWork;
+
+ // For now, only modified in the constructor and destructor, but non-const
+ std::vector<stdx::thread*> _threads;
+
+ // Shared work and worker activity information
+ std::shared_ptr<PoolContext> _context;
+};
}
diff --git a/src/mongo/s/client/multi_host_query_test.cpp b/src/mongo/s/client/multi_host_query_test.cpp
index d63c4d311c8..c2d3f3688df 100644
--- a/src/mongo/s/client/multi_host_query_test.cpp
+++ b/src/mongo/s/client/multi_host_query_test.cpp
@@ -36,740 +36,698 @@
namespace {
- using namespace mongo;
- using std::unique_ptr;
- using std::shared_ptr;
- using std::make_pair;
- using std::map;
- using std::string;
- using std::vector;
-
- class CallbackCheck {
- public:
-
- enum LinkMode {
- None, Notify_Other, Wait_For_Other
- };
-
- CallbackCheck() :
- _status(ErrorCodes::OperationIncomplete, ""), _linkMode(None) {
- }
-
- void blockUntil(CallbackCheck* other) {
-
- _otherNotification.reset(new Notification);
- _linkMode = Wait_For_Other;
-
- other->_otherNotification = _otherNotification;
- other->_linkMode = Notify_Other;
- }
-
- HostThreadPool::Callback getCallback() {
- return stdx::bind(&CallbackCheck::noteCallback, this);
- }
-
- HostThreadPool::Callback getHostCallback(const ConnectionString& host) {
- return stdx::bind(&CallbackCheck::noteHostCallback, this, host);
- }
-
- void noteHostCallback(const ConnectionString& host) {
- _host = host;
- noteCallback();
- }
-
- void noteCallback() {
+using namespace mongo;
+using std::unique_ptr;
+using std::shared_ptr;
+using std::make_pair;
+using std::map;
+using std::string;
+using std::vector;
+
+class CallbackCheck {
+public:
+ enum LinkMode { None, Notify_Other, Wait_For_Other };
+
+ CallbackCheck() : _status(ErrorCodes::OperationIncomplete, ""), _linkMode(None) {}
+
+ void blockUntil(CallbackCheck* other) {
+ _otherNotification.reset(new Notification);
+ _linkMode = Wait_For_Other;
+
+ other->_otherNotification = _otherNotification;
+ other->_linkMode = Notify_Other;
+ }
- _status = Status::OK();
- _notification.notifyOne();
+ HostThreadPool::Callback getCallback() {
+ return stdx::bind(&CallbackCheck::noteCallback, this);
+ }
- if (_linkMode == Wait_For_Other)
- _otherNotification->waitToBeNotified();
- else if (_linkMode == Notify_Other) {
- _otherNotification->notifyOne();
- }
- }
+ HostThreadPool::Callback getHostCallback(const ConnectionString& host) {
+ return stdx::bind(&CallbackCheck::noteHostCallback, this, host);
+ }
- void waitForCallback() {
- _notification.waitToBeNotified();
- }
+ void noteHostCallback(const ConnectionString& host) {
+ _host = host;
+ noteCallback();
+ }
- Status getStatus() {
- return _status;
- }
+ void noteCallback() {
+ _status = Status::OK();
+ _notification.notifyOne();
- const ConnectionString& getHost() {
- return _host;
+ if (_linkMode == Wait_For_Other)
+ _otherNotification->waitToBeNotified();
+ else if (_linkMode == Notify_Other) {
+ _otherNotification->notifyOne();
}
-
- private:
-
- Status _status;
- Notification _notification;
-
- ConnectionString _host;
-
- shared_ptr<Notification> _otherNotification;
- LinkMode _linkMode;
- };
-
- TEST(HostThreadPool, Schedule) {
-
- CallbackCheck cbCheck;
-
- // NOTE: pool must be initialized *after* the cbCheck that it executes - this avoids a
- // subtle race where the cbCheck structure is disposed before the callback is complete.
- HostThreadPool threadPool(1, true);
-
- threadPool.schedule(cbCheck.getCallback());
-
- cbCheck.waitForCallback();
- ASSERT_OK(cbCheck.getStatus());
}
- TEST(HostThreadPool, ScheduleTwoSerial) {
-
- CallbackCheck cbCheckA;
- CallbackCheck cbCheckB;
-
- // NOTE: pool must be initialized *after* the cbCheck that it executes
- HostThreadPool threadPool(1, true);
-
- threadPool.schedule(cbCheckA.getCallback());
- threadPool.schedule(cbCheckB.getCallback());
-
- cbCheckB.waitForCallback();
- cbCheckA.waitForCallback();
-
- ASSERT_OK(cbCheckA.getStatus());
- ASSERT_OK(cbCheckB.getStatus());
+ void waitForCallback() {
+ _notification.waitToBeNotified();
}
- TEST(HostThreadPool, ScheduleTwoParallel) {
-
- CallbackCheck cbCheckA;
- CallbackCheck cbCheckB;
-
- // NOTE: pool must be initialized *after* the cbCheck that it executes
- HostThreadPool threadPool(2, true);
-
- // Don't allow cbCheckA's callback to finish until cbCheckB's callback is processed
- cbCheckA.blockUntil(&cbCheckB);
-
- threadPool.schedule(cbCheckA.getCallback());
- cbCheckA.waitForCallback();
- ASSERT_OK(cbCheckA.getStatus());
- // We're still blocking the thread processing cbCheckA's callback
-
- threadPool.schedule(cbCheckB.getCallback());
- cbCheckB.waitForCallback();
- ASSERT_OK(cbCheckB.getStatus());
+ Status getStatus() {
+ return _status;
}
- TEST(HostThreadPool, ScheduleTwoHosts) {
-
- CallbackCheck cbCheckA;
- CallbackCheck cbCheckB;
-
- // NOTE: pool must be initialized *after* the cbCheck that it executes
- HostThreadPools threadPool(1, true);
-
- // Don't allow cbCheckA's callback to finish until cbCheckB's callback is processed.
- // This means a single thread pool with a single thread would hang.
- cbCheckA.blockUntil(&cbCheckB);
-
- ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
- ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
-
- threadPool.schedule(hostA, cbCheckA.getHostCallback(hostA));
- cbCheckA.waitForCallback();
- ASSERT_OK(cbCheckA.getStatus());
- ASSERT_EQUALS(cbCheckA.getHost().toString(), hostA.toString());
- // We're still blocking the thread processing cbCheckA's callback
-
- threadPool.schedule(hostB, cbCheckB.getHostCallback(hostB));
- cbCheckB.waitForCallback();
- ASSERT_OK(cbCheckB.getStatus());
- ASSERT_EQUALS(cbCheckB.getHost().toString(), hostB.toString());
+ const ConnectionString& getHost() {
+ return _host;
}
- class MockSystemEnv : public MultiHostQueryOp::SystemEnv {
- private:
+private:
+ Status _status;
+ Notification _notification;
- struct MockHostInfo;
- typedef map<ConnectionString, MockHostInfo*> HostInfoMap;
+ ConnectionString _host;
- public:
+ shared_ptr<Notification> _otherNotification;
+ LinkMode _linkMode;
+};
- MockSystemEnv(HostThreadPools* threadPool) :
- _threadPool(threadPool), _mockTimeMillis(0) {
- }
+TEST(HostThreadPool, Schedule) {
+ CallbackCheck cbCheck;
- virtual ~MockSystemEnv() {
- for (HostInfoMap::iterator it = _mockHostInfo.begin(); it != _mockHostInfo.end();
- ++it) {
- if (_threadPool)
- _threadPool->waitUntilIdle(it->first);
- delete it->second;
- }
- }
+ // NOTE: pool must be initialized *after* the cbCheck that it executes - this avoids a
+ // subtle race where the cbCheck structure is disposed before the callback is complete.
+ HostThreadPool threadPool(1, true);
- void setHostThreadPools(HostThreadPools* threadPool) {
- _threadPool = threadPool;
- }
+ threadPool.schedule(cbCheck.getCallback());
- void addMockHostResultAt(const ConnectionString& host, int timeMillis) {
- newMockHostResultAt(host, timeMillis, Status::OK(), NULL);
- }
+ cbCheck.waitForCallback();
+ ASSERT_OK(cbCheck.getStatus());
+}
- void addMockHostErrorAt(const ConnectionString& host, int timeMillis, Status error) {
- newMockHostResultAt(host, timeMillis, error, NULL);
- }
+TEST(HostThreadPool, ScheduleTwoSerial) {
+ CallbackCheck cbCheckA;
+ CallbackCheck cbCheckB;
- void addMockHungHostAt(const ConnectionString& host,
- int hangTimeMillis,
- Notification* hangUntilNotify) {
- newMockHostResultAt(host, hangTimeMillis, Status::OK(), hangUntilNotify);
- }
+ // NOTE: pool must be initialized *after* the cbCheck that it executes
+ HostThreadPool threadPool(1, true);
- void addMockTimestepAt(int timeMillis) {
+ threadPool.schedule(cbCheckA.getCallback());
+ threadPool.schedule(cbCheckB.getCallback());
- // Add a mock query to a host we aren't using at the provided time
- ConnectionString host = uassertStatusOK(ConnectionString::parse("$timestepHost:1000"));
- newMockHostResultAt(host, timeMillis, Status::OK(), NULL);
+ cbCheckB.waitForCallback();
+ cbCheckA.waitForCallback();
- // The query won't be scheduled by the multi op, so we need to do so ourselves
- _threadPool->schedule(host,
- stdx::bind(&MockSystemEnv::doBlockingQuerySwallowResult,
- this,
- host,
- QuerySpec()));
- }
+ ASSERT_OK(cbCheckA.getStatus());
+ ASSERT_OK(cbCheckB.getStatus());
+}
- Date_t currentTimeMillis() {
- return Date_t::fromMillisSinceEpoch(_mockTimeMillis);
- }
+TEST(HostThreadPool, ScheduleTwoParallel) {
+ CallbackCheck cbCheckA;
+ CallbackCheck cbCheckB;
- void doBlockingQuerySwallowResult(const ConnectionString& host,
- const QuerySpec& query) {
- StatusWith<DBClientCursor*> result = doBlockingQuery(host, query);
- if (result.isOK())
- delete result.getValue();
- }
+ // NOTE: pool must be initialized *after* the cbCheck that it executes
+ HostThreadPool threadPool(2, true);
- StatusWith<DBClientCursor*> doBlockingQuery(const ConnectionString& host,
- const QuerySpec& query) {
+ // Don't allow cbCheckA's callback to finish until cbCheckB's callback is processed
+ cbCheckA.blockUntil(&cbCheckB);
- ASSERT(_mockHostInfo.find(host) != _mockHostInfo.end());
+ threadPool.schedule(cbCheckA.getCallback());
+ cbCheckA.waitForCallback();
+ ASSERT_OK(cbCheckA.getStatus());
+ // We're still blocking the thread processing cbCheckA's callback
- MockHostInfo& info = *(_mockHostInfo.find(host)->second);
+ threadPool.schedule(cbCheckB.getCallback());
+ cbCheckB.waitForCallback();
+ ASSERT_OK(cbCheckB.getStatus());
+}
- if (info.prevHostActiveNotify) {
- info.prevHostActiveNotify->waitToBeNotified();
- if (info.waitForPrevHostIdle) {
- _threadPool->waitUntilIdle(info.prevHost);
- }
- }
+TEST(HostThreadPool, ScheduleTwoHosts) {
+ CallbackCheck cbCheckA;
+ CallbackCheck cbCheckB;
- _mockTimeMillis = info.queryTimeMillis;
+ // NOTE: pool must be initialized *after* the cbCheck that it executes
+ HostThreadPools threadPool(1, true);
- if (info.nextHostActiveNotify) {
- info.nextHostActiveNotify->notifyOne();
- }
+ // Don't allow cbCheckA's callback to finish until cbCheckB's callback is processed.
+ // This means a single thread pool with a single thread would hang.
+ cbCheckA.blockUntil(&cbCheckB);
- if (info.hangUntilNotify) {
- info.hangUntilNotify->waitToBeNotified();
- return StatusWith<DBClientCursor*>(ErrorCodes::InternalError, "");
- }
-
- if (!info.error.isOK()) {
- return StatusWith<DBClientCursor*>(info.error);
- }
-
- //
- // Successful mock query
- //
+ ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
+ ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
- if (!info.conn) {
- info.conn.reset(new DBClientConnection(false));
- // Need to do a connect failure so that we get an empty MessagingPort on the conn and
- // the host name is set.
- string errMsg;
- ASSERT(!info.conn->connect(HostAndPort(host.toString()), errMsg));
- }
-
- return StatusWith<DBClientCursor*>(new DBClientCursor(info.conn.get(),
- query.ns(),
- query.query(),
- query.ntoreturn(),
- query.ntoskip(),
- query.fieldsPtr(),
- query.options(),
- 0 /* batchSize */));
- }
+ threadPool.schedule(hostA, cbCheckA.getHostCallback(hostA));
+ cbCheckA.waitForCallback();
+ ASSERT_OK(cbCheckA.getStatus());
+ ASSERT_EQUALS(cbCheckA.getHost().toString(), hostA.toString());
+ // We're still blocking the thread processing cbCheckA's callback
- private:
+ threadPool.schedule(hostB, cbCheckB.getHostCallback(hostB));
+ cbCheckB.waitForCallback();
+ ASSERT_OK(cbCheckB.getStatus());
+ ASSERT_EQUALS(cbCheckB.getHost().toString(), hostB.toString());
+}
- MockHostInfo* newMockHostResultAt(const ConnectionString& host,
- int timeMillis,
- const Status& error,
- Notification* hangUntilNotify) {
+class MockSystemEnv : public MultiHostQueryOp::SystemEnv {
+private:
+ struct MockHostInfo;
+ typedef map<ConnectionString, MockHostInfo*> HostInfoMap;
- ASSERT(_mockHostInfo.find(host) == _mockHostInfo.end());
+public:
+ MockSystemEnv(HostThreadPools* threadPool) : _threadPool(threadPool), _mockTimeMillis(0) {}
- MockHostInfo* info = new MockHostInfo(timeMillis);
- _mockHostInfo.insert(make_pair(host, info));
- info->error = error;
- info->hangUntilNotify = hangUntilNotify;
-
- linkMockTimes(host, info);
- return info;
+ virtual ~MockSystemEnv() {
+ for (HostInfoMap::iterator it = _mockHostInfo.begin(); it != _mockHostInfo.end(); ++it) {
+ if (_threadPool)
+ _threadPool->waitUntilIdle(it->first);
+ delete it->second;
}
+ }
- void linkMockTimes(const ConnectionString& host, MockHostInfo* info) {
-
- //
- // This just basically sets up notifications between the processing of results such that
- // the results are returned in the order defined by the _mockQueryTimes map.
- //
- // Idea is (second host result) waits for (first host result) thread to start and end,
- // (third host result) waits for (second host result) thread to start and end,
- // (fourth host result) waits for (third host result) thread to start and end,
- // ... and so on ...
- //
+ void setHostThreadPools(HostThreadPools* threadPool) {
+ _threadPool = threadPool;
+ }
- ASSERT(_mockQueryTimes.find(info->queryTimeMillis) == _mockQueryTimes.end());
+ void addMockHostResultAt(const ConnectionString& host, int timeMillis) {
+ newMockHostResultAt(host, timeMillis, Status::OK(), NULL);
+ }
- HostQueryTimes::iterator prev = _mockQueryTimes.insert(make_pair(info->queryTimeMillis,
- host)).first;
+ void addMockHostErrorAt(const ConnectionString& host, int timeMillis, Status error) {
+ newMockHostResultAt(host, timeMillis, error, NULL);
+ }
- if (prev != _mockQueryTimes.begin())
- --prev;
- else
- prev = _mockQueryTimes.end();
+ void addMockHungHostAt(const ConnectionString& host,
+ int hangTimeMillis,
+ Notification* hangUntilNotify) {
+ newMockHostResultAt(host, hangTimeMillis, Status::OK(), hangUntilNotify);
+ }
- HostQueryTimes::iterator next = _mockQueryTimes.upper_bound(info->queryTimeMillis);
+ void addMockTimestepAt(int timeMillis) {
+ // Add a mock query to a host we aren't using at the provided time
+ ConnectionString host = uassertStatusOK(ConnectionString::parse("$timestepHost:1000"));
+ newMockHostResultAt(host, timeMillis, Status::OK(), NULL);
- if (prev != _mockQueryTimes.end()) {
+ // The query won't be scheduled by the multi op, so we need to do so ourselves
+ _threadPool->schedule(
+ host,
+ stdx::bind(&MockSystemEnv::doBlockingQuerySwallowResult, this, host, QuerySpec()));
+ }
- const ConnectionString& prevHost = prev->second;
- MockHostInfo* prevInfo = _mockHostInfo.find(prevHost)->second;
+ Date_t currentTimeMillis() {
+ return Date_t::fromMillisSinceEpoch(_mockTimeMillis);
+ }
- linkToNext(prevHost, prevInfo, info);
- }
+ void doBlockingQuerySwallowResult(const ConnectionString& host, const QuerySpec& query) {
+ StatusWith<DBClientCursor*> result = doBlockingQuery(host, query);
+ if (result.isOK())
+ delete result.getValue();
+ }
- if (next != _mockQueryTimes.end()) {
+ StatusWith<DBClientCursor*> doBlockingQuery(const ConnectionString& host,
+ const QuerySpec& query) {
+ ASSERT(_mockHostInfo.find(host) != _mockHostInfo.end());
- const ConnectionString& nextHost = next->second;
- MockHostInfo* nextInfo = _mockHostInfo.find(nextHost)->second;
+ MockHostInfo& info = *(_mockHostInfo.find(host)->second);
- linkToNext(host, info, nextInfo);
+ if (info.prevHostActiveNotify) {
+ info.prevHostActiveNotify->waitToBeNotified();
+ if (info.waitForPrevHostIdle) {
+ _threadPool->waitUntilIdle(info.prevHost);
}
}
- void linkToNext(const ConnectionString& host, MockHostInfo* info, MockHostInfo* nextInfo) {
-
- nextInfo->prevHost = host;
-
- nextInfo->prevHostActiveNotify.reset(new Notification());
- info->nextHostActiveNotify = nextInfo->prevHostActiveNotify.get();
+ _mockTimeMillis = info.queryTimeMillis;
- nextInfo->waitForPrevHostIdle = info->hangUntilNotify == NULL;
+ if (info.nextHostActiveNotify) {
+ info.nextHostActiveNotify->notifyOne();
}
- // Not owned here, needed to allow ordering of mock queries
- HostThreadPools* _threadPool;
-
- int _mockTimeMillis;
-
- typedef map<int, ConnectionString> HostQueryTimes;
- HostQueryTimes _mockQueryTimes;
-
- struct MockHostInfo {
-
- MockHostInfo(int queryTimeMillis) :
- nextHostActiveNotify( NULL),
- waitForPrevHostIdle(false),
- queryTimeMillis(queryTimeMillis),
- hangUntilNotify( NULL),
- error(Status::OK()) {
- }
-
- Notification* nextHostActiveNotify;
-
- ConnectionString prevHost;
- unique_ptr<Notification> prevHostActiveNotify;
- bool waitForPrevHostIdle;
-
- int queryTimeMillis;
+ if (info.hangUntilNotify) {
+ info.hangUntilNotify->waitToBeNotified();
+ return StatusWith<DBClientCursor*>(ErrorCodes::InternalError, "");
+ }
- unique_ptr<DBClientConnection> conn;
- Notification* hangUntilNotify;
- Status error;
- };
+ if (!info.error.isOK()) {
+ return StatusWith<DBClientCursor*>(info.error);
+ }
- HostInfoMap _mockHostInfo;
+ //
+ // Successful mock query
+ //
- };
+ if (!info.conn) {
+ info.conn.reset(new DBClientConnection(false));
+ // Need to do a connect failure so that we get an empty MessagingPort on the conn and
+ // the host name is set.
+ string errMsg;
+ ASSERT(!info.conn->connect(HostAndPort(host.toString()), errMsg));
+ }
- QuerySpec buildSpec(StringData ns, const BSONObj& query) {
- return QuerySpec(ns.toString(), query, BSONObj(), 0, 0, 0);
+ return StatusWith<DBClientCursor*>(new DBClientCursor(info.conn.get(),
+ query.ns(),
+ query.query(),
+ query.ntoreturn(),
+ query.ntoskip(),
+ query.fieldsPtr(),
+ query.options(),
+ 0 /* batchSize */));
}
- //
- // Tests for the MultiHostQueryOp
- //
-
- TEST(MultiHostQueryOp, SingleHost) {
-
- HostThreadPools threadPool(1, true);
- MockSystemEnv mockSystem(&threadPool);
-
- ConnectionString host = uassertStatusOK(ConnectionString::parse("$host:1000"));
- vector<ConnectionString> hosts;
- hosts.push_back(host);
-
- mockSystem.addMockHostResultAt(host, 1000);
-
- MultiHostQueryOp queryOp(&mockSystem, &threadPool);
+private:
+ MockHostInfo* newMockHostResultAt(const ConnectionString& host,
+ int timeMillis,
+ const Status& error,
+ Notification* hangUntilNotify) {
+ ASSERT(_mockHostInfo.find(host) == _mockHostInfo.end());
- QuerySpec query;
- StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 2000);
+ MockHostInfo* info = new MockHostInfo(timeMillis);
+ _mockHostInfo.insert(make_pair(host, info));
+ info->error = error;
+ info->hangUntilNotify = hangUntilNotify;
- ASSERT_OK(result.getStatus());
- ASSERT(NULL != result.getValue());
- ASSERT_EQUALS(result.getValue()->originalHost(), host.toString());
- delete result.getValue();
+ linkMockTimes(host, info);
+ return info;
}
- TEST(MultiHostQueryOp, SingleHostError) {
-
- HostThreadPools threadPool(1, true);
- MockSystemEnv mockSystem(&threadPool);
-
- ConnectionString host = uassertStatusOK(ConnectionString::parse("$host:1000"));
- vector<ConnectionString> hosts;
- hosts.push_back(host);
-
- Status hostError = Status(ErrorCodes::InternalError, "");
- mockSystem.addMockHostErrorAt(host, 1000, hostError);
-
- MultiHostQueryOp queryOp(&mockSystem, &threadPool);
-
- QuerySpec query;
- StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 2000);
-
- ASSERT_EQUALS(result.getStatus().code(), hostError.code());
- }
+ void linkMockTimes(const ConnectionString& host, MockHostInfo* info) {
+ //
+ // This just basically sets up notifications between the processing of results such that
+ // the results are returned in the order defined by the _mockQueryTimes map.
+ //
+ // Idea is (second host result) waits for (first host result) thread to start and end,
+ // (third host result) waits for (second host result) thread to start and end,
+ // (fourth host result) waits for (third host result) thread to start and end,
+ // ... and so on ...
+ //
- TEST(MultiHostQueryOp, SingleHostHang) {
+ ASSERT(_mockQueryTimes.find(info->queryTimeMillis) == _mockQueryTimes.end());
- // Initialize notifier before the thread pool, otherwise we may dispose while threads are
- // active
- Notification unhangNotify;
+ HostQueryTimes::iterator prev =
+ _mockQueryTimes.insert(make_pair(info->queryTimeMillis, host)).first;
- HostThreadPools threadPool(1, true);
- MockSystemEnv mockSystem(&threadPool);
+ if (prev != _mockQueryTimes.begin())
+ --prev;
+ else
+ prev = _mockQueryTimes.end();
- ConnectionString host = uassertStatusOK(ConnectionString::parse("$host:1000"));
- vector<ConnectionString> hosts;
- hosts.push_back(host);
+ HostQueryTimes::iterator next = _mockQueryTimes.upper_bound(info->queryTimeMillis);
- mockSystem.addMockHungHostAt(host, 4000, &unhangNotify);
+ if (prev != _mockQueryTimes.end()) {
+ const ConnectionString& prevHost = prev->second;
+ MockHostInfo* prevInfo = _mockHostInfo.find(prevHost)->second;
- MultiHostQueryOp queryOp(&mockSystem, &threadPool);
+ linkToNext(prevHost, prevInfo, info);
+ }
- QuerySpec query;
- StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 2000);
- // Unhang before checking status, in case it throws
- unhangNotify.notifyOne();
+ if (next != _mockQueryTimes.end()) {
+ const ConnectionString& nextHost = next->second;
+ MockHostInfo* nextInfo = _mockHostInfo.find(nextHost)->second;
- ASSERT_EQUALS(result.getStatus().code(), ErrorCodes::NetworkTimeout);
+ linkToNext(host, info, nextInfo);
+ }
}
- TEST(MultiHostQueryOp, TwoHostResponses) {
+ void linkToNext(const ConnectionString& host, MockHostInfo* info, MockHostInfo* nextInfo) {
+ nextInfo->prevHost = host;
- HostThreadPools threadPool(1, true);
- MockSystemEnv mockSystem(&threadPool);
+ nextInfo->prevHostActiveNotify.reset(new Notification());
+ info->nextHostActiveNotify = nextInfo->prevHostActiveNotify.get();
- ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
- ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
- vector<ConnectionString> hosts;
- hosts.push_back(hostA);
- hosts.push_back(hostB);
+ nextInfo->waitForPrevHostIdle = info->hangUntilNotify == NULL;
+ }
- // Make sure we return the first response, from hostB at time 1000
- mockSystem.addMockHostResultAt(hostA, 2000);
- mockSystem.addMockHostResultAt(hostB, 1000);
+ // Not owned here, needed to allow ordering of mock queries
+ HostThreadPools* _threadPool;
- MultiHostQueryOp queryOp(&mockSystem, &threadPool);
+ int _mockTimeMillis;
- QuerySpec query;
- StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 3000);
+ typedef map<int, ConnectionString> HostQueryTimes;
+ HostQueryTimes _mockQueryTimes;
- ASSERT_OK(result.getStatus());
- ASSERT(NULL != result.getValue());
- ASSERT_EQUALS(result.getValue()->originalHost(), hostB.toString());
- delete result.getValue();
- }
+ struct MockHostInfo {
+ MockHostInfo(int queryTimeMillis)
+ : nextHostActiveNotify(NULL),
+ waitForPrevHostIdle(false),
+ queryTimeMillis(queryTimeMillis),
+ hangUntilNotify(NULL),
+ error(Status::OK()) {}
- TEST(MultiHostQueryOp, TwoHostsOneErrorResponse) {
+ Notification* nextHostActiveNotify;
- HostThreadPools threadPool(1, true);
- MockSystemEnv mockSystem(&threadPool);
+ ConnectionString prevHost;
+ unique_ptr<Notification> prevHostActiveNotify;
+ bool waitForPrevHostIdle;
- ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
- ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
- vector<ConnectionString> hosts;
- hosts.push_back(hostA);
- hosts.push_back(hostB);
+ int queryTimeMillis;
- // The first response is a host error, the second is a successful result
- Status hostError = Status(ErrorCodes::InternalError, "");
- mockSystem.addMockHostErrorAt(hostA, 1000, hostError);
- mockSystem.addMockHostResultAt(hostB, 2000);
-
- MultiHostQueryOp queryOp(&mockSystem, &threadPool);
+ unique_ptr<DBClientConnection> conn;
+ Notification* hangUntilNotify;
+ Status error;
+ };
- QuerySpec query;
- StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 3000);
+ HostInfoMap _mockHostInfo;
+};
- ASSERT_OK(result.getStatus());
- ASSERT(NULL != result.getValue());
- ASSERT_EQUALS(result.getValue()->originalHost(), hostB.toString());
- delete result.getValue();
- }
+QuerySpec buildSpec(StringData ns, const BSONObj& query) {
+ return QuerySpec(ns.toString(), query, BSONObj(), 0, 0, 0);
+}
- TEST(MultiHostQueryOp, TwoHostsBothErrors) {
+//
+// Tests for the MultiHostQueryOp
+//
- HostThreadPools threadPool(1, true);
- MockSystemEnv mockSystem(&threadPool);
+TEST(MultiHostQueryOp, SingleHost) {
+ HostThreadPools threadPool(1, true);
+ MockSystemEnv mockSystem(&threadPool);
- ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
- ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
- vector<ConnectionString> hosts;
- hosts.push_back(hostA);
- hosts.push_back(hostB);
+ ConnectionString host = uassertStatusOK(ConnectionString::parse("$host:1000"));
+ vector<ConnectionString> hosts;
+ hosts.push_back(host);
- // Both responses are errors
- Status hostError = Status(ErrorCodes::InternalError, "");
- mockSystem.addMockHostErrorAt(hostA, 1000, hostError);
- mockSystem.addMockHostErrorAt(hostB, 2000, hostError);
+ mockSystem.addMockHostResultAt(host, 1000);
- MultiHostQueryOp queryOp(&mockSystem, &threadPool);
+ MultiHostQueryOp queryOp(&mockSystem, &threadPool);
- QuerySpec query;
- StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 3000);
+ QuerySpec query;
+ StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 2000);
- ASSERT_EQUALS(result.getStatus().code(), ErrorCodes::MultipleErrorsOccurred);
- }
+ ASSERT_OK(result.getStatus());
+ ASSERT(NULL != result.getValue());
+ ASSERT_EQUALS(result.getValue()->originalHost(), host.toString());
+ delete result.getValue();
+}
- TEST(MultiHostQueryOp, TwoHostsOneHang) {
+TEST(MultiHostQueryOp, SingleHostError) {
+ HostThreadPools threadPool(1, true);
+ MockSystemEnv mockSystem(&threadPool);
- // Initialize notifier before the thread pool
- Notification unhangNotify;
+ ConnectionString host = uassertStatusOK(ConnectionString::parse("$host:1000"));
+ vector<ConnectionString> hosts;
+ hosts.push_back(host);
- HostThreadPools threadPool(1, true);
- MockSystemEnv mockSystem(&threadPool);
+ Status hostError = Status(ErrorCodes::InternalError, "");
+ mockSystem.addMockHostErrorAt(host, 1000, hostError);
- ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
- ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
- vector<ConnectionString> hosts;
- hosts.push_back(hostA);
- hosts.push_back(hostB);
+ MultiHostQueryOp queryOp(&mockSystem, &threadPool);
- // One host hangs
- mockSystem.addMockHungHostAt(hostA, 1000, &unhangNotify);
- mockSystem.addMockHostResultAt(hostB, 2000);
+ QuerySpec query;
+ StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 2000);
- MultiHostQueryOp queryOp(&mockSystem, &threadPool);
+ ASSERT_EQUALS(result.getStatus().code(), hostError.code());
+}
- QuerySpec query;
- StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 3000);
- // Unhang before checking status, in case it throws
- unhangNotify.notifyOne();
+TEST(MultiHostQueryOp, SingleHostHang) {
+ // Initialize notifier before the thread pool, otherwise we may dispose while threads are
+ // active
+ Notification unhangNotify;
- ASSERT_OK(result.getStatus());
- ASSERT(NULL != result.getValue());
- ASSERT_EQUALS(result.getValue()->originalHost(), hostB.toString());
- delete result.getValue();
- }
+ HostThreadPools threadPool(1, true);
+ MockSystemEnv mockSystem(&threadPool);
- TEST(MultiHostQueryOp, TwoHostsOneHangOneError) {
+ ConnectionString host = uassertStatusOK(ConnectionString::parse("$host:1000"));
+ vector<ConnectionString> hosts;
+ hosts.push_back(host);
- // Initialize notifier before the thread pool
- Notification unhangNotify;
+ mockSystem.addMockHungHostAt(host, 4000, &unhangNotify);
- HostThreadPools threadPool(1, true);
- MockSystemEnv mockSystem(&threadPool);
+ MultiHostQueryOp queryOp(&mockSystem, &threadPool);
- ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
- ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
- vector<ConnectionString> hosts;
- hosts.push_back(hostA);
- hosts.push_back(hostB);
+ QuerySpec query;
+ StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 2000);
+ // Unhang before checking status, in case it throws
+ unhangNotify.notifyOne();
- // One host hangs, one host has an error (at the mock timeout point so the query finishes)
- Status hostError = Status(ErrorCodes::InternalError, "");
- mockSystem.addMockHungHostAt(hostA, 1000, &unhangNotify);
- mockSystem.addMockHostErrorAt(hostB, 3000, hostError);
- mockSystem.addMockTimestepAt(4000);
+ ASSERT_EQUALS(result.getStatus().code(), ErrorCodes::NetworkTimeout);
+}
- MultiHostQueryOp queryOp(&mockSystem, &threadPool);
+TEST(MultiHostQueryOp, TwoHostResponses) {
+ HostThreadPools threadPool(1, true);
+ MockSystemEnv mockSystem(&threadPool);
- QuerySpec query;
- StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 4000);
- // Unhang before checking status, in case it throws
- unhangNotify.notifyOne();
+ ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
+ ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
+ vector<ConnectionString> hosts;
+ hosts.push_back(hostA);
+ hosts.push_back(hostB);
- ASSERT_EQUALS(result.getStatus().code(), hostError.code());
- }
+ // Make sure we return the first response, from hostB at time 1000
+ mockSystem.addMockHostResultAt(hostA, 2000);
+ mockSystem.addMockHostResultAt(hostB, 1000);
- TEST(MultiHostQueryOp, ThreeHostsOneHang) {
- // Initialize notifier before the thread pool
- Notification unhangNotify;
+ MultiHostQueryOp queryOp(&mockSystem, &threadPool);
- HostThreadPools threadPool(1, true);
- MockSystemEnv mockSystem(&threadPool);
+ QuerySpec query;
+ StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 3000);
- ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
- ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
- ConnectionString hostC = uassertStatusOK(ConnectionString::parse("$hostC:1000"));
+ ASSERT_OK(result.getStatus());
+ ASSERT(NULL != result.getValue());
+ ASSERT_EQUALS(result.getValue()->originalHost(), hostB.toString());
+ delete result.getValue();
+}
- vector<ConnectionString> hosts;
- hosts.push_back(hostA);
- hosts.push_back(hostB);
- hosts.push_back(hostC);
+TEST(MultiHostQueryOp, TwoHostsOneErrorResponse) {
+ HostThreadPools threadPool(1, true);
+ MockSystemEnv mockSystem(&threadPool);
- // Host A hangs
- mockSystem.addMockHungHostAt(hostA, 1000, &unhangNotify);
- mockSystem.addMockHostResultAt(hostB, 2000);
- mockSystem.addMockHostResultAt(hostC, 3000);
+ ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
+ ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
+ vector<ConnectionString> hosts;
+ hosts.push_back(hostA);
+ hosts.push_back(hostB);
- MultiHostQueryOp queryOp(&mockSystem, &threadPool);
+ // The first response is a host error, the second is a successful result
+ Status hostError = Status(ErrorCodes::InternalError, "");
+ mockSystem.addMockHostErrorAt(hostA, 1000, hostError);
+ mockSystem.addMockHostResultAt(hostB, 2000);
- QuerySpec query;
- StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 4000);
+ MultiHostQueryOp queryOp(&mockSystem, &threadPool);
- // Unhang before checking status, in case it throws
- unhangNotify.notifyOne();
+ QuerySpec query;
+ StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 3000);
- ASSERT_OK(result.getStatus());
- ASSERT(NULL != result.getValue());
+ ASSERT_OK(result.getStatus());
+ ASSERT(NULL != result.getValue());
+ ASSERT_EQUALS(result.getValue()->originalHost(), hostB.toString());
+ delete result.getValue();
+}
- // We should never have results from hostA
- ASSERT_NOT_EQUALS(result.getValue()->originalHost(), hostA.toString());
+TEST(MultiHostQueryOp, TwoHostsBothErrors) {
+ HostThreadPools threadPool(1, true);
+ MockSystemEnv mockSystem(&threadPool);
- delete result.getValue();
- }
+ ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
+ ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
+ vector<ConnectionString> hosts;
+ hosts.push_back(hostA);
+ hosts.push_back(hostB);
- TEST(MultiHostQueryOp, ThreeHostsTwoErrors) {
+ // Both responses are errors
+ Status hostError = Status(ErrorCodes::InternalError, "");
+ mockSystem.addMockHostErrorAt(hostA, 1000, hostError);
+ mockSystem.addMockHostErrorAt(hostB, 2000, hostError);
- // Initialize notifier before the thread pool
- Notification unhangNotify;
+ MultiHostQueryOp queryOp(&mockSystem, &threadPool);
- HostThreadPools threadPool(1, true);
- MockSystemEnv mockSystem(&threadPool);
+ QuerySpec query;
+ StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 3000);
- ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
- ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
- ConnectionString hostC = uassertStatusOK(ConnectionString::parse("$hostC:1000"));
- vector<ConnectionString> hosts;
- hosts.push_back(hostA);
- hosts.push_back(hostB);
- hosts.push_back(hostC);
+ ASSERT_EQUALS(result.getStatus().code(), ErrorCodes::MultipleErrorsOccurred);
+}
- // One host hangs, two hosts have errors (finish at mock timeout point so query ends)
- Status hostError = Status(ErrorCodes::InternalError, "");
- mockSystem.addMockHungHostAt(hostA, 1000, &unhangNotify);
- mockSystem.addMockHostErrorAt(hostB, 4000, hostError);
- mockSystem.addMockHostErrorAt(hostC, 2000, hostError);
- mockSystem.addMockTimestepAt(5000);
+TEST(MultiHostQueryOp, TwoHostsOneHang) {
+ // Initialize notifier before the thread pool
+ Notification unhangNotify;
- MultiHostQueryOp queryOp(&mockSystem, &threadPool);
+ HostThreadPools threadPool(1, true);
+ MockSystemEnv mockSystem(&threadPool);
- QuerySpec query;
- StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 5000);
- // Unhang before checking status, in case it throws
- unhangNotify.notifyOne();
+ ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
+ ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
+ vector<ConnectionString> hosts;
+ hosts.push_back(hostA);
+ hosts.push_back(hostB);
- ASSERT_EQUALS(result.getStatus().code(), ErrorCodes::MultipleErrorsOccurred);
- }
+ // One host hangs
+ mockSystem.addMockHungHostAt(hostA, 1000, &unhangNotify);
+ mockSystem.addMockHostResultAt(hostB, 2000);
+
+ MultiHostQueryOp queryOp(&mockSystem, &threadPool);
+
+ QuerySpec query;
+ StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 3000);
+ // Unhang before checking status, in case it throws
+ unhangNotify.notifyOne();
+
+ ASSERT_OK(result.getStatus());
+ ASSERT(NULL != result.getValue());
+ ASSERT_EQUALS(result.getValue()->originalHost(), hostB.toString());
+ delete result.getValue();
+}
- TEST(MultiHostQueryOp, ThreeHostsOneHangOneError) {
+TEST(MultiHostQueryOp, TwoHostsOneHangOneError) {
+ // Initialize notifier before the thread pool
+ Notification unhangNotify;
- // Initialize notifier before the thread pool
- Notification unhangNotify;
+ HostThreadPools threadPool(1, true);
+ MockSystemEnv mockSystem(&threadPool);
- HostThreadPools threadPool(1, true);
- MockSystemEnv mockSystem(&threadPool);
+ ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
+ ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
+ vector<ConnectionString> hosts;
+ hosts.push_back(hostA);
+ hosts.push_back(hostB);
+
+ // One host hangs, one host has an error (at the mock timeout point so the query finishes)
+ Status hostError = Status(ErrorCodes::InternalError, "");
+ mockSystem.addMockHungHostAt(hostA, 1000, &unhangNotify);
+ mockSystem.addMockHostErrorAt(hostB, 3000, hostError);
+ mockSystem.addMockTimestepAt(4000);
+
+ MultiHostQueryOp queryOp(&mockSystem, &threadPool);
+
+ QuerySpec query;
+ StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 4000);
+ // Unhang before checking status, in case it throws
+ unhangNotify.notifyOne();
+
+ ASSERT_EQUALS(result.getStatus().code(), hostError.code());
+}
+
+TEST(MultiHostQueryOp, ThreeHostsOneHang) {
+ // Initialize notifier before the thread pool
+ Notification unhangNotify;
+
+ HostThreadPools threadPool(1, true);
+ MockSystemEnv mockSystem(&threadPool);
+
+ ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
+ ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
+ ConnectionString hostC = uassertStatusOK(ConnectionString::parse("$hostC:1000"));
+
+ vector<ConnectionString> hosts;
+ hosts.push_back(hostA);
+ hosts.push_back(hostB);
+ hosts.push_back(hostC);
+
+ // Host A hangs
+ mockSystem.addMockHungHostAt(hostA, 1000, &unhangNotify);
+ mockSystem.addMockHostResultAt(hostB, 2000);
+ mockSystem.addMockHostResultAt(hostC, 3000);
+
+ MultiHostQueryOp queryOp(&mockSystem, &threadPool);
+
+ QuerySpec query;
+ StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 4000);
+
+ // Unhang before checking status, in case it throws
+ unhangNotify.notifyOne();
+
+ ASSERT_OK(result.getStatus());
+ ASSERT(NULL != result.getValue());
- ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
- ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
- ConnectionString hostC = uassertStatusOK(ConnectionString::parse("$hostC:1000"));
- vector<ConnectionString> hosts;
- hosts.push_back(hostA);
- hosts.push_back(hostB);
- hosts.push_back(hostC);
+ // We should never have results from hostA
+ ASSERT_NOT_EQUALS(result.getValue()->originalHost(), hostA.toString());
- // One host hangs, two hosts have errors (finish at mock timeout point so query ends)
- Status hostError = Status(ErrorCodes::InternalError, "");
- mockSystem.addMockHungHostAt(hostA, 1000, &unhangNotify);
- mockSystem.addMockHostErrorAt(hostB, 2000, hostError);
- mockSystem.addMockHostResultAt(hostC, 3000);
+ delete result.getValue();
+}
+
+TEST(MultiHostQueryOp, ThreeHostsTwoErrors) {
+ // Initialize notifier before the thread pool
+ Notification unhangNotify;
+
+ HostThreadPools threadPool(1, true);
+ MockSystemEnv mockSystem(&threadPool);
- MultiHostQueryOp queryOp(&mockSystem, &threadPool);
+ ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
+ ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
+ ConnectionString hostC = uassertStatusOK(ConnectionString::parse("$hostC:1000"));
+ vector<ConnectionString> hosts;
+ hosts.push_back(hostA);
+ hosts.push_back(hostB);
+ hosts.push_back(hostC);
- QuerySpec query;
- StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 4000);
- // Unhang before checking status, in case it throws
- unhangNotify.notifyOne();
+ // One host hangs, two hosts have errors (finish at mock timeout point so query ends)
+ Status hostError = Status(ErrorCodes::InternalError, "");
+ mockSystem.addMockHungHostAt(hostA, 1000, &unhangNotify);
+ mockSystem.addMockHostErrorAt(hostB, 4000, hostError);
+ mockSystem.addMockHostErrorAt(hostC, 2000, hostError);
+ mockSystem.addMockTimestepAt(5000);
- ASSERT_OK(result.getStatus());
- ASSERT(NULL != result.getValue());
- ASSERT_EQUALS(result.getValue()->originalHost(), hostC.toString());
- delete result.getValue();
- }
+ MultiHostQueryOp queryOp(&mockSystem, &threadPool);
+
+ QuerySpec query;
+ StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 5000);
+ // Unhang before checking status, in case it throws
+ unhangNotify.notifyOne();
+
+ ASSERT_EQUALS(result.getStatus().code(), ErrorCodes::MultipleErrorsOccurred);
+}
- TEST(MultiHostQueryOp, TwoHostsOneHangUnscoped) {
+TEST(MultiHostQueryOp, ThreeHostsOneHangOneError) {
+ // Initialize notifier before the thread pool
+ Notification unhangNotify;
- // Initialize notifier before the thread pool
- Notification unhangNotify;
+ HostThreadPools threadPool(1, true);
+ MockSystemEnv mockSystem(&threadPool);
- // Create a thread pool which detaches itself from outstanding work on cleanup
- unique_ptr<HostThreadPools> threadPool(new HostThreadPools(1, false));
- MockSystemEnv mockSystem(threadPool.get());
+ ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
+ ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
+ ConnectionString hostC = uassertStatusOK(ConnectionString::parse("$hostC:1000"));
+ vector<ConnectionString> hosts;
+ hosts.push_back(hostA);
+ hosts.push_back(hostB);
+ hosts.push_back(hostC);
- ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
- ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
- vector<ConnectionString> hosts;
- hosts.push_back(hostA);
- hosts.push_back(hostB);
+ // One host hangs, two hosts have errors (finish at mock timeout point so query ends)
+ Status hostError = Status(ErrorCodes::InternalError, "");
+ mockSystem.addMockHungHostAt(hostA, 1000, &unhangNotify);
+ mockSystem.addMockHostErrorAt(hostB, 2000, hostError);
+ mockSystem.addMockHostResultAt(hostC, 3000);
- // One host hangs
- mockSystem.addMockHungHostAt(hostA, 1000, &unhangNotify);
- mockSystem.addMockHostResultAt(hostB, 2000);
+ MultiHostQueryOp queryOp(&mockSystem, &threadPool);
- MultiHostQueryOp queryOp(&mockSystem, threadPool.get());
+ QuerySpec query;
+ StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 4000);
+ // Unhang before checking status, in case it throws
+ unhangNotify.notifyOne();
- QuerySpec query;
- StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 3000);
+ ASSERT_OK(result.getStatus());
+ ASSERT(NULL != result.getValue());
+ ASSERT_EQUALS(result.getValue()->originalHost(), hostC.toString());
+ delete result.getValue();
+}
- // Clean up the thread pool
- mockSystem.setHostThreadPools( NULL);
- threadPool.reset();
+TEST(MultiHostQueryOp, TwoHostsOneHangUnscoped) {
+ // Initialize notifier before the thread pool
+ Notification unhangNotify;
- // Unhang before checking status, in case it throws
- unhangNotify.notifyOne();
+ // Create a thread pool which detaches itself from outstanding work on cleanup
+ unique_ptr<HostThreadPools> threadPool(new HostThreadPools(1, false));
+ MockSystemEnv mockSystem(threadPool.get());
- ASSERT_OK(result.getStatus());
- ASSERT(NULL != result.getValue());
- ASSERT_EQUALS(result.getValue()->originalHost(), hostB.toString());
- delete result.getValue();
+ ConnectionString hostA = uassertStatusOK(ConnectionString::parse("$hostA:1000"));
+ ConnectionString hostB = uassertStatusOK(ConnectionString::parse("$hostB:1000"));
+ vector<ConnectionString> hosts;
+ hosts.push_back(hostA);
+ hosts.push_back(hostB);
- // Make sure we get the next result
- result = queryOp.waitForNextResult(Date_t::fromMillisSinceEpoch(4000));
+ // One host hangs
+ mockSystem.addMockHungHostAt(hostA, 1000, &unhangNotify);
+ mockSystem.addMockHostResultAt(hostB, 2000);
- ASSERT_EQUALS(result.getStatus().code(), ErrorCodes::InternalError);
- }
+ MultiHostQueryOp queryOp(&mockSystem, threadPool.get());
-} // unnamed namespace
+ QuerySpec query;
+ StatusWith<DBClientCursor*> result = queryOp.queryAny(hosts, query, 3000);
+
+ // Clean up the thread pool
+ mockSystem.setHostThreadPools(NULL);
+ threadPool.reset();
+
+ // Unhang before checking status, in case it throws
+ unhangNotify.notifyOne();
+
+ ASSERT_OK(result.getStatus());
+ ASSERT(NULL != result.getValue());
+ ASSERT_EQUALS(result.getValue()->originalHost(), hostB.toString());
+ delete result.getValue();
+
+ // Make sure we get the next result
+ result = queryOp.waitForNextResult(Date_t::fromMillisSinceEpoch(4000));
+
+ ASSERT_EQUALS(result.getStatus().code(), ErrorCodes::InternalError);
+}
+
+} // unnamed namespace
diff --git a/src/mongo/s/client/scc_fast_query_handler.cpp b/src/mongo/s/client/scc_fast_query_handler.cpp
index f09dd767a21..20a8f1a0193 100644
--- a/src/mongo/s/client/scc_fast_query_handler.cpp
+++ b/src/mongo/s/client/scc_fast_query_handler.cpp
@@ -45,214 +45,196 @@
namespace mongo {
- using std::unique_ptr;
- using std::endl;
- using std::string;
- using std::vector;
-
- /**
- * This parameter turns on fastest config reads for auth data only - *.system.users collections
- * and the "usersInfo" command. This should be enough to prevent a non-responsive config from
- * hanging other operations in a cluster.
- */
- MONGO_EXPORT_SERVER_PARAMETER(internalSCCAllowFastestAuthConfigReads, bool, false);
-
- /**
- * TESTING ONLY.
- *
- * This parameter turns on fastest config reads for *all* config.* collections except those
- * deemed extremely critical (config.version, config.locks).
- *
- * NOT FOR PRODUCTION USE.
- */
- MONGO_EXPORT_SERVER_PARAMETER(internalSCCAllowFastestMetadataConfigReads, bool, false);
+using std::unique_ptr;
+using std::endl;
+using std::string;
+using std::vector;
- //
- // The shared environment for MultiHostQueries
- //
-
- namespace {
-
- class MultiQueryEnv : public MultiHostQueryOp::SystemEnv {
- public:
-
- virtual ~MultiQueryEnv() {
- }
-
- Date_t currentTimeMillis();
+/**
+ * This parameter turns on fastest config reads for auth data only - *.system.users collections
+ * and the "usersInfo" command. This should be enough to prevent a non-responsive config from
+ * hanging other operations in a cluster.
+ */
+MONGO_EXPORT_SERVER_PARAMETER(internalSCCAllowFastestAuthConfigReads, bool, false);
- StatusWith<DBClientCursor*> doBlockingQuery(const ConnectionString& host,
- const QuerySpec& query);
- };
+/**
+ * TESTING ONLY.
+ *
+ * This parameter turns on fastest config reads for *all* config.* collections except those
+ * deemed extremely critical (config.version, config.locks).
+ *
+ * NOT FOR PRODUCTION USE.
+ */
+MONGO_EXPORT_SERVER_PARAMETER(internalSCCAllowFastestMetadataConfigReads, bool, false);
- StatusWith<DBClientCursor*> MultiQueryEnv::doBlockingQuery(const ConnectionString& host,
- const QuerySpec& query) {
+//
+// The shared environment for MultiHostQueries
+//
- //
- // Note that this function may be active during shutdown. This means that we must
- // handle connection pool shutdown exceptions (uasserts). The results of this
- // operation must then be correctly discarded by the calling thread.
- //
+namespace {
- unique_ptr<DBClientCursor> cursor;
+class MultiQueryEnv : public MultiHostQueryOp::SystemEnv {
+public:
+ virtual ~MultiQueryEnv() {}
- try {
+ Date_t currentTimeMillis();
- ScopedDbConnection conn(host, 30.0 /* timeout secs */);
+ StatusWith<DBClientCursor*> doBlockingQuery(const ConnectionString& host,
+ const QuerySpec& query);
+};
- cursor = conn->query(query.ns(),
- query.filter(),
- query.ntoreturn(),
- query.ntoskip(),
- query.fieldsPtr(),
- query.options(),
- 0);
+StatusWith<DBClientCursor*> MultiQueryEnv::doBlockingQuery(const ConnectionString& host,
+ const QuerySpec& query) {
+ //
+ // Note that this function may be active during shutdown. This means that we must
+ // handle connection pool shutdown exceptions (uasserts). The results of this
+ // operation must then be correctly discarded by the calling thread.
+ //
- if ( NULL == cursor.get()) {
+ unique_ptr<DBClientCursor> cursor;
- // Confusingly, exceptions here are written to the log, not thrown
+ try {
+ ScopedDbConnection conn(host, 30.0 /* timeout secs */);
- StringBuilder builder;
- builder << "error querying server " << host.toString()
- << ", could not create cursor for query";
+ cursor = conn->query(query.ns(),
+ query.filter(),
+ query.ntoreturn(),
+ query.ntoskip(),
+ query.fieldsPtr(),
+ query.options(),
+ 0);
- warning() << builder.str() << endl;
- return StatusWith<DBClientCursor*>(ErrorCodes::HostUnreachable, builder.str());
- }
+ if (NULL == cursor.get()) {
+ // Confusingly, exceptions here are written to the log, not thrown
- // Confusingly, this *detaches* the cursor from the connection it was established
- // on, further getMore calls will use a (potentially different) ScopedDbConnection.
- //
- // Calls done() too.
- cursor->attach(&conn);
- }
- catch (const DBException& ex) {
- return StatusWith<DBClientCursor*>(ex.toStatus());
- }
+ StringBuilder builder;
+ builder << "error querying server " << host.toString()
+ << ", could not create cursor for query";
- return StatusWith<DBClientCursor*>(cursor.release());
+ warning() << builder.str() << endl;
+ return StatusWith<DBClientCursor*>(ErrorCodes::HostUnreachable, builder.str());
}
- Date_t MultiQueryEnv::currentTimeMillis() {
- return jsTime();
- }
- }
-
- // Shared networking environment which executes queries.
- // NOTE: This environment must stay in scope as long as per-host threads are executing queries -
- // i.e. for the lifetime of the server.
- // Once the thread pools are disposed and connections shut down, the per-host threads should
- // be self-contained and correctly shut down after discarding the results.
- static MultiQueryEnv* _multiQueryEnv;
-
- namespace {
-
+ // Confusingly, this *detaches* the cursor from the connection it was established
+ // on, further getMore calls will use a (potentially different) ScopedDbConnection.
//
- // Create the shared multi-query environment at startup
- //
-
- MONGO_INITIALIZER(InitMultiQueryEnv)(InitializerContext* context) {
- // Leaked intentionally
- _multiQueryEnv = new MultiQueryEnv();
- return Status::OK();
- }
+ // Calls done() too.
+ cursor->attach(&conn);
+ } catch (const DBException& ex) {
+ return StatusWith<DBClientCursor*>(ex.toStatus());
}
- //
- // Per-SCC handling of queries
- //
+ return StatusWith<DBClientCursor*>(cursor.release());
+}
- SCCFastQueryHandler::SCCFastQueryHandler() :
- _queryThreads(1, false) {
- }
+Date_t MultiQueryEnv::currentTimeMillis() {
+ return jsTime();
+}
+}
- bool SCCFastQueryHandler::canHandleQuery(const string& ns, Query query) {
+// Shared networking environment which executes queries.
+// NOTE: This environment must stay in scope as long as per-host threads are executing queries -
+// i.e. for the lifetime of the server.
+// Once the thread pools are disposed and connections shut down, the per-host threads should
+// be self-contained and correctly shut down after discarding the results.
+static MultiQueryEnv* _multiQueryEnv;
- if (!internalSCCAllowFastestAuthConfigReads &&
- !internalSCCAllowFastestMetadataConfigReads) {
- return false;
- }
+namespace {
- //
- // More operations can be added here
- //
- // NOTE: Not all operations actually pass through the SCC _queryOnActive path - notable
- // exceptions include anything related to direct query ops and direct operations for
- // connection maintenance.
- //
+//
+// Create the shared multi-query environment at startup
+//
- NamespaceString nss(ns);
- if (nss.isCommand()) {
- BSONObj cmdObj = query.getFilter();
- string cmdName = cmdObj.firstElement().fieldName();
- if (cmdName == "usersInfo")
- return true;
- }
- else if (nss.coll() == "system.users") {
- return true;
- }
+MONGO_INITIALIZER(InitMultiQueryEnv)(InitializerContext* context) {
+ // Leaked intentionally
+ _multiQueryEnv = new MultiQueryEnv();
+ return Status::OK();
+}
+}
- //
- // Allow fastest config reads for all collections except for those involved in locks and
- // cluster versioning.
- //
+//
+// Per-SCC handling of queries
+//
- if (!internalSCCAllowFastestMetadataConfigReads)
- return false;
+SCCFastQueryHandler::SCCFastQueryHandler() : _queryThreads(1, false) {}
- if (nss.db() != "config")
- return false;
+bool SCCFastQueryHandler::canHandleQuery(const string& ns, Query query) {
+ if (!internalSCCAllowFastestAuthConfigReads && !internalSCCAllowFastestMetadataConfigReads) {
+ return false;
+ }
- if (nss.coll() != "version" && nss.coll() != "locks" && nss.coll() != "lockpings") {
- return true;
- }
+ //
+ // More operations can be added here
+ //
+ // NOTE: Not all operations actually pass through the SCC _queryOnActive path - notable
+ // exceptions include anything related to direct query ops and direct operations for
+ // connection maintenance.
+ //
- return false;
+ NamespaceString nss(ns);
+ if (nss.isCommand()) {
+ BSONObj cmdObj = query.getFilter();
+ string cmdName = cmdObj.firstElement().fieldName();
+ if (cmdName == "usersInfo")
+ return true;
+ } else if (nss.coll() == "system.users") {
+ return true;
}
- static vector<ConnectionString> getHosts(const vector<string> hostStrings) {
+ //
+ // Allow fastest config reads for all collections except for those involved in locks and
+ // cluster versioning.
+ //
- vector<ConnectionString> hosts;
- for (vector<string>::const_iterator it = hostStrings.begin(); it != hostStrings.end();
- ++it) {
+ if (!internalSCCAllowFastestMetadataConfigReads)
+ return false;
- string errMsg;
- ConnectionString host;
- hosts.push_back(ConnectionString::parse(*it, errMsg));
- invariant( hosts.back().type() != ConnectionString::INVALID );
- }
+ if (nss.db() != "config")
+ return false;
- return hosts;
+ if (nss.coll() != "version" && nss.coll() != "locks" && nss.coll() != "lockpings") {
+ return true;
}
- unique_ptr<DBClientCursor> SCCFastQueryHandler::handleQuery(const vector<string>& hostStrings,
- const string& ns,
- Query query,
- int nToReturn,
- int nToSkip,
- const BSONObj* fieldsToReturn,
- int queryOptions,
- int batchSize) {
-
- MultiHostQueryOp queryOp(_multiQueryEnv, &_queryThreads);
-
- QuerySpec querySpec(ns,
- query.obj,
- fieldsToReturn ? *fieldsToReturn : BSONObj(),
- nToSkip,
- nToReturn,
- queryOptions);
-
- // TODO: Timeout must be passed down here as well - 30s timeout may not be applicable for
- // all operations handled.
- StatusWith<DBClientCursor*> status = queryOp.queryAny(getHosts(hostStrings),
- querySpec,
- 30 * 1000);
- uassertStatusOK(status.getStatus());
-
- unique_ptr<DBClientCursor> cursor(status.getValue());
- return cursor;
+ return false;
+}
+
+static vector<ConnectionString> getHosts(const vector<string> hostStrings) {
+ vector<ConnectionString> hosts;
+ for (vector<string>::const_iterator it = hostStrings.begin(); it != hostStrings.end(); ++it) {
+ string errMsg;
+ ConnectionString host;
+ hosts.push_back(ConnectionString::parse(*it, errMsg));
+ invariant(hosts.back().type() != ConnectionString::INVALID);
}
+ return hosts;
}
+unique_ptr<DBClientCursor> SCCFastQueryHandler::handleQuery(const vector<string>& hostStrings,
+ const string& ns,
+ Query query,
+ int nToReturn,
+ int nToSkip,
+ const BSONObj* fieldsToReturn,
+ int queryOptions,
+ int batchSize) {
+ MultiHostQueryOp queryOp(_multiQueryEnv, &_queryThreads);
+
+ QuerySpec querySpec(ns,
+ query.obj,
+ fieldsToReturn ? *fieldsToReturn : BSONObj(),
+ nToSkip,
+ nToReturn,
+ queryOptions);
+
+ // TODO: Timeout must be passed down here as well - 30s timeout may not be applicable for
+ // all operations handled.
+ StatusWith<DBClientCursor*> status =
+ queryOp.queryAny(getHosts(hostStrings), querySpec, 30 * 1000);
+ uassertStatusOK(status.getStatus());
+
+ unique_ptr<DBClientCursor> cursor(status.getValue());
+ return cursor;
+}
+}
diff --git a/src/mongo/s/client/scc_fast_query_handler.h b/src/mongo/s/client/scc_fast_query_handler.h
index bc942556389..c2079e717ff 100644
--- a/src/mongo/s/client/scc_fast_query_handler.h
+++ b/src/mongo/s/client/scc_fast_query_handler.h
@@ -33,44 +33,40 @@
namespace mongo {
- /**
- * Query handler that plugs in to a SyncClusterConnection and allows query on fastest host
- * (if enabled).
- *
- * Glue code which shields the MultiHostQuery and server parameters from the separate client
- * module which knows about neither.
- *
- * There is a *single* SCCFastQueryHandler for every SCC. Each SCCFastQueryHandler contains
- * its own thread pool (lazily initialized) so that at maximum there is a thread-per-SCC-host
- * and this thread may have an open connection to the host until it times out.
- * If using the "fastestConfigReads" options, clients must be ready for the additional thread
- * and connection load when configs are slow.
- */
- class SCCFastQueryHandler : public SyncClusterConnection::QueryHandler {
- public:
-
- SCCFastQueryHandler();
-
- virtual ~SCCFastQueryHandler() {
- }
-
- virtual bool canHandleQuery(const std::string& ns, Query query);
+/**
+ * Query handler that plugs in to a SyncClusterConnection and allows query on fastest host
+ * (if enabled).
+ *
+ * Glue code which shields the MultiHostQuery and server parameters from the separate client
+ * module which knows about neither.
+ *
+ * There is a *single* SCCFastQueryHandler for every SCC. Each SCCFastQueryHandler contains
+ * its own thread pool (lazily initialized) so that at maximum there is a thread-per-SCC-host
+ * and this thread may have an open connection to the host until it times out.
+ * If using the "fastestConfigReads" options, clients must be ready for the additional thread
+ * and connection load when configs are slow.
+ */
+class SCCFastQueryHandler : public SyncClusterConnection::QueryHandler {
+public:
+ SCCFastQueryHandler();
- virtual std::unique_ptr<DBClientCursor> handleQuery(const std::vector<std::string>& hostStrings,
- const std::string &ns,
- Query query,
- int nToReturn,
- int nToSkip,
- const BSONObj *fieldsToReturn,
- int queryOptions,
- int batchSize);
+ virtual ~SCCFastQueryHandler() {}
- private:
+ virtual bool canHandleQuery(const std::string& ns, Query query);
- // The thread pool itself is scoped to the handler and SCC, and lazily creates threads
- // per-host as needed. This ensures query starvation cannot occur due to other active
- // client threads - though a thread must be created for every client.
- HostThreadPools _queryThreads;
- };
+ virtual std::unique_ptr<DBClientCursor> handleQuery(const std::vector<std::string>& hostStrings,
+ const std::string& ns,
+ Query query,
+ int nToReturn,
+ int nToSkip,
+ const BSONObj* fieldsToReturn,
+ int queryOptions,
+ int batchSize);
+private:
+ // The thread pool itself is scoped to the handler and SCC, and lazily creates threads
+ // per-host as needed. This ensures query starvation cannot occur due to other active
+ // client threads - though a thread must be created for every client.
+ HostThreadPools _queryThreads;
+};
}
diff --git a/src/mongo/s/client/shard.cpp b/src/mongo/s/client/shard.cpp
index 54626a59321..767c7f7a7cc 100644
--- a/src/mongo/s/client/shard.cpp
+++ b/src/mongo/s/client/shard.cpp
@@ -48,149 +48,141 @@
namespace mongo {
- using std::string;
- using std::stringstream;
- using std::vector;
-
- Shard::Shard(const ShardId& id,
- const ConnectionString& connStr,
- std::unique_ptr<RemoteCommandTargeter> targeter)
- : _id(id),
- _cs(connStr),
- _targeter(std::move(targeter)) {
-
+using std::string;
+using std::stringstream;
+using std::vector;
+
+Shard::Shard(const ShardId& id,
+ const ConnectionString& connStr,
+ std::unique_ptr<RemoteCommandTargeter> targeter)
+ : _id(id), _cs(connStr), _targeter(std::move(targeter)) {}
+
+Shard::~Shard() = default;
+
+ShardPtr Shard::lookupRSName(const string& name) {
+ return grid.shardRegistry()->lookupRSName(name);
+}
+
+BSONObj Shard::runCommand(const std::string& db, const std::string& simple) const {
+ return runCommand(db, BSON(simple << 1));
+}
+
+BSONObj Shard::runCommand(const string& db, const BSONObj& cmd) const {
+ BSONObj res;
+ bool ok = runCommand(db, cmd, res);
+ if (!ok) {
+ stringstream ss;
+ ss << "runCommand (" << cmd << ") on shard (" << _id << ") failed : " << res;
+ throw UserException(13136, ss.str());
}
-
- Shard::~Shard() = default;
-
- ShardPtr Shard::lookupRSName(const string& name) {
- return grid.shardRegistry()->lookupRSName(name);
+ res = res.getOwned();
+ return res;
+}
+
+bool Shard::runCommand(const std::string& db, const std::string& simple, BSONObj& res) const {
+ return runCommand(db, BSON(simple << 1), res);
+}
+
+bool Shard::runCommand(const string& db, const BSONObj& cmd, BSONObj& res) const {
+ const ReadPreferenceSetting readPref(ReadPreference::PrimaryOnly, TagSet::primaryOnly());
+ auto selectedHost = getTargeter()->findHost(readPref);
+ if (!selectedHost.isOK()) {
+ return false;
}
- BSONObj Shard::runCommand(const std::string& db, const std::string& simple) const {
- return runCommand(db, BSON(simple << 1));
- }
-
- BSONObj Shard::runCommand( const string& db , const BSONObj& cmd ) const {
- BSONObj res;
- bool ok = runCommand(db, cmd, res);
- if ( ! ok ) {
- stringstream ss;
- ss << "runCommand (" << cmd << ") on shard (" << _id << ") failed : " << res;
- throw UserException( 13136 , ss.str() );
- }
- res = res.getOwned();
- return res;
- }
+ const RemoteCommandRequest request(selectedHost.getValue(), db, cmd);
- bool Shard::runCommand(const std::string& db, const std::string& simple, BSONObj& res) const {
- return runCommand(db, BSON(simple << 1), res);
+ auto statusCommand = grid.shardRegistry()->getCommandRunner()->runCommand(request);
+ if (!statusCommand.isOK()) {
+ return false;
}
- bool Shard::runCommand(const string& db, const BSONObj& cmd, BSONObj& res) const {
- const ReadPreferenceSetting readPref(ReadPreference::PrimaryOnly, TagSet::primaryOnly());
- auto selectedHost = getTargeter()->findHost(readPref);
- if (!selectedHost.isOK()) {
- return false;
- }
+ res = statusCommand.getValue().data.getOwned();
- const RemoteCommandRequest request(selectedHost.getValue(), db, cmd);
+ return getStatusFromCommandResult(res).isOK();
+}
- auto statusCommand = grid.shardRegistry()->getCommandRunner()->runCommand(request);
- if (!statusCommand.isOK()) {
- return false;
- }
+ShardStatus Shard::getStatus() const {
+ BSONObj listDatabases;
+ uassert(28589,
+ str::stream() << "call to listDatabases on " << getConnString().toString()
+ << " failed: " << listDatabases,
+ runCommand("admin", BSON("listDatabases" << 1), listDatabases));
- res = statusCommand.getValue().data.getOwned();
+ BSONElement totalSizeElem = listDatabases["totalSize"];
+ uassert(28590, "totalSize field not found in listDatabases", totalSizeElem.isNumber());
- return getStatusFromCommandResult(res).isOK();
- }
+ BSONObj serverStatus;
+ uassert(28591,
+ str::stream() << "call to serverStatus on " << getConnString().toString()
+ << " failed: " << serverStatus,
+ runCommand("admin", BSON("serverStatus" << 1), serverStatus));
- ShardStatus Shard::getStatus() const {
- BSONObj listDatabases;
- uassert(28589,
- str::stream() << "call to listDatabases on " << getConnString().toString()
- << " failed: " << listDatabases,
- runCommand("admin", BSON("listDatabases" << 1), listDatabases));
+ BSONElement versionElement = serverStatus["version"];
+ uassert(28599, "version field not found in serverStatus", versionElement.type() == String);
- BSONElement totalSizeElem = listDatabases["totalSize"];
- uassert(28590, "totalSize field not found in listDatabases", totalSizeElem.isNumber());
+ return ShardStatus(totalSizeElem.numberLong(), versionElement.str());
+}
- BSONObj serverStatus;
- uassert(28591,
- str::stream() << "call to serverStatus on " << getConnString().toString()
- << " failed: " << serverStatus,
- runCommand("admin", BSON("serverStatus" << 1), serverStatus));
+std::string Shard::toString() const {
+ return _id + ":" + _cs.toString();
+}
- BSONElement versionElement = serverStatus["version"];
- uassert(28599, "version field not found in serverStatus", versionElement.type() == String);
+void Shard::reloadShardInfo() {
+ grid.shardRegistry()->reload();
+}
- return ShardStatus(totalSizeElem.numberLong(), versionElement.str());
- }
+void Shard::removeShard(const ShardId& id) {
+ grid.shardRegistry()->remove(id);
+}
- std::string Shard::toString() const {
- return _id + ":" + _cs.toString();
- }
+ShardPtr Shard::pick() {
+ vector<ShardId> all;
- void Shard::reloadShardInfo() {
+ grid.shardRegistry()->getAllShardIds(&all);
+ if (all.size() == 0) {
grid.shardRegistry()->reload();
- }
-
- void Shard::removeShard(const ShardId& id) {
- grid.shardRegistry()->remove(id);
- }
-
- ShardPtr Shard::pick() {
- vector<ShardId> all;
-
grid.shardRegistry()->getAllShardIds(&all);
- if (all.size() == 0) {
- grid.shardRegistry()->reload();
- grid.shardRegistry()->getAllShardIds(&all);
- if (all.empty()) {
- return nullptr;
- }
- }
-
- auto bestShard = grid.shardRegistry()->getShard(all[0]);
- if (!bestShard) {
+ if (all.empty()) {
return nullptr;
}
+ }
- ShardStatus bestStatus = bestShard->getStatus();
-
- for (size_t i = 1; i < all.size(); i++) {
- const auto shard = grid.shardRegistry()->getShard(all[i]);
- if (!shard) {
- continue;
- }
+ auto bestShard = grid.shardRegistry()->getShard(all[0]);
+ if (!bestShard) {
+ return nullptr;
+ }
- const ShardStatus status = shard->getStatus();
+ ShardStatus bestStatus = bestShard->getStatus();
- if (status < bestStatus) {
- bestShard = shard;
- bestStatus = status;
- }
+ for (size_t i = 1; i < all.size(); i++) {
+ const auto shard = grid.shardRegistry()->getShard(all[i]);
+ if (!shard) {
+ continue;
}
- LOG(1) << "best shard for new allocation is " << bestStatus;
- return bestShard;
+ const ShardStatus status = shard->getStatus();
+
+ if (status < bestStatus) {
+ bestShard = shard;
+ bestStatus = status;
+ }
}
- ShardStatus::ShardStatus(long long dataSizeBytes, const string& mongoVersion)
- : _dataSizeBytes(dataSizeBytes),
- _mongoVersion(mongoVersion) {
+ LOG(1) << "best shard for new allocation is " << bestStatus;
+ return bestShard;
+}
- }
+ShardStatus::ShardStatus(long long dataSizeBytes, const string& mongoVersion)
+ : _dataSizeBytes(dataSizeBytes), _mongoVersion(mongoVersion) {}
- std::string ShardStatus::toString() const {
- return str::stream() << " dataSizeBytes: " << _dataSizeBytes
- << " version: " << _mongoVersion;
- }
+std::string ShardStatus::toString() const {
+ return str::stream() << " dataSizeBytes: " << _dataSizeBytes << " version: " << _mongoVersion;
+}
- bool ShardStatus::operator< (const ShardStatus& other) const {
- return dataSizeBytes() < other.dataSizeBytes();
- }
+bool ShardStatus::operator<(const ShardStatus& other) const {
+ return dataSizeBytes() < other.dataSizeBytes();
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/client/shard.h b/src/mongo/s/client/shard.h
index e5140dcee7e..f0d5d7833c2 100644
--- a/src/mongo/s/client/shard.h
+++ b/src/mongo/s/client/shard.h
@@ -35,98 +35,109 @@
namespace mongo {
- class BSONObj;
- class RemoteCommandTargeter;
+class BSONObj;
+class RemoteCommandTargeter;
- using ShardId = std::string;
+using ShardId = std::string;
+/**
+ * Contains runtime information obtained from the shard.
+ */
+class ShardStatus {
+public:
+ ShardStatus(long long dataSizeBytes, const std::string& version);
+
+ long long dataSizeBytes() const {
+ return _dataSizeBytes;
+ }
+ const std::string& mongoVersion() const {
+ return _mongoVersion;
+ }
+
+ std::string toString() const;
+
+ bool operator<(const ShardStatus& other) const;
+
+private:
+ long long _dataSizeBytes;
+ std::string _mongoVersion;
+};
+
+class Shard;
+using ShardPtr = std::shared_ptr<Shard>;
+
+/*
+ * Maintains the targeting and command execution logic for a single shard. Performs polling of
+ * the shard (if replica set).
+ */
+class Shard {
+ MONGO_DISALLOW_COPYING(Shard);
+
+public:
/**
- * Contains runtime information obtained from the shard.
+ * Instantiates a new shard connection management object for the specified shard.
*/
- class ShardStatus {
- public:
- ShardStatus(long long dataSizeBytes, const std::string& version);
+ Shard(const ShardId& id,
+ const ConnectionString& connStr,
+ std::unique_ptr<RemoteCommandTargeter> targeter);
+
+ ~Shard();
+
+ const ShardId& getId() const {
+ return _id;
+ }
- long long dataSizeBytes() const { return _dataSizeBytes; }
- const std::string& mongoVersion() const { return _mongoVersion; }
+ const ConnectionString& getConnString() const {
+ return _cs;
+ }
- std::string toString() const;
+ RemoteCommandTargeter* getTargeter() const {
+ return _targeter.get();
+ }
- bool operator< (const ShardStatus& other) const;
+ BSONObj runCommand(const std::string& db, const std::string& simple) const;
+ BSONObj runCommand(const std::string& db, const BSONObj& cmd) const;
- private:
- long long _dataSizeBytes;
- std::string _mongoVersion;
- };
+ bool runCommand(const std::string& db, const std::string& simple, BSONObj& res) const;
+ bool runCommand(const std::string& db, const BSONObj& cmd, BSONObj& res) const;
- class Shard;
- using ShardPtr = std::shared_ptr<Shard>;
+ /**
+ * Returns metadata and stats for this shard.
+ */
+ ShardStatus getStatus() const;
- /*
- * Maintains the targeting and command execution logic for a single shard. Performs polling of
- * the shard (if replica set).
+ /**
+ * Returns a string description of this shard entry.
*/
- class Shard {
- MONGO_DISALLOW_COPYING(Shard);
- public:
- /**
- * Instantiates a new shard connection management object for the specified shard.
- */
- Shard(const ShardId& id,
- const ConnectionString& connStr,
- std::unique_ptr<RemoteCommandTargeter> targeter);
-
- ~Shard();
-
- const ShardId& getId() const { return _id; }
-
- const ConnectionString& getConnString() const { return _cs; }
-
- RemoteCommandTargeter* getTargeter() const { return _targeter.get(); }
-
- BSONObj runCommand(const std::string& db, const std::string& simple) const;
- BSONObj runCommand(const std::string& db, const BSONObj& cmd) const;
-
- bool runCommand(const std::string& db, const std::string& simple, BSONObj& res) const;
- bool runCommand(const std::string& db, const BSONObj& cmd, BSONObj& res) const;
-
- /**
- * Returns metadata and stats for this shard.
- */
- ShardStatus getStatus() const;
-
- /**
- * Returns a string description of this shard entry.
- */
- std::string toString() const;
-
- static ShardPtr lookupRSName(const std::string& name);
-
- /**
- * @parm current - shard where the chunk/database currently lives in
- * @return the currently emptiest shard, if best then current, or nullptr
- */
- static ShardPtr pick();
-
- static void reloadShardInfo();
-
- static void removeShard(const ShardId& id);
-
- private:
- /**
- * Identifier of the shard as obtained from the configuration data (i.e. shard0000).
- */
- const ShardId _id;
-
- /**
- * Connection string for the shard.
- */
- const ConnectionString _cs;
-
- /**
- * Targeter for obtaining hosts from which to read or to which to write.
- */
- const std::unique_ptr<RemoteCommandTargeter> _targeter;
- };
-
-} // namespace mongo
+ std::string toString() const;
+
+ static ShardPtr lookupRSName(const std::string& name);
+
+ /**
+ * @parm current - shard where the chunk/database currently lives in
+ * @return the currently emptiest shard, if best then current, or nullptr
+ */
+ static ShardPtr pick();
+
+ static void reloadShardInfo();
+
+ static void removeShard(const ShardId& id);
+
+private:
+ /**
+ * Identifier of the shard as obtained from the configuration data (i.e. shard0000).
+ */
+ const ShardId _id;
+
+ /**
+ * Connection string for the shard.
+ */
+ const ConnectionString _cs;
+
+ /**
+ * Targeter for obtaining hosts from which to read or to which to write.
+ */
+ const std::unique_ptr<RemoteCommandTargeter> _targeter;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/client/shard_connection.cpp b/src/mongo/s/client/shard_connection.cpp
index 226bea4fc2c..4607a14baae 100644
--- a/src/mongo/s/client/shard_connection.cpp
+++ b/src/mongo/s/client/shard_connection.cpp
@@ -49,514 +49,501 @@
namespace mongo {
- using std::unique_ptr;
- using std::map;
- using std::set;
- using std::string;
- using std::stringstream;
- using std::vector;
+using std::unique_ptr;
+using std::map;
+using std::set;
+using std::string;
+using std::stringstream;
+using std::vector;
namespace {
- class ClientConnections;
+class ClientConnections;
- /**
- * Class which tracks ClientConnections (the client connection pool) for each incoming
- * connection, allowing stats access.
- */
- class ActiveClientConnections {
- public:
- void add(const ClientConnections* cc) {
- stdx::lock_guard<stdx::mutex> lock(_mutex);
- _clientConnections.insert(cc);
- }
+/**
+ * Class which tracks ClientConnections (the client connection pool) for each incoming
+ * connection, allowing stats access.
+ */
+class ActiveClientConnections {
+public:
+ void add(const ClientConnections* cc) {
+ stdx::lock_guard<stdx::mutex> lock(_mutex);
+ _clientConnections.insert(cc);
+ }
- void remove(const ClientConnections* cc) {
- stdx::lock_guard<stdx::mutex> lock(_mutex);
- _clientConnections.erase(cc);
- }
+ void remove(const ClientConnections* cc) {
+ stdx::lock_guard<stdx::mutex> lock(_mutex);
+ _clientConnections.erase(cc);
+ }
- void appendInfo(BSONObjBuilder& b);
+ void appendInfo(BSONObjBuilder& b);
- private:
- stdx::mutex _mutex;
- set<const ClientConnections*> _clientConnections;
+private:
+ stdx::mutex _mutex;
+ set<const ClientConnections*> _clientConnections;
- } activeClientConnections;
+} activeClientConnections;
- /**
- * Command to allow access to the sharded conn pool information in mongos.
- */
- class ShardedPoolStats : public Command {
- public:
-
- ShardedPoolStats() : Command( "shardConnPoolStats" ) { }
- virtual void help( stringstream &help ) const { help << "stats about the shard connection pool"; }
- virtual bool isWriteCommandForConfigServer() const { return false; }
- virtual bool slaveOk() const { return true; }
-
- // Same privs as connPoolStats
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::connPoolStats);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
- }
-
- virtual bool run(OperationContext* txn,
- const string& dbname,
- mongo::BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- mongo::BSONObjBuilder& result) {
+/**
+ * Command to allow access to the sharded conn pool information in mongos.
+ */
+class ShardedPoolStats : public Command {
+public:
+ ShardedPoolStats() : Command("shardConnPoolStats") {}
+ virtual void help(stringstream& help) const {
+ help << "stats about the shard connection pool";
+ }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+ virtual bool slaveOk() const {
+ return true;
+ }
- // Base pool info
- shardConnectionPool.appendInfo(result);
+ // Same privs as connPoolStats
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::connPoolStats);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
- // Thread connection info
- activeClientConnections.appendInfo(result);
+ virtual bool run(OperationContext* txn,
+ const string& dbname,
+ mongo::BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ mongo::BSONObjBuilder& result) {
+ // Base pool info
+ shardConnectionPool.appendInfo(result);
- return true;
- }
+ // Thread connection info
+ activeClientConnections.appendInfo(result);
- } shardedPoolStatsCmd;
+ return true;
+ }
- /**
- * holds all the actual db connections for a client to various servers 1 per thread, so
- * doesn't have to be thread safe.
- */
- class ClientConnections {
- MONGO_DISALLOW_COPYING(ClientConnections);
- public:
+} shardedPoolStatsCmd;
- struct Status {
- Status() : created(0), avail(0) { }
+/**
+ * holds all the actual db connections for a client to various servers 1 per thread, so
+ * doesn't have to be thread safe.
+ */
+class ClientConnections {
+ MONGO_DISALLOW_COPYING(ClientConnections);
- // May be read concurrently, but only written from
- // this thread.
- long long created;
- DBClientBase* avail;
- };
+public:
+ struct Status {
+ Status() : created(0), avail(0) {}
- // Gets or creates the status object for the host
- Status* _getStatus(const string& addr) {
- scoped_spinlock lock(_lock);
- Status* &temp = _hosts[addr];
- if (!temp) {
- temp = new Status();
- }
+ // May be read concurrently, but only written from
+ // this thread.
+ long long created;
+ DBClientBase* avail;
+ };
- return temp;
+ // Gets or creates the status object for the host
+ Status* _getStatus(const string& addr) {
+ scoped_spinlock lock(_lock);
+ Status*& temp = _hosts[addr];
+ if (!temp) {
+ temp = new Status();
}
- ClientConnections() {
- // Start tracking client connections
- activeClientConnections.add(this);
- }
+ return temp;
+ }
- ~ClientConnections() {
- // Stop tracking these client connections
- activeClientConnections.remove(this);
+ ClientConnections() {
+ // Start tracking client connections
+ activeClientConnections.add(this);
+ }
- releaseAll(true);
- }
+ ~ClientConnections() {
+ // Stop tracking these client connections
+ activeClientConnections.remove(this);
- void releaseAll(bool fromDestructor = false) {
- // Don't need spinlock protection because if not in the destructor, we don't modify
- // _hosts, and if in the destructor we are not accessible to external threads.
- for (HostMap::iterator i = _hosts.begin(); i != _hosts.end(); ++i) {
- const string addr = i->first;
- Status* ss = i->second;
- invariant(ss);
-
- if (ss->avail) {
- // If we're shutting down, don't want to initiate release mechanism as it is
- // slow, and isn't needed since all connections will be closed anyway.
- if (inShutdown()) {
- if (versionManager.isVersionableCB(ss->avail)) {
- versionManager.resetShardVersionCB(ss->avail);
- }
-
- delete ss->avail;
- }
- else {
- release(addr, ss->avail);
+ releaseAll(true);
+ }
+
+ void releaseAll(bool fromDestructor = false) {
+ // Don't need spinlock protection because if not in the destructor, we don't modify
+ // _hosts, and if in the destructor we are not accessible to external threads.
+ for (HostMap::iterator i = _hosts.begin(); i != _hosts.end(); ++i) {
+ const string addr = i->first;
+ Status* ss = i->second;
+ invariant(ss);
+
+ if (ss->avail) {
+ // If we're shutting down, don't want to initiate release mechanism as it is
+ // slow, and isn't needed since all connections will be closed anyway.
+ if (inShutdown()) {
+ if (versionManager.isVersionableCB(ss->avail)) {
+ versionManager.resetShardVersionCB(ss->avail);
}
- ss->avail = 0;
+ delete ss->avail;
+ } else {
+ release(addr, ss->avail);
}
- if (fromDestructor) {
- delete ss;
- }
+ ss->avail = 0;
}
if (fromDestructor) {
- _hosts.clear();
+ delete ss;
}
}
- DBClientBase * get(const string& addr, const string& ns) {
-
- {
- // We want to report ns stats
- scoped_spinlock lock(_lock);
- if (ns.size() > 0)
- _seenNS.insert(ns);
- }
+ if (fromDestructor) {
+ _hosts.clear();
+ }
+ }
- Status* s = _getStatus(addr);
+ DBClientBase* get(const string& addr, const string& ns) {
+ {
+ // We want to report ns stats
+ scoped_spinlock lock(_lock);
+ if (ns.size() > 0)
+ _seenNS.insert(ns);
+ }
- unique_ptr<DBClientBase> c;
- if (s->avail) {
- c.reset(s->avail);
- s->avail = 0;
+ Status* s = _getStatus(addr);
- // May throw an exception
- shardConnectionPool.onHandedOut(c.get());
- }
- else {
- c.reset(shardConnectionPool.get(addr));
+ unique_ptr<DBClientBase> c;
+ if (s->avail) {
+ c.reset(s->avail);
+ s->avail = 0;
- // After, so failed creation doesn't get counted
- s->created++;
- }
+ // May throw an exception
+ shardConnectionPool.onHandedOut(c.get());
+ } else {
+ c.reset(shardConnectionPool.get(addr));
- return c.release();
+ // After, so failed creation doesn't get counted
+ s->created++;
}
- void done( const string& addr , DBClientBase* conn ) {
- Status* s = _hosts[addr];
- verify( s );
-
- const bool isConnGood = shardConnectionPool.isConnectionGood(addr, conn);
+ return c.release();
+ }
- if (s->avail != NULL) {
- warning() << "Detected additional sharded connection in the "
- << "thread local pool for " << addr;
+ void done(const string& addr, DBClientBase* conn) {
+ Status* s = _hosts[addr];
+ verify(s);
- if (DBException::traceExceptions) {
- // There shouldn't be more than one connection checked out to the same
- // host on the same thread.
- printStackTrace();
- }
+ const bool isConnGood = shardConnectionPool.isConnectionGood(addr, conn);
- if (!isConnGood) {
- delete s->avail;
- s->avail = NULL;
- }
+ if (s->avail != NULL) {
+ warning() << "Detected additional sharded connection in the "
+ << "thread local pool for " << addr;
- // Let the internal pool handle the bad connection, this can also
- // update the lower bounds for the known good socket creation time
- // for this host.
- release(addr, conn);
- return;
+ if (DBException::traceExceptions) {
+ // There shouldn't be more than one connection checked out to the same
+ // host on the same thread.
+ printStackTrace();
}
if (!isConnGood) {
- // Let the internal pool handle the bad connection.
- release(addr, conn);
- return;
+ delete s->avail;
+ s->avail = NULL;
}
- // Note: Although we try our best to clear bad connections as much as possible,
- // some of them can still slip through because of how ClientConnections are being
- // used - as thread local variables. This means that threads won't be able to
- // see the s->avail connection of other threads.
-
- s->avail = conn;
+ // Let the internal pool handle the bad connection, this can also
+ // update the lower bounds for the known good socket creation time
+ // for this host.
+ release(addr, conn);
+ return;
}
- void sync() {
- for ( HostMap::iterator i=_hosts.begin(); i!=_hosts.end(); ++i ) {
- string addr = i->first;
- Status* ss = i->second;
- if ( ss->avail )
- ss->avail->getLastError();
- }
+ if (!isConnGood) {
+ // Let the internal pool handle the bad connection.
+ release(addr, conn);
+ return;
}
- void checkVersions( const string& ns ) {
-
- vector<ShardId> all;
- grid.shardRegistry()->getAllShardIds(&all);
+ // Note: Although we try our best to clear bad connections as much as possible,
+ // some of them can still slip through because of how ClientConnections are being
+ // used - as thread local variables. This means that threads won't be able to
+ // see the s->avail connection of other threads.
- // Don't report exceptions here as errors in GetLastError
- LastError::Disabled ignoreForGLE(&LastError::get(cc()));
+ s->avail = conn;
+ }
- // Now only check top-level shard connections
- for (const ShardId& shardId : all) {
- try {
- const auto shard = grid.shardRegistry()->getShard(shardId);
- if (!shard) {
- continue;
- }
+ void sync() {
+ for (HostMap::iterator i = _hosts.begin(); i != _hosts.end(); ++i) {
+ string addr = i->first;
+ Status* ss = i->second;
+ if (ss->avail)
+ ss->avail->getLastError();
+ }
+ }
- string sconnString = shard->getConnString().toString();
- Status* s = _getStatus( sconnString );
+ void checkVersions(const string& ns) {
+ vector<ShardId> all;
+ grid.shardRegistry()->getAllShardIds(&all);
- if( ! s->avail ) {
- s->avail = shardConnectionPool.get( sconnString );
- s->created++; // After, so failed creation doesn't get counted
- }
+ // Don't report exceptions here as errors in GetLastError
+ LastError::Disabled ignoreForGLE(&LastError::get(cc()));
- versionManager.checkShardVersionCB( s->avail, ns, false, 1 );
+ // Now only check top-level shard connections
+ for (const ShardId& shardId : all) {
+ try {
+ const auto shard = grid.shardRegistry()->getShard(shardId);
+ if (!shard) {
+ continue;
}
- catch ( const DBException& ex ) {
- warning() << "problem while initially checking shard versions on" << " "
- << shardId << causedBy(ex);
+ string sconnString = shard->getConnString().toString();
+ Status* s = _getStatus(sconnString);
- // NOTE: This is only a heuristic, to avoid multiple stale version retries
- // across multiple shards, and does not affect correctness.
+ if (!s->avail) {
+ s->avail = shardConnectionPool.get(sconnString);
+ s->created++; // After, so failed creation doesn't get counted
}
+
+ versionManager.checkShardVersionCB(s->avail, ns, false, 1);
+ } catch (const DBException& ex) {
+ warning() << "problem while initially checking shard versions on"
+ << " " << shardId << causedBy(ex);
+
+ // NOTE: This is only a heuristic, to avoid multiple stale version retries
+ // across multiple shards, and does not affect correctness.
}
}
+ }
- void release( const string& addr , DBClientBase * conn ) {
- shardConnectionPool.release( addr , conn );
+ void release(const string& addr, DBClientBase* conn) {
+ shardConnectionPool.release(addr, conn);
+ }
+
+ /**
+ * Appends info about the client connection pool to a BOBuilder
+ * Safe to call with activeClientConnections lock
+ */
+ void appendInfo(BSONObjBuilder& b) const {
+ scoped_spinlock lock(_lock);
+
+ BSONArrayBuilder hostsArrB(b.subarrayStart("hosts"));
+ for (HostMap::const_iterator i = _hosts.begin(); i != _hosts.end(); ++i) {
+ BSONObjBuilder bb(hostsArrB.subobjStart());
+ bb.append("host", i->first);
+ bb.append("created", i->second->created);
+ bb.appendBool("avail", static_cast<bool>(i->second->avail));
+ bb.done();
}
+ hostsArrB.done();
- /**
- * Appends info about the client connection pool to a BOBuilder
- * Safe to call with activeClientConnections lock
- */
- void appendInfo( BSONObjBuilder& b ) const {
-
- scoped_spinlock lock( _lock );
-
- BSONArrayBuilder hostsArrB( b.subarrayStart( "hosts" ) );
- for ( HostMap::const_iterator i = _hosts.begin(); i != _hosts.end(); ++i ) {
- BSONObjBuilder bb( hostsArrB.subobjStart() );
- bb.append( "host", i->first );
- bb.append( "created", i->second->created );
- bb.appendBool( "avail", static_cast<bool>( i->second->avail ) );
- bb.done();
- }
- hostsArrB.done();
+ BSONArrayBuilder nsArrB(b.subarrayStart("seenNS"));
+ for (set<string>::const_iterator i = _seenNS.begin(); i != _seenNS.end(); ++i) {
+ nsArrB.append(*i);
+ }
+ nsArrB.done();
+ }
- BSONArrayBuilder nsArrB( b.subarrayStart( "seenNS" ) );
- for ( set<string>::const_iterator i = _seenNS.begin(); i != _seenNS.end(); ++i ) {
- nsArrB.append(*i);
+ // Protects only the creation of new entries in the _hosts and _seenNS map
+ // from external threads. Reading _hosts / _seenNS in this thread doesn't
+ // need protection.
+ mutable SpinLock _lock;
+ typedef map<string, Status*, DBConnectionPool::serverNameCompare> HostMap;
+ HostMap _hosts;
+ set<string> _seenNS;
+
+ /**
+ * Clears the connections kept by this pool (ie, not including the global pool)
+ */
+ void clearPool() {
+ for (HostMap::iterator iter = _hosts.begin(); iter != _hosts.end(); ++iter) {
+ if (iter->second->avail != NULL) {
+ delete iter->second->avail;
}
- nsArrB.done();
+ delete iter->second;
}
-
- // Protects only the creation of new entries in the _hosts and _seenNS map
- // from external threads. Reading _hosts / _seenNS in this thread doesn't
- // need protection.
- mutable SpinLock _lock;
- typedef map<string,Status*,DBConnectionPool::serverNameCompare> HostMap;
- HostMap _hosts;
- set<string> _seenNS;
-
- /**
- * Clears the connections kept by this pool (ie, not including the global pool)
- */
- void clearPool() {
- for(HostMap::iterator iter = _hosts.begin(); iter != _hosts.end(); ++iter) {
- if (iter->second->avail != NULL) {
- delete iter->second->avail;
- }
- delete iter->second;
- }
- _hosts.clear();
- }
+ _hosts.clear();
+ }
- void forgetNS( const string& ns ) {
- scoped_spinlock lock( _lock );
- _seenNS.erase( ns );
- }
+ void forgetNS(const string& ns) {
+ scoped_spinlock lock(_lock);
+ _seenNS.erase(ns);
+ }
- // -----
+ // -----
- static thread_specific_ptr<ClientConnections> _perThread;
+ static thread_specific_ptr<ClientConnections> _perThread;
- static ClientConnections* threadInstance() {
- ClientConnections* cc = _perThread.get();
- if ( ! cc ) {
- cc = new ClientConnections();
- _perThread.reset( cc );
- }
- return cc;
+ static ClientConnections* threadInstance() {
+ ClientConnections* cc = _perThread.get();
+ if (!cc) {
+ cc = new ClientConnections();
+ _perThread.reset(cc);
}
- };
+ return cc;
+ }
+};
- void ActiveClientConnections::appendInfo(BSONObjBuilder& b) {
- BSONArrayBuilder arr(64 * 1024); // There may be quite a few threads
+void ActiveClientConnections::appendInfo(BSONObjBuilder& b) {
+ BSONArrayBuilder arr(64 * 1024); // There may be quite a few threads
- {
- stdx::lock_guard<stdx::mutex> lock(_mutex);
- for (set<const ClientConnections*>::const_iterator i = _clientConnections.begin();
- i != _clientConnections.end();
- ++i) {
-
- BSONObjBuilder bb(arr.subobjStart());
- (*i)->appendInfo(bb);
- bb.done();
- }
+ {
+ stdx::lock_guard<stdx::mutex> lock(_mutex);
+ for (set<const ClientConnections*>::const_iterator i = _clientConnections.begin();
+ i != _clientConnections.end();
+ ++i) {
+ BSONObjBuilder bb(arr.subobjStart());
+ (*i)->appendInfo(bb);
+ bb.done();
}
-
- b.appendArray("threads", arr.obj());
}
- thread_specific_ptr<ClientConnections> ClientConnections::_perThread;
-
-} // namespace
+ b.appendArray("threads", arr.obj());
+}
- // The global connection pool
- DBConnectionPool shardConnectionPool;
+thread_specific_ptr<ClientConnections> ClientConnections::_perThread;
- // Different between mongos and mongod
- void usingAShardConnection(const string& addr);
+} // namespace
+// The global connection pool
+DBConnectionPool shardConnectionPool;
- ShardConnection::ShardConnection(const ConnectionString& connectionString,
- const string& ns,
- std::shared_ptr<ChunkManager> manager)
- : _cs(connectionString),
- _ns(ns),
- _manager(manager) {
+// Different between mongos and mongod
+void usingAShardConnection(const string& addr);
- _init();
- }
- ShardConnection::~ShardConnection() {
- if (_conn) {
- if (_conn->isFailed()) {
- if (_conn->getSockCreationMicroSec() == DBClientBase::INVALID_SOCK_CREATION_TIME) {
- kill();
- }
- else {
- // The pool takes care of deleting the failed connection - this
- // will also trigger disposal of older connections in the pool
- done();
- }
- }
- else {
- // see done() comments above for why we log this line
- log() << "sharded connection to " << _conn->getServerAddress()
- << " not being returned to the pool";
+ShardConnection::ShardConnection(const ConnectionString& connectionString,
+ const string& ns,
+ std::shared_ptr<ChunkManager> manager)
+ : _cs(connectionString), _ns(ns), _manager(manager) {
+ _init();
+}
+ShardConnection::~ShardConnection() {
+ if (_conn) {
+ if (_conn->isFailed()) {
+ if (_conn->getSockCreationMicroSec() == DBClientBase::INVALID_SOCK_CREATION_TIME) {
kill();
+ } else {
+ // The pool takes care of deleting the failed connection - this
+ // will also trigger disposal of older connections in the pool
+ done();
}
+ } else {
+ // see done() comments above for why we log this line
+ log() << "sharded connection to " << _conn->getServerAddress()
+ << " not being returned to the pool";
+
+ kill();
}
}
+}
+
+void ShardConnection::_init() {
+ invariant(_cs.isValid());
+ _conn = ClientConnections::threadInstance()->get(_cs.toString(), _ns);
+ _finishedInit = false;
+ usingAShardConnection(_cs.toString());
+}
- void ShardConnection::_init() {
- invariant(_cs.isValid());
- _conn = ClientConnections::threadInstance()->get(_cs.toString(), _ns);
- _finishedInit = false;
- usingAShardConnection(_cs.toString());
+void ShardConnection::_finishInit() {
+ if (_finishedInit)
+ return;
+ _finishedInit = true;
+
+ if (versionManager.isVersionableCB(_conn)) {
+ // Make sure we specified a manager for the correct namespace
+ if (_ns.size() && _manager)
+ verify(_manager->getns() == _ns);
+ _setVersion = versionManager.checkShardVersionCB(this, false, 1);
+ } else {
+ // Make sure we didn't specify a manager for a non-versionable connection (i.e. config)
+ verify(!_manager);
+ _setVersion = false;
}
+}
- void ShardConnection::_finishInit() {
- if ( _finishedInit )
- return;
+void ShardConnection::done() {
+ if (_conn) {
+ ClientConnections::threadInstance()->done(_cs.toString(), _conn);
+ _conn = 0;
_finishedInit = true;
+ }
+}
+void ShardConnection::kill() {
+ if (_conn) {
if (versionManager.isVersionableCB(_conn)) {
- // Make sure we specified a manager for the correct namespace
- if (_ns.size() && _manager)
- verify(_manager->getns() == _ns);
- _setVersion = versionManager.checkShardVersionCB( this , false , 1 );
- }
- else {
- // Make sure we didn't specify a manager for a non-versionable connection (i.e. config)
- verify(!_manager);
- _setVersion = false;
+ versionManager.resetShardVersionCB(_conn);
}
- }
- void ShardConnection::done() {
- if ( _conn ) {
+ if (_conn->isFailed()) {
+ // Let the pool know about the bad connection and also delegate disposal to it.
ClientConnections::threadInstance()->done(_cs.toString(), _conn);
- _conn = 0;
- _finishedInit = true;
+ } else {
+ delete _conn;
}
+
+ _conn = 0;
+ _finishedInit = true;
}
+}
- void ShardConnection::kill() {
- if ( _conn ) {
- if (versionManager.isVersionableCB(_conn)) {
- versionManager.resetShardVersionCB(_conn);
- }
+void ShardConnection::sync() {
+ ClientConnections::threadInstance()->sync();
+}
- if (_conn->isFailed()) {
- // Let the pool know about the bad connection and also delegate disposal to it.
- ClientConnections::threadInstance()->done(_cs.toString(), _conn);
- }
- else {
- delete _conn;
- }
+void ShardConnection::checkMyConnectionVersions(const string& ns) {
+ ClientConnections::threadInstance()->checkVersions(ns);
+}
- _conn = 0;
- _finishedInit = true;
- }
- }
+void ShardConnection::releaseMyConnections() {
+ ClientConnections::threadInstance()->releaseAll();
+}
- void ShardConnection::sync() {
- ClientConnections::threadInstance()->sync();
- }
+void ShardConnection::clearPool() {
+ shardConnectionPool.clear();
+ ClientConnections::threadInstance()->clearPool();
+}
- void ShardConnection::checkMyConnectionVersions( const string & ns ) {
- ClientConnections::threadInstance()->checkVersions( ns );
- }
+void ShardConnection::forgetNS(const string& ns) {
+ ClientConnections::threadInstance()->forgetNS(ns);
+}
- void ShardConnection::releaseMyConnections() {
- ClientConnections::threadInstance()->releaseAll();
- }
- void ShardConnection::clearPool() {
- shardConnectionPool.clear();
- ClientConnections::threadInstance()->clearPool();
+bool setShardVersion(DBClientBase& conn,
+ const string& ns,
+ const string& configServerPrimary,
+ ChunkVersion version,
+ ChunkManager* manager,
+ bool authoritative,
+ BSONObj& result) {
+ BSONObjBuilder cmdBuilder;
+ cmdBuilder.append("setShardVersion", ns);
+ cmdBuilder.append("configdb", configServerPrimary);
+
+ ShardId shardId;
+ {
+ const auto shard = grid.shardRegistry()->getShard(conn.getServerAddress());
+ shardId = shard->getId();
+ cmdBuilder.append("shard", shardId);
+ cmdBuilder.append("shardHost", shard->getConnString().toString());
}
- void ShardConnection::forgetNS( const string& ns ) {
- ClientConnections::threadInstance()->forgetNS( ns );
+ if (ns.size() > 0) {
+ version.addToBSON(cmdBuilder);
+ } else {
+ cmdBuilder.append("init", true);
}
+ if (authoritative) {
+ cmdBuilder.appendBool("authoritative", 1);
+ }
- bool setShardVersion(DBClientBase& conn,
- const string& ns,
- const string& configServerPrimary,
- ChunkVersion version,
- ChunkManager* manager,
- bool authoritative,
- BSONObj& result) {
-
- BSONObjBuilder cmdBuilder;
- cmdBuilder.append("setShardVersion", ns);
- cmdBuilder.append("configdb", configServerPrimary);
-
- ShardId shardId;
- {
- const auto shard = grid.shardRegistry()->getShard(conn.getServerAddress());
- shardId = shard->getId();
- cmdBuilder.append("shard", shardId);
- cmdBuilder.append("shardHost", shard->getConnString().toString());
- }
-
- if (ns.size() > 0) {
- version.addToBSON(cmdBuilder);
- }
- else {
- cmdBuilder.append("init", true);
- }
-
- if (authoritative) {
- cmdBuilder.appendBool("authoritative", 1);
- }
-
- BSONObj cmd = cmdBuilder.obj();
+ BSONObj cmd = cmdBuilder.obj();
- LOG(1) << " setShardVersion " << shardId << " " << conn.getServerAddress()
- << " " << ns << " " << cmd
- << (manager ? string(str::stream() << " " << manager->getSequenceNumber()) : "");
+ LOG(1) << " setShardVersion " << shardId << " " << conn.getServerAddress() << " " << ns
+ << " " << cmd
+ << (manager ? string(str::stream() << " " << manager->getSequenceNumber()) : "");
- return conn.runCommand("admin", cmd, result, 0);
- }
+ return conn.runCommand("admin", cmd, result, 0);
+}
}
diff --git a/src/mongo/s/client/shard_connection.h b/src/mongo/s/client/shard_connection.h
index 4fc7f0f01a8..42040a3ab79 100644
--- a/src/mongo/s/client/shard_connection.h
+++ b/src/mongo/s/client/shard_connection.h
@@ -35,124 +35,126 @@
namespace mongo {
- class ChunkManager;
- typedef std::shared_ptr<ChunkManager> ChunkManagerPtr;
-
-
- class ShardConnection : public AScopedConnection {
- public:
- ShardConnection(const ConnectionString& connectionString,
- const std::string& ns,
- std::shared_ptr<ChunkManager> manager = nullptr);
-
- ~ShardConnection();
-
- void done();
- void kill();
-
- DBClientBase& conn() {
- _finishInit();
- verify( _conn );
- return *_conn;
- }
-
- DBClientBase* operator->() {
- _finishInit();
- verify( _conn );
- return _conn;
- }
-
- DBClientBase* get() {
- _finishInit();
- verify( _conn );
- return _conn;
- }
-
- /**
- * @return the connection object underneath without setting the shard version.
- * @throws AssertionException if _conn is uninitialized.
- */
- DBClientBase* getRawConn() const {
- verify( _conn );
- return _conn;
- }
-
- std::string getHost() const {
- return _cs.toString();
- }
-
- std::string getNS() const {
- return _ns;
- }
-
- std::shared_ptr<ChunkManager> getManager() const {
- return _manager;
- }
-
- bool setVersion() {
- _finishInit();
- return _setVersion;
- }
-
- static void sync();
-
- void donotCheckVersion() {
- _setVersion = false;
- _finishedInit = true;
- }
-
- bool ok() const { return _conn != NULL; }
-
- /** checks all of my thread local connections for the version of this ns */
- static void checkMyConnectionVersions( const std::string & ns );
-
- /**
- * Returns all the current sharded connections to the pool.
- * Note: This is *dangerous* if we have GLE state.
- */
- static void releaseMyConnections();
-
- /**
- * Clears all connections in the sharded pool, including connections in the
- * thread local storage pool of the current thread.
- */
- static void clearPool();
-
- /**
- * Forgets a namespace to prevent future versioning.
- */
- static void forgetNS( const std::string& ns );
-
- private:
- void _init();
- void _finishInit();
-
- const ConnectionString _cs;
- const std::string _ns;
-
- std::shared_ptr<ChunkManager> _manager;
-
- bool _finishedInit;
-
- DBClientBase* _conn;
- bool _setVersion;
- };
+class ChunkManager;
+typedef std::shared_ptr<ChunkManager> ChunkManagerPtr;
+class ShardConnection : public AScopedConnection {
+public:
+ ShardConnection(const ConnectionString& connectionString,
+ const std::string& ns,
+ std::shared_ptr<ChunkManager> manager = nullptr);
+
+ ~ShardConnection();
+
+ void done();
+ void kill();
+
+ DBClientBase& conn() {
+ _finishInit();
+ verify(_conn);
+ return *_conn;
+ }
+
+ DBClientBase* operator->() {
+ _finishInit();
+ verify(_conn);
+ return _conn;
+ }
+
+ DBClientBase* get() {
+ _finishInit();
+ verify(_conn);
+ return _conn;
+ }
+
+ /**
+ * @return the connection object underneath without setting the shard version.
+ * @throws AssertionException if _conn is uninitialized.
+ */
+ DBClientBase* getRawConn() const {
+ verify(_conn);
+ return _conn;
+ }
+
+ std::string getHost() const {
+ return _cs.toString();
+ }
+
+ std::string getNS() const {
+ return _ns;
+ }
+
+ std::shared_ptr<ChunkManager> getManager() const {
+ return _manager;
+ }
+
+ bool setVersion() {
+ _finishInit();
+ return _setVersion;
+ }
+
+ static void sync();
+
+ void donotCheckVersion() {
+ _setVersion = false;
+ _finishedInit = true;
+ }
+
+ bool ok() const {
+ return _conn != NULL;
+ }
+
+ /** checks all of my thread local connections for the version of this ns */
+ static void checkMyConnectionVersions(const std::string& ns);
+
+ /**
+ * Returns all the current sharded connections to the pool.
+ * Note: This is *dangerous* if we have GLE state.
+ */
+ static void releaseMyConnections();
+
/**
- * Sends the setShardVersion command on the specified connection.
+ * Clears all connections in the sharded pool, including connections in the
+ * thread local storage pool of the current thread.
*/
- bool setShardVersion(DBClientBase & conn,
- const std::string& ns,
- const std::string& configServerPrimary,
- ChunkVersion version,
- ChunkManager* manager,
- bool authoritative,
- BSONObj& result);
+ static void clearPool();
+
+ /**
+ * Forgets a namespace to prevent future versioning.
+ */
+ static void forgetNS(const std::string& ns);
+
+private:
+ void _init();
+ void _finishInit();
+
+ const ConnectionString _cs;
+ const std::string _ns;
+
+ std::shared_ptr<ChunkManager> _manager;
+
+ bool _finishedInit;
+
+ DBClientBase* _conn;
+ bool _setVersion;
+};
+
+
+/**
+ * Sends the setShardVersion command on the specified connection.
+ */
+bool setShardVersion(DBClientBase& conn,
+ const std::string& ns,
+ const std::string& configServerPrimary,
+ ChunkVersion version,
+ ChunkManager* manager,
+ bool authoritative,
+ BSONObj& result);
- typedef std::shared_ptr<ShardConnection> ShardConnectionPtr;
+typedef std::shared_ptr<ShardConnection> ShardConnectionPtr;
- extern DBConnectionPool shardConnectionPool;
+extern DBConnectionPool shardConnectionPool;
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/client/shard_connection_test.cpp b/src/mongo/s/client/shard_connection_test.cpp
index 1751dd1c84e..5f14a3872f7 100644
--- a/src/mongo/s/client/shard_connection_test.cpp
+++ b/src/mongo/s/client/shard_connection_test.cpp
@@ -52,263 +52,261 @@
namespace mongo {
namespace {
- using std::string;
- using std::vector;
+using std::string;
+using std::vector;
- const string TARGET_HOST = "$dummy:27017";
+const string TARGET_HOST = "$dummy:27017";
- /**
- * Warning: cannot run in parallel
- */
- class ShardConnFixture: public mongo::unittest::Test {
- public:
- void setUp() {
- if (!haveClient()) {
- Client::initThread("ShardConnFixture", getGlobalServiceContext(), NULL);
- }
- _maxPoolSizePerHost = mongo::shardConnectionPool.getMaxPoolSize();
-
- mongo::ConnectionString::setConnectionHook(
- mongo::MockConnRegistry::get()->getConnStrHook());
- _dummyServer = new MockRemoteDBServer(TARGET_HOST);
- mongo::MockConnRegistry::get()->addServer(_dummyServer);
+/**
+ * Warning: cannot run in parallel
+ */
+class ShardConnFixture : public mongo::unittest::Test {
+public:
+ void setUp() {
+ if (!haveClient()) {
+ Client::initThread("ShardConnFixture", getGlobalServiceContext(), NULL);
}
+ _maxPoolSizePerHost = mongo::shardConnectionPool.getMaxPoolSize();
- void tearDown() {
- ShardConnection::clearPool();
+ mongo::ConnectionString::setConnectionHook(
+ mongo::MockConnRegistry::get()->getConnStrHook());
+ _dummyServer = new MockRemoteDBServer(TARGET_HOST);
+ mongo::MockConnRegistry::get()->addServer(_dummyServer);
+ }
- mongo::MockConnRegistry::get()->removeServer(_dummyServer->getServerAddress());
- delete _dummyServer;
+ void tearDown() {
+ ShardConnection::clearPool();
- mongo::shardConnectionPool.setMaxPoolSize(_maxPoolSizePerHost);
- }
+ mongo::MockConnRegistry::get()->removeServer(_dummyServer->getServerAddress());
+ delete _dummyServer;
- void killServer() {
- _dummyServer->shutdown();
- }
+ mongo::shardConnectionPool.setMaxPoolSize(_maxPoolSizePerHost);
+ }
- void restartServer() {
- _dummyServer->reboot();
- }
+ void killServer() {
+ _dummyServer->shutdown();
+ }
- protected:
- static void assertGreaterThan(uint64_t a, uint64_t b) {
- ASSERT_GREATER_THAN(a, b);
- }
+ void restartServer() {
+ _dummyServer->reboot();
+ }
- static void assertNotEqual(uint64_t a, uint64_t b) {
- ASSERT_NOT_EQUALS(a, b);
- }
+protected:
+ static void assertGreaterThan(uint64_t a, uint64_t b) {
+ ASSERT_GREATER_THAN(a, b);
+ }
- /**
- * Tries to grab a series of connections from the pool, perform checks on
- * them, then put them back into the pool. After that, it checks these
- * connections can be retrieved again from the pool.
- *
- * @param checkFunc method for comparing new connections and arg2.
- * @param arg2 the value to pass as the 2nd parameter of checkFunc.
- * @param newConnsToCreate the number of new connections to make.
- */
- void checkNewConns(void (*checkFunc)(uint64_t, uint64_t), uint64_t arg2,
- size_t newConnsToCreate) {
- vector<ShardConnection*> newConnList;
- for (size_t x = 0; x < newConnsToCreate; x++) {
- ShardConnection* newConn = new ShardConnection(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- checkFunc(newConn->get()->getSockCreationMicroSec(), arg2);
- newConnList.push_back(newConn);
- }
-
- const uint64_t oldCreationTime = mongo::curTimeMicros64();
-
- for (vector<ShardConnection*>::iterator iter = newConnList.begin();
- iter != newConnList.end(); ++iter) {
- (*iter)->done();
- delete *iter;
- }
-
- newConnList.clear();
-
- // Check that connections created after the purge was put back to the pool.
- for (size_t x = 0; x < newConnsToCreate; x++) {
- ShardConnection* newConn = new ShardConnection(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- ASSERT_LESS_THAN(newConn->get()->getSockCreationMicroSec(), oldCreationTime);
- newConnList.push_back(newConn);
- }
-
- for (vector<ShardConnection*>::iterator iter = newConnList.begin();
- iter != newConnList.end(); ++iter) {
- (*iter)->done();
- delete *iter;
- }
+ static void assertNotEqual(uint64_t a, uint64_t b) {
+ ASSERT_NOT_EQUALS(a, b);
+ }
+
+ /**
+ * Tries to grab a series of connections from the pool, perform checks on
+ * them, then put them back into the pool. After that, it checks these
+ * connections can be retrieved again from the pool.
+ *
+ * @param checkFunc method for comparing new connections and arg2.
+ * @param arg2 the value to pass as the 2nd parameter of checkFunc.
+ * @param newConnsToCreate the number of new connections to make.
+ */
+ void checkNewConns(void (*checkFunc)(uint64_t, uint64_t),
+ uint64_t arg2,
+ size_t newConnsToCreate) {
+ vector<ShardConnection*> newConnList;
+ for (size_t x = 0; x < newConnsToCreate; x++) {
+ ShardConnection* newConn =
+ new ShardConnection(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ checkFunc(newConn->get()->getSockCreationMicroSec(), arg2);
+ newConnList.push_back(newConn);
}
- private:
- MockRemoteDBServer* _dummyServer;
- uint32_t _maxPoolSizePerHost;
- };
+ const uint64_t oldCreationTime = mongo::curTimeMicros64();
- TEST_F(ShardConnFixture, BasicShardConnection) {
- ShardConnection conn1(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- ShardConnection conn2(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ for (vector<ShardConnection*>::iterator iter = newConnList.begin();
+ iter != newConnList.end();
+ ++iter) {
+ (*iter)->done();
+ delete *iter;
+ }
- DBClientBase* conn1Ptr = conn1.get();
- conn1.done();
+ newConnList.clear();
- ShardConnection conn3(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- ASSERT_EQUALS(conn1Ptr, conn3.get());
+ // Check that connections created after the purge was put back to the pool.
+ for (size_t x = 0; x < newConnsToCreate; x++) {
+ ShardConnection* newConn =
+ new ShardConnection(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ ASSERT_LESS_THAN(newConn->get()->getSockCreationMicroSec(), oldCreationTime);
+ newConnList.push_back(newConn);
+ }
- conn2.done();
- conn3.done();
+ for (vector<ShardConnection*>::iterator iter = newConnList.begin();
+ iter != newConnList.end();
+ ++iter) {
+ (*iter)->done();
+ delete *iter;
+ }
}
- TEST_F(ShardConnFixture, InvalidateBadConnInPool) {
- ShardConnection conn1(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- ShardConnection conn2(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- ShardConnection conn3(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+private:
+ MockRemoteDBServer* _dummyServer;
+ uint32_t _maxPoolSizePerHost;
+};
- conn1.done();
- conn3.done();
+TEST_F(ShardConnFixture, BasicShardConnection) {
+ ShardConnection conn1(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ ShardConnection conn2(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- const uint64_t badCreationTime = mongo::curTimeMicros64();
- killServer();
+ DBClientBase* conn1Ptr = conn1.get();
+ conn1.done();
- try {
- conn2.get()->query("test.user", mongo::Query());
- }
- catch (const mongo::SocketException&) {
- }
+ ShardConnection conn3(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ ASSERT_EQUALS(conn1Ptr, conn3.get());
- conn2.done();
+ conn2.done();
+ conn3.done();
+}
- restartServer();
- checkNewConns(assertGreaterThan, badCreationTime, 10);
- }
+TEST_F(ShardConnFixture, InvalidateBadConnInPool) {
+ ShardConnection conn1(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ ShardConnection conn2(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ ShardConnection conn3(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- TEST_F(ShardConnFixture, DontReturnKnownBadConnToPool) {
- ShardConnection conn1(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- ShardConnection conn2(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- ShardConnection conn3(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ conn1.done();
+ conn3.done();
- conn1.done();
- killServer();
+ const uint64_t badCreationTime = mongo::curTimeMicros64();
+ killServer();
- try {
- conn3.get()->query("test.user", mongo::Query());
- }
- catch (const mongo::SocketException&) {
- }
+ try {
+ conn2.get()->query("test.user", mongo::Query());
+ } catch (const mongo::SocketException&) {
+ }
+
+ conn2.done();
- restartServer();
+ restartServer();
+ checkNewConns(assertGreaterThan, badCreationTime, 10);
+}
- const uint64_t badCreationTime = conn3.get()->getSockCreationMicroSec();
- conn3.done();
- // attempting to put a 'bad' connection back to the pool
- conn2.done();
+TEST_F(ShardConnFixture, DontReturnKnownBadConnToPool) {
+ ShardConnection conn1(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ ShardConnection conn2(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ ShardConnection conn3(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- checkNewConns(assertGreaterThan, badCreationTime, 10);
+ conn1.done();
+ killServer();
+
+ try {
+ conn3.get()->query("test.user", mongo::Query());
+ } catch (const mongo::SocketException&) {
}
- TEST_F(ShardConnFixture, BadConnClearsPoolWhenKilled) {
- ShardConnection conn1(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- ShardConnection conn2(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- ShardConnection conn3(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ restartServer();
- conn1.done();
- killServer();
+ const uint64_t badCreationTime = conn3.get()->getSockCreationMicroSec();
+ conn3.done();
+ // attempting to put a 'bad' connection back to the pool
+ conn2.done();
- try {
- conn3.get()->query("test.user", mongo::Query());
- }
- catch (const mongo::SocketException&) {
- }
+ checkNewConns(assertGreaterThan, badCreationTime, 10);
+}
- restartServer();
+TEST_F(ShardConnFixture, BadConnClearsPoolWhenKilled) {
+ ShardConnection conn1(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ ShardConnection conn2(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ ShardConnection conn3(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- const uint64_t badCreationTime = conn3.get()->getSockCreationMicroSec();
- conn3.kill();
- // attempting to put a 'bad' connection back to the pool
- conn2.done();
+ conn1.done();
+ killServer();
- checkNewConns(assertGreaterThan, badCreationTime, 10);
+ try {
+ conn3.get()->query("test.user", mongo::Query());
+ } catch (const mongo::SocketException&) {
}
- TEST_F(ShardConnFixture, KilledGoodConnShouldNotClearPool) {
- ShardConnection conn1(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- ShardConnection conn2(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- ShardConnection conn3(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ restartServer();
- const uint64_t upperBoundCreationTime =
- conn3.get()->getSockCreationMicroSec();
- conn3.done();
+ const uint64_t badCreationTime = conn3.get()->getSockCreationMicroSec();
+ conn3.kill();
+ // attempting to put a 'bad' connection back to the pool
+ conn2.done();
- const uint64_t badCreationTime = conn1.get()->getSockCreationMicroSec();
- conn1.kill();
+ checkNewConns(assertGreaterThan, badCreationTime, 10);
+}
- conn2.done();
+TEST_F(ShardConnFixture, KilledGoodConnShouldNotClearPool) {
+ ShardConnection conn1(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ ShardConnection conn2(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ ShardConnection conn3(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- ShardConnection conn4(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- ShardConnection conn5(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ const uint64_t upperBoundCreationTime = conn3.get()->getSockCreationMicroSec();
+ conn3.done();
- ASSERT_GREATER_THAN(conn4.get()->getSockCreationMicroSec(), badCreationTime);
- ASSERT_LESS_THAN_OR_EQUALS(conn4.get()->getSockCreationMicroSec(),
- upperBoundCreationTime);
+ const uint64_t badCreationTime = conn1.get()->getSockCreationMicroSec();
+ conn1.kill();
- ASSERT_GREATER_THAN(conn5.get()->getSockCreationMicroSec(), badCreationTime);
- ASSERT_LESS_THAN_OR_EQUALS(conn5.get()->getSockCreationMicroSec(),
- upperBoundCreationTime);
+ conn2.done();
- checkNewConns(assertGreaterThan, upperBoundCreationTime, 10);
- }
+ ShardConnection conn4(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ ShardConnection conn5(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- TEST_F(ShardConnFixture, InvalidateBadConnEvenWhenPoolIsFull) {
- mongo::shardConnectionPool.setMaxPoolSize(2);
+ ASSERT_GREATER_THAN(conn4.get()->getSockCreationMicroSec(), badCreationTime);
+ ASSERT_LESS_THAN_OR_EQUALS(conn4.get()->getSockCreationMicroSec(), upperBoundCreationTime);
- ShardConnection conn1(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- ShardConnection conn2(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- ShardConnection conn3(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ ASSERT_GREATER_THAN(conn5.get()->getSockCreationMicroSec(), badCreationTime);
+ ASSERT_LESS_THAN_OR_EQUALS(conn5.get()->getSockCreationMicroSec(), upperBoundCreationTime);
- conn1.done();
- conn3.done();
+ checkNewConns(assertGreaterThan, upperBoundCreationTime, 10);
+}
- const uint64_t badCreationTime = mongo::curTimeMicros64();
- killServer();
+TEST_F(ShardConnFixture, InvalidateBadConnEvenWhenPoolIsFull) {
+ mongo::shardConnectionPool.setMaxPoolSize(2);
- try {
- conn2.get()->query("test.user", mongo::Query());
- }
- catch (const mongo::SocketException&) {
- }
+ ShardConnection conn1(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ ShardConnection conn2(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ ShardConnection conn3(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+
+ conn1.done();
+ conn3.done();
- conn2.done();
+ const uint64_t badCreationTime = mongo::curTimeMicros64();
+ killServer();
- restartServer();
- checkNewConns(assertGreaterThan, badCreationTime, 2);
+ try {
+ conn2.get()->query("test.user", mongo::Query());
+ } catch (const mongo::SocketException&) {
}
- TEST_F(ShardConnFixture, DontReturnConnGoneBadToPool) {
- ShardConnection conn1(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- const uint64_t conn1CreationTime = conn1.get()->getSockCreationMicroSec();
+ conn2.done();
- uint64_t conn2CreationTime = 0;
+ restartServer();
+ checkNewConns(assertGreaterThan, badCreationTime, 2);
+}
- {
- ShardConnection conn2(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- conn2CreationTime = conn2.get()->getSockCreationMicroSec();
+TEST_F(ShardConnFixture, DontReturnConnGoneBadToPool) {
+ ShardConnection conn1(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ const uint64_t conn1CreationTime = conn1.get()->getSockCreationMicroSec();
- conn1.done();
- // conn2 gets out of scope without calling done()
- }
+ uint64_t conn2CreationTime = 0;
- // conn2 should not have been put back into the pool but it should
- // also not invalidate older connections since it didn't encounter
- // a socket exception.
-
- ShardConnection conn1Again(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
- ASSERT_EQUALS(conn1CreationTime, conn1Again.get()->getSockCreationMicroSec());
+ {
+ ShardConnection conn2(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ conn2CreationTime = conn2.get()->getSockCreationMicroSec();
- checkNewConns(assertNotEqual, conn2CreationTime, 10);
- conn1Again.done();
+ conn1.done();
+ // conn2 gets out of scope without calling done()
}
-} // namespace
-} // namespace mongo
+ // conn2 should not have been put back into the pool but it should
+ // also not invalidate older connections since it didn't encounter
+ // a socket exception.
+
+ ShardConnection conn1Again(ConnectionString(HostAndPort(TARGET_HOST)), "test.user");
+ ASSERT_EQUALS(conn1CreationTime, conn1Again.get()->getSockCreationMicroSec());
+
+ checkNewConns(assertNotEqual, conn2CreationTime, 10);
+ conn1Again.done();
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/client/shard_registry.cpp b/src/mongo/s/client/shard_registry.cpp
index 01efc45aaa4..ad33f35c661 100644
--- a/src/mongo/s/client/shard_registry.cpp
+++ b/src/mongo/s/client/shard_registry.cpp
@@ -48,281 +48,271 @@
namespace mongo {
- using std::shared_ptr;
- using std::string;
- using std::unique_ptr;
- using std::vector;
+using std::shared_ptr;
+using std::string;
+using std::unique_ptr;
+using std::vector;
- using executor::TaskExecutor;
- using RemoteCommandCallbackArgs = TaskExecutor::RemoteCommandCallbackArgs;
+using executor::TaskExecutor;
+using RemoteCommandCallbackArgs = TaskExecutor::RemoteCommandCallbackArgs;
namespace {
- const Seconds kConfigCommandTimeout{30};
-} // unnamed namespace
-
- ShardRegistry::ShardRegistry(std::unique_ptr<RemoteCommandTargeterFactory> targeterFactory,
- std::unique_ptr<RemoteCommandRunner> commandRunner,
- std::unique_ptr<executor::TaskExecutor> executor,
- CatalogManager* catalogManager)
- : _targeterFactory(std::move(targeterFactory)),
- _commandRunner(std::move(commandRunner)),
- _executor(std::move(executor)),
- _catalogManager(catalogManager) {
-
- // add config shard registry entry so know it's always there
- std::lock_guard<std::mutex> lk(_mutex);
- _addConfigShard_inlock();
+const Seconds kConfigCommandTimeout{30};
+} // unnamed namespace
- }
+ShardRegistry::ShardRegistry(std::unique_ptr<RemoteCommandTargeterFactory> targeterFactory,
+ std::unique_ptr<RemoteCommandRunner> commandRunner,
+ std::unique_ptr<executor::TaskExecutor> executor,
+ CatalogManager* catalogManager)
+ : _targeterFactory(std::move(targeterFactory)),
+ _commandRunner(std::move(commandRunner)),
+ _executor(std::move(executor)),
+ _catalogManager(catalogManager) {
+ // add config shard registry entry so know it's always there
+ std::lock_guard<std::mutex> lk(_mutex);
+ _addConfigShard_inlock();
+}
- ShardRegistry::~ShardRegistry() = default;
+ShardRegistry::~ShardRegistry() = default;
- void ShardRegistry::reload() {
- vector<ShardType> shards;
- Status status = _catalogManager->getAllShards(&shards);
- massert(13632, "couldn't get updated shard list from config server", status.isOK());
+void ShardRegistry::reload() {
+ vector<ShardType> shards;
+ Status status = _catalogManager->getAllShards(&shards);
+ massert(13632, "couldn't get updated shard list from config server", status.isOK());
- int numShards = shards.size();
+ int numShards = shards.size();
- LOG(1) << "found " << numShards << " shards listed on config server(s)";
+ LOG(1) << "found " << numShards << " shards listed on config server(s)";
- std::lock_guard<std::mutex> lk(_mutex);
+ std::lock_guard<std::mutex> lk(_mutex);
- _lookup.clear();
- _rsLookup.clear();
+ _lookup.clear();
+ _rsLookup.clear();
- _addConfigShard_inlock();
+ _addConfigShard_inlock();
- for (const ShardType& shardType : shards) {
- uassertStatusOK(shardType.validate());
+ for (const ShardType& shardType : shards) {
+ uassertStatusOK(shardType.validate());
- // Skip the config host even if there is one left over from legacy installations. The
- // config host is installed manually from the catalog manager data.
- if (shardType.getName() == "config") {
- continue;
- }
-
- _addShard_inlock(shardType);
+ // Skip the config host even if there is one left over from legacy installations. The
+ // config host is installed manually from the catalog manager data.
+ if (shardType.getName() == "config") {
+ continue;
}
+
+ _addShard_inlock(shardType);
}
+}
- shared_ptr<Shard> ShardRegistry::getShard(const ShardId& shardId) {
- shared_ptr<Shard> shard = _findUsingLookUp(shardId);
- if (shard) {
- return shard;
- }
+shared_ptr<Shard> ShardRegistry::getShard(const ShardId& shardId) {
+ shared_ptr<Shard> shard = _findUsingLookUp(shardId);
+ if (shard) {
+ return shard;
+ }
- // If we can't find the shard, we might just need to reload the cache
- reload();
+ // If we can't find the shard, we might just need to reload the cache
+ reload();
- return _findUsingLookUp(shardId);
- }
+ return _findUsingLookUp(shardId);
+}
- shared_ptr<Shard> ShardRegistry::lookupRSName(const string& name) const {
- std::lock_guard<std::mutex> lk(_mutex);
- ShardMap::const_iterator i = _rsLookup.find(name);
+shared_ptr<Shard> ShardRegistry::lookupRSName(const string& name) const {
+ std::lock_guard<std::mutex> lk(_mutex);
+ ShardMap::const_iterator i = _rsLookup.find(name);
- return (i == _rsLookup.end()) ? nullptr : i->second;
- }
+ return (i == _rsLookup.end()) ? nullptr : i->second;
+}
- void ShardRegistry::remove(const ShardId& id) {
- std::lock_guard<std::mutex> lk(_mutex);
+void ShardRegistry::remove(const ShardId& id) {
+ std::lock_guard<std::mutex> lk(_mutex);
- for (ShardMap::iterator i = _lookup.begin(); i != _lookup.end();) {
- shared_ptr<Shard> s = i->second;
- if (s->getId() == id) {
- _lookup.erase(i++);
- }
- else {
- ++i;
- }
+ for (ShardMap::iterator i = _lookup.begin(); i != _lookup.end();) {
+ shared_ptr<Shard> s = i->second;
+ if (s->getId() == id) {
+ _lookup.erase(i++);
+ } else {
+ ++i;
}
+ }
- for (ShardMap::iterator i = _rsLookup.begin(); i != _rsLookup.end();) {
- shared_ptr<Shard> s = i->second;
- if (s->getId() == id) {
- _rsLookup.erase(i++);
- }
- else {
- ++i;
- }
+ for (ShardMap::iterator i = _rsLookup.begin(); i != _rsLookup.end();) {
+ shared_ptr<Shard> s = i->second;
+ if (s->getId() == id) {
+ _rsLookup.erase(i++);
+ } else {
+ ++i;
}
}
+}
- void ShardRegistry::getAllShardIds(vector<ShardId>* all) const {
- std::set<string> seen;
-
- {
- std::lock_guard<std::mutex> lk(_mutex);
- for (ShardMap::const_iterator i = _lookup.begin(); i != _lookup.end(); ++i) {
- const shared_ptr<Shard>& s = i->second;
- if (s->getId() == "config") {
- continue;
- }
-
- if (seen.count(s->getId())) {
- continue;
- }
- seen.insert(s->getId());
+void ShardRegistry::getAllShardIds(vector<ShardId>* all) const {
+ std::set<string> seen;
+
+ {
+ std::lock_guard<std::mutex> lk(_mutex);
+ for (ShardMap::const_iterator i = _lookup.begin(); i != _lookup.end(); ++i) {
+ const shared_ptr<Shard>& s = i->second;
+ if (s->getId() == "config") {
+ continue;
}
- }
- all->assign(seen.begin(), seen.end());
+ if (seen.count(s->getId())) {
+ continue;
+ }
+ seen.insert(s->getId());
+ }
}
- void ShardRegistry::toBSON(BSONObjBuilder* result) {
- BSONObjBuilder b(_lookup.size() + 50);
+ all->assign(seen.begin(), seen.end());
+}
- std::lock_guard<std::mutex> lk(_mutex);
+void ShardRegistry::toBSON(BSONObjBuilder* result) {
+ BSONObjBuilder b(_lookup.size() + 50);
- for (ShardMap::const_iterator i = _lookup.begin(); i != _lookup.end(); ++i) {
- b.append(i->first, i->second->getConnString().toString());
- }
+ std::lock_guard<std::mutex> lk(_mutex);
- result->append("map", b.obj());
+ for (ShardMap::const_iterator i = _lookup.begin(); i != _lookup.end(); ++i) {
+ b.append(i->first, i->second->getConnString().toString());
}
- void ShardRegistry::_addConfigShard_inlock() {
- ShardType configServerShard;
- configServerShard.setName("config");
- configServerShard.setHost(_catalogManager->connectionString().toString());
- _addShard_inlock(configServerShard);
+ result->append("map", b.obj());
+}
+
+void ShardRegistry::_addConfigShard_inlock() {
+ ShardType configServerShard;
+ configServerShard.setName("config");
+ configServerShard.setHost(_catalogManager->connectionString().toString());
+ _addShard_inlock(configServerShard);
+}
+
+void ShardRegistry::_addShard_inlock(const ShardType& shardType) {
+ // This validation should ideally go inside the ShardType::validate call. However, doing
+ // it there would prevent us from loading previously faulty shard hosts, which might have
+ // been stored (i.e., the entire getAllShards call would fail).
+ auto shardHostStatus = ConnectionString::parse(shardType.getHost());
+ if (!shardHostStatus.isOK()) {
+ warning() << "Unable to parse shard host " << shardHostStatus.getStatus().toString();
}
- void ShardRegistry::_addShard_inlock(const ShardType& shardType) {
- // This validation should ideally go inside the ShardType::validate call. However, doing
- // it there would prevent us from loading previously faulty shard hosts, which might have
- // been stored (i.e., the entire getAllShards call would fail).
- auto shardHostStatus = ConnectionString::parse(shardType.getHost());
- if (!shardHostStatus.isOK()) {
- warning() << "Unable to parse shard host "
- << shardHostStatus.getStatus().toString();
- }
-
- const ConnectionString& shardHost(shardHostStatus.getValue());
-
- // Sync cluster connections (legacy config server) do not go through the normal targeting
- // mechanism and must only be reachable through CatalogManagerLegacy or legacy-style
- // queries and inserts. Do not create targeter for these connections. This code should go
- // away after 3.2 is released.
- if (shardHost.type() == ConnectionString::SYNC) {
- _lookup[shardType.getName()] =
- std::make_shared<Shard>(shardType.getName(), shardHost, nullptr);
- return;
- }
+ const ConnectionString& shardHost(shardHostStatus.getValue());
- // Non-SYNC shards
- shared_ptr<Shard> shard =
- std::make_shared<Shard>(shardType.getName(),
- shardHost,
- std::move(_targeterFactory->create(shardHost)));
+ // Sync cluster connections (legacy config server) do not go through the normal targeting
+ // mechanism and must only be reachable through CatalogManagerLegacy or legacy-style
+ // queries and inserts. Do not create targeter for these connections. This code should go
+ // away after 3.2 is released.
+ if (shardHost.type() == ConnectionString::SYNC) {
+ _lookup[shardType.getName()] =
+ std::make_shared<Shard>(shardType.getName(), shardHost, nullptr);
+ return;
+ }
- _lookup[shardType.getName()] = shard;
+ // Non-SYNC shards
+ shared_ptr<Shard> shard = std::make_shared<Shard>(
+ shardType.getName(), shardHost, std::move(_targeterFactory->create(shardHost)));
- // TODO: The only reason to have the shard host names in the lookup table is for the
- // setShardVersion call, which resolves the shard id from the shard address. This is
- // error-prone and will go away eventually when we switch all communications to go through
- // the remote command runner.
- _lookup[shardType.getHost()] = shard;
+ _lookup[shardType.getName()] = shard;
- for (const HostAndPort& hostAndPort : shardHost.getServers()) {
- _lookup[hostAndPort.toString()] = shard;
+ // TODO: The only reason to have the shard host names in the lookup table is for the
+ // setShardVersion call, which resolves the shard id from the shard address. This is
+ // error-prone and will go away eventually when we switch all communications to go through
+ // the remote command runner.
+ _lookup[shardType.getHost()] = shard;
- // Maintain a mapping from host to shard it belongs to for the case where we need to
- // update the shard connection string on reconfigurations.
- if (shardHost.type() == ConnectionString::SET) {
- _rsLookup[hostAndPort.toString()] = shard;
- }
- }
+ for (const HostAndPort& hostAndPort : shardHost.getServers()) {
+ _lookup[hostAndPort.toString()] = shard;
+ // Maintain a mapping from host to shard it belongs to for the case where we need to
+ // update the shard connection string on reconfigurations.
if (shardHost.type() == ConnectionString::SET) {
- _rsLookup[shardHost.getSetName()] = shard;
+ _rsLookup[hostAndPort.toString()] = shard;
}
}
- shared_ptr<Shard> ShardRegistry::_findUsingLookUp(const ShardId& shardId) {
- std::lock_guard<std::mutex> lk(_mutex);
- ShardMap::iterator it = _lookup.find(shardId);
- if (it != _lookup.end()) {
- return it->second;
- }
-
- return nullptr;
+ if (shardHost.type() == ConnectionString::SET) {
+ _rsLookup[shardHost.getSetName()] = shard;
}
+}
- StatusWith<std::vector<BSONObj>> ShardRegistry::exhaustiveFind(const HostAndPort& host,
- const NamespaceString& nss,
- const BSONObj& query,
- boost::optional<int> limit) {
- // If for some reason the callback never gets invoked, we will return this status
- Status status = Status(ErrorCodes::InternalError, "Internal error running find command");
- vector<BSONObj> results;
-
- auto fetcherCallback = [&status, &results](const Fetcher::QueryResponseStatus& dataStatus,
- Fetcher::NextAction* nextAction) {
-
- // Throw out any accumulated results on error
- if (!dataStatus.isOK()) {
- status = dataStatus.getStatus();
- results.clear();
- return;
- }
+shared_ptr<Shard> ShardRegistry::_findUsingLookUp(const ShardId& shardId) {
+ std::lock_guard<std::mutex> lk(_mutex);
+ ShardMap::iterator it = _lookup.find(shardId);
+ if (it != _lookup.end()) {
+ return it->second;
+ }
- auto& data = dataStatus.getValue();
- for (const BSONObj& doc : data.documents) {
- results.push_back(std::move(doc.getOwned()));
- }
+ return nullptr;
+}
- status = Status::OK();
- };
+StatusWith<std::vector<BSONObj>> ShardRegistry::exhaustiveFind(const HostAndPort& host,
+ const NamespaceString& nss,
+ const BSONObj& query,
+ boost::optional<int> limit) {
+ // If for some reason the callback never gets invoked, we will return this status
+ Status status = Status(ErrorCodes::InternalError, "Internal error running find command");
+ vector<BSONObj> results;
- unique_ptr<LiteParsedQuery> findCmd(
- fassertStatusOK(28688, LiteParsedQuery::makeAsFindCmd(nss, query, std::move(limit))));
+ auto fetcherCallback = [&status, &results](const Fetcher::QueryResponseStatus& dataStatus,
+ Fetcher::NextAction* nextAction) {
- QueryFetcher fetcher(_executor.get(),
- host,
- nss,
- findCmd->asFindCommand(),
- fetcherCallback);
+ // Throw out any accumulated results on error
+ if (!dataStatus.isOK()) {
+ status = dataStatus.getStatus();
+ results.clear();
+ return;
+ }
- Status scheduleStatus = fetcher.schedule();
- if (!scheduleStatus.isOK()) {
- return scheduleStatus;
+ auto& data = dataStatus.getValue();
+ for (const BSONObj& doc : data.documents) {
+ results.push_back(std::move(doc.getOwned()));
}
- fetcher.wait();
+ status = Status::OK();
+ };
- if (!status.isOK()) {
- return status;
- }
+ unique_ptr<LiteParsedQuery> findCmd(
+ fassertStatusOK(28688, LiteParsedQuery::makeAsFindCmd(nss, query, std::move(limit))));
+
+ QueryFetcher fetcher(_executor.get(), host, nss, findCmd->asFindCommand(), fetcherCallback);
- return results;
+ Status scheduleStatus = fetcher.schedule();
+ if (!scheduleStatus.isOK()) {
+ return scheduleStatus;
}
- StatusWith<BSONObj> ShardRegistry::runCommand(const HostAndPort& host,
- const std::string& dbName,
- const BSONObj& cmdObj) {
- StatusWith<RemoteCommandResponse> responseStatus =
- Status(ErrorCodes::InternalError, "Internal error running command");
+ fetcher.wait();
- RemoteCommandRequest request(host, dbName, cmdObj, kConfigCommandTimeout);
- auto callStatus = _executor->scheduleRemoteCommand(request,
- [&responseStatus](const RemoteCommandCallbackArgs& args) {
- responseStatus = args.response;
- });
+ if (!status.isOK()) {
+ return status;
+ }
- if (!callStatus.isOK()) {
- return callStatus.getStatus();
- }
+ return results;
+}
- // Block until the command is carried out
- _executor->wait(callStatus.getValue());
+StatusWith<BSONObj> ShardRegistry::runCommand(const HostAndPort& host,
+ const std::string& dbName,
+ const BSONObj& cmdObj) {
+ StatusWith<RemoteCommandResponse> responseStatus =
+ Status(ErrorCodes::InternalError, "Internal error running command");
- if (!responseStatus.isOK()) {
- return responseStatus.getStatus();
- }
+ RemoteCommandRequest request(host, dbName, cmdObj, kConfigCommandTimeout);
+ auto callStatus =
+ _executor->scheduleRemoteCommand(request,
+ [&responseStatus](const RemoteCommandCallbackArgs& args) {
+ responseStatus = args.response;
+ });
+
+ if (!callStatus.isOK()) {
+ return callStatus.getStatus();
+ }
+
+ // Block until the command is carried out
+ _executor->wait(callStatus.getValue());
- return responseStatus.getValue().data;
+ if (!responseStatus.isOK()) {
+ return responseStatus.getStatus();
}
-} // namespace mongo
+ return responseStatus.getValue().data;
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/client/shard_registry.h b/src/mongo/s/client/shard_registry.h
index 57f6526d723..ce9898b23f2 100644
--- a/src/mongo/s/client/shard_registry.h
+++ b/src/mongo/s/client/shard_registry.h
@@ -38,127 +38,132 @@
namespace mongo {
- class BSONObjBuilder;
- class CatalogManager;
- struct HostAndPort;
- class NamespaceString;
- class RemoteCommandRunner;
- class RemoteCommandTargeterFactory;
- class Shard;
- class ShardType;
-
- template<typename T> class StatusWith;
+class BSONObjBuilder;
+class CatalogManager;
+struct HostAndPort;
+class NamespaceString;
+class RemoteCommandRunner;
+class RemoteCommandTargeterFactory;
+class Shard;
+class ShardType;
+
+template <typename T>
+class StatusWith;
namespace executor {
- class TaskExecutor;
+class TaskExecutor;
+
+} // namespace executor
+
+/**
+ * Maintains the set of all shards known to the instance and their connections. Manages polling
+ * the respective replica sets for membership changes.
+ */
+class ShardRegistry {
+public:
+ /**
+ * Instantiates a new shard registry.
+ *
+ * @param targeterFactory Produces targeters for each shard's individual connection string
+ * @param commandRunner Command runner for executing commands against hosts
+ * @param executor Asynchronous task executor to use for making calls to shards.
+ * @param catalogManager Used to retrieve the list of registered shard. TODO: remove.
+ */
+ ShardRegistry(std::unique_ptr<RemoteCommandTargeterFactory> targeterFactory,
+ std::unique_ptr<RemoteCommandRunner> commandRunner,
+ std::unique_ptr<executor::TaskExecutor> executor,
+ CatalogManager* catalogManager);
+
+ ~ShardRegistry();
+
+ RemoteCommandRunner* getCommandRunner() const {
+ return _commandRunner.get();
+ }
+
+ executor::TaskExecutor* getExecutor() const {
+ return _executor.get();
+ }
-} // namespace executor
+ void reload();
/**
- * Maintains the set of all shards known to the instance and their connections. Manages polling
- * the respective replica sets for membership changes.
+ * Returns shared pointer to shard object with given shard id.
*/
- class ShardRegistry {
- public:
- /**
- * Instantiates a new shard registry.
- *
- * @param targeterFactory Produces targeters for each shard's individual connection string
- * @param commandRunner Command runner for executing commands against hosts
- * @param executor Asynchronous task executor to use for making calls to shards.
- * @param catalogManager Used to retrieve the list of registered shard. TODO: remove.
- */
- ShardRegistry(std::unique_ptr<RemoteCommandTargeterFactory> targeterFactory,
- std::unique_ptr<RemoteCommandRunner> commandRunner,
- std::unique_ptr<executor::TaskExecutor> executor,
- CatalogManager* catalogManager);
-
- ~ShardRegistry();
-
- RemoteCommandRunner* getCommandRunner() const { return _commandRunner.get(); }
-
- executor::TaskExecutor* getExecutor() const { return _executor.get(); }
-
- void reload();
-
- /**
- * Returns shared pointer to shard object with given shard id.
- */
- std::shared_ptr<Shard> getShard(const ShardId& shardId);
-
- /**
- * Lookup shard by replica set name. Returns nullptr if the name can't be found.
- * Note: this doesn't refresh the table if the name isn't found, so it's possible that a
- * newly added shard/Replica Set may not be found.
- */
- std::shared_ptr<Shard> lookupRSName(const std::string& name) const;
-
- void remove(const ShardId& id);
-
- void getAllShardIds(std::vector<ShardId>* all) const;
-
- void toBSON(BSONObjBuilder* result);
-
- /**
- * Executes 'find' command against the specified host and fetches *all* the results that
- * the host will return until there are no more or until an error is returned.
- *
- * Returns either the complete set of results or an error, never partial results.
- *
- * Note: should never be used outside of CatalogManagerReplicaSet or DistLockCatalogImpl.
- */
- StatusWith<std::vector<BSONObj>> exhaustiveFind(const HostAndPort& host,
- const NamespaceString& nss,
- const BSONObj& query,
- boost::optional<int> limit);
-
- /**
- * Runs a command against the specified host and returns the result.
- */
- StatusWith<BSONObj> runCommand(const HostAndPort& host,
- const std::string& dbName,
- const BSONObj& cmdObj);
-
- private:
- typedef std::map<ShardId, std::shared_ptr<Shard>> ShardMap;
-
- /**
- * Creates a shard based on the specified information and puts it into the lookup maps.
- */
- void _addShard_inlock(const ShardType& shardType);
-
- /**
- * Adds the "config" shard (representing the config server) to the shard registry.
- */
- void _addConfigShard_inlock();
-
- std::shared_ptr<Shard> _findUsingLookUp(const ShardId& shardId);
-
- // Factory to obtain remote command targeters for shards
- const std::unique_ptr<RemoteCommandTargeterFactory> _targeterFactory;
-
- // API to run remote commands to shards in a synchronous manner
- const std::unique_ptr<RemoteCommandRunner> _commandRunner;
-
- // Executor for scheduling work and remote commands to shards that run in an asynchronous
- // manner.
- const std::unique_ptr<executor::TaskExecutor> _executor;
-
- // Catalog manager from which to load the shard information. Not owned and must outlive
- // the shard registry object.
- CatalogManager* const _catalogManager;
-
- // Protects the maps below
- mutable std::mutex _mutex;
-
- // Map of both shardName -> Shard and hostName -> Shard
- ShardMap _lookup;
-
- // TODO: These should eventually disappear and become parts of Shard
-
- // Map from all hosts within a replica set to the shard representing this replica set
- ShardMap _rsLookup;
- };
-
-} // namespace mongo
+ std::shared_ptr<Shard> getShard(const ShardId& shardId);
+
+ /**
+ * Lookup shard by replica set name. Returns nullptr if the name can't be found.
+ * Note: this doesn't refresh the table if the name isn't found, so it's possible that a
+ * newly added shard/Replica Set may not be found.
+ */
+ std::shared_ptr<Shard> lookupRSName(const std::string& name) const;
+
+ void remove(const ShardId& id);
+
+ void getAllShardIds(std::vector<ShardId>* all) const;
+
+ void toBSON(BSONObjBuilder* result);
+
+ /**
+ * Executes 'find' command against the specified host and fetches *all* the results that
+ * the host will return until there are no more or until an error is returned.
+ *
+ * Returns either the complete set of results or an error, never partial results.
+ *
+ * Note: should never be used outside of CatalogManagerReplicaSet or DistLockCatalogImpl.
+ */
+ StatusWith<std::vector<BSONObj>> exhaustiveFind(const HostAndPort& host,
+ const NamespaceString& nss,
+ const BSONObj& query,
+ boost::optional<int> limit);
+
+ /**
+ * Runs a command against the specified host and returns the result.
+ */
+ StatusWith<BSONObj> runCommand(const HostAndPort& host,
+ const std::string& dbName,
+ const BSONObj& cmdObj);
+
+private:
+ typedef std::map<ShardId, std::shared_ptr<Shard>> ShardMap;
+
+ /**
+ * Creates a shard based on the specified information and puts it into the lookup maps.
+ */
+ void _addShard_inlock(const ShardType& shardType);
+
+ /**
+ * Adds the "config" shard (representing the config server) to the shard registry.
+ */
+ void _addConfigShard_inlock();
+
+ std::shared_ptr<Shard> _findUsingLookUp(const ShardId& shardId);
+
+ // Factory to obtain remote command targeters for shards
+ const std::unique_ptr<RemoteCommandTargeterFactory> _targeterFactory;
+
+ // API to run remote commands to shards in a synchronous manner
+ const std::unique_ptr<RemoteCommandRunner> _commandRunner;
+
+ // Executor for scheduling work and remote commands to shards that run in an asynchronous
+ // manner.
+ const std::unique_ptr<executor::TaskExecutor> _executor;
+
+ // Catalog manager from which to load the shard information. Not owned and must outlive
+ // the shard registry object.
+ CatalogManager* const _catalogManager;
+
+ // Protects the maps below
+ mutable std::mutex _mutex;
+
+ // Map of both shardName -> Shard and hostName -> Shard
+ ShardMap _lookup;
+
+ // TODO: These should eventually disappear and become parts of Shard
+
+ // Map from all hosts within a replica set to the shard representing this replica set
+ ShardMap _rsLookup;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/client/sharding_connection_hook.cpp b/src/mongo/s/client/sharding_connection_hook.cpp
index b1567441d83..80e924e1121 100644
--- a/src/mongo/s/client/sharding_connection_hook.cpp
+++ b/src/mongo/s/client/sharding_connection_hook.cpp
@@ -47,101 +47,97 @@
namespace mongo {
- using std::string;
+using std::string;
namespace {
- bool initWireVersion(DBClientBase* conn, std::string* errMsg) {
- BSONObj response;
- if (!conn->runCommand("admin", BSON("isMaster" << 1), response)) {
- *errMsg = str::stream() << "Failed to determine wire version "
- << "for internal connection: " << response;
- return false;
- }
-
- if (response.hasField("minWireVersion") && response.hasField("maxWireVersion")) {
- int minWireVersion = response["minWireVersion"].numberInt();
- int maxWireVersion = response["maxWireVersion"].numberInt();
- conn->setWireVersions(minWireVersion, maxWireVersion);
- }
-
- return true;
+bool initWireVersion(DBClientBase* conn, std::string* errMsg) {
+ BSONObj response;
+ if (!conn->runCommand("admin", BSON("isMaster" << 1), response)) {
+ *errMsg = str::stream() << "Failed to determine wire version "
+ << "for internal connection: " << response;
+ return false;
}
-} // namespace
+ if (response.hasField("minWireVersion") && response.hasField("maxWireVersion")) {
+ int minWireVersion = response["minWireVersion"].numberInt();
+ int maxWireVersion = response["maxWireVersion"].numberInt();
+ conn->setWireVersions(minWireVersion, maxWireVersion);
+ }
+ return true;
+}
- ShardingConnectionHook::ShardingConnectionHook(bool shardedConnections)
- : _shardedConnections(shardedConnections) {
+} // namespace
- }
- void ShardingConnectionHook::onCreate(DBClientBase * conn) {
+ShardingConnectionHook::ShardingConnectionHook(bool shardedConnections)
+ : _shardedConnections(shardedConnections) {}
- // Authenticate as the first thing we do
- // NOTE: Replica set authentication allows authentication against *any* online host
- if (getGlobalAuthorizationManager()->isAuthEnabled()) {
- LOG(2) << "calling onCreate auth for " << conn->toString();
+void ShardingConnectionHook::onCreate(DBClientBase* conn) {
+ // Authenticate as the first thing we do
+ // NOTE: Replica set authentication allows authentication against *any* online host
+ if (getGlobalAuthorizationManager()->isAuthEnabled()) {
+ LOG(2) << "calling onCreate auth for " << conn->toString();
- bool result = authenticateInternalUser(conn);
+ bool result = authenticateInternalUser(conn);
- uassert(15847, str::stream() << "can't authenticate to server "
- << conn->getServerAddress(),
+ uassert(15847,
+ str::stream() << "can't authenticate to server " << conn->getServerAddress(),
result);
- }
-
- // Initialize the wire version of single connections
- if (conn->type() == ConnectionString::MASTER) {
+ }
- LOG(2) << "checking wire version of new connection " << conn->toString();
+ // Initialize the wire version of single connections
+ if (conn->type() == ConnectionString::MASTER) {
+ LOG(2) << "checking wire version of new connection " << conn->toString();
- // Initialize the wire protocol version of the connection to find out if we
- // can send write commands to this connection.
- string errMsg;
- if (!initWireVersion(conn, &errMsg)) {
- uasserted(17363, errMsg);
- }
+ // Initialize the wire protocol version of the connection to find out if we
+ // can send write commands to this connection.
+ string errMsg;
+ if (!initWireVersion(conn, &errMsg)) {
+ uasserted(17363, errMsg);
}
+ }
- if (_shardedConnections) {
- // For every DBClient created by mongos, add a hook that will capture the response from
- // commands we pass along from the client, so that we can target the correct node when
- // subsequent getLastError calls are made by mongos.
- conn->setReplyMetadataReader([](const BSONObj& metadataObj, StringData hostString)
+ if (_shardedConnections) {
+ // For every DBClient created by mongos, add a hook that will capture the response from
+ // commands we pass along from the client, so that we can target the correct node when
+ // subsequent getLastError calls are made by mongos.
+ conn->setReplyMetadataReader([](const BSONObj& metadataObj, StringData hostString)
-> Status {
- saveGLEStats(metadataObj, hostString);
- return Status::OK();
- });
- }
-
- // For every DBClient created by mongos, add a hook that will append impersonated users
- // to the end of every runCommand. mongod uses this information to produce auditing
- // records attributed to the proper authenticated user(s).
- conn->setRequestMetadataWriter([](BSONObjBuilder* metadataBob) -> Status {
- audit::writeImpersonatedUsersToMetadata(metadataBob);
- return Status::OK();
- });
-
- // For every SCC created, add a hook that will allow fastest-config-first config reads if
- // the appropriate server options are set.
- if (conn->type() == ConnectionString::SYNC) {
- SyncClusterConnection* scc = dynamic_cast<SyncClusterConnection*>(conn);
- if (scc) {
- scc->attachQueryHandler(new SCCFastQueryHandler);
- }
- }
+ saveGLEStats(metadataObj, hostString);
+ return Status::OK();
+ });
}
- void ShardingConnectionHook::onDestroy(DBClientBase * conn) {
- if (_shardedConnections && versionManager.isVersionableCB(conn)){
- versionManager.resetShardVersionCB(conn);
+ // For every DBClient created by mongos, add a hook that will append impersonated users
+ // to the end of every runCommand. mongod uses this information to produce auditing
+ // records attributed to the proper authenticated user(s).
+ conn->setRequestMetadataWriter([](BSONObjBuilder* metadataBob) -> Status {
+ audit::writeImpersonatedUsersToMetadata(metadataBob);
+ return Status::OK();
+ });
+
+ // For every SCC created, add a hook that will allow fastest-config-first config reads if
+ // the appropriate server options are set.
+ if (conn->type() == ConnectionString::SYNC) {
+ SyncClusterConnection* scc = dynamic_cast<SyncClusterConnection*>(conn);
+ if (scc) {
+ scc->attachQueryHandler(new SCCFastQueryHandler);
}
}
+}
- void ShardingConnectionHook::onRelease(DBClientBase* conn) {
- // This is currently for making the replica set connections release
- // secondary connections to the pool.
- conn->reset();
+void ShardingConnectionHook::onDestroy(DBClientBase* conn) {
+ if (_shardedConnections && versionManager.isVersionableCB(conn)) {
+ versionManager.resetShardVersionCB(conn);
}
+}
+
+void ShardingConnectionHook::onRelease(DBClientBase* conn) {
+ // This is currently for making the replica set connections release
+ // secondary connections to the pool.
+ conn->reset();
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/client/sharding_connection_hook.h b/src/mongo/s/client/sharding_connection_hook.h
index 661bb42f34c..a131f526472 100644
--- a/src/mongo/s/client/sharding_connection_hook.h
+++ b/src/mongo/s/client/sharding_connection_hook.h
@@ -32,22 +32,22 @@
namespace mongo {
- class DBClientBase;
+class DBClientBase;
- /**
- * Intercepts creation of sharded connections and transparently performs the internal
- * authentication on them.
- */
- class ShardingConnectionHook : public DBConnectionHook {
- public:
- ShardingConnectionHook(bool shardedConnections);
+/**
+ * Intercepts creation of sharded connections and transparently performs the internal
+ * authentication on them.
+ */
+class ShardingConnectionHook : public DBConnectionHook {
+public:
+ ShardingConnectionHook(bool shardedConnections);
- virtual void onCreate(DBClientBase* conn);
- virtual void onDestroy(DBClientBase* conn);
- virtual void onRelease(DBClientBase* conn);
+ virtual void onCreate(DBClientBase* conn);
+ virtual void onDestroy(DBClientBase* conn);
+ virtual void onRelease(DBClientBase* conn);
- private:
- bool _shardedConnections;
- };
+private:
+ bool _shardedConnections;
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/cluster_explain.cpp b/src/mongo/s/cluster_explain.cpp
index 1859b9b9a94..1c62c90e8fd 100644
--- a/src/mongo/s/cluster_explain.cpp
+++ b/src/mongo/s/cluster_explain.cpp
@@ -35,323 +35,311 @@
namespace mongo {
- using std::vector;
-
- const char* ClusterExplain::kSingleShard = "SINGLE_SHARD";
- const char* ClusterExplain::kMergeFromShards = "SHARD_MERGE";
- const char* ClusterExplain::kMergeSortFromShards = "SHARD_MERGE_SORT";
- const char* ClusterExplain::kWriteOnShards = "SHARD_WRITE";
-
- namespace {
-
- //
- // BSON size limit management: these functions conditionally append to a
- // BSON object or BSON array buidlder, depending on whether or not the
- // maximum user size for a BSON object will be exceeded.
- //
-
- bool appendIfRoom(BSONObjBuilder* bob,
- const BSONObj& toAppend,
- StringData fieldName) {
- if ((bob->len() + toAppend.objsize()) < BSONObjMaxUserSize) {
- bob->append(fieldName, toAppend);
- return true;
- }
+using std::vector;
- // Unless 'bob' has already exceeded the max BSON user size, add a warning indicating
- // that data has been truncated.
- if (bob->len() < BSONObjMaxUserSize) {
- bob->append("warning",
- "output truncated due to nearing BSON max user size");
- }
+const char* ClusterExplain::kSingleShard = "SINGLE_SHARD";
+const char* ClusterExplain::kMergeFromShards = "SHARD_MERGE";
+const char* ClusterExplain::kMergeSortFromShards = "SHARD_MERGE_SORT";
+const char* ClusterExplain::kWriteOnShards = "SHARD_WRITE";
- return false;
- }
+namespace {
- bool appendToArrayIfRoom(BSONArrayBuilder* arrayBuilder,
- const BSONElement& toAppend) {
- if ((arrayBuilder->len() + toAppend.size()) < BSONObjMaxUserSize) {
- arrayBuilder->append(toAppend);
- return true;
- }
+//
+// BSON size limit management: these functions conditionally append to a
+// BSON object or BSON array buidlder, depending on whether or not the
+// maximum user size for a BSON object will be exceeded.
+//
- // Unless 'arrayBuilder' has already exceeded the max BSON user size, add a warning
- // indicating that data has been truncated.
- if (arrayBuilder->len() < BSONObjMaxUserSize) {
- arrayBuilder->append(BSON("warning" <<
- "output truncated due to nearing BSON max user size"));
- }
+bool appendIfRoom(BSONObjBuilder* bob, const BSONObj& toAppend, StringData fieldName) {
+ if ((bob->len() + toAppend.objsize()) < BSONObjMaxUserSize) {
+ bob->append(fieldName, toAppend);
+ return true;
+ }
- return false;
- }
+ // Unless 'bob' has already exceeded the max BSON user size, add a warning indicating
+ // that data has been truncated.
+ if (bob->len() < BSONObjMaxUserSize) {
+ bob->append("warning", "output truncated due to nearing BSON max user size");
+ }
- bool appendElementsIfRoom(BSONObjBuilder* bob,
- const BSONObj& toAppend) {
- if ((bob->len() + toAppend.objsize()) < BSONObjMaxUserSize) {
- bob->appendElements(toAppend);
- return true;
- }
+ return false;
+}
- // Unless 'bob' has already exceeded the max BSON user size, add a warning indicating
- // that data has been truncated.
- if (bob->len() < BSONObjMaxUserSize) {
- bob->append("warning",
- "output truncated due to nearing BSON max user size");
- }
+bool appendToArrayIfRoom(BSONArrayBuilder* arrayBuilder, const BSONElement& toAppend) {
+ if ((arrayBuilder->len() + toAppend.size()) < BSONObjMaxUserSize) {
+ arrayBuilder->append(toAppend);
+ return true;
+ }
- return false;
- }
+ // Unless 'arrayBuilder' has already exceeded the max BSON user size, add a warning
+ // indicating that data has been truncated.
+ if (arrayBuilder->len() < BSONObjMaxUserSize) {
+ arrayBuilder->append(BSON("warning"
+ << "output truncated due to nearing BSON max user size"));
+ }
- } // namespace
+ return false;
+}
- // static
- void ClusterExplain::wrapAsExplain(const BSONObj& cmdObj,
- ExplainCommon::Verbosity verbosity,
- BSONObjBuilder* out) {
- out->append("explain", cmdObj);
- out->append("verbosity", ExplainCommon::verbosityString(verbosity));
+bool appendElementsIfRoom(BSONObjBuilder* bob, const BSONObj& toAppend) {
+ if ((bob->len() + toAppend.objsize()) < BSONObjMaxUserSize) {
+ bob->appendElements(toAppend);
+ return true;
+ }
- // If the command has a readPreference, then pull it up to the top level.
- if (cmdObj.hasField("$readPreference")) {
- out->append("$queryOptions", cmdObj["$readPreference"].wrap());
- }
+ // Unless 'bob' has already exceeded the max BSON user size, add a warning indicating
+ // that data has been truncated.
+ if (bob->len() < BSONObjMaxUserSize) {
+ bob->append("warning", "output truncated due to nearing BSON max user size");
}
- // static
- Status ClusterExplain::validateShardResults(
- const vector<Strategy::CommandResult>& shardResults) {
- // Error we didn't get results from any shards.
- if (shardResults.empty()) {
- return Status(ErrorCodes::InternalError, "no shards found for explain");
- }
+ return false;
+}
- // Count up the number of shards that have execution stats and all plans
- // execution level information.
- size_t numShardsExecStats = 0;
- size_t numShardsAllPlansStats = 0;
-
- // Check that the result from each shard has a true value for "ok" and has
- // the expected "queryPlanner" field.
- for (size_t i = 0; i < shardResults.size(); i++) {
- if (!shardResults[i].result["ok"].trueValue()) {
- // Try to pass up the error code from the shard.
- ErrorCodes::Error error = ErrorCodes::OperationFailed;
- if (shardResults[i].result["code"].isNumber()) {
- error = ErrorCodes::fromInt(shardResults[i].result["code"].numberInt());
- }
-
- return Status(error, str::stream()
- << "Explain command on shard " << shardResults[i].target.toString()
- << " failed, caused by: " << shardResults[i].result);
- }
+} // namespace
- if (Object != shardResults[i].result["queryPlanner"].type()) {
- return Status(ErrorCodes::OperationFailed, str::stream()
- << "Explain command on shard " << shardResults[i].target.toString()
- << " failed, caused by: " << shardResults[i].result);
- }
+// static
+void ClusterExplain::wrapAsExplain(const BSONObj& cmdObj,
+ ExplainCommon::Verbosity verbosity,
+ BSONObjBuilder* out) {
+ out->append("explain", cmdObj);
+ out->append("verbosity", ExplainCommon::verbosityString(verbosity));
+
+ // If the command has a readPreference, then pull it up to the top level.
+ if (cmdObj.hasField("$readPreference")) {
+ out->append("$queryOptions", cmdObj["$readPreference"].wrap());
+ }
+}
+
+// static
+Status ClusterExplain::validateShardResults(const vector<Strategy::CommandResult>& shardResults) {
+ // Error we didn't get results from any shards.
+ if (shardResults.empty()) {
+ return Status(ErrorCodes::InternalError, "no shards found for explain");
+ }
- if (shardResults[i].result.hasField("executionStats")) {
- numShardsExecStats++;
- BSONObj execStats = shardResults[i].result["executionStats"].Obj();
- if (execStats.hasField("allPlansExecution")) {
- numShardsAllPlansStats++;
- }
+ // Count up the number of shards that have execution stats and all plans
+ // execution level information.
+ size_t numShardsExecStats = 0;
+ size_t numShardsAllPlansStats = 0;
+
+ // Check that the result from each shard has a true value for "ok" and has
+ // the expected "queryPlanner" field.
+ for (size_t i = 0; i < shardResults.size(); i++) {
+ if (!shardResults[i].result["ok"].trueValue()) {
+ // Try to pass up the error code from the shard.
+ ErrorCodes::Error error = ErrorCodes::OperationFailed;
+ if (shardResults[i].result["code"].isNumber()) {
+ error = ErrorCodes::fromInt(shardResults[i].result["code"].numberInt());
}
+
+ return Status(error,
+ str::stream() << "Explain command on shard "
+ << shardResults[i].target.toString()
+ << " failed, caused by: " << shardResults[i].result);
}
- // Either all shards should have execution stats info, or none should.
- if (0 != numShardsExecStats && shardResults.size() != numShardsExecStats) {
- return Status(ErrorCodes::InternalError,
- str::stream() << "Only " << numShardsExecStats
- << "/" << shardResults.size()
- << " had executionStats explain information.");
+ if (Object != shardResults[i].result["queryPlanner"].type()) {
+ return Status(ErrorCodes::OperationFailed,
+ str::stream() << "Explain command on shard "
+ << shardResults[i].target.toString()
+ << " failed, caused by: " << shardResults[i].result);
}
- // Either all shards should have all plans execution stats, or none should.
- if (0 != numShardsAllPlansStats && shardResults.size() != numShardsAllPlansStats) {
- return Status(ErrorCodes::InternalError,
- str::stream() << "Only " << numShardsAllPlansStats
- << "/" << shardResults.size()
- << " had allPlansExecution explain information.");
+ if (shardResults[i].result.hasField("executionStats")) {
+ numShardsExecStats++;
+ BSONObj execStats = shardResults[i].result["executionStats"].Obj();
+ if (execStats.hasField("allPlansExecution")) {
+ numShardsAllPlansStats++;
+ }
}
+ }
- return Status::OK();
+ // Either all shards should have execution stats info, or none should.
+ if (0 != numShardsExecStats && shardResults.size() != numShardsExecStats) {
+ return Status(ErrorCodes::InternalError,
+ str::stream() << "Only " << numShardsExecStats << "/" << shardResults.size()
+ << " had executionStats explain information.");
}
- // static
- const char* ClusterExplain::getStageNameForReadOp(
- const vector<Strategy::CommandResult>& shardResults,
- const BSONObj& explainObj) {
- if (shardResults.size() == 1) {
- return kSingleShard;
- }
- else if (explainObj.hasField("sort")) {
- return kMergeSortFromShards;
- }
- else {
- return kMergeFromShards;
- }
+ // Either all shards should have all plans execution stats, or none should.
+ if (0 != numShardsAllPlansStats && shardResults.size() != numShardsAllPlansStats) {
+ return Status(ErrorCodes::InternalError,
+ str::stream() << "Only " << numShardsAllPlansStats << "/"
+ << shardResults.size()
+ << " had allPlansExecution explain information.");
}
- // static
- void ClusterExplain::buildPlannerInfo(const vector<Strategy::CommandResult>& shardResults,
- const char* mongosStageName,
- BSONObjBuilder* out) {
- BSONObjBuilder queryPlannerBob(out->subobjStart("queryPlanner"));
+ return Status::OK();
+}
+
+// static
+const char* ClusterExplain::getStageNameForReadOp(
+ const vector<Strategy::CommandResult>& shardResults, const BSONObj& explainObj) {
+ if (shardResults.size() == 1) {
+ return kSingleShard;
+ } else if (explainObj.hasField("sort")) {
+ return kMergeSortFromShards;
+ } else {
+ return kMergeFromShards;
+ }
+}
- queryPlannerBob.appendNumber("mongosPlannerVersion", 1);
- BSONObjBuilder winningPlanBob(queryPlannerBob.subobjStart("winningPlan"));
+// static
+void ClusterExplain::buildPlannerInfo(const vector<Strategy::CommandResult>& shardResults,
+ const char* mongosStageName,
+ BSONObjBuilder* out) {
+ BSONObjBuilder queryPlannerBob(out->subobjStart("queryPlanner"));
- winningPlanBob.append("stage", mongosStageName);
- BSONArrayBuilder shardsBuilder(winningPlanBob.subarrayStart("shards"));
- for (size_t i = 0; i < shardResults.size(); i++) {
- BSONObjBuilder singleShardBob(shardsBuilder.subobjStart());
+ queryPlannerBob.appendNumber("mongosPlannerVersion", 1);
+ BSONObjBuilder winningPlanBob(queryPlannerBob.subobjStart("winningPlan"));
- BSONObj queryPlanner = shardResults[i].result["queryPlanner"].Obj();
- BSONObj serverInfo = shardResults[i].result["serverInfo"].Obj();
+ winningPlanBob.append("stage", mongosStageName);
+ BSONArrayBuilder shardsBuilder(winningPlanBob.subarrayStart("shards"));
+ for (size_t i = 0; i < shardResults.size(); i++) {
+ BSONObjBuilder singleShardBob(shardsBuilder.subobjStart());
- singleShardBob.append("shardName", shardResults[i].shardTargetId);
- {
- const auto shard =
- grid.shardRegistry()->getShard(shardResults[i].shardTargetId);
- singleShardBob.append("connectionString", shard->getConnString().toString());
- }
- appendIfRoom(&singleShardBob, serverInfo, "serverInfo");
- appendElementsIfRoom(&singleShardBob, queryPlanner);
+ BSONObj queryPlanner = shardResults[i].result["queryPlanner"].Obj();
+ BSONObj serverInfo = shardResults[i].result["serverInfo"].Obj();
- singleShardBob.doneFast();
+ singleShardBob.append("shardName", shardResults[i].shardTargetId);
+ {
+ const auto shard = grid.shardRegistry()->getShard(shardResults[i].shardTargetId);
+ singleShardBob.append("connectionString", shard->getConnString().toString());
}
- shardsBuilder.doneFast();
- winningPlanBob.doneFast();
- queryPlannerBob.doneFast();
+ appendIfRoom(&singleShardBob, serverInfo, "serverInfo");
+ appendElementsIfRoom(&singleShardBob, queryPlanner);
+
+ singleShardBob.doneFast();
+ }
+ shardsBuilder.doneFast();
+ winningPlanBob.doneFast();
+ queryPlannerBob.doneFast();
+}
+
+// static
+void ClusterExplain::buildExecStats(const vector<Strategy::CommandResult>& shardResults,
+ const char* mongosStageName,
+ long long millisElapsed,
+ BSONObjBuilder* out) {
+ if (!shardResults[0].result.hasField("executionStats")) {
+ // The shards don't have execution stats info. Bail out without adding anything
+ // to 'out'.
+ return;
}
- // static
- void ClusterExplain::buildExecStats(const vector<Strategy::CommandResult>& shardResults,
- const char* mongosStageName,
- long long millisElapsed,
- BSONObjBuilder* out) {
- if (!shardResults[0].result.hasField("executionStats")) {
- // The shards don't have execution stats info. Bail out without adding anything
- // to 'out'.
- return;
+ BSONObjBuilder executionStatsBob(out->subobjStart("executionStats"));
+
+ // Collect summary stats from the shards.
+ long long nReturned = 0;
+ long long keysExamined = 0;
+ long long docsExamined = 0;
+ long long totalChildMillis = 0;
+ for (size_t i = 0; i < shardResults.size(); i++) {
+ BSONObj execStats = shardResults[i].result["executionStats"].Obj();
+ if (execStats.hasField("nReturned")) {
+ nReturned += execStats["nReturned"].numberLong();
}
-
- BSONObjBuilder executionStatsBob(out->subobjStart("executionStats"));
-
- // Collect summary stats from the shards.
- long long nReturned = 0;
- long long keysExamined = 0;
- long long docsExamined = 0;
- long long totalChildMillis = 0;
- for (size_t i = 0; i < shardResults.size(); i++) {
- BSONObj execStats = shardResults[i].result["executionStats"].Obj();
- if (execStats.hasField("nReturned")) {
- nReturned += execStats["nReturned"].numberLong();
- }
- if (execStats.hasField("totalKeysExamined")) {
- keysExamined += execStats["totalKeysExamined"].numberLong();
- }
- if (execStats.hasField("totalDocsExamined")) {
- docsExamined += execStats["totalDocsExamined"].numberLong();
- }
- if (execStats.hasField("executionTimeMillis")) {
- totalChildMillis += execStats["executionTimeMillis"].numberLong();
- }
+ if (execStats.hasField("totalKeysExamined")) {
+ keysExamined += execStats["totalKeysExamined"].numberLong();
}
+ if (execStats.hasField("totalDocsExamined")) {
+ docsExamined += execStats["totalDocsExamined"].numberLong();
+ }
+ if (execStats.hasField("executionTimeMillis")) {
+ totalChildMillis += execStats["executionTimeMillis"].numberLong();
+ }
+ }
- // Fill in top-level stats.
- executionStatsBob.appendNumber("nReturned", nReturned);
- executionStatsBob.appendNumber("executionTimeMillis", millisElapsed);
- executionStatsBob.appendNumber("totalKeysExamined", keysExamined);
- executionStatsBob.appendNumber("totalDocsExamined", docsExamined);
-
- // Fill in the tree of stages.
- BSONObjBuilder executionStagesBob(
- executionStatsBob.subobjStart("executionStages"));
-
- // Info for the root mongos stage.
- executionStagesBob.append("stage", mongosStageName);
- executionStatsBob.appendNumber("nReturned", nReturned);
- executionStatsBob.appendNumber("executionTimeMillis", millisElapsed);
- executionStatsBob.appendNumber("totalKeysExamined", keysExamined);
- executionStatsBob.appendNumber("totalDocsExamined", docsExamined);
- executionStagesBob.append("totalChildMillis", totalChildMillis);
+ // Fill in top-level stats.
+ executionStatsBob.appendNumber("nReturned", nReturned);
+ executionStatsBob.appendNumber("executionTimeMillis", millisElapsed);
+ executionStatsBob.appendNumber("totalKeysExamined", keysExamined);
+ executionStatsBob.appendNumber("totalDocsExamined", docsExamined);
- BSONArrayBuilder execShardsBuilder(executionStagesBob.subarrayStart("shards"));
- for (size_t i = 0; i < shardResults.size(); i++) {
- BSONObjBuilder singleShardBob(execShardsBuilder.subobjStart());
+ // Fill in the tree of stages.
+ BSONObjBuilder executionStagesBob(executionStatsBob.subobjStart("executionStages"));
- BSONObj execStats = shardResults[i].result["executionStats"].Obj();
- BSONObj execStages = execStats["executionStages"].Obj();
+ // Info for the root mongos stage.
+ executionStagesBob.append("stage", mongosStageName);
+ executionStatsBob.appendNumber("nReturned", nReturned);
+ executionStatsBob.appendNumber("executionTimeMillis", millisElapsed);
+ executionStatsBob.appendNumber("totalKeysExamined", keysExamined);
+ executionStatsBob.appendNumber("totalDocsExamined", docsExamined);
+ executionStagesBob.append("totalChildMillis", totalChildMillis);
- singleShardBob.append("shardName", shardResults[i].shardTargetId);
+ BSONArrayBuilder execShardsBuilder(executionStagesBob.subarrayStart("shards"));
+ for (size_t i = 0; i < shardResults.size(); i++) {
+ BSONObjBuilder singleShardBob(execShardsBuilder.subobjStart());
- // Append error-related fields, if present.
- if (!execStats["executionSuccess"].eoo()) {
- singleShardBob.append(execStats["executionSuccess"]);
- }
- if (!execStats["errorMessage"].eoo()) {
- singleShardBob.append(execStats["errorMessage"]);
- }
- if (!execStats["errorCode"].eoo()) {
- singleShardBob.append(execStats["errorCode"]);
- }
+ BSONObj execStats = shardResults[i].result["executionStats"].Obj();
+ BSONObj execStages = execStats["executionStages"].Obj();
- appendIfRoom(&singleShardBob, execStages, "executionStages");
+ singleShardBob.append("shardName", shardResults[i].shardTargetId);
- singleShardBob.doneFast();
+ // Append error-related fields, if present.
+ if (!execStats["executionSuccess"].eoo()) {
+ singleShardBob.append(execStats["executionSuccess"]);
+ }
+ if (!execStats["errorMessage"].eoo()) {
+ singleShardBob.append(execStats["errorMessage"]);
+ }
+ if (!execStats["errorCode"].eoo()) {
+ singleShardBob.append(execStats["errorCode"]);
}
- execShardsBuilder.doneFast();
- executionStagesBob.doneFast();
+ appendIfRoom(&singleShardBob, execStages, "executionStages");
- if (!shardResults[0].result["executionStats"].Obj().hasField("allPlansExecution")) {
- // The shards don't have execution stats for all plans, so we're done.
- executionStatsBob.doneFast();
- return;
- }
+ singleShardBob.doneFast();
+ }
+ execShardsBuilder.doneFast();
- // Add the allPlans stats from each shard.
- BSONArrayBuilder allPlansExecBob(
- executionStatsBob.subarrayStart("allPlansExecution"));
- for (size_t i = 0; i < shardResults.size(); i++) {
- BSONObjBuilder singleShardBob(execShardsBuilder.subobjStart());
+ executionStagesBob.doneFast();
- singleShardBob.append("shardName", shardResults[i].shardTargetId);
+ if (!shardResults[0].result["executionStats"].Obj().hasField("allPlansExecution")) {
+ // The shards don't have execution stats for all plans, so we're done.
+ executionStatsBob.doneFast();
+ return;
+ }
- BSONObj execStats = shardResults[i].result["executionStats"].Obj();
- vector<BSONElement> allPlans = execStats["allPlansExecution"].Array();
+ // Add the allPlans stats from each shard.
+ BSONArrayBuilder allPlansExecBob(executionStatsBob.subarrayStart("allPlansExecution"));
+ for (size_t i = 0; i < shardResults.size(); i++) {
+ BSONObjBuilder singleShardBob(execShardsBuilder.subobjStart());
- BSONArrayBuilder innerArrayBob(singleShardBob.subarrayStart("allPlans"));
- for (size_t j = 0; j < allPlans.size(); j++) {
- appendToArrayIfRoom(&innerArrayBob, allPlans[j]);
- }
- innerArrayBob.done();
+ singleShardBob.append("shardName", shardResults[i].shardTargetId);
- singleShardBob.doneFast();
+ BSONObj execStats = shardResults[i].result["executionStats"].Obj();
+ vector<BSONElement> allPlans = execStats["allPlansExecution"].Array();
+
+ BSONArrayBuilder innerArrayBob(singleShardBob.subarrayStart("allPlans"));
+ for (size_t j = 0; j < allPlans.size(); j++) {
+ appendToArrayIfRoom(&innerArrayBob, allPlans[j]);
}
- allPlansExecBob.doneFast();
+ innerArrayBob.done();
- executionStatsBob.doneFast();
+ singleShardBob.doneFast();
}
+ allPlansExecBob.doneFast();
- // static
- Status ClusterExplain::buildExplainResult(const vector<Strategy::CommandResult>& shardResults,
- const char* mongosStageName,
- long long millisElapsed,
- BSONObjBuilder* out) {
- // Explain only succeeds if all shards support the explain command.
- Status validateStatus = ClusterExplain::validateShardResults(shardResults);
- if (!validateStatus.isOK()) {
- return validateStatus;
- }
-
- buildPlannerInfo(shardResults, mongosStageName, out);
- buildExecStats(shardResults, mongosStageName, millisElapsed, out);
+ executionStatsBob.doneFast();
+}
- return Status::OK();
+// static
+Status ClusterExplain::buildExplainResult(const vector<Strategy::CommandResult>& shardResults,
+ const char* mongosStageName,
+ long long millisElapsed,
+ BSONObjBuilder* out) {
+ // Explain only succeeds if all shards support the explain command.
+ Status validateStatus = ClusterExplain::validateShardResults(shardResults);
+ if (!validateStatus.isOK()) {
+ return validateStatus;
}
-} // namespace mongo
+ buildPlannerInfo(shardResults, mongosStageName, out);
+ buildExecStats(shardResults, mongosStageName, millisElapsed, out);
+
+ return Status::OK();
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/cluster_explain.h b/src/mongo/s/cluster_explain.h
index ef86af7f76f..dad3b25c5e7 100644
--- a/src/mongo/s/cluster_explain.h
+++ b/src/mongo/s/cluster_explain.h
@@ -36,80 +36,80 @@
namespace mongo {
+/**
+ * Namespace for the collection of static methods used by commands in the implementation of
+ * explain on mongos.
+ */
+class ClusterExplain {
+public:
/**
- * Namespace for the collection of static methods used by commands in the implementation of
- * explain on mongos.
+ * Given the BSON specification for a command, 'cmdObj', wraps the object in order to
+ * produce the BSON for an explain of that command, at the given verbosity level
+ * 'verbosity'.
+ *
+ * Adds the result to the BSONObj builder 'out'.
*/
- class ClusterExplain {
- public:
- /**
- * Given the BSON specification for a command, 'cmdObj', wraps the object in order to
- * produce the BSON for an explain of that command, at the given verbosity level
- * 'verbosity'.
- *
- * Adds the result to the BSONObj builder 'out'.
- */
- static void wrapAsExplain(const BSONObj& cmdObj,
- ExplainCommon::Verbosity verbosity,
- BSONObjBuilder* out);
+ static void wrapAsExplain(const BSONObj& cmdObj,
+ ExplainCommon::Verbosity verbosity,
+ BSONObjBuilder* out);
- /**
- * Determines the kind of "execution stage" that mongos would use in order to collect
- * the results from the shards, assuming that the command being explained is a read
- * operation such as find or count.
- */
- static const char* getStageNameForReadOp(const std::vector<Strategy::CommandResult>& shardResults,
- const BSONObj& explainObj);
+ /**
+ * Determines the kind of "execution stage" that mongos would use in order to collect
+ * the results from the shards, assuming that the command being explained is a read
+ * operation such as find or count.
+ */
+ static const char* getStageNameForReadOp(
+ const std::vector<Strategy::CommandResult>& shardResults, const BSONObj& explainObj);
- /**
- * Command implementations on mongos use this method to construct the sharded explain
- * output format based on the results from the shards in 'shardResults'.
- *
- * On success, the output is added to the BSONObj builder 'out'.
- */
- static Status buildExplainResult(const std::vector<Strategy::CommandResult>& shardResults,
- const char* mongosStageName,
- long long millisElapsed,
- BSONObjBuilder* out);
+ /**
+ * Command implementations on mongos use this method to construct the sharded explain
+ * output format based on the results from the shards in 'shardResults'.
+ *
+ * On success, the output is added to the BSONObj builder 'out'.
+ */
+ static Status buildExplainResult(const std::vector<Strategy::CommandResult>& shardResults,
+ const char* mongosStageName,
+ long long millisElapsed,
+ BSONObjBuilder* out);
- //
- // Names of mock mongos execution stages.
- //
+ //
+ // Names of mock mongos execution stages.
+ //
- static const char* kSingleShard;
- static const char* kMergeFromShards;
- static const char* kMergeSortFromShards;
- static const char* kWriteOnShards;
+ static const char* kSingleShard;
+ static const char* kMergeFromShards;
+ static const char* kMergeSortFromShards;
+ static const char* kWriteOnShards;
- private:
- /**
- * Returns an OK status if all shards support the explain command and returned sensible
- * results. Otherwise, returns a non-OK status and the entire explain should fail.
- */
- static Status validateShardResults(const std::vector<Strategy::CommandResult>& shardResults);
+private:
+ /**
+ * Returns an OK status if all shards support the explain command and returned sensible
+ * results. Otherwise, returns a non-OK status and the entire explain should fail.
+ */
+ static Status validateShardResults(const std::vector<Strategy::CommandResult>& shardResults);
- /**
- * Populates the BSONObj builder 'out' with query planner explain information, based on
- * the results from the shards contained in 'shardResults'.
- *
- * The planner info will display 'mongosStageName' as the name of the execution stage
- * performed by mongos after gathering results from the shards.
- */
- static void buildPlannerInfo(const std::vector<Strategy::CommandResult>& shardResults,
- const char* mongosStageName,
- BSONObjBuilder* out);
+ /**
+ * Populates the BSONObj builder 'out' with query planner explain information, based on
+ * the results from the shards contained in 'shardResults'.
+ *
+ * The planner info will display 'mongosStageName' as the name of the execution stage
+ * performed by mongos after gathering results from the shards.
+ */
+ static void buildPlannerInfo(const std::vector<Strategy::CommandResult>& shardResults,
+ const char* mongosStageName,
+ BSONObjBuilder* out);
- /**
- * Populates the BSONObj builder 'out' with execution stats explain information,
- * if the results from the shards in 'shardsResults' contain this info.
- *
- * Will display 'mongosStageName' as the name of the execution stage performed by mongos,
- * and 'millisElapsed' as the execution time of the mongos stage.
- */
- static void buildExecStats(const std::vector<Strategy::CommandResult>& shardResults,
- const char* mongosStageName,
- long long millisElapsed,
- BSONObjBuilder* out);
- };
+ /**
+ * Populates the BSONObj builder 'out' with execution stats explain information,
+ * if the results from the shards in 'shardsResults' contain this info.
+ *
+ * Will display 'mongosStageName' as the name of the execution stage performed by mongos,
+ * and 'millisElapsed' as the execution time of the mongos stage.
+ */
+ static void buildExecStats(const std::vector<Strategy::CommandResult>& shardResults,
+ const char* mongosStageName,
+ long long millisElapsed,
+ BSONObjBuilder* out);
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/cluster_last_error_info.cpp b/src/mongo/s/cluster_last_error_info.cpp
index 30c79001be3..18124138825 100644
--- a/src/mongo/s/cluster_last_error_info.cpp
+++ b/src/mongo/s/cluster_last_error_info.cpp
@@ -43,72 +43,69 @@
namespace mongo {
- const ClientBasic::Decoration<ClusterLastErrorInfo> ClusterLastErrorInfo::get =
- ClientBasic::declareDecoration<ClusterLastErrorInfo>();
+const ClientBasic::Decoration<ClusterLastErrorInfo> ClusterLastErrorInfo::get =
+ ClientBasic::declareDecoration<ClusterLastErrorInfo>();
- void ClusterLastErrorInfo::addShardHost(const std::string& shardHost) {
- _cur->shardHostsWritten.insert( shardHost );
- }
+void ClusterLastErrorInfo::addShardHost(const std::string& shardHost) {
+ _cur->shardHostsWritten.insert(shardHost);
+}
- void ClusterLastErrorInfo::addHostOpTime(ConnectionString connStr, HostOpTime stat) {
- _cur->hostOpTimes[connStr] = stat;
- }
+void ClusterLastErrorInfo::addHostOpTime(ConnectionString connStr, HostOpTime stat) {
+ _cur->hostOpTimes[connStr] = stat;
+}
- void ClusterLastErrorInfo::addHostOpTimes( const HostOpTimeMap& hostOpTimes ) {
- for ( HostOpTimeMap::const_iterator it = hostOpTimes.begin();
- it != hostOpTimes.end(); ++it ) {
- addHostOpTime(it->first, it->second);
- }
+void ClusterLastErrorInfo::addHostOpTimes(const HostOpTimeMap& hostOpTimes) {
+ for (HostOpTimeMap::const_iterator it = hostOpTimes.begin(); it != hostOpTimes.end(); ++it) {
+ addHostOpTime(it->first, it->second);
+ }
+}
+
+void ClusterLastErrorInfo::newRequest() {
+ std::swap(_cur, _prev);
+ _cur->clear();
+}
+
+void ClusterLastErrorInfo::disableForCommand() {
+ RequestInfo* temp = _cur;
+ _cur = _prev;
+ _prev = temp;
+}
+
+static TimerStats gleWtimeStats;
+static ServerStatusMetricField<TimerStats> displayGleLatency("getLastError.wtime", &gleWtimeStats);
+
+void saveGLEStats(const BSONObj& metadata, StringData hostString) {
+ if (!haveClient()) {
+ // TODO: how can this happen?
+ return;
}
- void ClusterLastErrorInfo::newRequest() {
- std::swap(_cur, _prev);
- _cur->clear();
+ auto swShardingMetadata = rpc::ShardingMetadata::readFromMetadata(metadata);
+ if (swShardingMetadata.getStatus() == ErrorCodes::NoSuchKey) {
+ return;
+ } else if (!swShardingMetadata.isOK()) {
+ warning() << "Got invalid sharding metadata " << swShardingMetadata.getStatus()
+ << " metadata object was '" << metadata << "'";
+ return;
}
- void ClusterLastErrorInfo::disableForCommand() {
- RequestInfo* temp = _cur;
- _cur = _prev;
- _prev = temp;
+ auto shardConn = ConnectionString::parse(hostString.toString());
+ // If we got the reply from this host, we expect that its 'hostString' must be valid.
+ if (!shardConn.isOK()) {
+ severe() << "got bad host string in saveGLEStats: " << hostString;
}
+ invariantOK(shardConn.getStatus());
- static TimerStats gleWtimeStats;
- static ServerStatusMetricField<TimerStats> displayGleLatency("getLastError.wtime",
- &gleWtimeStats);
-
- void saveGLEStats(const BSONObj& metadata, StringData hostString) {
- if (!haveClient()) {
- // TODO: how can this happen?
- return;
- }
-
- auto swShardingMetadata = rpc::ShardingMetadata::readFromMetadata(metadata);
- if (swShardingMetadata.getStatus() == ErrorCodes::NoSuchKey) {
- return;
- }
- else if (!swShardingMetadata.isOK()) {
- warning() << "Got invalid sharding metadata " << swShardingMetadata.getStatus()
- << " metadata object was '" << metadata << "'";
- return;
- }
-
- auto shardConn = ConnectionString::parse(hostString.toString());
- // If we got the reply from this host, we expect that its 'hostString' must be valid.
- if (!shardConn.isOK()) {
- severe() << "got bad host string in saveGLEStats: " << hostString;
- }
- invariantOK(shardConn.getStatus());
-
- auto shardingMetadata = std::move(swShardingMetadata.getValue());
-
- auto& clientInfo = cc();
- LOG(4) << "saveGLEStats lastOpTime:" << shardingMetadata.getLastOpTime()
- << " electionId:" << shardingMetadata.getLastElectionId();
-
- ClusterLastErrorInfo::get(clientInfo).addHostOpTime(
+ auto shardingMetadata = std::move(swShardingMetadata.getValue());
+
+ auto& clientInfo = cc();
+ LOG(4) << "saveGLEStats lastOpTime:" << shardingMetadata.getLastOpTime()
+ << " electionId:" << shardingMetadata.getLastElectionId();
+
+ ClusterLastErrorInfo::get(clientInfo)
+ .addHostOpTime(
shardConn.getValue(),
- HostOpTime(shardingMetadata.getLastOpTime(),
- shardingMetadata.getLastElectionId()));
- }
+ HostOpTime(shardingMetadata.getLastOpTime(), shardingMetadata.getLastElectionId()));
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/cluster_last_error_info.h b/src/mongo/s/cluster_last_error_info.h
index a3110e3d40b..d6104518957 100644
--- a/src/mongo/s/cluster_last_error_info.h
+++ b/src/mongo/s/cluster_last_error_info.h
@@ -34,76 +34,79 @@
namespace mongo {
+/**
+ * Client decoration that holds information needed to by mongos to process
+ * getLastError commands.
+ */
+class ClusterLastErrorInfo {
+public:
+ static const ClientBasic::Decoration<ClusterLastErrorInfo> get;
+
+ /** new request not associated (yet or ever) with a client */
+ void newRequest();
+
/**
- * Client decoration that holds information needed to by mongos to process
- * getLastError commands.
+ * notes that this client use this shard
+ * keeps track of all shards accessed this request
*/
- class ClusterLastErrorInfo {
- public:
- static const ClientBasic::Decoration<ClusterLastErrorInfo> get;
-
- /** new request not associated (yet or ever) with a client */
- void newRequest();
-
- /**
- * notes that this client use this shard
- * keeps track of all shards accessed this request
- */
- void addShardHost( const std::string& shardHost );
-
- /**
- * Notes that this client wrote to these particular hosts with write commands.
- */
- void addHostOpTime(ConnectionString connstr, HostOpTime stat);
- void addHostOpTimes( const HostOpTimeMap& hostOpTimes );
-
- /**
- * gets shards used on the previous request
- */
- std::set<std::string>* getPrevShardHosts() const { return &_prev->shardHostsWritten; }
-
- /**
- * Gets the shards, hosts, and opTimes the client last wrote to with write commands.
- */
- const HostOpTimeMap& getPrevHostOpTimes() const {
- return _prev->hostOpTimes;
- }
+ void addShardHost(const std::string& shardHost);
- /**
- * resets the information stored for the current request
- */
- void clearRequestInfo(){ _cur->clear(); }
+ /**
+ * Notes that this client wrote to these particular hosts with write commands.
+ */
+ void addHostOpTime(ConnectionString connstr, HostOpTime stat);
+ void addHostOpTimes(const HostOpTimeMap& hostOpTimes);
- void disableForCommand();
+ /**
+ * gets shards used on the previous request
+ */
+ std::set<std::string>* getPrevShardHosts() const {
+ return &_prev->shardHostsWritten;
+ }
- private:
- struct RequestInfo {
+ /**
+ * Gets the shards, hosts, and opTimes the client last wrote to with write commands.
+ */
+ const HostOpTimeMap& getPrevHostOpTimes() const {
+ return _prev->hostOpTimes;
+ }
- void clear() {
- shardHostsWritten.clear();
- hostOpTimes.clear();
- }
+ /**
+ * resets the information stored for the current request
+ */
+ void clearRequestInfo() {
+ _cur->clear();
+ }
- std::set<std::string> shardHostsWritten;
- HostOpTimeMap hostOpTimes;
- };
+ void disableForCommand();
- // We use 2 so we can flip for getLastError type operations.
- RequestInfo _infos[2];
- RequestInfo* _cur = &_infos[0];
- RequestInfo* _prev = &_infos[1];
+private:
+ struct RequestInfo {
+ void clear() {
+ shardHostsWritten.clear();
+ hostOpTimes.clear();
+ }
+
+ std::set<std::string> shardHostsWritten;
+ HostOpTimeMap hostOpTimes;
};
- /**
- * Looks for $gleStats in a command's reply metadata, and fills in the ClusterLastErrorInfo
- * for this thread's associated Client with the data, if found.
- *
- * This data will be used by subsequent GLE calls, to ensure we look for the correct
- * write on the correct PRIMARY.
- * result: the result from calling runCommand
- * conn: the std::string name of the hostAndPort where the command ran. This can be a replica
- * set seed list.
- */
- void saveGLEStats(const BSONObj& metadataObj, StringData conn);
+ // We use 2 so we can flip for getLastError type operations.
+ RequestInfo _infos[2];
+ RequestInfo* _cur = &_infos[0];
+ RequestInfo* _prev = &_infos[1];
+};
+
+/**
+ * Looks for $gleStats in a command's reply metadata, and fills in the ClusterLastErrorInfo
+ * for this thread's associated Client with the data, if found.
+ *
+ * This data will be used by subsequent GLE calls, to ensure we look for the correct
+ * write on the correct PRIMARY.
+ * result: the result from calling runCommand
+ * conn: the std::string name of the hostAndPort where the command ran. This can be a replica
+ * set seed list.
+ */
+void saveGLEStats(const BSONObj& metadataObj, StringData conn);
} // namespace mongo
diff --git a/src/mongo/s/cluster_write.cpp b/src/mongo/s/cluster_write.cpp
index 4d6e5b878b5..45b3b0abb52 100644
--- a/src/mongo/s/cluster_write.cpp
+++ b/src/mongo/s/cluster_write.cpp
@@ -51,250 +51,239 @@
namespace mongo {
- using std::shared_ptr;
- using std::unique_ptr;
- using std::vector;
- using std::map;
- using std::string;
- using std::stringstream;
-
- const int ConfigOpTimeoutMillis = 30 * 1000;
-
- namespace {
-
- /**
- * Constructs the BSON specification document for the given namespace, index key
- * and options.
- */
- BSONObj createIndexDoc(const string& ns, const BSONObj& keys, bool unique) {
- BSONObjBuilder indexDoc;
- indexDoc.append("ns", ns);
- indexDoc.append("key", keys);
-
- stringstream indexName;
-
- bool isFirstKey = true;
- for (BSONObjIterator keyIter(keys); keyIter.more();) {
- BSONElement currentKey = keyIter.next();
-
- if (isFirstKey) {
- isFirstKey = false;
- }
- else {
- indexName << "_";
- }
-
- indexName << currentKey.fieldName() << "_";
- if (currentKey.isNumber()) {
- indexName << currentKey.numberInt();
- }
- else {
- indexName << currentKey.str(); //this should match up with shell command
- }
- }
-
- indexDoc.append("name", indexName.str());
-
- if (unique) {
- indexDoc.appendBool("unique", unique);
- }
-
- return indexDoc.obj();
- }
+using std::shared_ptr;
+using std::unique_ptr;
+using std::vector;
+using std::map;
+using std::string;
+using std::stringstream;
+
+const int ConfigOpTimeoutMillis = 30 * 1000;
+
+namespace {
+
+/**
+ * Constructs the BSON specification document for the given namespace, index key
+ * and options.
+ */
+BSONObj createIndexDoc(const string& ns, const BSONObj& keys, bool unique) {
+ BSONObjBuilder indexDoc;
+ indexDoc.append("ns", ns);
+ indexDoc.append("key", keys);
+
+ stringstream indexName;
- void toBatchError(const Status& status, BatchedCommandResponse* response) {
- response->clear();
- response->setErrCode(status.code());
- response->setErrMessage(status.reason());
- response->setOk(false);
- dassert(response->isValid(NULL));
+ bool isFirstKey = true;
+ for (BSONObjIterator keyIter(keys); keyIter.more();) {
+ BSONElement currentKey = keyIter.next();
+
+ if (isFirstKey) {
+ isFirstKey = false;
+ } else {
+ indexName << "_";
}
- /**
- * Splits the chunks touched based from the targeter stats if needed.
- */
- void splitIfNeeded(const NamespaceString& nss, const TargeterStats& stats) {
- if (!Chunk::ShouldAutoSplit) {
- return;
- }
-
- auto status = grid.catalogCache()->getDatabase(nss.db().toString());
- if (!status.isOK()) {
- warning() << "failed to get database config for " << nss
- << " while checking for auto-split: " << status.getStatus();
- return;
- }
-
- shared_ptr<DBConfig> config = status.getValue();
-
- ChunkManagerPtr chunkManager;
- ShardPtr dummyShard;
- config->getChunkManagerOrPrimary(nss, chunkManager, dummyShard);
-
- if (!chunkManager) {
- return;
- }
-
- for (map<BSONObj, int>::const_iterator it = stats.chunkSizeDelta.begin();
- it != stats.chunkSizeDelta.end();
- ++it) {
-
- ChunkPtr chunk;
- try {
- chunk = chunkManager->findIntersectingChunk(it->first);
- }
- catch (const AssertionException& ex) {
- warning() << "could not find chunk while checking for auto-split: "
- << causedBy(ex);
- return;
- }
-
- chunk->splitIfShould(it->second);
- }
+ indexName << currentKey.fieldName() << "_";
+ if (currentKey.isNumber()) {
+ indexName << currentKey.numberInt();
+ } else {
+ indexName << currentKey.str(); // this should match up with shell command
}
+ }
- } // namespace
+ indexDoc.append("name", indexName.str());
- Status clusterCreateIndex( const string& ns,
- BSONObj keys,
- bool unique,
- BatchedCommandResponse* response ) {
+ if (unique) {
+ indexDoc.appendBool("unique", unique);
+ }
- const NamespaceString nss(ns);
- const std::string dbName = nss.db().toString();
+ return indexDoc.obj();
+}
- BSONObj indexDoc = createIndexDoc(ns, keys, unique);
+void toBatchError(const Status& status, BatchedCommandResponse* response) {
+ response->clear();
+ response->setErrCode(status.code());
+ response->setErrMessage(status.reason());
+ response->setOk(false);
+ dassert(response->isValid(NULL));
+}
- // Go through the shard insert path
- std::unique_ptr<BatchedInsertRequest> insert(new BatchedInsertRequest());
- insert->addToDocuments(indexDoc);
+/**
+ * Splits the chunks touched based from the targeter stats if needed.
+ */
+void splitIfNeeded(const NamespaceString& nss, const TargeterStats& stats) {
+ if (!Chunk::ShouldAutoSplit) {
+ return;
+ }
- BatchedCommandRequest request(insert.release());
- request.setNS(nss.getSystemIndexesCollection());
- request.setWriteConcern(WriteConcernOptions::Acknowledged);
+ auto status = grid.catalogCache()->getDatabase(nss.db().toString());
+ if (!status.isOK()) {
+ warning() << "failed to get database config for " << nss
+ << " while checking for auto-split: " << status.getStatus();
+ return;
+ }
- BatchedCommandResponse dummyResponse;
- if (response == NULL) {
- response = &dummyResponse;
- }
+ shared_ptr<DBConfig> config = status.getValue();
+
+ ChunkManagerPtr chunkManager;
+ ShardPtr dummyShard;
+ config->getChunkManagerOrPrimary(nss, chunkManager, dummyShard);
- ClusterWriter writer(false, 0);
- writer.write(request, response);
+ if (!chunkManager) {
+ return;
+ }
- if (response->getOk() != 1) {
- return Status(static_cast<ErrorCodes::Error>(response->getErrCode()),
- response->getErrMessage());
+ for (map<BSONObj, int>::const_iterator it = stats.chunkSizeDelta.begin();
+ it != stats.chunkSizeDelta.end();
+ ++it) {
+ ChunkPtr chunk;
+ try {
+ chunk = chunkManager->findIntersectingChunk(it->first);
+ } catch (const AssertionException& ex) {
+ warning() << "could not find chunk while checking for auto-split: " << causedBy(ex);
+ return;
}
- if (response->isErrDetailsSet()) {
- const WriteErrorDetail* errDetail = response->getErrDetails().front();
+ chunk->splitIfShould(it->second);
+ }
+}
+
+} // namespace
- return Status(static_cast<ErrorCodes::Error>(errDetail->getErrCode()),
- errDetail->getErrMessage());
- }
+Status clusterCreateIndex(const string& ns,
+ BSONObj keys,
+ bool unique,
+ BatchedCommandResponse* response) {
+ const NamespaceString nss(ns);
+ const std::string dbName = nss.db().toString();
- if (response->isWriteConcernErrorSet()) {
- const WCErrorDetail* errDetail = response->getWriteConcernError();
+ BSONObj indexDoc = createIndexDoc(ns, keys, unique);
- return Status(static_cast<ErrorCodes::Error>(errDetail->getErrCode()),
- errDetail->getErrMessage());
- }
+ // Go through the shard insert path
+ std::unique_ptr<BatchedInsertRequest> insert(new BatchedInsertRequest());
+ insert->addToDocuments(indexDoc);
- return Status::OK();
+ BatchedCommandRequest request(insert.release());
+ request.setNS(nss.getSystemIndexesCollection());
+ request.setWriteConcern(WriteConcernOptions::Acknowledged);
+
+ BatchedCommandResponse dummyResponse;
+ if (response == NULL) {
+ response = &dummyResponse;
}
+ ClusterWriter writer(false, 0);
+ writer.write(request, response);
- void ClusterWriter::write( const BatchedCommandRequest& origRequest,
- BatchedCommandResponse* response ) {
+ if (response->getOk() != 1) {
+ return Status(static_cast<ErrorCodes::Error>(response->getErrCode()),
+ response->getErrMessage());
+ }
- // Add _ids to insert request if req'd
- unique_ptr<BatchedCommandRequest> idRequest(BatchedCommandRequest::cloneWithIds(origRequest));
- const BatchedCommandRequest& request = NULL != idRequest.get() ? *idRequest : origRequest;
+ if (response->isErrDetailsSet()) {
+ const WriteErrorDetail* errDetail = response->getErrDetails().front();
- const NamespaceString& nss = request.getNSS();
- if ( !nss.isValid() ) {
- toBatchError( Status( ErrorCodes::InvalidNamespace,
- nss.ns() + " is not a valid namespace" ),
- response );
- return;
- }
+ return Status(static_cast<ErrorCodes::Error>(errDetail->getErrCode()),
+ errDetail->getErrMessage());
+ }
- if ( !NamespaceString::validCollectionName( nss.coll() ) ) {
- toBatchError( Status( ErrorCodes::BadValue,
- str::stream() << "invalid collection name " << nss.coll() ),
- response );
- return;
- }
+ if (response->isWriteConcernErrorSet()) {
+ const WCErrorDetail* errDetail = response->getWriteConcernError();
- if ( request.sizeWriteOps() == 0u ) {
- toBatchError( Status( ErrorCodes::InvalidLength,
- "no write ops were included in the batch" ),
- response );
- return;
- }
+ return Status(static_cast<ErrorCodes::Error>(errDetail->getErrCode()),
+ errDetail->getErrMessage());
+ }
- if ( request.sizeWriteOps() > BatchedCommandRequest::kMaxWriteBatchSize ) {
- toBatchError( Status( ErrorCodes::InvalidLength,
- str::stream() << "exceeded maximum write batch size of "
- << BatchedCommandRequest::kMaxWriteBatchSize ),
- response );
- return;
- }
+ return Status::OK();
+}
- string errMsg;
- if ( request.isInsertIndexRequest() && !request.isValidIndexRequest( &errMsg ) ) {
- toBatchError( Status( ErrorCodes::InvalidOptions, errMsg ), response );
- return;
- }
- // Config writes and shard writes are done differently
- const string dbName = nss.db().toString();
+void ClusterWriter::write(const BatchedCommandRequest& origRequest,
+ BatchedCommandResponse* response) {
+ // Add _ids to insert request if req'd
+ unique_ptr<BatchedCommandRequest> idRequest(BatchedCommandRequest::cloneWithIds(origRequest));
+ const BatchedCommandRequest& request = NULL != idRequest.get() ? *idRequest : origRequest;
- if (dbName == "config" || dbName == "admin") {
- grid.catalogManager()->writeConfigServerDirect(request, response);
- }
- else {
- ChunkManagerTargeter targeter(request.getTargetingNSS());
- Status targetInitStatus = targeter.init();
-
- if (!targetInitStatus.isOK()) {
- // Errors will be reported in response if we are unable to target
- warning() << "could not initialize targeter for"
- << (request.isInsertIndexRequest() ? " index" : "")
- << " write op in collection " << request.getTargetingNS();
- }
-
- DBClientShardResolver resolver;
- DBClientMultiCommand dispatcher;
- BatchWriteExec exec(&targeter, &resolver, &dispatcher);
- exec.executeBatch(request, response);
-
- if (_autoSplit) {
- splitIfNeeded(request.getNSS(), *targeter.getStats());
- }
-
- _stats->setShardStats(exec.releaseStats());
- }
+ const NamespaceString& nss = request.getNSS();
+ if (!nss.isValid()) {
+ toBatchError(Status(ErrorCodes::InvalidNamespace, nss.ns() + " is not a valid namespace"),
+ response);
+ return;
}
- ClusterWriter::ClusterWriter( bool autoSplit, int timeoutMillis ) :
- _autoSplit( autoSplit ), _timeoutMillis( timeoutMillis ), _stats( new ClusterWriterStats ) {
+ if (!NamespaceString::validCollectionName(nss.coll())) {
+ toBatchError(
+ Status(ErrorCodes::BadValue, str::stream() << "invalid collection name " << nss.coll()),
+ response);
+ return;
}
- const ClusterWriterStats& ClusterWriter::getStats() {
- return *_stats;
+ if (request.sizeWriteOps() == 0u) {
+ toBatchError(Status(ErrorCodes::InvalidLength, "no write ops were included in the batch"),
+ response);
+ return;
}
- void ClusterWriterStats::setShardStats( BatchWriteExecStats* shardStats ) {
- _shardStats.reset( shardStats );
+ if (request.sizeWriteOps() > BatchedCommandRequest::kMaxWriteBatchSize) {
+ toBatchError(Status(ErrorCodes::InvalidLength,
+ str::stream() << "exceeded maximum write batch size of "
+ << BatchedCommandRequest::kMaxWriteBatchSize),
+ response);
+ return;
}
- bool ClusterWriterStats::hasShardStats() const {
- return NULL != _shardStats.get();
+ string errMsg;
+ if (request.isInsertIndexRequest() && !request.isValidIndexRequest(&errMsg)) {
+ toBatchError(Status(ErrorCodes::InvalidOptions, errMsg), response);
+ return;
}
- const BatchWriteExecStats& ClusterWriterStats::getShardStats() const {
- return *_shardStats;
+ // Config writes and shard writes are done differently
+ const string dbName = nss.db().toString();
+
+ if (dbName == "config" || dbName == "admin") {
+ grid.catalogManager()->writeConfigServerDirect(request, response);
+ } else {
+ ChunkManagerTargeter targeter(request.getTargetingNSS());
+ Status targetInitStatus = targeter.init();
+
+ if (!targetInitStatus.isOK()) {
+ // Errors will be reported in response if we are unable to target
+ warning() << "could not initialize targeter for"
+ << (request.isInsertIndexRequest() ? " index" : "")
+ << " write op in collection " << request.getTargetingNS();
+ }
+
+ DBClientShardResolver resolver;
+ DBClientMultiCommand dispatcher;
+ BatchWriteExec exec(&targeter, &resolver, &dispatcher);
+ exec.executeBatch(request, response);
+
+ if (_autoSplit) {
+ splitIfNeeded(request.getNSS(), *targeter.getStats());
+ }
+
+ _stats->setShardStats(exec.releaseStats());
}
+}
+
+ClusterWriter::ClusterWriter(bool autoSplit, int timeoutMillis)
+ : _autoSplit(autoSplit), _timeoutMillis(timeoutMillis), _stats(new ClusterWriterStats) {}
+
+const ClusterWriterStats& ClusterWriter::getStats() {
+ return *_stats;
+}
+
+void ClusterWriterStats::setShardStats(BatchWriteExecStats* shardStats) {
+ _shardStats.reset(shardStats);
+}
+
+bool ClusterWriterStats::hasShardStats() const {
+ return NULL != _shardStats.get();
+}
+
+const BatchWriteExecStats& ClusterWriterStats::getShardStats() const {
+ return *_shardStats;
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/cluster_write.h b/src/mongo/s/cluster_write.h
index fce2f26564d..9fd177f756a 100644
--- a/src/mongo/s/cluster_write.h
+++ b/src/mongo/s/cluster_write.h
@@ -35,50 +35,47 @@
namespace mongo {
- class ClusterWriterStats;
- class BatchWriteExecStats;
+class ClusterWriterStats;
+class BatchWriteExecStats;
- class ClusterWriter {
- public:
+class ClusterWriter {
+public:
+ ClusterWriter(bool autoSplit, int timeoutMillis);
- ClusterWriter( bool autoSplit, int timeoutMillis );
+ void write(const BatchedCommandRequest& request, BatchedCommandResponse* response);
- void write( const BatchedCommandRequest& request, BatchedCommandResponse* response );
+ const ClusterWriterStats& getStats();
- const ClusterWriterStats& getStats();
+private:
+ const bool _autoSplit;
+ const int _timeoutMillis;
- private:
- const bool _autoSplit;
- const int _timeoutMillis;
+ std::unique_ptr<ClusterWriterStats> _stats;
+};
- std::unique_ptr<ClusterWriterStats> _stats;
- };
+class ClusterWriterStats {
+public:
+ // Transfers ownership to the cluster write stats
+ void setShardStats(BatchWriteExecStats* _shardStats);
- class ClusterWriterStats {
- public:
+ bool hasShardStats() const;
- // Transfers ownership to the cluster write stats
- void setShardStats( BatchWriteExecStats* _shardStats );
+ const BatchWriteExecStats& getShardStats() const;
- bool hasShardStats() const;
+ // TODO: When we have ConfigCoordinator stats, put these here too.
- const BatchWriteExecStats& getShardStats() const;
+private:
+ std::unique_ptr<BatchWriteExecStats> _shardStats;
+};
- // TODO: When we have ConfigCoordinator stats, put these here too.
-
- private:
-
- std::unique_ptr<BatchWriteExecStats> _shardStats;
- };
-
- /**
- * Used only for writes to the config server, config and admin databases.
- *
- * Note: response can be NULL if you don't care about the write statistics.
- */
- Status clusterCreateIndex( const std::string& ns,
- BSONObj keys,
- bool unique,
- BatchedCommandResponse* response );
+/**
+ * Used only for writes to the config server, config and admin databases.
+ *
+ * Note: response can be NULL if you don't care about the write statistics.
+ */
+Status clusterCreateIndex(const std::string& ns,
+ BSONObj keys,
+ bool unique,
+ BatchedCommandResponse* response);
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/collection_metadata.cpp b/src/mongo/s/collection_metadata.cpp
index 98f62c90a01..05d448dc239 100644
--- a/src/mongo/s/collection_metadata.cpp
+++ b/src/mongo/s/collection_metadata.cpp
@@ -38,431 +38,398 @@
namespace mongo {
- using std::unique_ptr;
- using std::endl;
- using std::make_pair;
- using std::string;
- using std::vector;
+using std::unique_ptr;
+using std::endl;
+using std::make_pair;
+using std::string;
+using std::vector;
- using mongoutils::str::stream;
+using mongoutils::str::stream;
- CollectionMetadata::CollectionMetadata() { }
+CollectionMetadata::CollectionMetadata() {}
- CollectionMetadata::~CollectionMetadata() { }
+CollectionMetadata::~CollectionMetadata() {}
- CollectionMetadata* CollectionMetadata::cloneMigrate( const ChunkType& chunk,
- const ChunkVersion& newShardVersion,
- string* errMsg ) const {
- // The error message string is optional.
- string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
+CollectionMetadata* CollectionMetadata::cloneMigrate(const ChunkType& chunk,
+ const ChunkVersion& newShardVersion,
+ string* errMsg) const {
+ // The error message string is optional.
+ string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
+ }
- // Check that we have the exact chunk that will be subtracted.
- if ( !rangeMapContains( _chunksMap, chunk.getMin(), chunk.getMax() ) ) {
+ // Check that we have the exact chunk that will be subtracted.
+ if (!rangeMapContains(_chunksMap, chunk.getMin(), chunk.getMax())) {
+ *errMsg = stream() << "cannot remove chunk "
+ << rangeToString(chunk.getMin(), chunk.getMax())
+ << ", this shard does not contain the chunk";
- *errMsg = stream() << "cannot remove chunk "
- << rangeToString( chunk.getMin(), chunk.getMax() )
- << ", this shard does not contain the chunk";
+ if (rangeMapOverlaps(_chunksMap, chunk.getMin(), chunk.getMax())) {
+ RangeVector overlap;
+ getRangeMapOverlap(_chunksMap, chunk.getMin(), chunk.getMax(), &overlap);
- if ( rangeMapOverlaps( _chunksMap, chunk.getMin(), chunk.getMax() ) ) {
+ *errMsg += stream() << " and it overlaps " << overlapToString(overlap);
+ }
- RangeVector overlap;
- getRangeMapOverlap( _chunksMap, chunk.getMin(), chunk.getMax(), &overlap );
+ warning() << *errMsg << endl;
+ return NULL;
+ }
- *errMsg += stream() << " and it overlaps " << overlapToString( overlap );
- }
+ // If left with no chunks, check that the version is zero.
+ if (_chunksMap.size() == 1) {
+ if (newShardVersion.isSet()) {
+ *errMsg = stream() << "cannot set shard version to non-zero value "
+ << newShardVersion.toString() << " when removing last chunk "
+ << rangeToString(chunk.getMin(), chunk.getMax());
warning() << *errMsg << endl;
return NULL;
}
+ }
+ // Can't move version backwards when subtracting chunks. This is what guarantees that
+ // no read or write would be taken once we subtract data from the current shard.
+ else if (newShardVersion <= _shardVersion) {
+ *errMsg = stream() << "cannot remove chunk "
+ << rangeToString(chunk.getMin(), chunk.getMax())
+ << " because the new shard version " << newShardVersion.toString()
+ << " is not greater than the current shard version "
+ << _shardVersion.toString();
+
+ warning() << *errMsg << endl;
+ return NULL;
+ }
- // If left with no chunks, check that the version is zero.
- if (_chunksMap.size() == 1) {
- if (newShardVersion.isSet()) {
-
- *errMsg = stream() << "cannot set shard version to non-zero value "
- << newShardVersion.toString() << " when removing last chunk "
- << rangeToString( chunk.getMin(), chunk.getMax() );
-
- warning() << *errMsg << endl;
- return NULL;
- }
- }
- // Can't move version backwards when subtracting chunks. This is what guarantees that
- // no read or write would be taken once we subtract data from the current shard.
- else if (newShardVersion <= _shardVersion) {
+ unique_ptr<CollectionMetadata> metadata(new CollectionMetadata);
+ metadata->_keyPattern = this->_keyPattern;
+ metadata->_keyPattern.getOwned();
+ metadata->fillKeyPatternFields();
+ metadata->_pendingMap = this->_pendingMap;
+ metadata->_chunksMap = this->_chunksMap;
+ metadata->_chunksMap.erase(chunk.getMin());
+ metadata->_shardVersion = newShardVersion;
+ metadata->_collVersion = newShardVersion > _collVersion ? newShardVersion : this->_collVersion;
+ metadata->fillRanges();
+
+ invariant(metadata->isValid());
+ return metadata.release();
+}
+
+CollectionMetadata* CollectionMetadata::clonePlusChunk(const ChunkType& chunk,
+ const ChunkVersion& newShardVersion,
+ string* errMsg) const {
+ // The error message string is optional.
+ string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
+ }
- *errMsg = stream() << "cannot remove chunk "
- << rangeToString( chunk.getMin(), chunk.getMax() )
- << " because the new shard version " << newShardVersion.toString()
- << " is not greater than the current shard version "
- << _shardVersion.toString();
+ // It is acceptable to move version backwards (e.g., undoing a migration that went bad
+ // during commit) but only cloning away the last chunk may reset the version to 0.
+ if (!newShardVersion.isSet()) {
+ *errMsg = stream() << "cannot add chunk " << rangeToString(chunk.getMin(), chunk.getMax())
+ << " with zero shard version";
- warning() << *errMsg << endl;
- return NULL;
- }
+ warning() << *errMsg << endl;
+ return NULL;
+ }
- unique_ptr<CollectionMetadata> metadata( new CollectionMetadata );
- metadata->_keyPattern = this->_keyPattern;
- metadata->_keyPattern.getOwned();
- metadata->fillKeyPatternFields();
- metadata->_pendingMap = this->_pendingMap;
- metadata->_chunksMap = this->_chunksMap;
- metadata->_chunksMap.erase( chunk.getMin() );
- metadata->_shardVersion = newShardVersion;
- metadata->_collVersion =
- newShardVersion > _collVersion ? newShardVersion : this->_collVersion;
- metadata->fillRanges();
-
- invariant(metadata->isValid());
- return metadata.release();
- }
-
- CollectionMetadata* CollectionMetadata::clonePlusChunk( const ChunkType& chunk,
- const ChunkVersion& newShardVersion,
- string* errMsg ) const {
- // The error message string is optional.
- string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
+ invariant(chunk.getMin().woCompare(chunk.getMax()) < 0);
- // It is acceptable to move version backwards (e.g., undoing a migration that went bad
- // during commit) but only cloning away the last chunk may reset the version to 0.
- if (!newShardVersion.isSet()) {
+ // Check that there isn't any chunk on the interval to be added.
+ if (rangeMapOverlaps(_chunksMap, chunk.getMin(), chunk.getMax())) {
+ RangeVector overlap;
+ getRangeMapOverlap(_chunksMap, chunk.getMin(), chunk.getMax(), &overlap);
- *errMsg = stream() << "cannot add chunk "
- << rangeToString( chunk.getMin(), chunk.getMax() )
- << " with zero shard version";
+ *errMsg = stream() << "cannot add chunk " << rangeToString(chunk.getMin(), chunk.getMax())
+ << " because the chunk overlaps " << overlapToString(overlap);
- warning() << *errMsg << endl;
- return NULL;
- }
+ warning() << *errMsg << endl;
+ return NULL;
+ }
- invariant( chunk.getMin().woCompare(chunk.getMax()) < 0 );
+ unique_ptr<CollectionMetadata> metadata(new CollectionMetadata);
+ metadata->_keyPattern = this->_keyPattern;
+ metadata->_keyPattern.getOwned();
+ metadata->fillKeyPatternFields();
+ metadata->_pendingMap = this->_pendingMap;
+ metadata->_chunksMap = this->_chunksMap;
+ metadata->_chunksMap.insert(make_pair(chunk.getMin().getOwned(), chunk.getMax().getOwned()));
+ metadata->_shardVersion = newShardVersion;
+ metadata->_collVersion = newShardVersion > _collVersion ? newShardVersion : this->_collVersion;
+ metadata->fillRanges();
+
+ invariant(metadata->isValid());
+ return metadata.release();
+}
+
+CollectionMetadata* CollectionMetadata::cloneMinusPending(const ChunkType& pending,
+ string* errMsg) const {
+ // The error message string is optional.
+ string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
+ }
- // Check that there isn't any chunk on the interval to be added.
- if ( rangeMapOverlaps( _chunksMap, chunk.getMin(), chunk.getMax() ) ) {
+ // Check that we have the exact chunk that will be subtracted.
+ if (!rangeMapContains(_pendingMap, pending.getMin(), pending.getMax())) {
+ *errMsg = stream() << "cannot remove pending chunk "
+ << rangeToString(pending.getMin(), pending.getMax())
+ << ", this shard does not contain the chunk";
+ if (rangeMapOverlaps(_pendingMap, pending.getMin(), pending.getMax())) {
RangeVector overlap;
- getRangeMapOverlap( _chunksMap, chunk.getMin(), chunk.getMax(), &overlap );
-
- *errMsg = stream() << "cannot add chunk "
- << rangeToString( chunk.getMin(), chunk.getMax() )
- << " because the chunk overlaps " << overlapToString( overlap );
+ getRangeMapOverlap(_pendingMap, pending.getMin(), pending.getMax(), &overlap);
- warning() << *errMsg << endl;
- return NULL;
+ *errMsg += stream() << " and it overlaps " << overlapToString(overlap);
}
- unique_ptr<CollectionMetadata> metadata( new CollectionMetadata );
- metadata->_keyPattern = this->_keyPattern;
- metadata->_keyPattern.getOwned();
- metadata->fillKeyPatternFields();
- metadata->_pendingMap = this->_pendingMap;
- metadata->_chunksMap = this->_chunksMap;
- metadata->_chunksMap.insert( make_pair( chunk.getMin().getOwned(),
- chunk.getMax().getOwned() ) );
- metadata->_shardVersion = newShardVersion;
- metadata->_collVersion =
- newShardVersion > _collVersion ? newShardVersion : this->_collVersion;
- metadata->fillRanges();
-
- invariant(metadata->isValid());
- return metadata.release();
- }
-
- CollectionMetadata* CollectionMetadata::cloneMinusPending( const ChunkType& pending,
- string* errMsg ) const {
- // The error message string is optional.
- string dummy;
- if ( errMsg == NULL ) {
- errMsg = &dummy;
- }
-
- // Check that we have the exact chunk that will be subtracted.
- if ( !rangeMapContains( _pendingMap, pending.getMin(), pending.getMax() ) ) {
-
- *errMsg = stream() << "cannot remove pending chunk "
- << rangeToString( pending.getMin(), pending.getMax() )
- << ", this shard does not contain the chunk";
-
- if ( rangeMapOverlaps( _pendingMap, pending.getMin(), pending.getMax() ) ) {
-
- RangeVector overlap;
- getRangeMapOverlap( _pendingMap, pending.getMin(), pending.getMax(), &overlap );
-
- *errMsg += stream() << " and it overlaps " << overlapToString( overlap );
- }
-
- warning() << *errMsg << endl;
- return NULL;
- }
+ warning() << *errMsg << endl;
+ return NULL;
+ }
- unique_ptr<CollectionMetadata> metadata( new CollectionMetadata );
- metadata->_keyPattern = this->_keyPattern;
- metadata->_keyPattern.getOwned();
- metadata->fillKeyPatternFields();
- metadata->_pendingMap = this->_pendingMap;
- metadata->_pendingMap.erase( pending.getMin() );
- metadata->_chunksMap = this->_chunksMap;
- metadata->_rangesMap = this->_rangesMap;
- metadata->_shardVersion = _shardVersion;
- metadata->_collVersion = _collVersion;
-
- invariant(metadata->isValid());
- return metadata.release();
- }
-
- CollectionMetadata* CollectionMetadata::clonePlusPending( const ChunkType& pending,
- string* errMsg ) const {
- // The error message string is optional.
- string dummy;
- if ( errMsg == NULL ) {
- errMsg = &dummy;
- }
+ unique_ptr<CollectionMetadata> metadata(new CollectionMetadata);
+ metadata->_keyPattern = this->_keyPattern;
+ metadata->_keyPattern.getOwned();
+ metadata->fillKeyPatternFields();
+ metadata->_pendingMap = this->_pendingMap;
+ metadata->_pendingMap.erase(pending.getMin());
+ metadata->_chunksMap = this->_chunksMap;
+ metadata->_rangesMap = this->_rangesMap;
+ metadata->_shardVersion = _shardVersion;
+ metadata->_collVersion = _collVersion;
+
+ invariant(metadata->isValid());
+ return metadata.release();
+}
+
+CollectionMetadata* CollectionMetadata::clonePlusPending(const ChunkType& pending,
+ string* errMsg) const {
+ // The error message string is optional.
+ string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
+ }
- if ( rangeMapOverlaps( _chunksMap, pending.getMin(), pending.getMax() ) ) {
+ if (rangeMapOverlaps(_chunksMap, pending.getMin(), pending.getMax())) {
+ RangeVector overlap;
+ getRangeMapOverlap(_chunksMap, pending.getMin(), pending.getMax(), &overlap);
- RangeVector overlap;
- getRangeMapOverlap( _chunksMap, pending.getMin(), pending.getMax(), &overlap );
+ *errMsg = stream() << "cannot add pending chunk "
+ << rangeToString(pending.getMin(), pending.getMax())
+ << " because the chunk overlaps " << overlapToString(overlap);
- *errMsg = stream() << "cannot add pending chunk "
- << rangeToString( pending.getMin(), pending.getMax() )
- << " because the chunk overlaps " << overlapToString( overlap );
+ warning() << *errMsg << endl;
+ return NULL;
+ }
- warning() << *errMsg << endl;
- return NULL;
+ unique_ptr<CollectionMetadata> metadata(new CollectionMetadata);
+ metadata->_keyPattern = this->_keyPattern;
+ metadata->_keyPattern.getOwned();
+ metadata->fillKeyPatternFields();
+ metadata->_pendingMap = this->_pendingMap;
+ metadata->_chunksMap = this->_chunksMap;
+ metadata->_rangesMap = this->_rangesMap;
+ metadata->_shardVersion = _shardVersion;
+ metadata->_collVersion = _collVersion;
+
+ // If there are any pending chunks on the interval to be added this is ok, since pending
+ // chunks aren't officially tracked yet and something may have changed on servers we do not
+ // see yet.
+ // We remove any chunks we overlap, the remote request starting a chunk migration must have
+ // been authoritative.
+
+ if (rangeMapOverlaps(_pendingMap, pending.getMin(), pending.getMax())) {
+ RangeVector pendingOverlap;
+ getRangeMapOverlap(_pendingMap, pending.getMin(), pending.getMax(), &pendingOverlap);
+
+ warning() << "new pending chunk " << rangeToString(pending.getMin(), pending.getMax())
+ << " overlaps existing pending chunks " << overlapToString(pendingOverlap)
+ << ", a migration may not have completed" << endl;
+
+ for (RangeVector::iterator it = pendingOverlap.begin(); it != pendingOverlap.end(); ++it) {
+ metadata->_pendingMap.erase(it->first);
}
+ }
- unique_ptr<CollectionMetadata> metadata( new CollectionMetadata );
- metadata->_keyPattern = this->_keyPattern;
- metadata->_keyPattern.getOwned();
- metadata->fillKeyPatternFields();
- metadata->_pendingMap = this->_pendingMap;
- metadata->_chunksMap = this->_chunksMap;
- metadata->_rangesMap = this->_rangesMap;
- metadata->_shardVersion = _shardVersion;
- metadata->_collVersion = _collVersion;
-
- // If there are any pending chunks on the interval to be added this is ok, since pending
- // chunks aren't officially tracked yet and something may have changed on servers we do not
- // see yet.
- // We remove any chunks we overlap, the remote request starting a chunk migration must have
- // been authoritative.
-
- if ( rangeMapOverlaps( _pendingMap, pending.getMin(), pending.getMax() ) ) {
-
- RangeVector pendingOverlap;
- getRangeMapOverlap( _pendingMap, pending.getMin(), pending.getMax(), &pendingOverlap );
-
- warning() << "new pending chunk " << rangeToString( pending.getMin(), pending.getMax() )
- << " overlaps existing pending chunks " << overlapToString( pendingOverlap )
- << ", a migration may not have completed" << endl;
-
- for ( RangeVector::iterator it = pendingOverlap.begin(); it != pendingOverlap.end();
- ++it ) {
- metadata->_pendingMap.erase( it->first );
- }
- }
+ metadata->_pendingMap.insert(make_pair(pending.getMin(), pending.getMax()));
- metadata->_pendingMap.insert( make_pair( pending.getMin(), pending.getMax() ) );
+ invariant(metadata->isValid());
+ return metadata.release();
+}
- invariant(metadata->isValid());
- return metadata.release();
+CollectionMetadata* CollectionMetadata::cloneSplit(const ChunkType& chunk,
+ const vector<BSONObj>& splitKeys,
+ const ChunkVersion& newShardVersion,
+ string* errMsg) const {
+ // The error message string is optional.
+ string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
}
- CollectionMetadata* CollectionMetadata::cloneSplit( const ChunkType& chunk,
- const vector<BSONObj>& splitKeys,
- const ChunkVersion& newShardVersion,
- string* errMsg ) const {
- // The error message string is optional.
- string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
+ // The version required in both resulting chunks could be simply an increment in the
+ // minor portion of the current version. However, we are enforcing uniqueness over the
+ // attributes <ns, version> of the configdb collection 'chunks'. So in practice, a
+ // migrate somewhere may force this split to pick up a version that has the major
+ // portion higher than the one that this shard has been using.
+ //
+ // TODO drop the uniqueness constraint and tighten the check below so that only the
+ // minor portion of version changes
+ if (newShardVersion <= _shardVersion) {
+ *errMsg = stream() << "cannot split chunk " << rangeToString(chunk.getMin(), chunk.getMax())
+ << ", new shard version " << newShardVersion.toString()
+ << " is not greater than current version " << _shardVersion.toString();
+
+ warning() << *errMsg << endl;
+ return NULL;
+ }
- // The version required in both resulting chunks could be simply an increment in the
- // minor portion of the current version. However, we are enforcing uniqueness over the
- // attributes <ns, version> of the configdb collection 'chunks'. So in practice, a
- // migrate somewhere may force this split to pick up a version that has the major
- // portion higher than the one that this shard has been using.
- //
- // TODO drop the uniqueness constraint and tighten the check below so that only the
- // minor portion of version changes
- if (newShardVersion <= _shardVersion) {
+ // Check that we have the exact chunk that will be subtracted.
+ if (!rangeMapContains(_chunksMap, chunk.getMin(), chunk.getMax())) {
+ *errMsg = stream() << "cannot split chunk " << rangeToString(chunk.getMin(), chunk.getMax())
+ << ", this shard does not contain the chunk";
- *errMsg = stream() << "cannot split chunk "
- << rangeToString( chunk.getMin(), chunk.getMax() )
- << ", new shard version "
- << newShardVersion.toString()
- << " is not greater than current version "
- << _shardVersion.toString();
+ if (rangeMapOverlaps(_chunksMap, chunk.getMin(), chunk.getMax())) {
+ RangeVector overlap;
+ getRangeMapOverlap(_chunksMap, chunk.getMin(), chunk.getMax(), &overlap);
- warning() << *errMsg << endl;
- return NULL;
+ *errMsg += stream() << " and it overlaps " << overlapToString(overlap);
}
- // Check that we have the exact chunk that will be subtracted.
- if ( !rangeMapContains( _chunksMap, chunk.getMin(), chunk.getMax() ) ) {
+ warning() << *errMsg << endl;
+ return NULL;
+ }
+ // Check that the split key is valid
+ for (vector<BSONObj>::const_iterator it = splitKeys.begin(); it != splitKeys.end(); ++it) {
+ if (!rangeContains(chunk.getMin(), chunk.getMax(), *it)) {
*errMsg = stream() << "cannot split chunk "
- << rangeToString( chunk.getMin(), chunk.getMax() )
- << ", this shard does not contain the chunk";
-
- if ( rangeMapOverlaps( _chunksMap, chunk.getMin(), chunk.getMax() ) ) {
-
- RangeVector overlap;
- getRangeMapOverlap( _chunksMap, chunk.getMin(), chunk.getMax(), &overlap );
-
- *errMsg += stream() << " and it overlaps " << overlapToString( overlap );
- }
+ << rangeToString(chunk.getMin(), chunk.getMax()) << " at key "
+ << *it;
warning() << *errMsg << endl;
return NULL;
}
-
- // Check that the split key is valid
- for ( vector<BSONObj>::const_iterator it = splitKeys.begin(); it != splitKeys.end(); ++it )
- {
- if (!rangeContains(chunk.getMin(), chunk.getMax(), *it)) {
-
- *errMsg = stream() << "cannot split chunk "
- << rangeToString( chunk.getMin(), chunk.getMax() ) << " at key "
- << *it;
-
- warning() << *errMsg << endl;
- return NULL;
- }
- }
-
- unique_ptr<CollectionMetadata> metadata(new CollectionMetadata);
- metadata->_keyPattern = this->_keyPattern;
- metadata->_keyPattern.getOwned();
- metadata->fillKeyPatternFields();
- metadata->_pendingMap = this->_pendingMap;
- metadata->_chunksMap = this->_chunksMap;
- metadata->_shardVersion = newShardVersion; // will increment 2nd, 3rd,... chunks below
-
- BSONObj startKey = chunk.getMin();
- for ( vector<BSONObj>::const_iterator it = splitKeys.begin(); it != splitKeys.end();
- ++it ) {
- BSONObj split = *it;
- invariant(split.woCompare(startKey) > 0);
- metadata->_chunksMap[startKey] = split.getOwned();
- metadata->_chunksMap.insert( make_pair( split.getOwned(), chunk.getMax().getOwned() ));
- metadata->_shardVersion.incMinor();
- startKey = split;
- }
-
- metadata->_collVersion =
- metadata->_shardVersion > _collVersion ? metadata->_shardVersion : _collVersion;
- metadata->fillRanges();
-
- invariant(metadata->isValid());
- return metadata.release();
}
- CollectionMetadata* CollectionMetadata::cloneMerge( const BSONObj& minKey,
- const BSONObj& maxKey,
- const ChunkVersion& newShardVersion,
- string* errMsg ) const {
-
- if (newShardVersion <= _shardVersion) {
-
- *errMsg = stream() << "cannot merge range " << rangeToString( minKey, maxKey )
- << ", new shard version " << newShardVersion.toString()
- << " is not greater than current version "
- << _shardVersion.toString();
-
- warning() << *errMsg << endl;
- return NULL;
- }
+ unique_ptr<CollectionMetadata> metadata(new CollectionMetadata);
+ metadata->_keyPattern = this->_keyPattern;
+ metadata->_keyPattern.getOwned();
+ metadata->fillKeyPatternFields();
+ metadata->_pendingMap = this->_pendingMap;
+ metadata->_chunksMap = this->_chunksMap;
+ metadata->_shardVersion = newShardVersion; // will increment 2nd, 3rd,... chunks below
+
+ BSONObj startKey = chunk.getMin();
+ for (vector<BSONObj>::const_iterator it = splitKeys.begin(); it != splitKeys.end(); ++it) {
+ BSONObj split = *it;
+ invariant(split.woCompare(startKey) > 0);
+ metadata->_chunksMap[startKey] = split.getOwned();
+ metadata->_chunksMap.insert(make_pair(split.getOwned(), chunk.getMax().getOwned()));
+ metadata->_shardVersion.incMinor();
+ startKey = split;
+ }
- RangeVector overlap;
- getRangeMapOverlap( _chunksMap, minKey, maxKey, &overlap );
+ metadata->_collVersion =
+ metadata->_shardVersion > _collVersion ? metadata->_shardVersion : _collVersion;
+ metadata->fillRanges();
+
+ invariant(metadata->isValid());
+ return metadata.release();
+}
+
+CollectionMetadata* CollectionMetadata::cloneMerge(const BSONObj& minKey,
+ const BSONObj& maxKey,
+ const ChunkVersion& newShardVersion,
+ string* errMsg) const {
+ if (newShardVersion <= _shardVersion) {
+ *errMsg = stream() << "cannot merge range " << rangeToString(minKey, maxKey)
+ << ", new shard version " << newShardVersion.toString()
+ << " is not greater than current version " << _shardVersion.toString();
+
+ warning() << *errMsg << endl;
+ return NULL;
+ }
- if ( overlap.empty() || overlap.size() == 1 ) {
+ RangeVector overlap;
+ getRangeMapOverlap(_chunksMap, minKey, maxKey, &overlap);
- *errMsg = stream() << "cannot merge range " << rangeToString( minKey, maxKey )
- << ( overlap.empty() ? ", no chunks found in this range" :
- ", only one chunk found in this range" );
+ if (overlap.empty() || overlap.size() == 1) {
+ *errMsg = stream() << "cannot merge range " << rangeToString(minKey, maxKey)
+ << (overlap.empty() ? ", no chunks found in this range"
+ : ", only one chunk found in this range");
- warning() << *errMsg << endl;
- return NULL;
- }
+ warning() << *errMsg << endl;
+ return NULL;
+ }
- bool validStartEnd = true;
- bool validNoHoles = true;
- if ( overlap.begin()->first.woCompare( minKey ) != 0 ) {
- // First chunk doesn't start with minKey
- validStartEnd = false;
- }
- else if ( overlap.rbegin()->second.woCompare( maxKey ) != 0 ) {
- // Last chunk doesn't end with maxKey
- validStartEnd = false;
- }
- else {
- // Check that there are no holes
- BSONObj prevMaxKey = minKey;
- for ( RangeVector::iterator it = overlap.begin(); it != overlap.end(); ++it ) {
- if ( it->first.woCompare( prevMaxKey ) != 0 ) {
- validNoHoles = false;
- break;
- }
- prevMaxKey = it->second;
+ bool validStartEnd = true;
+ bool validNoHoles = true;
+ if (overlap.begin()->first.woCompare(minKey) != 0) {
+ // First chunk doesn't start with minKey
+ validStartEnd = false;
+ } else if (overlap.rbegin()->second.woCompare(maxKey) != 0) {
+ // Last chunk doesn't end with maxKey
+ validStartEnd = false;
+ } else {
+ // Check that there are no holes
+ BSONObj prevMaxKey = minKey;
+ for (RangeVector::iterator it = overlap.begin(); it != overlap.end(); ++it) {
+ if (it->first.woCompare(prevMaxKey) != 0) {
+ validNoHoles = false;
+ break;
}
+ prevMaxKey = it->second;
}
+ }
- if ( !validStartEnd || !validNoHoles ) {
+ if (!validStartEnd || !validNoHoles) {
+ *errMsg = stream() << "cannot merge range " << rangeToString(minKey, maxKey)
+ << ", overlapping chunks " << overlapToString(overlap)
+ << (!validStartEnd ? " do not have the same min and max key"
+ : " are not all adjacent");
- *errMsg = stream() << "cannot merge range " << rangeToString( minKey, maxKey )
- << ", overlapping chunks " << overlapToString( overlap )
- << ( !validStartEnd ? " do not have the same min and max key" :
- " are not all adjacent" );
+ warning() << *errMsg << endl;
+ return NULL;
+ }
- warning() << *errMsg << endl;
- return NULL;
- }
+ unique_ptr<CollectionMetadata> metadata(new CollectionMetadata);
+ metadata->_keyPattern = this->_keyPattern;
+ metadata->_keyPattern.getOwned();
+ metadata->fillKeyPatternFields();
+ metadata->_pendingMap = this->_pendingMap;
+ metadata->_chunksMap = this->_chunksMap;
+ metadata->_rangesMap = this->_rangesMap;
+ metadata->_shardVersion = newShardVersion;
+ metadata->_collVersion = newShardVersion > _collVersion ? newShardVersion : this->_collVersion;
+
+ for (RangeVector::iterator it = overlap.begin(); it != overlap.end(); ++it) {
+ metadata->_chunksMap.erase(it->first);
+ }
- unique_ptr<CollectionMetadata> metadata( new CollectionMetadata );
- metadata->_keyPattern = this->_keyPattern;
- metadata->_keyPattern.getOwned();
- metadata->fillKeyPatternFields();
- metadata->_pendingMap = this->_pendingMap;
- metadata->_chunksMap = this->_chunksMap;
- metadata->_rangesMap = this->_rangesMap;
- metadata->_shardVersion = newShardVersion;
- metadata->_collVersion =
- newShardVersion > _collVersion ? newShardVersion : this->_collVersion;
-
- for ( RangeVector::iterator it = overlap.begin(); it != overlap.end(); ++it ) {
- metadata->_chunksMap.erase( it->first );
- }
+ metadata->_chunksMap.insert(make_pair(minKey, maxKey));
- metadata->_chunksMap.insert( make_pair( minKey, maxKey ) );
+ invariant(metadata->isValid());
+ return metadata.release();
+}
- invariant(metadata->isValid());
- return metadata.release();
+bool CollectionMetadata::keyBelongsToMe(const BSONObj& key) const {
+ // For now, collections don't move. So if the collection is not sharded, assume
+ // the document with the given key can be accessed.
+ if (_keyPattern.isEmpty()) {
+ return true;
}
- bool CollectionMetadata::keyBelongsToMe( const BSONObj& key ) const {
- // For now, collections don't move. So if the collection is not sharded, assume
- // the document with the given key can be accessed.
- if ( _keyPattern.isEmpty() ) {
- return true;
- }
-
- if ( _rangesMap.size() <= 0 ) {
- return false;
- }
+ if (_rangesMap.size() <= 0) {
+ return false;
+ }
- RangeMap::const_iterator it = _rangesMap.upper_bound( key );
- if ( it != _rangesMap.begin() ) it--;
+ RangeMap::const_iterator it = _rangesMap.upper_bound(key);
+ if (it != _rangesMap.begin())
+ it--;
- bool good = rangeContains( it->first, it->second, key );
+ bool good = rangeContains(it->first, it->second, key);
#if 0
// DISABLED because of SERVER-11175 - huge amount of logging
@@ -477,286 +444,285 @@ namespace mongo {
}
#endif
- return good;
- }
-
- bool CollectionMetadata::keyIsPending( const BSONObj& key ) const {
- // If we aren't sharded, then the key is never pending (though it belongs-to-me)
- if ( _keyPattern.isEmpty() ) {
- return false;
- }
-
- if ( _pendingMap.size() <= 0 ) {
- return false;
- }
-
- RangeMap::const_iterator it = _pendingMap.upper_bound( key );
- if ( it != _pendingMap.begin() ) it--;
+ return good;
+}
- bool isPending = rangeContains( it->first, it->second, key );
- return isPending;
+bool CollectionMetadata::keyIsPending(const BSONObj& key) const {
+ // If we aren't sharded, then the key is never pending (though it belongs-to-me)
+ if (_keyPattern.isEmpty()) {
+ return false;
}
- bool CollectionMetadata::getNextChunk( const BSONObj& lookupKey, ChunkType* chunk ) const {
- RangeMap::const_iterator upperChunkIt = _chunksMap.upper_bound(lookupKey);
- RangeMap::const_iterator lowerChunkIt = upperChunkIt;
+ if (_pendingMap.size() <= 0) {
+ return false;
+ }
- if (upperChunkIt != _chunksMap.begin()) {
- --lowerChunkIt;
- }
- else {
- lowerChunkIt = _chunksMap.end();
- }
+ RangeMap::const_iterator it = _pendingMap.upper_bound(key);
+ if (it != _pendingMap.begin())
+ it--;
- if (lowerChunkIt != _chunksMap.end() && lowerChunkIt->second.woCompare(lookupKey) > 0) {
- chunk->setMin(lowerChunkIt->first);
- chunk->setMax(lowerChunkIt->second);
- return true;
- }
+ bool isPending = rangeContains(it->first, it->second, key);
+ return isPending;
+}
- if (upperChunkIt != _chunksMap.end()) {
- chunk->setMin(upperChunkIt->first);
- chunk->setMax(upperChunkIt->second);
- return true;
- }
+bool CollectionMetadata::getNextChunk(const BSONObj& lookupKey, ChunkType* chunk) const {
+ RangeMap::const_iterator upperChunkIt = _chunksMap.upper_bound(lookupKey);
+ RangeMap::const_iterator lowerChunkIt = upperChunkIt;
- return false;
+ if (upperChunkIt != _chunksMap.begin()) {
+ --lowerChunkIt;
+ } else {
+ lowerChunkIt = _chunksMap.end();
}
- BSONObj CollectionMetadata::toBSON() const {
- BSONObjBuilder bb;
- toBSON( bb );
- return bb.obj();
+ if (lowerChunkIt != _chunksMap.end() && lowerChunkIt->second.woCompare(lookupKey) > 0) {
+ chunk->setMin(lowerChunkIt->first);
+ chunk->setMax(lowerChunkIt->second);
+ return true;
}
- void CollectionMetadata::toBSONChunks( BSONArrayBuilder& bb ) const {
-
- if ( _chunksMap.empty() ) return;
-
- for (RangeMap::const_iterator it = _chunksMap.begin(); it != _chunksMap.end(); ++it ) {
- BSONArrayBuilder chunkBB( bb.subarrayStart() );
- chunkBB.append( it->first );
- chunkBB.append( it->second );
- chunkBB.done();
- }
+ if (upperChunkIt != _chunksMap.end()) {
+ chunk->setMin(upperChunkIt->first);
+ chunk->setMax(upperChunkIt->second);
+ return true;
}
- void CollectionMetadata::toBSONPending( BSONArrayBuilder& bb ) const {
+ return false;
+}
- if ( _pendingMap.empty() ) return;
-
- for (RangeMap::const_iterator it = _pendingMap.begin(); it != _pendingMap.end(); ++it ) {
- BSONArrayBuilder pendingBB( bb.subarrayStart() );
- pendingBB.append( it->first );
- pendingBB.append( it->second );
- pendingBB.done();
- }
- }
+BSONObj CollectionMetadata::toBSON() const {
+ BSONObjBuilder bb;
+ toBSON(bb);
+ return bb.obj();
+}
- void CollectionMetadata::toBSON( BSONObjBuilder& bb ) const {
+void CollectionMetadata::toBSONChunks(BSONArrayBuilder& bb) const {
+ if (_chunksMap.empty())
+ return;
- _collVersion.addToBSON( bb, "collVersion" );
- _shardVersion.addToBSON( bb, "shardVersion" );
- bb.append( "keyPattern", _keyPattern );
+ for (RangeMap::const_iterator it = _chunksMap.begin(); it != _chunksMap.end(); ++it) {
+ BSONArrayBuilder chunkBB(bb.subarrayStart());
+ chunkBB.append(it->first);
+ chunkBB.append(it->second);
+ chunkBB.done();
+ }
+}
- BSONArrayBuilder chunksBB( bb.subarrayStart( "chunks" ) );
- toBSONChunks( chunksBB );
- chunksBB.done();
+void CollectionMetadata::toBSONPending(BSONArrayBuilder& bb) const {
+ if (_pendingMap.empty())
+ return;
- BSONArrayBuilder pendingBB( bb.subarrayStart( "pending" ) );
- toBSONPending( pendingBB );
+ for (RangeMap::const_iterator it = _pendingMap.begin(); it != _pendingMap.end(); ++it) {
+ BSONArrayBuilder pendingBB(bb.subarrayStart());
+ pendingBB.append(it->first);
+ pendingBB.append(it->second);
pendingBB.done();
}
+}
- bool CollectionMetadata::getNextOrphanRange( const BSONObj& origLookupKey,
- KeyRange* range ) const {
+void CollectionMetadata::toBSON(BSONObjBuilder& bb) const {
+ _collVersion.addToBSON(bb, "collVersion");
+ _shardVersion.addToBSON(bb, "shardVersion");
+ bb.append("keyPattern", _keyPattern);
- if ( _keyPattern.isEmpty() ) return false;
+ BSONArrayBuilder chunksBB(bb.subarrayStart("chunks"));
+ toBSONChunks(chunksBB);
+ chunksBB.done();
- BSONObj lookupKey = origLookupKey;
- BSONObj maxKey = getMaxKey(); // so we don't keep rebuilding
- while ( lookupKey.woCompare( maxKey ) < 0 ) {
+ BSONArrayBuilder pendingBB(bb.subarrayStart("pending"));
+ toBSONPending(pendingBB);
+ pendingBB.done();
+}
- RangeMap::const_iterator lowerChunkIt = _chunksMap.end();
- RangeMap::const_iterator upperChunkIt = _chunksMap.end();
+bool CollectionMetadata::getNextOrphanRange(const BSONObj& origLookupKey, KeyRange* range) const {
+ if (_keyPattern.isEmpty())
+ return false;
- if ( !_chunksMap.empty() ) {
- upperChunkIt = _chunksMap.upper_bound( lookupKey );
- lowerChunkIt = upperChunkIt;
- if ( upperChunkIt != _chunksMap.begin() ) --lowerChunkIt;
- else lowerChunkIt = _chunksMap.end();
- }
+ BSONObj lookupKey = origLookupKey;
+ BSONObj maxKey = getMaxKey(); // so we don't keep rebuilding
+ while (lookupKey.woCompare(maxKey) < 0) {
+ RangeMap::const_iterator lowerChunkIt = _chunksMap.end();
+ RangeMap::const_iterator upperChunkIt = _chunksMap.end();
- // If we overlap, continue after the overlap
- // TODO: Could optimize slightly by finding next non-contiguous chunk
- if ( lowerChunkIt != _chunksMap.end()
- && lowerChunkIt->second.woCompare( lookupKey ) > 0 ) {
- lookupKey = lowerChunkIt->second;
- continue;
- }
-
- RangeMap::const_iterator lowerPendingIt = _pendingMap.end();
- RangeMap::const_iterator upperPendingIt = _pendingMap.end();
+ if (!_chunksMap.empty()) {
+ upperChunkIt = _chunksMap.upper_bound(lookupKey);
+ lowerChunkIt = upperChunkIt;
+ if (upperChunkIt != _chunksMap.begin())
+ --lowerChunkIt;
+ else
+ lowerChunkIt = _chunksMap.end();
+ }
- if ( !_pendingMap.empty() ) {
+ // If we overlap, continue after the overlap
+ // TODO: Could optimize slightly by finding next non-contiguous chunk
+ if (lowerChunkIt != _chunksMap.end() && lowerChunkIt->second.woCompare(lookupKey) > 0) {
+ lookupKey = lowerChunkIt->second;
+ continue;
+ }
- upperPendingIt = _pendingMap.upper_bound( lookupKey );
- lowerPendingIt = upperPendingIt;
- if ( upperPendingIt != _pendingMap.begin() ) --lowerPendingIt;
- else lowerPendingIt = _pendingMap.end();
- }
+ RangeMap::const_iterator lowerPendingIt = _pendingMap.end();
+ RangeMap::const_iterator upperPendingIt = _pendingMap.end();
- // If we overlap, continue after the overlap
- // TODO: Could optimize slightly by finding next non-contiguous chunk
- if ( lowerPendingIt != _pendingMap.end()
- && lowerPendingIt->second.woCompare( lookupKey ) > 0 ) {
- lookupKey = lowerPendingIt->second;
- continue;
- }
+ if (!_pendingMap.empty()) {
+ upperPendingIt = _pendingMap.upper_bound(lookupKey);
+ lowerPendingIt = upperPendingIt;
+ if (upperPendingIt != _pendingMap.begin())
+ --lowerPendingIt;
+ else
+ lowerPendingIt = _pendingMap.end();
+ }
- //
- // We know that the lookup key is not covered by a chunk or pending range, and where the
- // previous chunk and pending chunks are. Now we fill in the bounds as the closest
- // bounds of the surrounding ranges in both maps.
- //
+ // If we overlap, continue after the overlap
+ // TODO: Could optimize slightly by finding next non-contiguous chunk
+ if (lowerPendingIt != _pendingMap.end() &&
+ lowerPendingIt->second.woCompare(lookupKey) > 0) {
+ lookupKey = lowerPendingIt->second;
+ continue;
+ }
- range->keyPattern = _keyPattern;
- range->minKey = getMinKey();
- range->maxKey = maxKey;
+ //
+ // We know that the lookup key is not covered by a chunk or pending range, and where the
+ // previous chunk and pending chunks are. Now we fill in the bounds as the closest
+ // bounds of the surrounding ranges in both maps.
+ //
- if ( lowerChunkIt != _chunksMap.end()
- && lowerChunkIt->second.woCompare( range->minKey ) > 0 ) {
- range->minKey = lowerChunkIt->second;
- }
+ range->keyPattern = _keyPattern;
+ range->minKey = getMinKey();
+ range->maxKey = maxKey;
- if ( upperChunkIt != _chunksMap.end()
- && upperChunkIt->first.woCompare( range->maxKey ) < 0 ) {
- range->maxKey = upperChunkIt->first;
- }
+ if (lowerChunkIt != _chunksMap.end() && lowerChunkIt->second.woCompare(range->minKey) > 0) {
+ range->minKey = lowerChunkIt->second;
+ }
- if ( lowerPendingIt != _pendingMap.end()
- && lowerPendingIt->second.woCompare( range->minKey ) > 0 ) {
- range->minKey = lowerPendingIt->second;
- }
+ if (upperChunkIt != _chunksMap.end() && upperChunkIt->first.woCompare(range->maxKey) < 0) {
+ range->maxKey = upperChunkIt->first;
+ }
- if ( upperPendingIt != _pendingMap.end()
- && upperPendingIt->first.woCompare( range->maxKey ) < 0 ) {
- range->maxKey = upperPendingIt->first;
- }
+ if (lowerPendingIt != _pendingMap.end() &&
+ lowerPendingIt->second.woCompare(range->minKey) > 0) {
+ range->minKey = lowerPendingIt->second;
+ }
- return true;
+ if (upperPendingIt != _pendingMap.end() &&
+ upperPendingIt->first.woCompare(range->maxKey) < 0) {
+ range->maxKey = upperPendingIt->first;
}
- return false;
+ return true;
}
- string CollectionMetadata::toString() const {
- StringBuilder ss;
- ss << " CollectionManager version: " << _shardVersion.toString() << " key: " << _keyPattern;
- if (_rangesMap.empty()) {
- return ss.str();
- }
+ return false;
+}
- RangeMap::const_iterator it = _rangesMap.begin();
- ss << it->first << " -> " << it->second;
- while (it != _rangesMap.end()) {
- ss << ", "<< it->first << " -> " << it->second;
- }
+string CollectionMetadata::toString() const {
+ StringBuilder ss;
+ ss << " CollectionManager version: " << _shardVersion.toString() << " key: " << _keyPattern;
+ if (_rangesMap.empty()) {
return ss.str();
}
- BSONObj CollectionMetadata::getMinKey() const {
- BSONObjIterator it( _keyPattern );
- BSONObjBuilder minKeyB;
- while ( it.more() ) minKeyB << it.next().fieldName() << MINKEY;
- return minKeyB.obj();
- }
-
- BSONObj CollectionMetadata::getMaxKey() const {
- BSONObjIterator it( _keyPattern );
- BSONObjBuilder maxKeyB;
- while ( it.more() ) maxKeyB << it.next().fieldName() << MAXKEY;
- return maxKeyB.obj();
+ RangeMap::const_iterator it = _rangesMap.begin();
+ ss << it->first << " -> " << it->second;
+ while (it != _rangesMap.end()) {
+ ss << ", " << it->first << " -> " << it->second;
}
+ return ss.str();
+}
+
+BSONObj CollectionMetadata::getMinKey() const {
+ BSONObjIterator it(_keyPattern);
+ BSONObjBuilder minKeyB;
+ while (it.more())
+ minKeyB << it.next().fieldName() << MINKEY;
+ return minKeyB.obj();
+}
+
+BSONObj CollectionMetadata::getMaxKey() const {
+ BSONObjIterator it(_keyPattern);
+ BSONObjBuilder maxKeyB;
+ while (it.more())
+ maxKeyB << it.next().fieldName() << MAXKEY;
+ return maxKeyB.obj();
+}
+
+bool CollectionMetadata::isValid() const {
+ if (_shardVersion > _collVersion)
+ return false;
+ if (_collVersion.majorVersion() == 0)
+ return false;
+ if (_collVersion.epoch() != _shardVersion.epoch())
+ return false;
- bool CollectionMetadata::isValid() const {
- if (_shardVersion > _collVersion)
+ if (_shardVersion.majorVersion() > 0) {
+ // Must be chunks
+ if (_rangesMap.size() == 0 || _chunksMap.size() == 0)
return false;
- if (_collVersion.majorVersion() == 0)
+ } else {
+ // No chunks
+ if (_shardVersion.minorVersion() > 0)
return false;
- if (_collVersion.epoch() != _shardVersion.epoch())
+ if (_rangesMap.size() > 0 || _chunksMap.size() > 0)
return false;
-
- if (_shardVersion.majorVersion() > 0) {
- // Must be chunks
- if (_rangesMap.size() == 0 || _chunksMap.size() == 0)
- return false;
- }
- else {
- // No chunks
- if (_shardVersion.minorVersion() > 0)
- return false;
- if (_rangesMap.size() > 0 || _chunksMap.size() > 0)
- return false;
- }
-
- return true;
}
- bool CollectionMetadata::isValidKey( const BSONObj& key ) const {
- BSONObjIterator it( _keyPattern );
- while ( it.more() ) {
- BSONElement next = it.next();
- if ( !key.hasField( next.fieldName() ) ) return false;
- }
- return key.nFields() == _keyPattern.nFields();
- }
-
- void CollectionMetadata::fillRanges() {
- if (_chunksMap.empty())
- return;
-
- // Load the chunk information, coallesceing their ranges. The version for this shard
- // would be the highest version for any of the chunks.
- RangeMap::const_iterator it = _chunksMap.begin();
- BSONObj min,max;
- while (it != _chunksMap.end()) {
- BSONObj currMin = it->first;
- BSONObj currMax = it->second;
- ++it;
-
- // coalesce the chunk's bounds in ranges if they are adjacent chunks
- if (min.isEmpty()) {
- min = currMin;
- max = currMax;
- continue;
- }
- if (max == currMin) {
- max = currMax;
- continue;
- }
-
- _rangesMap.insert(make_pair(min, max));
+ return true;
+}
+bool CollectionMetadata::isValidKey(const BSONObj& key) const {
+ BSONObjIterator it(_keyPattern);
+ while (it.more()) {
+ BSONElement next = it.next();
+ if (!key.hasField(next.fieldName()))
+ return false;
+ }
+ return key.nFields() == _keyPattern.nFields();
+}
+
+void CollectionMetadata::fillRanges() {
+ if (_chunksMap.empty())
+ return;
+
+ // Load the chunk information, coallesceing their ranges. The version for this shard
+ // would be the highest version for any of the chunks.
+ RangeMap::const_iterator it = _chunksMap.begin();
+ BSONObj min, max;
+ while (it != _chunksMap.end()) {
+ BSONObj currMin = it->first;
+ BSONObj currMax = it->second;
+ ++it;
+
+ // coalesce the chunk's bounds in ranges if they are adjacent chunks
+ if (min.isEmpty()) {
min = currMin;
max = currMax;
+ continue;
+ }
+ if (max == currMin) {
+ max = currMax;
+ continue;
}
- dassert(!min.isEmpty());
_rangesMap.insert(make_pair(min, max));
+
+ min = currMin;
+ max = currMax;
}
+ dassert(!min.isEmpty());
- void CollectionMetadata::fillKeyPatternFields() {
- // Parse the shard keys into the states 'keys' and 'keySet' members.
- BSONObjIterator patternIter = _keyPattern.begin();
- while (patternIter.more()) {
- BSONElement current = patternIter.next();
+ _rangesMap.insert(make_pair(min, max));
+}
- _keyFields.mutableVector().push_back(new FieldRef);
- FieldRef* const newFieldRef = _keyFields.mutableVector().back();
- newFieldRef->parse(current.fieldNameStringData());
- }
+void CollectionMetadata::fillKeyPatternFields() {
+ // Parse the shard keys into the states 'keys' and 'keySet' members.
+ BSONObjIterator patternIter = _keyPattern.begin();
+ while (patternIter.more()) {
+ BSONElement current = patternIter.next();
+
+ _keyFields.mutableVector().push_back(new FieldRef);
+ FieldRef* const newFieldRef = _keyFields.mutableVector().back();
+ newFieldRef->parse(current.fieldNameStringData());
}
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/collection_metadata.h b/src/mongo/s/collection_metadata.h
index b7f30c6a760..5f3ae419dbd 100644
--- a/src/mongo/s/collection_metadata.h
+++ b/src/mongo/s/collection_metadata.h
@@ -39,280 +39,280 @@
namespace mongo {
- class MetadataLoader;
- class CollectionMetadata;
+class MetadataLoader;
+class CollectionMetadata;
- typedef std::shared_ptr<const CollectionMetadata> CollectionMetadataPtr;
+typedef std::shared_ptr<const CollectionMetadata> CollectionMetadataPtr;
+
+/**
+ * The collection metadata has metadata information about a collection, in particular the
+ * sharding information. It's main goal in life is to be capable of answering if a certain
+ * document belongs to it or not. (In some scenarios such as chunk migration, a given
+ * document is in a shard but cannot be accessed.)
+ *
+ * To build a collection from config data, please check the MetadataLoader. The methods
+ * here allow building a new incarnation of a collection's metadata based on an existing
+ * one (e.g, we're splitting in a given collection.).
+ *
+ * This class is immutable once constructed.
+ */
+class CollectionMetadata {
+ MONGO_DISALLOW_COPYING(CollectionMetadata);
+
+public:
+ ~CollectionMetadata();
+
+ //
+ // cloning support
+ //
+
+ /**
+ * Returns a new metadata's instance based on 'this's state by removing a 'pending' chunk.
+ *
+ * The shard and collection version of the new metadata are unaffected. The caller owns the
+ * new metadata.
+ *
+ * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was
+ * provided.
+ */
+ CollectionMetadata* cloneMinusPending(const ChunkType& pending, std::string* errMsg) const;
+
+ /**
+ * Returns a new metadata's instance based on 'this's state by adding a 'pending' chunk.
+ *
+ * The shard and collection version of the new metadata are unaffected. The caller owns the
+ * new metadata.
+ *
+ * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was
+ * provided.
+ */
+ CollectionMetadata* clonePlusPending(const ChunkType& pending, std::string* errMsg) const;
+
+ /**
+ * Returns a new metadata's instance based on 'this's state by removing 'chunk'.
+ * When cloning away the last chunk, 'newShardVersion' must be zero. In any case,
+ * the caller owns the new metadata when the cloning is successful.
+ *
+ * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was
+ * provided.
+ */
+ CollectionMetadata* cloneMigrate(const ChunkType& chunk,
+ const ChunkVersion& newShardVersion,
+ std::string* errMsg) const;
+
+ /**
+ * Returns a new metadata's instance by splitting an existing 'chunk' at the points
+ * described by 'splitKeys'. The first resulting chunk will have 'newShardVersion' and
+ * subsequent one would have that with the minor version incremented at each chunk. The
+ * caller owns the metadata.
+ *
+ * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was
+ * provided.
+ *
+ * Note: 'splitKeys' must be sorted in ascending order.
+ */
+ CollectionMetadata* cloneSplit(const ChunkType& chunk,
+ const std::vector<BSONObj>& splitKeys,
+ const ChunkVersion& newShardVersion,
+ std::string* errMsg) const;
/**
- * The collection metadata has metadata information about a collection, in particular the
- * sharding information. It's main goal in life is to be capable of answering if a certain
- * document belongs to it or not. (In some scenarios such as chunk migration, a given
- * document is in a shard but cannot be accessed.)
+ * Returns a new metadata instance by merging a key range which starts and ends at existing
+ * chunks into a single chunk. The range may not have holes. The resulting metadata will
+ * have the 'newShardVersion'. The caller owns the new metadata.
*
- * To build a collection from config data, please check the MetadataLoader. The methods
- * here allow building a new incarnation of a collection's metadata based on an existing
- * one (e.g, we're splitting in a given collection.).
+ * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was
+ * provided.
+ */
+ CollectionMetadata* cloneMerge(const BSONObj& minKey,
+ const BSONObj& maxKey,
+ const ChunkVersion& newShardVersion,
+ std::string* errMsg) const;
+
+ //
+ // verification logic
+ //
+
+ /**
+ * Returns true if the document key 'key' is a valid instance of a shard key for this
+ * metadata. The 'key' must contain exactly the same fields as the shard key pattern.
+ */
+ bool isValidKey(const BSONObj& key) const;
+
+ /**
+ * Returns true if the document key 'key' belongs to this chunkset. Recall that documents of
+ * an in-flight chunk migration may be present and should not be considered part of the
+ * collection / chunkset yet. Key must be the full shard key.
+ */
+ bool keyBelongsToMe(const BSONObj& key) const;
+
+ /**
+ * Returns true if the document key 'key' is or has been migrated to this shard, and may
+ * belong to us after a subsequent config reload. Key must be the full shard key.
+ */
+ bool keyIsPending(const BSONObj& key) const;
+
+ /**
+ * Given a key 'lookupKey' in the shard key range, get the next chunk which overlaps or is
+ * greater than this key. Returns true if a chunk exists, false otherwise.
+ *
+ * Passing a key that is not a valid shard key for this range results in undefined behavior.
+ */
+ bool getNextChunk(const BSONObj& lookupKey, ChunkType* chunk) const;
+
+ /**
+ * Given a key in the shard key range, get the next range which overlaps or is greater than
+ * this key.
+ *
+ * This allows us to do the following to iterate over all orphan ranges:
*
- * This class is immutable once constructed.
+ * KeyRange range;
+ * BSONObj lookupKey = metadata->getMinKey();
+ * while( metadata->getNextOrphanRange( lookupKey, &orphanRange ) ) {
+ * // Do stuff with range
+ * lookupKey = orphanRange.maxKey;
+ * }
+ *
+ * @param lookupKey passing a key that does not belong to this metadata is undefined.
+ * @param orphanRange the output range. Note that the NS is not set.
*/
- class CollectionMetadata {
- MONGO_DISALLOW_COPYING(CollectionMetadata);
- public:
-
- ~CollectionMetadata();
-
- //
- // cloning support
- //
-
- /**
- * Returns a new metadata's instance based on 'this's state by removing a 'pending' chunk.
- *
- * The shard and collection version of the new metadata are unaffected. The caller owns the
- * new metadata.
- *
- * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was
- * provided.
- */
- CollectionMetadata* cloneMinusPending( const ChunkType& pending, std::string* errMsg ) const;
-
- /**
- * Returns a new metadata's instance based on 'this's state by adding a 'pending' chunk.
- *
- * The shard and collection version of the new metadata are unaffected. The caller owns the
- * new metadata.
- *
- * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was
- * provided.
- */
- CollectionMetadata* clonePlusPending( const ChunkType& pending, std::string* errMsg ) const;
-
- /**
- * Returns a new metadata's instance based on 'this's state by removing 'chunk'.
- * When cloning away the last chunk, 'newShardVersion' must be zero. In any case,
- * the caller owns the new metadata when the cloning is successful.
- *
- * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was
- * provided.
- */
- CollectionMetadata* cloneMigrate( const ChunkType& chunk,
- const ChunkVersion& newShardVersion,
- std::string* errMsg ) const;
-
- /**
- * Returns a new metadata's instance by splitting an existing 'chunk' at the points
- * described by 'splitKeys'. The first resulting chunk will have 'newShardVersion' and
- * subsequent one would have that with the minor version incremented at each chunk. The
- * caller owns the metadata.
- *
- * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was
- * provided.
- *
- * Note: 'splitKeys' must be sorted in ascending order.
- */
- CollectionMetadata* cloneSplit( const ChunkType& chunk,
- const std::vector<BSONObj>& splitKeys,
- const ChunkVersion& newShardVersion,
- std::string* errMsg ) const;
-
- /**
- * Returns a new metadata instance by merging a key range which starts and ends at existing
- * chunks into a single chunk. The range may not have holes. The resulting metadata will
- * have the 'newShardVersion'. The caller owns the new metadata.
- *
- * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was
- * provided.
- */
- CollectionMetadata* cloneMerge( const BSONObj& minKey,
- const BSONObj& maxKey,
- const ChunkVersion& newShardVersion,
- std::string* errMsg ) const;
-
- //
- // verification logic
- //
-
- /**
- * Returns true if the document key 'key' is a valid instance of a shard key for this
- * metadata. The 'key' must contain exactly the same fields as the shard key pattern.
- */
- bool isValidKey( const BSONObj& key ) const;
-
- /**
- * Returns true if the document key 'key' belongs to this chunkset. Recall that documents of
- * an in-flight chunk migration may be present and should not be considered part of the
- * collection / chunkset yet. Key must be the full shard key.
- */
- bool keyBelongsToMe( const BSONObj& key ) const;
-
- /**
- * Returns true if the document key 'key' is or has been migrated to this shard, and may
- * belong to us after a subsequent config reload. Key must be the full shard key.
- */
- bool keyIsPending( const BSONObj& key ) const;
-
- /**
- * Given a key 'lookupKey' in the shard key range, get the next chunk which overlaps or is
- * greater than this key. Returns true if a chunk exists, false otherwise.
- *
- * Passing a key that is not a valid shard key for this range results in undefined behavior.
- */
- bool getNextChunk( const BSONObj& lookupKey, ChunkType* chunk ) const;
-
- /**
- * Given a key in the shard key range, get the next range which overlaps or is greater than
- * this key.
- *
- * This allows us to do the following to iterate over all orphan ranges:
- *
- * KeyRange range;
- * BSONObj lookupKey = metadata->getMinKey();
- * while( metadata->getNextOrphanRange( lookupKey, &orphanRange ) ) {
- * // Do stuff with range
- * lookupKey = orphanRange.maxKey;
- * }
- *
- * @param lookupKey passing a key that does not belong to this metadata is undefined.
- * @param orphanRange the output range. Note that the NS is not set.
- */
- bool getNextOrphanRange( const BSONObj& lookupKey, KeyRange* orphanRange ) const;
-
- //
- // accessors
- //
-
- ChunkVersion getCollVersion() const {
- return _collVersion;
- }
-
- ChunkVersion getShardVersion() const {
- return _shardVersion;
- }
-
- BSONObj getKeyPattern() const {
- return _keyPattern;
- }
-
- const std::vector<FieldRef*>& getKeyPatternFields() const {
- return _keyFields.vector();
- }
-
- BSONObj getMinKey() const;
-
- BSONObj getMaxKey() const;
-
- std::size_t getNumChunks() const {
- return _chunksMap.size();
- }
-
- std::size_t getNumPending() const {
- return _pendingMap.size();
- }
-
- //
- // reporting
- //
-
- /**
- * BSON output of the metadata information.
- */
- BSONObj toBSON() const;
-
- /**
- * BSON output of the metadata information, into a builder.
- */
- void toBSON( BSONObjBuilder& bb ) const;
-
- /**
- * BSON output of the chunks metadata into a BSONArray
- */
- void toBSONChunks( BSONArrayBuilder& bb ) const;
-
- /**
- * BSON output of the pending metadata into a BSONArray
- */
- void toBSONPending( BSONArrayBuilder& bb ) const;
-
- /**
- * std::string output of the metadata information.
- */
- std::string toString() const;
-
- /**
- * Use the MetadataLoader to fill the empty metadata from the config server, or use
- * clone*() methods to use existing metadatas to build new ones.
- *
- * Unless you are the MetadataLoader or a test you should probably not be using this
- * directly.
- */
- CollectionMetadata();
-
- /**
- * TESTING ONLY
- *
- * Returns a new metadata's instance based on 'this's state by adding 'chunk'. The new
- * metadata can never be zero, though (see cloneMinus). The caller owns the new metadata.
- *
- * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was
- * provided.
- */
- CollectionMetadata* clonePlusChunk( const ChunkType& chunk,
- const ChunkVersion& newShardVersion,
- std::string* errMsg ) const;
-
- private:
- // Effectively, the MetadataLoader is this class's builder. So we open an exception
- // and grant it friendship.
- friend class MetadataLoader;
-
- // a version for this collection that identifies the collection incarnation (ie, a
- // dropped and recreated collection with the same name would have a different version)
- ChunkVersion _collVersion;
-
- //
- // sharded state below, for when the collection gets sharded
- //
-
- // highest ChunkVersion for which this metadata's information is accurate
- ChunkVersion _shardVersion;
-
- // key pattern for chunks under this range
- BSONObj _keyPattern;
-
- // A vector owning the FieldRefs parsed from the shard-key pattern of field names.
- OwnedPointerVector<FieldRef> _keyFields;
-
- //
- // RangeMaps represent chunks by mapping the min key to the chunk's max key, allowing
- // efficient lookup and intersection.
- //
-
- // Map of ranges of chunks that are migrating but have not been confirmed added yet
- RangeMap _pendingMap;
-
- // Map of chunks tracked by this shard
- RangeMap _chunksMap;
-
- // A second map from a min key into a range or contiguous chunks. The map is redundant
- // w.r.t. _chunkMap but we expect high chunk contiguity, especially in small
- // installations.
- RangeMap _rangesMap;
-
- /**
- * Returns true if this metadata was loaded with all necessary information.
- */
- bool isValid() const;
-
- /**
- * Try to find chunks that are adjacent and record these intervals in the _rangesMap
- */
- void fillRanges();
-
- /**
- * Creates the _keyField* local data
- */
- void fillKeyPatternFields();
- };
-
-} // namespace mongo
+ bool getNextOrphanRange(const BSONObj& lookupKey, KeyRange* orphanRange) const;
+
+ //
+ // accessors
+ //
+
+ ChunkVersion getCollVersion() const {
+ return _collVersion;
+ }
+
+ ChunkVersion getShardVersion() const {
+ return _shardVersion;
+ }
+
+ BSONObj getKeyPattern() const {
+ return _keyPattern;
+ }
+
+ const std::vector<FieldRef*>& getKeyPatternFields() const {
+ return _keyFields.vector();
+ }
+
+ BSONObj getMinKey() const;
+
+ BSONObj getMaxKey() const;
+
+ std::size_t getNumChunks() const {
+ return _chunksMap.size();
+ }
+
+ std::size_t getNumPending() const {
+ return _pendingMap.size();
+ }
+
+ //
+ // reporting
+ //
+
+ /**
+ * BSON output of the metadata information.
+ */
+ BSONObj toBSON() const;
+
+ /**
+ * BSON output of the metadata information, into a builder.
+ */
+ void toBSON(BSONObjBuilder& bb) const;
+
+ /**
+ * BSON output of the chunks metadata into a BSONArray
+ */
+ void toBSONChunks(BSONArrayBuilder& bb) const;
+
+ /**
+ * BSON output of the pending metadata into a BSONArray
+ */
+ void toBSONPending(BSONArrayBuilder& bb) const;
+
+ /**
+ * std::string output of the metadata information.
+ */
+ std::string toString() const;
+
+ /**
+ * Use the MetadataLoader to fill the empty metadata from the config server, or use
+ * clone*() methods to use existing metadatas to build new ones.
+ *
+ * Unless you are the MetadataLoader or a test you should probably not be using this
+ * directly.
+ */
+ CollectionMetadata();
+
+ /**
+ * TESTING ONLY
+ *
+ * Returns a new metadata's instance based on 'this's state by adding 'chunk'. The new
+ * metadata can never be zero, though (see cloneMinus). The caller owns the new metadata.
+ *
+ * If a new metadata can't be created, returns NULL and fills in 'errMsg', if it was
+ * provided.
+ */
+ CollectionMetadata* clonePlusChunk(const ChunkType& chunk,
+ const ChunkVersion& newShardVersion,
+ std::string* errMsg) const;
+
+private:
+ // Effectively, the MetadataLoader is this class's builder. So we open an exception
+ // and grant it friendship.
+ friend class MetadataLoader;
+
+ // a version for this collection that identifies the collection incarnation (ie, a
+ // dropped and recreated collection with the same name would have a different version)
+ ChunkVersion _collVersion;
+
+ //
+ // sharded state below, for when the collection gets sharded
+ //
+
+ // highest ChunkVersion for which this metadata's information is accurate
+ ChunkVersion _shardVersion;
+
+ // key pattern for chunks under this range
+ BSONObj _keyPattern;
+
+ // A vector owning the FieldRefs parsed from the shard-key pattern of field names.
+ OwnedPointerVector<FieldRef> _keyFields;
+
+ //
+ // RangeMaps represent chunks by mapping the min key to the chunk's max key, allowing
+ // efficient lookup and intersection.
+ //
+
+ // Map of ranges of chunks that are migrating but have not been confirmed added yet
+ RangeMap _pendingMap;
+
+ // Map of chunks tracked by this shard
+ RangeMap _chunksMap;
+
+ // A second map from a min key into a range or contiguous chunks. The map is redundant
+ // w.r.t. _chunkMap but we expect high chunk contiguity, especially in small
+ // installations.
+ RangeMap _rangesMap;
+
+ /**
+ * Returns true if this metadata was loaded with all necessary information.
+ */
+ bool isValid() const;
+
+ /**
+ * Try to find chunks that are adjacent and record these intervals in the _rangesMap
+ */
+ void fillRanges();
+
+ /**
+ * Creates the _keyField* local data
+ */
+ void fillKeyPatternFields();
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/collection_metadata_test.cpp b/src/mongo/s/collection_metadata_test.cpp
index 8774d209e03..05a44fb5c56 100644
--- a/src/mongo/s/collection_metadata_test.cpp
+++ b/src/mongo/s/collection_metadata_test.cpp
@@ -45,1382 +45,1277 @@
namespace {
- using namespace mongo;
-
- using std::make_pair;
- using std::string;
- using std::unique_ptr;
- using std::vector;
-
- const std::string CONFIG_HOST_PORT = "$dummy_config:27017";
-
- class NoChunkFixture : public mongo::unittest::Test {
- protected:
- void setUp() {
- _dummyConfig.reset( new MockRemoteDBServer( CONFIG_HOST_PORT ) );
- mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() );
- MockConnRegistry::get()->addServer( _dummyConfig.get() );
-
- OID epoch = OID::gen();
-
- CollectionType collType;
- collType.setNs(NamespaceString{"test.foo"});
- collType.setKeyPattern( BSON("a" << 1) );
- collType.setUnique( false );
- collType.setUpdatedAt( Date_t::fromMillisSinceEpoch(1) );
- collType.setEpoch( epoch );
- ASSERT_OK(collType.validate());
-
- _dummyConfig->insert( CollectionType::ConfigNS, collType.toBSON() );
-
- // Need a chunk on another shard, otherwise the chunks are invalid in general and we
- // can't load metadata
- ChunkType chunkType;
- chunkType.setNS(NamespaceString{"test.foo"});
- chunkType.setShard( "shard0001" );
- chunkType.setMin( BSON( "a" << MINKEY ) );
- chunkType.setMax( BSON( "a" << MAXKEY ) );
- chunkType.setVersion( ChunkVersion( 1, 0, epoch ) );
- chunkType.setName( OID::gen().toString() );
- ASSERT_OK(collType.validate());
-
- _dummyConfig->insert( ChunkType::ConfigNS, chunkType.toBSON() );
-
- ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT));
- ASSERT(configLoc.isValid());
- CatalogManagerLegacy catalogManager;
- catalogManager.init(configLoc);
-
- MetadataLoader loader;
- Status status = loader.makeCollectionMetadata(&catalogManager,
- "test.foo",
- "shard0000",
- NULL,
- &_metadata);
- ASSERT_OK(status);
- ASSERT_EQUALS( 0u, _metadata.getNumChunks() );
- }
-
- void tearDown() {
- MockConnRegistry::get()->clear();
- }
-
- const CollectionMetadata& getCollMetadata() const {
- return _metadata;
- }
-
- private:
- unique_ptr<MockRemoteDBServer> _dummyConfig;
- CollectionMetadata _metadata;
- };
-
- TEST_F(NoChunkFixture, BasicBelongsToMe) {
- ASSERT_FALSE( getCollMetadata().keyBelongsToMe(BSON("a" << MINKEY)) );
- ASSERT_FALSE( getCollMetadata().keyBelongsToMe(BSON("a" << 10)) );
+using namespace mongo;
+
+using std::make_pair;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+const std::string CONFIG_HOST_PORT = "$dummy_config:27017";
+
+class NoChunkFixture : public mongo::unittest::Test {
+protected:
+ void setUp() {
+ _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT));
+ mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook());
+ MockConnRegistry::get()->addServer(_dummyConfig.get());
+
+ OID epoch = OID::gen();
+
+ CollectionType collType;
+ collType.setNs(NamespaceString{"test.foo"});
+ collType.setKeyPattern(BSON("a" << 1));
+ collType.setUnique(false);
+ collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
+ collType.setEpoch(epoch);
+ ASSERT_OK(collType.validate());
+
+ _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON());
+
+ // Need a chunk on another shard, otherwise the chunks are invalid in general and we
+ // can't load metadata
+ ChunkType chunkType;
+ chunkType.setNS(NamespaceString{"test.foo"});
+ chunkType.setShard("shard0001");
+ chunkType.setMin(BSON("a" << MINKEY));
+ chunkType.setMax(BSON("a" << MAXKEY));
+ chunkType.setVersion(ChunkVersion(1, 0, epoch));
+ chunkType.setName(OID::gen().toString());
+ ASSERT_OK(collType.validate());
+
+ _dummyConfig->insert(ChunkType::ConfigNS, chunkType.toBSON());
+
+ ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT));
+ ASSERT(configLoc.isValid());
+ CatalogManagerLegacy catalogManager;
+ catalogManager.init(configLoc);
+
+ MetadataLoader loader;
+ Status status = loader.makeCollectionMetadata(
+ &catalogManager, "test.foo", "shard0000", NULL, &_metadata);
+ ASSERT_OK(status);
+ ASSERT_EQUALS(0u, _metadata.getNumChunks());
}
- TEST_F(NoChunkFixture, CompoundKeyBelongsToMe) {
- ASSERT_FALSE( getCollMetadata().keyBelongsToMe(BSON("a" << 1 << "b" << 2)) );
+ void tearDown() {
+ MockConnRegistry::get()->clear();
}
- TEST_F(NoChunkFixture, IsKeyValid) {
- ASSERT_TRUE( getCollMetadata().isValidKey( BSON( "a" << "abcde" ) ) );
- ASSERT_TRUE( getCollMetadata().isValidKey( BSON( "a" << 3 ) ) );
- ASSERT_FALSE( getCollMetadata().isValidKey( BSON( "a" << "abcde" << "b" << 1 ) ) );
- ASSERT_FALSE( getCollMetadata().isValidKey( BSON( "c" << "abcde" ) ) );
+ const CollectionMetadata& getCollMetadata() const {
+ return _metadata;
}
- TEST_F(NoChunkFixture, getNextFromEmpty) {
- ChunkType nextChunk;
- ASSERT( !getCollMetadata().getNextChunk( getCollMetadata().getMinKey(), &nextChunk ) );
- }
+private:
+ unique_ptr<MockRemoteDBServer> _dummyConfig;
+ CollectionMetadata _metadata;
+};
+
+TEST_F(NoChunkFixture, BasicBelongsToMe) {
+ ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << MINKEY)));
+ ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 10)));
+}
+
+TEST_F(NoChunkFixture, CompoundKeyBelongsToMe) {
+ ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 1 << "b" << 2)));
+}
+
+TEST_F(NoChunkFixture, IsKeyValid) {
+ ASSERT_TRUE(getCollMetadata().isValidKey(BSON("a"
+ << "abcde")));
+ ASSERT_TRUE(getCollMetadata().isValidKey(BSON("a" << 3)));
+ ASSERT_FALSE(getCollMetadata().isValidKey(BSON("a"
+ << "abcde"
+ << "b" << 1)));
+ ASSERT_FALSE(getCollMetadata().isValidKey(BSON("c"
+ << "abcde")));
+}
+
+TEST_F(NoChunkFixture, getNextFromEmpty) {
+ ChunkType nextChunk;
+ ASSERT(!getCollMetadata().getNextChunk(getCollMetadata().getMinKey(), &nextChunk));
+}
- TEST_F(NoChunkFixture, FirstChunkClonePlus) {
- ChunkType chunk;
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 20) );
-
- string errMsg;
- const ChunkVersion version( 99, 0, OID() );
- unique_ptr<CollectionMetadata> cloned( getCollMetadata().clonePlusChunk( chunk,
- version,
- &errMsg ) );
-
- ASSERT( errMsg.empty() );
- ASSERT_EQUALS( 1u, cloned->getNumChunks() );
- ASSERT_EQUALS( cloned->getShardVersion().toLong(), version.toLong() );
- ASSERT_EQUALS( cloned->getCollVersion().toLong(), version.toLong() );
- ASSERT( cloned->keyBelongsToMe(BSON("a" << 15)) );
- }
+TEST_F(NoChunkFixture, FirstChunkClonePlus) {
+ ChunkType chunk;
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 20));
+
+ string errMsg;
+ const ChunkVersion version(99, 0, OID());
+ unique_ptr<CollectionMetadata> cloned(
+ getCollMetadata().clonePlusChunk(chunk, version, &errMsg));
+
+ ASSERT(errMsg.empty());
+ ASSERT_EQUALS(1u, cloned->getNumChunks());
+ ASSERT_EQUALS(cloned->getShardVersion().toLong(), version.toLong());
+ ASSERT_EQUALS(cloned->getCollVersion().toLong(), version.toLong());
+ ASSERT(cloned->keyBelongsToMe(BSON("a" << 15)));
+}
- TEST_F(NoChunkFixture, MustHaveVersionForFirstChunk) {
- ChunkType chunk;
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 20) );
+TEST_F(NoChunkFixture, MustHaveVersionForFirstChunk) {
+ ChunkType chunk;
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 20));
- string errMsg;
- unique_ptr<CollectionMetadata> cloned( getCollMetadata() // br
- .clonePlusChunk( chunk, ChunkVersion( 0, 0, OID() ), &errMsg ) );
+ string errMsg;
+ unique_ptr<CollectionMetadata> cloned(
+ getCollMetadata() // br
+ .clonePlusChunk(chunk, ChunkVersion(0, 0, OID()), &errMsg));
- ASSERT( cloned == NULL );
- ASSERT_FALSE( errMsg.empty() );
- }
+ ASSERT(cloned == NULL);
+ ASSERT_FALSE(errMsg.empty());
+}
- TEST_F(NoChunkFixture, NoPendingChunks) {
- ASSERT( !getCollMetadata().keyIsPending(BSON( "a" << 15 )) );
- ASSERT( !getCollMetadata().keyIsPending(BSON( "a" << 25 )) );
- }
+TEST_F(NoChunkFixture, NoPendingChunks) {
+ ASSERT(!getCollMetadata().keyIsPending(BSON("a" << 15)));
+ ASSERT(!getCollMetadata().keyIsPending(BSON("a" << 25)));
+}
- TEST_F(NoChunkFixture, FirstPendingChunk) {
+TEST_F(NoChunkFixture, FirstPendingChunk) {
+ string errMsg;
+ ChunkType chunk;
+ unique_ptr<CollectionMetadata> cloned;
- string errMsg;
- ChunkType chunk;
- unique_ptr<CollectionMetadata> cloned;
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 20));
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 20) );
+ cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg));
- cloned.reset( getCollMetadata().clonePlusPending( chunk, &errMsg ) );
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
+ ASSERT(cloned->keyIsPending(BSON("a" << 15)));
+ ASSERT(!cloned->keyIsPending(BSON("a" << 25)));
+ ASSERT(cloned->keyIsPending(BSON("a" << 10)));
+ ASSERT(!cloned->keyIsPending(BSON("a" << 20)));
+}
- ASSERT( cloned->keyIsPending(BSON( "a" << 15 )) );
- ASSERT( !cloned->keyIsPending(BSON( "a" << 25 )) );
- ASSERT( cloned->keyIsPending(BSON( "a" << 10 )) );
- ASSERT( !cloned->keyIsPending(BSON( "a" << 20 )) );
- }
+TEST_F(NoChunkFixture, EmptyMultiPendingChunk) {
+ string errMsg;
+ ChunkType chunk;
+ unique_ptr<CollectionMetadata> cloned;
- TEST_F(NoChunkFixture, EmptyMultiPendingChunk) {
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 20));
- string errMsg;
- ChunkType chunk;
- unique_ptr<CollectionMetadata> cloned;
+ cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg));
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 20) );
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- cloned.reset( getCollMetadata().clonePlusPending( chunk, &errMsg ) );
+ chunk.setMin(BSON("a" << 40));
+ chunk.setMax(BSON("a" << 50));
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
+ cloned.reset(cloned->clonePlusPending(chunk, &errMsg));
- chunk.setMin( BSON("a" << 40) );
- chunk.setMax( BSON("a" << 50) );
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- cloned.reset( cloned->clonePlusPending( chunk, &errMsg ) );
+ ASSERT(cloned->keyIsPending(BSON("a" << 15)));
+ ASSERT(!cloned->keyIsPending(BSON("a" << 25)));
+ ASSERT(cloned->keyIsPending(BSON("a" << 45)));
+ ASSERT(!cloned->keyIsPending(BSON("a" << 55)));
+}
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
+TEST_F(NoChunkFixture, MinusPendingChunk) {
+ string errMsg;
+ ChunkType chunk;
+ unique_ptr<CollectionMetadata> cloned;
- ASSERT( cloned->keyIsPending(BSON( "a" << 15 )) );
- ASSERT( !cloned->keyIsPending(BSON( "a" << 25 )) );
- ASSERT( cloned->keyIsPending(BSON( "a" << 45 )) );
- ASSERT( !cloned->keyIsPending(BSON( "a" << 55 )) );
- }
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 20));
- TEST_F(NoChunkFixture, MinusPendingChunk) {
+ cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg));
- string errMsg;
- ChunkType chunk;
- unique_ptr<CollectionMetadata> cloned;
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 20) );
+ cloned.reset(cloned->cloneMinusPending(chunk, &errMsg));
- cloned.reset( getCollMetadata().clonePlusPending( chunk, &errMsg ) );
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
+ ASSERT(!cloned->keyIsPending(BSON("a" << 15)));
+ ASSERT(!cloned->keyIsPending(BSON("a" << 25)));
+}
- cloned.reset( cloned->cloneMinusPending( chunk, &errMsg ) );
+TEST_F(NoChunkFixture, OverlappingPendingChunk) {
+ string errMsg;
+ ChunkType chunk;
+ unique_ptr<CollectionMetadata> cloned;
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 30));
- ASSERT( !cloned->keyIsPending(BSON( "a" << 15 )) );
- ASSERT( !cloned->keyIsPending(BSON( "a" << 25 )) );
- }
+ cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg));
- TEST_F(NoChunkFixture, OverlappingPendingChunk) {
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- string errMsg;
- ChunkType chunk;
- unique_ptr<CollectionMetadata> cloned;
+ chunk.setMin(BSON("a" << 20));
+ chunk.setMax(BSON("a" << 40));
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 30) );
+ cloned.reset(cloned->clonePlusPending(chunk, &errMsg));
- cloned.reset( getCollMetadata().clonePlusPending( chunk, &errMsg ) );
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
+ ASSERT(!cloned->keyIsPending(BSON("a" << 15)));
+ ASSERT(cloned->keyIsPending(BSON("a" << 25)));
+ ASSERT(cloned->keyIsPending(BSON("a" << 35)));
+ ASSERT(!cloned->keyIsPending(BSON("a" << 45)));
+}
- chunk.setMin( BSON("a" << 20) );
- chunk.setMax( BSON("a" << 40) );
+TEST_F(NoChunkFixture, OverlappingPendingChunks) {
+ string errMsg;
+ ChunkType chunk;
+ unique_ptr<CollectionMetadata> cloned;
- cloned.reset( cloned->clonePlusPending( chunk, &errMsg ) );
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 30));
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
+ cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg));
- ASSERT( !cloned->keyIsPending(BSON( "a" << 15 )) );
- ASSERT( cloned->keyIsPending(BSON( "a" << 25 )) );
- ASSERT( cloned->keyIsPending(BSON( "a" << 35 )) );
- ASSERT( !cloned->keyIsPending(BSON( "a" << 45 )) );
- }
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- TEST_F(NoChunkFixture, OverlappingPendingChunks) {
+ chunk.setMin(BSON("a" << 30));
+ chunk.setMax(BSON("a" << 50));
- string errMsg;
- ChunkType chunk;
- unique_ptr<CollectionMetadata> cloned;
+ cloned.reset(cloned->clonePlusPending(chunk, &errMsg));
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 30) );
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- cloned.reset( getCollMetadata().clonePlusPending( chunk, &errMsg ) );
+ chunk.setMin(BSON("a" << 20));
+ chunk.setMax(BSON("a" << 40));
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
+ cloned.reset(cloned->clonePlusPending(chunk, &errMsg));
- chunk.setMin( BSON("a" << 30) );
- chunk.setMax( BSON("a" << 50) );
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- cloned.reset( cloned->clonePlusPending( chunk, &errMsg ) );
+ ASSERT(!cloned->keyIsPending(BSON("a" << 15)));
+ ASSERT(cloned->keyIsPending(BSON("a" << 25)));
+ ASSERT(cloned->keyIsPending(BSON("a" << 35)));
+ ASSERT(!cloned->keyIsPending(BSON("a" << 45)));
+}
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
+TEST_F(NoChunkFixture, MinusInvalidPendingChunk) {
+ string errMsg;
+ ChunkType chunk;
+ unique_ptr<CollectionMetadata> cloned;
- chunk.setMin( BSON("a" << 20) );
- chunk.setMax( BSON("a" << 40) );
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 30));
- cloned.reset( cloned->clonePlusPending( chunk, &errMsg ) );
+ cloned.reset(getCollMetadata().cloneMinusPending(chunk, &errMsg));
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
+ ASSERT_NOT_EQUALS(errMsg, "");
+ ASSERT(cloned == NULL);
+}
- ASSERT( !cloned->keyIsPending(BSON( "a" << 15 )) );
- ASSERT( cloned->keyIsPending(BSON( "a" << 25 )) );
- ASSERT( cloned->keyIsPending(BSON( "a" << 35 )) );
- ASSERT( !cloned->keyIsPending(BSON( "a" << 45 )) );
- }
+TEST_F(NoChunkFixture, MinusOverlappingPendingChunk) {
+ string errMsg;
+ ChunkType chunk;
+ unique_ptr<CollectionMetadata> cloned;
- TEST_F(NoChunkFixture, MinusInvalidPendingChunk) {
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 30));
- string errMsg;
- ChunkType chunk;
- unique_ptr<CollectionMetadata> cloned;
+ cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg));
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 30) );
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- cloned.reset( getCollMetadata().cloneMinusPending( chunk, &errMsg ) );
+ chunk.setMin(BSON("a" << 15));
+ chunk.setMax(BSON("a" << 35));
- ASSERT_NOT_EQUALS( errMsg, "" );
- ASSERT( cloned == NULL );
- }
+ cloned.reset(cloned->cloneMinusPending(chunk, &errMsg));
- TEST_F(NoChunkFixture, MinusOverlappingPendingChunk) {
+ ASSERT_NOT_EQUALS(errMsg, "");
+ ASSERT(cloned == NULL);
+}
- string errMsg;
- ChunkType chunk;
- unique_ptr<CollectionMetadata> cloned;
+TEST_F(NoChunkFixture, PlusChunkWithPending) {
+ string errMsg;
+ ChunkType chunk;
+ unique_ptr<CollectionMetadata> cloned;
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 30) );
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 20));
- cloned.reset( getCollMetadata().clonePlusPending( chunk, &errMsg ) );
+ cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg));
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- chunk.setMin( BSON("a" << 15) );
- chunk.setMax( BSON("a" << 35) );
+ ASSERT(cloned->keyIsPending(BSON("a" << 15)));
+ ASSERT(!cloned->keyIsPending(BSON("a" << 25)));
- cloned.reset( cloned->cloneMinusPending( chunk, &errMsg ) );
+ chunk.setMin(BSON("a" << 20));
+ chunk.setMax(BSON("a" << 30));
- ASSERT_NOT_EQUALS( errMsg, "" );
- ASSERT( cloned == NULL );
- }
-
- TEST_F(NoChunkFixture, PlusChunkWithPending) {
-
- string errMsg;
- ChunkType chunk;
- unique_ptr<CollectionMetadata> cloned;
+ cloned.reset(cloned->clonePlusChunk(
+ chunk, ChunkVersion(1, 0, cloned->getCollVersion().epoch()), &errMsg));
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 20) );
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- cloned.reset( getCollMetadata().clonePlusPending( chunk, &errMsg ) );
+ ASSERT(cloned->keyIsPending(BSON("a" << 15)));
+ ASSERT(!cloned->keyIsPending(BSON("a" << 25)));
+}
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
+TEST_F(NoChunkFixture, MergeChunkEmpty) {
+ string errMsg;
+ unique_ptr<CollectionMetadata> cloned;
- ASSERT( cloned->keyIsPending(BSON( "a" << 15 )) );
- ASSERT( !cloned->keyIsPending(BSON( "a" << 25 )) );
+ cloned.reset(getCollMetadata().cloneMerge(
+ BSON("a" << 15), BSON("a" << 25), ChunkVersion(1, 0, OID::gen()), &errMsg));
- chunk.setMin( BSON("a" << 20) );
- chunk.setMax( BSON("a" << 30) );
+ ASSERT_NOT_EQUALS(errMsg, "");
+ ASSERT(cloned == NULL);
+}
- cloned.reset( cloned->clonePlusChunk( chunk,
- ChunkVersion( 1,
- 0,
- cloned->getCollVersion().epoch() ),
- &errMsg ) );
+TEST_F(NoChunkFixture, OrphanedDataRangeBegin) {
+ const CollectionMetadata& metadata = getCollMetadata();
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
+ KeyRange keyRange;
+ BSONObj lookupKey = metadata.getMinKey();
+ ASSERT(metadata.getNextOrphanRange(lookupKey, &keyRange));
- ASSERT( cloned->keyIsPending(BSON( "a" << 15 )) );
- ASSERT( !cloned->keyIsPending(BSON( "a" << 25 )) );
- }
+ ASSERT(keyRange.minKey.woCompare(metadata.getMinKey()) == 0);
+ ASSERT(keyRange.maxKey.woCompare(metadata.getMaxKey()) == 0);
- TEST_F(NoChunkFixture, MergeChunkEmpty) {
+ // Make sure we don't have any more ranges
+ ASSERT(!metadata.getNextOrphanRange(keyRange.maxKey, &keyRange));
+}
- string errMsg;
- unique_ptr<CollectionMetadata> cloned;
+TEST_F(NoChunkFixture, OrphanedDataRangeMiddle) {
+ const CollectionMetadata& metadata = getCollMetadata();
- cloned.reset( getCollMetadata().cloneMerge( BSON( "a" << 15 ),
- BSON( "a" << 25 ),
- ChunkVersion( 1, 0, OID::gen() ),
- &errMsg ) );
+ KeyRange keyRange;
+ BSONObj lookupKey = BSON("a" << 20);
+ ASSERT(metadata.getNextOrphanRange(lookupKey, &keyRange));
- ASSERT_NOT_EQUALS( errMsg, "" );
- ASSERT( cloned == NULL );
- }
+ ASSERT(keyRange.minKey.woCompare(metadata.getMinKey()) == 0);
+ ASSERT(keyRange.maxKey.woCompare(metadata.getMaxKey()) == 0);
+ ASSERT(keyRange.keyPattern.woCompare(metadata.getKeyPattern()) == 0);
- TEST_F(NoChunkFixture, OrphanedDataRangeBegin) {
+ // Make sure we don't have any more ranges
+ ASSERT(!metadata.getNextOrphanRange(keyRange.maxKey, &keyRange));
+}
- const CollectionMetadata& metadata = getCollMetadata();
+TEST_F(NoChunkFixture, OrphanedDataRangeEnd) {
+ const CollectionMetadata& metadata = getCollMetadata();
- KeyRange keyRange;
- BSONObj lookupKey = metadata.getMinKey();
- ASSERT( metadata.getNextOrphanRange( lookupKey, &keyRange ) );
+ KeyRange keyRange;
+ ASSERT(!metadata.getNextOrphanRange(metadata.getMaxKey(), &keyRange));
+}
- ASSERT( keyRange.minKey.woCompare( metadata.getMinKey() ) == 0 );
- ASSERT( keyRange.maxKey.woCompare( metadata.getMaxKey() ) == 0 );
+TEST_F(NoChunkFixture, PendingOrphanedDataRanges) {
+ string errMsg;
+ ChunkType chunk;
+ unique_ptr<CollectionMetadata> cloned;
- // Make sure we don't have any more ranges
- ASSERT( !metadata.getNextOrphanRange( keyRange.maxKey, &keyRange ) );
- }
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 20));
- TEST_F(NoChunkFixture, OrphanedDataRangeMiddle) {
+ cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg));
+ ASSERT_EQUALS(errMsg, string(""));
+ ASSERT(cloned != NULL);
- const CollectionMetadata& metadata = getCollMetadata();
+ KeyRange keyRange;
+ ASSERT(cloned->getNextOrphanRange(cloned->getMinKey(), &keyRange));
+ ASSERT(keyRange.minKey.woCompare(cloned->getMinKey()) == 0);
+ ASSERT(keyRange.maxKey.woCompare(BSON("a" << 10)) == 0);
+ ASSERT(keyRange.keyPattern.woCompare(cloned->getKeyPattern()) == 0);
- KeyRange keyRange;
- BSONObj lookupKey = BSON( "a" << 20 );
- ASSERT( metadata.getNextOrphanRange( lookupKey, &keyRange ) );
+ ASSERT(cloned->getNextOrphanRange(keyRange.maxKey, &keyRange));
+ ASSERT(keyRange.minKey.woCompare(BSON("a" << 20)) == 0);
+ ASSERT(keyRange.maxKey.woCompare(cloned->getMaxKey()) == 0);
+ ASSERT(keyRange.keyPattern.woCompare(cloned->getKeyPattern()) == 0);
- ASSERT( keyRange.minKey.woCompare( metadata.getMinKey() ) == 0 );
- ASSERT( keyRange.maxKey.woCompare( metadata.getMaxKey() ) == 0 );
- ASSERT( keyRange.keyPattern.woCompare( metadata.getKeyPattern() ) == 0 );
+ ASSERT(!cloned->getNextOrphanRange(keyRange.maxKey, &keyRange));
+}
- // Make sure we don't have any more ranges
- ASSERT( !metadata.getNextOrphanRange( keyRange.maxKey, &keyRange ) );
+/**
+ * Fixture with single chunk containing:
+ * [10->20)
+ */
+class SingleChunkFixture : public mongo::unittest::Test {
+protected:
+ void setUp() {
+ _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT));
+ mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook());
+ MockConnRegistry::get()->addServer(_dummyConfig.get());
+
+ OID epoch = OID::gen();
+ ChunkVersion chunkVersion = ChunkVersion(1, 0, epoch);
+
+ CollectionType collType;
+ collType.setNs(NamespaceString{"test.foo"});
+ collType.setKeyPattern(BSON("a" << 1));
+ collType.setUnique(false);
+ collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
+ collType.setEpoch(epoch);
+ _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON());
+
+ BSONObj fooSingle = BSON(
+ ChunkType::name("test.foo-a_10")
+ << ChunkType::ns("test.foo") << ChunkType::min(BSON("a" << 10))
+ << ChunkType::max(BSON("a" << 20))
+ << ChunkType::DEPRECATED_lastmod(Date_t::fromMillisSinceEpoch(chunkVersion.toLong()))
+ << ChunkType::DEPRECATED_epoch(epoch) << ChunkType::shard("shard0000"));
+ _dummyConfig->insert(ChunkType::ConfigNS, fooSingle);
+
+ ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT));
+ ASSERT(configLoc.isValid());
+ CatalogManagerLegacy catalogManager;
+ catalogManager.init(configLoc);
+
+ MetadataLoader loader;
+ Status status = loader.makeCollectionMetadata(
+ &catalogManager, "test.foo", "shard0000", NULL, &_metadata);
+ ASSERT_OK(status);
}
- TEST_F(NoChunkFixture, OrphanedDataRangeEnd) {
-
- const CollectionMetadata& metadata = getCollMetadata();
-
- KeyRange keyRange;
- ASSERT( !metadata.getNextOrphanRange( metadata.getMaxKey(), &keyRange ) );
+ void tearDown() {
+ MockConnRegistry::get()->clear();
}
- TEST_F(NoChunkFixture, PendingOrphanedDataRanges) {
-
- string errMsg;
- ChunkType chunk;
- unique_ptr<CollectionMetadata> cloned;
-
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 20) );
-
- cloned.reset( getCollMetadata().clonePlusPending( chunk, &errMsg ) );
- ASSERT_EQUALS( errMsg, string("") );
- ASSERT( cloned != NULL );
-
- KeyRange keyRange;
- ASSERT( cloned->getNextOrphanRange( cloned->getMinKey(), &keyRange ) );
- ASSERT( keyRange.minKey.woCompare( cloned->getMinKey() ) == 0 );
- ASSERT( keyRange.maxKey.woCompare( BSON( "a" << 10 ) ) == 0 );
- ASSERT( keyRange.keyPattern.woCompare( cloned->getKeyPattern() ) == 0 );
-
- ASSERT( cloned->getNextOrphanRange( keyRange.maxKey, &keyRange ) );
- ASSERT( keyRange.minKey.woCompare( BSON( "a" << 20 ) ) == 0 );
- ASSERT( keyRange.maxKey.woCompare( cloned->getMaxKey() ) == 0 );
- ASSERT( keyRange.keyPattern.woCompare( cloned->getKeyPattern() ) == 0 );
-
- ASSERT( !cloned->getNextOrphanRange( keyRange.maxKey, &keyRange ) );
+ const CollectionMetadata& getCollMetadata() const {
+ return _metadata;
}
- /**
- * Fixture with single chunk containing:
- * [10->20)
- */
- class SingleChunkFixture : public mongo::unittest::Test {
- protected:
- void setUp() {
- _dummyConfig.reset( new MockRemoteDBServer( CONFIG_HOST_PORT ) );
- mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() );
- MockConnRegistry::get()->addServer( _dummyConfig.get() );
-
- OID epoch = OID::gen();
- ChunkVersion chunkVersion = ChunkVersion( 1, 0, epoch );
-
- CollectionType collType;
- collType.setNs(NamespaceString{"test.foo"});
- collType.setKeyPattern(BSON("a" << 1));
- collType.setUnique(false);
- collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
- collType.setEpoch(epoch);
- _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON());
-
- BSONObj fooSingle = BSON(ChunkType::name("test.foo-a_10") <<
- ChunkType::ns("test.foo") <<
- ChunkType::min(BSON("a" << 10)) <<
- ChunkType::max(BSON("a" << 20)) <<
- ChunkType::DEPRECATED_lastmod(
- Date_t::fromMillisSinceEpoch(chunkVersion.toLong())) <<
- ChunkType::DEPRECATED_epoch(epoch) <<
- ChunkType::shard("shard0000"));
- _dummyConfig->insert( ChunkType::ConfigNS, fooSingle );
-
- ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT));
- ASSERT(configLoc.isValid());
- CatalogManagerLegacy catalogManager;
- catalogManager.init(configLoc);
-
- MetadataLoader loader;
- Status status = loader.makeCollectionMetadata(&catalogManager,
- "test.foo",
- "shard0000",
- NULL,
- &_metadata);
- ASSERT_OK(status);
- }
+private:
+ unique_ptr<MockRemoteDBServer> _dummyConfig;
+ CollectionMetadata _metadata;
+};
+
+TEST_F(SingleChunkFixture, BasicBelongsToMe) {
+ ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 10)));
+ ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 15)));
+ ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 19)));
+}
+
+TEST_F(SingleChunkFixture, DoesntBelongsToMe) {
+ ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 0)));
+ ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 9)));
+ ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 20)));
+ ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 1234)));
+ ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << MINKEY)));
+ ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << MAXKEY)));
+}
+
+TEST_F(SingleChunkFixture, CompoudKeyBelongsToMe) {
+ ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 15 << "a" << 14)));
+}
+
+TEST_F(SingleChunkFixture, getNextFromEmpty) {
+ ChunkType nextChunk;
+ ASSERT(getCollMetadata().getNextChunk(getCollMetadata().getMinKey(), &nextChunk));
+ ASSERT_EQUALS(0, nextChunk.getMin().woCompare(BSON("a" << 10)));
+ ASSERT_EQUALS(0, nextChunk.getMax().woCompare(BSON("a" << 20)));
+}
+
+TEST_F(SingleChunkFixture, GetLastChunkIsFalse) {
+ ChunkType nextChunk;
+ ASSERT(!getCollMetadata().getNextChunk(getCollMetadata().getMaxKey(), &nextChunk));
+}
+
+TEST_F(SingleChunkFixture, LastChunkCloneMinus) {
+ ChunkType chunk;
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 20));
+
+ string errMsg;
+ const ChunkVersion zeroVersion(0, 0, getCollMetadata().getShardVersion().epoch());
+ unique_ptr<CollectionMetadata> cloned(
+ getCollMetadata().cloneMigrate(chunk, zeroVersion, &errMsg));
+
+ ASSERT(errMsg.empty());
+ ASSERT_EQUALS(0u, cloned->getNumChunks());
+ ASSERT_EQUALS(cloned->getShardVersion().toLong(), zeroVersion.toLong());
+ ASSERT_EQUALS(cloned->getCollVersion().toLong(), getCollMetadata().getCollVersion().toLong());
+ ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 15)));
+}
+
+TEST_F(SingleChunkFixture, LastChunkMinusCantHaveNonZeroVersion) {
+ ChunkType chunk;
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 20));
+
+ string errMsg;
+ ChunkVersion version(99, 0, OID());
+ unique_ptr<CollectionMetadata> cloned(getCollMetadata().cloneMigrate(chunk, version, &errMsg));
+
+ ASSERT(cloned == NULL);
+ ASSERT_FALSE(errMsg.empty());
+}
+
+TEST_F(SingleChunkFixture, PlusPendingChunk) {
+ string errMsg;
+ ChunkType chunk;
+ unique_ptr<CollectionMetadata> cloned;
+
+ chunk.setMin(BSON("a" << 20));
+ chunk.setMax(BSON("a" << 30));
+
+ cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg));
+
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
+
+ ASSERT(cloned->keyBelongsToMe(BSON("a" << 15)));
+ ASSERT(!cloned->keyBelongsToMe(BSON("a" << 25)));
+ ASSERT(!cloned->keyIsPending(BSON("a" << 15)));
+ ASSERT(cloned->keyIsPending(BSON("a" << 25)));
+}
+
+TEST_F(SingleChunkFixture, PlusOverlapPendingChunk) {
+ string errMsg;
+ ChunkType chunk;
+ unique_ptr<CollectionMetadata> cloned;
+
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 20));
+
+ cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg));
+
+ ASSERT_NOT_EQUALS(errMsg, "");
+ ASSERT(cloned == NULL);
+}
+
+TEST_F(SingleChunkFixture, MinusChunkWithPending) {
+ string errMsg;
+ ChunkType chunk;
+ unique_ptr<CollectionMetadata> cloned;
+
+ chunk.setMin(BSON("a" << 20));
+ chunk.setMax(BSON("a" << 30));
+
+ cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg));
+
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
+
+ ASSERT(cloned->keyIsPending(BSON("a" << 25)));
+ ASSERT(!cloned->keyIsPending(BSON("a" << 35)));
- void tearDown() {
- MockConnRegistry::get()->clear();
- }
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 20));
- const CollectionMetadata& getCollMetadata() const {
- return _metadata;
- }
+ cloned.reset(
+ cloned->cloneMigrate(chunk, ChunkVersion(0, 0, cloned->getCollVersion().epoch()), &errMsg));
- private:
- unique_ptr<MockRemoteDBServer> _dummyConfig;
- CollectionMetadata _metadata;
- };
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- TEST_F(SingleChunkFixture, BasicBelongsToMe) {
- ASSERT( getCollMetadata().keyBelongsToMe(BSON("a" << 10)) );
- ASSERT( getCollMetadata().keyBelongsToMe(BSON("a" << 15)) );
- ASSERT( getCollMetadata().keyBelongsToMe(BSON("a" << 19)) );
- }
+ ASSERT(cloned->keyIsPending(BSON("a" << 25)));
+ ASSERT(!cloned->keyIsPending(BSON("a" << 35)));
+}
- TEST_F(SingleChunkFixture, DoesntBelongsToMe) {
- ASSERT_FALSE( getCollMetadata().keyBelongsToMe(BSON("a" << 0)) );
- ASSERT_FALSE( getCollMetadata().keyBelongsToMe(BSON("a" << 9)) );
- ASSERT_FALSE( getCollMetadata().keyBelongsToMe(BSON("a" << 20)) );
- ASSERT_FALSE( getCollMetadata().keyBelongsToMe(BSON("a" << 1234)) );
- ASSERT_FALSE( getCollMetadata().keyBelongsToMe(BSON("a" << MINKEY)) );
- ASSERT_FALSE( getCollMetadata().keyBelongsToMe(BSON("a" << MAXKEY)) );
- }
+TEST_F(SingleChunkFixture, SingleSplit) {
+ ChunkVersion version;
+ getCollMetadata().getCollVersion().cloneTo(&version);
+ version.incMinor();
- TEST_F(SingleChunkFixture, CompoudKeyBelongsToMe) {
- ASSERT( getCollMetadata().keyBelongsToMe(BSON("a" << 15 << "a" << 14)) );
- }
+ ChunkType chunk;
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 20));
- TEST_F(SingleChunkFixture, getNextFromEmpty) {
- ChunkType nextChunk;
- ASSERT( getCollMetadata().getNextChunk( getCollMetadata().getMinKey(), &nextChunk ) );
- ASSERT_EQUALS( 0, nextChunk.getMin().woCompare(BSON("a" << 10)) );
- ASSERT_EQUALS( 0, nextChunk.getMax().woCompare(BSON("a" << 20)) );
- }
+ vector<BSONObj> splitPoints;
+ splitPoints.push_back(BSON("a" << 14));
- TEST_F(SingleChunkFixture, GetLastChunkIsFalse) {
- ChunkType nextChunk;
- ASSERT( !getCollMetadata().getNextChunk( getCollMetadata().getMaxKey(), &nextChunk ) );
- }
+ string errMsg;
+ unique_ptr<CollectionMetadata> cloned(
+ getCollMetadata().cloneSplit(chunk, splitPoints, version, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- TEST_F(SingleChunkFixture, LastChunkCloneMinus) {
- ChunkType chunk;
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 20) );
-
- string errMsg;
- const ChunkVersion zeroVersion( 0, 0, getCollMetadata().getShardVersion().epoch() );
- unique_ptr<CollectionMetadata> cloned( getCollMetadata().cloneMigrate( chunk,
- zeroVersion,
- &errMsg ) );
-
- ASSERT( errMsg.empty() );
- ASSERT_EQUALS( 0u, cloned->getNumChunks() );
- ASSERT_EQUALS( cloned->getShardVersion().toLong(), zeroVersion.toLong() );
- ASSERT_EQUALS( cloned->getCollVersion().toLong(),
- getCollMetadata().getCollVersion().toLong() );
- ASSERT_FALSE( cloned->keyBelongsToMe(BSON("a" << 15)) );
- }
+ ChunkVersion newVersion(cloned->getCollVersion());
+ ASSERT_EQUALS(version.epoch(), newVersion.epoch());
+ ASSERT_EQUALS(version.majorVersion(), newVersion.majorVersion());
+ ASSERT_EQUALS(version.minorVersion() + 1, newVersion.minorVersion());
- TEST_F(SingleChunkFixture, LastChunkMinusCantHaveNonZeroVersion) {
- ChunkType chunk;
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 20) );
+ ASSERT(cloned->getNextChunk(BSON("a" << MINKEY), &chunk));
+ ASSERT(chunk.getMin().woCompare(BSON("a" << 10)) == 0);
+ ASSERT(chunk.getMax().woCompare(BSON("a" << 14)) == 0);
- string errMsg;
- ChunkVersion version( 99, 0, OID() );
- unique_ptr<CollectionMetadata> cloned( getCollMetadata().cloneMigrate( chunk,
- version,
- &errMsg ) );
+ ASSERT(cloned->getNextChunk(BSON("a" << 14), &chunk));
+ ASSERT(chunk.getMin().woCompare(BSON("a" << 14)) == 0);
+ ASSERT(chunk.getMax().woCompare(BSON("a" << 20)) == 0);
- ASSERT( cloned == NULL );
- ASSERT_FALSE( errMsg.empty() );
- }
+ ASSERT_FALSE(cloned->getNextChunk(BSON("a" << 20), &chunk));
+}
- TEST_F(SingleChunkFixture, PlusPendingChunk) {
+TEST_F(SingleChunkFixture, MultiSplit) {
+ string errMsg;
+ ChunkType chunk;
+ unique_ptr<CollectionMetadata> cloned;
- string errMsg;
- ChunkType chunk;
- unique_ptr<CollectionMetadata> cloned;
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 20));
- chunk.setMin( BSON("a" << 20) );
- chunk.setMax( BSON("a" << 30) );
+ vector<BSONObj> splitPoints;
+ splitPoints.push_back(BSON("a" << 14));
+ splitPoints.push_back(BSON("a" << 16));
- cloned.reset( getCollMetadata().clonePlusPending( chunk, &errMsg ) );
+ ChunkVersion version;
+ getCollMetadata().getCollVersion().cloneTo(&version);
+ version.incMinor();
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
+ cloned.reset(getCollMetadata().cloneSplit(chunk, splitPoints, version, &errMsg));
- ASSERT( cloned->keyBelongsToMe(BSON("a" << 15)) );
- ASSERT( !cloned->keyBelongsToMe(BSON("a" << 25)) );
- ASSERT( !cloned->keyIsPending(BSON("a" << 15)) );
- ASSERT( cloned->keyIsPending(BSON("a" << 25)) );
- }
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- TEST_F(SingleChunkFixture, PlusOverlapPendingChunk) {
+ ChunkVersion newVersion(cloned->getCollVersion());
+ ASSERT_EQUALS(version.epoch(), newVersion.epoch());
+ ASSERT_EQUALS(version.majorVersion(), newVersion.majorVersion());
+ ASSERT_EQUALS(version.minorVersion() + 2, newVersion.minorVersion());
- string errMsg;
- ChunkType chunk;
- unique_ptr<CollectionMetadata> cloned;
+ ASSERT(cloned->getNextChunk(BSON("a" << MINKEY), &chunk));
+ ASSERT(chunk.getMin().woCompare(BSON("a" << 10)) == 0);
+ ASSERT(chunk.getMax().woCompare(BSON("a" << 14)) == 0);
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 20) );
+ ASSERT(cloned->getNextChunk(BSON("a" << 14), &chunk));
+ ASSERT(chunk.getMin().woCompare(BSON("a" << 14)) == 0);
+ ASSERT(chunk.getMax().woCompare(BSON("a" << 16)) == 0);
- cloned.reset( getCollMetadata().clonePlusPending( chunk, &errMsg ) );
+ ASSERT(cloned->getNextChunk(BSON("a" << 16), &chunk));
+ ASSERT(chunk.getMin().woCompare(BSON("a" << 16)) == 0);
+ ASSERT(chunk.getMax().woCompare(BSON("a" << 20)) == 0);
- ASSERT_NOT_EQUALS( errMsg, "" );
- ASSERT( cloned == NULL );
- }
+ ASSERT_FALSE(cloned->getNextChunk(BSON("a" << 20), &chunk));
+}
- TEST_F(SingleChunkFixture, MinusChunkWithPending) {
+TEST_F(SingleChunkFixture, SplitChunkWithPending) {
+ string errMsg;
+ ChunkType chunk;
+ unique_ptr<CollectionMetadata> cloned;
- string errMsg;
- ChunkType chunk;
- unique_ptr<CollectionMetadata> cloned;
+ chunk.setMin(BSON("a" << 20));
+ chunk.setMax(BSON("a" << 30));
- chunk.setMin( BSON("a" << 20) );
- chunk.setMax( BSON("a" << 30) );
+ cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg));
- cloned.reset( getCollMetadata().clonePlusPending( chunk, &errMsg ) );
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
+ ASSERT(cloned->keyIsPending(BSON("a" << 25)));
+ ASSERT(!cloned->keyIsPending(BSON("a" << 35)));
- ASSERT( cloned->keyIsPending(BSON( "a" << 25 )) );
- ASSERT( !cloned->keyIsPending(BSON( "a" << 35 )) );
+ chunk.setMin(BSON("a" << 10));
+ chunk.setMax(BSON("a" << 20));
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 20) );
+ vector<BSONObj> splitPoints;
+ splitPoints.push_back(BSON("a" << 14));
+ splitPoints.push_back(BSON("a" << 16));
- cloned.reset( cloned->cloneMigrate( chunk,
- ChunkVersion( 0, 0, cloned->getCollVersion().epoch() ),
- &errMsg ) );
+ cloned.reset(cloned->cloneSplit(chunk,
+ splitPoints,
+ ChunkVersion(cloned->getCollVersion().majorVersion() + 1,
+ 0,
+ cloned->getCollVersion().epoch()),
+ &errMsg));
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
- ASSERT( cloned->keyIsPending(BSON( "a" << 25 )) );
- ASSERT( !cloned->keyIsPending(BSON( "a" << 35 )) );
- }
+ ASSERT(cloned->keyIsPending(BSON("a" << 25)));
+ ASSERT(!cloned->keyIsPending(BSON("a" << 35)));
+}
- TEST_F(SingleChunkFixture, SingleSplit) {
- ChunkVersion version;
- getCollMetadata().getCollVersion().cloneTo(&version);
- version.incMinor();
- ChunkType chunk;
- chunk.setMin(BSON("a" << 10));
- chunk.setMax(BSON("a" << 20));
+TEST_F(SingleChunkFixture, MergeChunkSingle) {
+ string errMsg;
+ unique_ptr<CollectionMetadata> cloned;
- vector<BSONObj> splitPoints;
- splitPoints.push_back(BSON("a" << 14));
+ cloned.reset(getCollMetadata().cloneMerge(
+ BSON("a" << 10), BSON("a" << 20), ChunkVersion(2, 0, OID::gen()), &errMsg));
- string errMsg;
- unique_ptr<CollectionMetadata> cloned(getCollMetadata().cloneSplit(chunk,
- splitPoints,
- version,
- &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT(cloned != NULL);
+ ASSERT_NOT_EQUALS(errMsg, "");
+ ASSERT(cloned == NULL);
+}
- ChunkVersion newVersion( cloned->getCollVersion() );
- ASSERT_EQUALS( version.epoch(), newVersion.epoch() );
- ASSERT_EQUALS( version.majorVersion(), newVersion.majorVersion() );
- ASSERT_EQUALS( version.minorVersion() + 1, newVersion.minorVersion() );
+TEST_F(SingleChunkFixture, ChunkOrphanedDataRanges) {
+ KeyRange keyRange;
+ ASSERT(getCollMetadata().getNextOrphanRange(getCollMetadata().getMinKey(), &keyRange));
+ ASSERT(keyRange.minKey.woCompare(getCollMetadata().getMinKey()) == 0);
+ ASSERT(keyRange.maxKey.woCompare(BSON("a" << 10)) == 0);
+ ASSERT(keyRange.keyPattern.woCompare(getCollMetadata().getKeyPattern()) == 0);
- ASSERT( cloned->getNextChunk(BSON("a" << MINKEY), &chunk) );
- ASSERT( chunk.getMin().woCompare( BSON("a" << 10) ) == 0 );
- ASSERT( chunk.getMax().woCompare( BSON("a" << 14) ) == 0 );
+ ASSERT(getCollMetadata().getNextOrphanRange(keyRange.maxKey, &keyRange));
+ ASSERT(keyRange.minKey.woCompare(BSON("a" << 20)) == 0);
+ ASSERT(keyRange.maxKey.woCompare(getCollMetadata().getMaxKey()) == 0);
+ ASSERT(keyRange.keyPattern.woCompare(getCollMetadata().getKeyPattern()) == 0);
- ASSERT( cloned->getNextChunk(BSON("a" << 14), &chunk) );
- ASSERT( chunk.getMin().woCompare( BSON("a" << 14) ) == 0 );
- ASSERT( chunk.getMax().woCompare( BSON("a" << 20) ) == 0 );
+ ASSERT(!getCollMetadata().getNextOrphanRange(keyRange.maxKey, &keyRange));
+}
- ASSERT_FALSE( cloned->getNextChunk(BSON("a" << 20), &chunk) );
+/**
+ * Fixture with single chunk containing:
+ * [(min, min)->(max, max))
+ */
+class SingleChunkMinMaxCompoundKeyFixture : public mongo::unittest::Test {
+protected:
+ void setUp() {
+ _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT));
+ mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook());
+ MockConnRegistry::get()->addServer(_dummyConfig.get());
+
+ OID epoch = OID::gen();
+ ChunkVersion chunkVersion = ChunkVersion(1, 0, epoch);
+
+ CollectionType collType;
+ collType.setNs(NamespaceString{"test.foo"});
+ collType.setKeyPattern(BSON("a" << 1));
+ collType.setUnique(false);
+ collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
+ collType.setEpoch(epoch);
+ _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON());
+
+ BSONObj fooSingle = BSON(
+ ChunkType::name("test.foo-a_MinKey")
+ << ChunkType::ns("test.foo") << ChunkType::min(BSON("a" << MINKEY << "b" << MINKEY))
+ << ChunkType::max(BSON("a" << MAXKEY << "b" << MAXKEY))
+ << ChunkType::DEPRECATED_lastmod(Date_t::fromMillisSinceEpoch(chunkVersion.toLong()))
+ << ChunkType::DEPRECATED_epoch(epoch) << ChunkType::shard("shard0000"));
+ _dummyConfig->insert(ChunkType::ConfigNS, fooSingle);
+
+ ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT));
+ ASSERT(configLoc.isValid());
+ CatalogManagerLegacy catalogManager;
+ catalogManager.init(configLoc);
+
+ MetadataLoader loader;
+ Status status = loader.makeCollectionMetadata(
+ &catalogManager, "test.foo", "shard0000", NULL, &_metadata);
+ ASSERT_OK(status);
}
- TEST_F(SingleChunkFixture, MultiSplit) {
- string errMsg;
- ChunkType chunk;
- unique_ptr<CollectionMetadata> cloned;
-
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 20) );
-
- vector<BSONObj> splitPoints;
- splitPoints.push_back( BSON("a" << 14) );
- splitPoints.push_back( BSON("a" << 16) );
-
- ChunkVersion version;
- getCollMetadata().getCollVersion().cloneTo( &version );
- version.incMinor();
-
- cloned.reset( getCollMetadata().cloneSplit( chunk,
- splitPoints,
- version,
- &errMsg ) );
-
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
-
- ChunkVersion newVersion( cloned->getCollVersion() );
- ASSERT_EQUALS( version.epoch(), newVersion.epoch() );
- ASSERT_EQUALS( version.majorVersion(), newVersion.majorVersion() );
- ASSERT_EQUALS( version.minorVersion() + 2, newVersion.minorVersion() );
-
- ASSERT( cloned->getNextChunk(BSON("a" << MINKEY), &chunk) );
- ASSERT( chunk.getMin().woCompare( BSON("a" << 10) ) == 0 );
- ASSERT( chunk.getMax().woCompare( BSON("a" << 14) ) == 0 );
-
- ASSERT( cloned->getNextChunk(BSON("a" << 14), &chunk) );
- ASSERT( chunk.getMin().woCompare( BSON("a" << 14) ) == 0 );
- ASSERT( chunk.getMax().woCompare( BSON("a" << 16) ) == 0 );
-
- ASSERT( cloned->getNextChunk(BSON("a" << 16), &chunk) );
- ASSERT( chunk.getMin().woCompare( BSON("a" << 16) ) == 0 );
- ASSERT( chunk.getMax().woCompare( BSON("a" << 20) ) == 0 );
-
- ASSERT_FALSE( cloned->getNextChunk(BSON("a" << 20), &chunk) );
+ void tearDown() {
+ MockConnRegistry::get()->clear();
}
- TEST_F(SingleChunkFixture, SplitChunkWithPending) {
-
- string errMsg;
- ChunkType chunk;
- unique_ptr<CollectionMetadata> cloned;
-
- chunk.setMin( BSON("a" << 20) );
- chunk.setMax( BSON("a" << 30) );
-
- cloned.reset( getCollMetadata().clonePlusPending( chunk, &errMsg ) );
-
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
-
- ASSERT( cloned->keyIsPending(BSON( "a" << 25 )) );
- ASSERT( !cloned->keyIsPending(BSON( "a" << 35 )) );
-
- chunk.setMin( BSON("a" << 10) );
- chunk.setMax( BSON("a" << 20) );
-
- vector<BSONObj> splitPoints;
- splitPoints.push_back( BSON("a" << 14) );
- splitPoints.push_back( BSON("a" << 16) );
-
- cloned.reset( cloned->cloneSplit( chunk,
- splitPoints,
- ChunkVersion( cloned->getCollVersion().majorVersion() + 1,
- 0,
- cloned->getCollVersion().epoch() ),
- &errMsg ) );
-
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
-
- ASSERT( cloned->keyIsPending(BSON( "a" << 25 )) );
- ASSERT( !cloned->keyIsPending(BSON( "a" << 35 )) );
+ const CollectionMetadata& getCollMetadata() const {
+ return _metadata;
}
+private:
+ unique_ptr<MockRemoteDBServer> _dummyConfig;
+ CollectionMetadata _metadata;
+};
- TEST_F(SingleChunkFixture, MergeChunkSingle) {
-
- string errMsg;
- unique_ptr<CollectionMetadata> cloned;
+// Note: no tests for single key belongsToMe because they are not allowed
+// if shard key is compound.
- cloned.reset( getCollMetadata().cloneMerge( BSON( "a" << 10 ),
- BSON( "a" << 20 ),
- ChunkVersion( 2, 0, OID::gen() ),
- &errMsg ) );
+TEST_F(SingleChunkMinMaxCompoundKeyFixture, CompoudKeyBelongsToMe) {
+ ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << MINKEY << "b" << MINKEY)));
+ ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << MAXKEY << "b" << MAXKEY)));
+ ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << MINKEY << "b" << 10)));
+ ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 10 << "b" << 20)));
+}
- ASSERT_NOT_EQUALS( errMsg, "" );
- ASSERT( cloned == NULL );
+/**
+ * Fixture with chunks:
+ * [(10, 0)->(20, 0)), [(30, 0)->(40, 0))
+ */
+class TwoChunksWithGapCompoundKeyFixture : public mongo::unittest::Test {
+protected:
+ void setUp() {
+ _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT));
+ mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook());
+ MockConnRegistry::get()->addServer(_dummyConfig.get());
+
+ OID epoch = OID::gen();
+ ChunkVersion chunkVersion = ChunkVersion(1, 0, epoch);
+
+ CollectionType collType;
+ collType.setNs(NamespaceString{"test.foo"});
+ collType.setKeyPattern(BSON("a" << 1));
+ collType.setUnique(false);
+ collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
+ collType.setEpoch(epoch);
+ _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON());
+
+ _dummyConfig->insert(
+ ChunkType::ConfigNS,
+ BSON(ChunkType::name("test.foo-a_10")
+ << ChunkType::ns("test.foo") << ChunkType::min(BSON("a" << 10 << "b" << 0))
+ << ChunkType::max(BSON("a" << 20 << "b" << 0))
+ << ChunkType::DEPRECATED_lastmod(
+ Date_t::fromMillisSinceEpoch(chunkVersion.toLong()))
+ << ChunkType::DEPRECATED_epoch(epoch) << ChunkType::shard("shard0000")));
+
+ _dummyConfig->insert(
+ ChunkType::ConfigNS,
+ BSON(ChunkType::name("test.foo-a_10")
+ << ChunkType::ns("test.foo") << ChunkType::min(BSON("a" << 30 << "b" << 0))
+ << ChunkType::max(BSON("a" << 40 << "b" << 0))
+ << ChunkType::DEPRECATED_lastmod(
+ Date_t::fromMillisSinceEpoch(chunkVersion.toLong()))
+ << ChunkType::DEPRECATED_epoch(epoch) << ChunkType::shard("shard0000")));
+
+ ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT));
+ ASSERT(configLoc.isValid());
+ CatalogManagerLegacy catalogManager;
+ catalogManager.init(configLoc);
+
+ MetadataLoader loader;
+ Status status = loader.makeCollectionMetadata(
+ &catalogManager, "test.foo", "shard0000", NULL, &_metadata);
+ ASSERT_OK(status);
}
- TEST_F(SingleChunkFixture, ChunkOrphanedDataRanges) {
-
- KeyRange keyRange;
- ASSERT( getCollMetadata().getNextOrphanRange( getCollMetadata().getMinKey(), &keyRange ) );
- ASSERT( keyRange.minKey.woCompare( getCollMetadata().getMinKey() ) == 0 );
- ASSERT( keyRange.maxKey.woCompare( BSON( "a" << 10 ) ) == 0 );
- ASSERT( keyRange.keyPattern.woCompare( getCollMetadata().getKeyPattern() ) == 0 );
-
- ASSERT( getCollMetadata().getNextOrphanRange( keyRange.maxKey, &keyRange ) );
- ASSERT( keyRange.minKey.woCompare( BSON( "a" << 20 ) ) == 0 );
- ASSERT( keyRange.maxKey.woCompare( getCollMetadata().getMaxKey() ) == 0 );
- ASSERT( keyRange.keyPattern.woCompare( getCollMetadata().getKeyPattern() ) == 0 );
-
- ASSERT( !getCollMetadata().getNextOrphanRange( keyRange.maxKey, &keyRange ) );
+ void tearDown() {
+ MockConnRegistry::get()->clear();
}
- /**
- * Fixture with single chunk containing:
- * [(min, min)->(max, max))
- */
- class SingleChunkMinMaxCompoundKeyFixture : public mongo::unittest::Test {
- protected:
- void setUp() {
- _dummyConfig.reset( new MockRemoteDBServer( CONFIG_HOST_PORT ) );
- mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() );
- MockConnRegistry::get()->addServer( _dummyConfig.get() );
-
- OID epoch = OID::gen();
- ChunkVersion chunkVersion = ChunkVersion( 1, 0, epoch );
-
- CollectionType collType;
- collType.setNs(NamespaceString{"test.foo"});
- collType.setKeyPattern(BSON("a" << 1));
- collType.setUnique(false);
- collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
- collType.setEpoch(epoch);
- _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON());
-
- BSONObj fooSingle = BSON(
- ChunkType::name("test.foo-a_MinKey") <<
- ChunkType::ns("test.foo") <<
- ChunkType::min(BSON("a" << MINKEY << "b" << MINKEY)) <<
- ChunkType::max(BSON("a" << MAXKEY << "b" << MAXKEY)) <<
- ChunkType::DEPRECATED_lastmod(Date_t::fromMillisSinceEpoch(
- chunkVersion.toLong())) <<
- ChunkType::DEPRECATED_epoch(epoch) <<
- ChunkType::shard("shard0000"));
- _dummyConfig->insert( ChunkType::ConfigNS, fooSingle );
-
- ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT));
- ASSERT(configLoc.isValid());
- CatalogManagerLegacy catalogManager;
- catalogManager.init(configLoc);
-
- MetadataLoader loader;
- Status status = loader.makeCollectionMetadata(&catalogManager,
- "test.foo",
- "shard0000",
- NULL,
- &_metadata);
- ASSERT_OK(status);
- }
-
- void tearDown() {
- MockConnRegistry::get()->clear();
- }
-
- const CollectionMetadata& getCollMetadata() const {
- return _metadata;
- }
-
- private:
- unique_ptr<MockRemoteDBServer> _dummyConfig;
- CollectionMetadata _metadata;
- };
-
- // Note: no tests for single key belongsToMe because they are not allowed
- // if shard key is compound.
-
- TEST_F(SingleChunkMinMaxCompoundKeyFixture, CompoudKeyBelongsToMe) {
- ASSERT( getCollMetadata().keyBelongsToMe(BSON("a" << MINKEY << "b" << MINKEY)) );
- ASSERT_FALSE( getCollMetadata().keyBelongsToMe(BSON("a" << MAXKEY << "b" << MAXKEY)) );
- ASSERT( getCollMetadata().keyBelongsToMe(BSON("a" << MINKEY << "b" << 10)) );
- ASSERT( getCollMetadata().keyBelongsToMe(BSON("a" << 10 << "b" << 20)) );
+ const CollectionMetadata& getCollMetadata() const {
+ return _metadata;
}
- /**
- * Fixture with chunks:
- * [(10, 0)->(20, 0)), [(30, 0)->(40, 0))
- */
- class TwoChunksWithGapCompoundKeyFixture : public mongo::unittest::Test {
- protected:
- void setUp() {
- _dummyConfig.reset( new MockRemoteDBServer( CONFIG_HOST_PORT ) );
- mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() );
- MockConnRegistry::get()->addServer( _dummyConfig.get() );
+private:
+ unique_ptr<MockRemoteDBServer> _dummyConfig;
+ CollectionMetadata _metadata;
+};
+
+TEST_F(TwoChunksWithGapCompoundKeyFixture, ClonePlusBasic) {
+ ChunkType chunk;
+ chunk.setMin(BSON("a" << 40 << "b" << 0));
+ chunk.setMax(BSON("a" << 50 << "b" << 0));
+
+ string errMsg;
+ ChunkVersion version(1, 0, getCollMetadata().getShardVersion().epoch());
+ unique_ptr<CollectionMetadata> cloned(
+ getCollMetadata().clonePlusChunk(chunk, version, &errMsg));
+
+ ASSERT(errMsg.empty());
+ ASSERT_EQUALS(2u, getCollMetadata().getNumChunks());
+ ASSERT_EQUALS(3u, cloned->getNumChunks());
+
+ // TODO: test maxShardVersion, maxCollVersion
+
+ ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 25 << "b" << 0)));
+ ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 29 << "b" << 0)));
+ ASSERT(cloned->keyBelongsToMe(BSON("a" << 30 << "b" << 0)));
+ ASSERT(cloned->keyBelongsToMe(BSON("a" << 45 << "b" << 0)));
+ ASSERT(cloned->keyBelongsToMe(BSON("a" << 49 << "b" << 0)));
+ ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 50 << "b" << 0)));
+}
+
+TEST_F(TwoChunksWithGapCompoundKeyFixture, ClonePlusOverlappingRange) {
+ ChunkType chunk;
+ chunk.setMin(BSON("a" << 15 << "b" << 0));
+ chunk.setMax(BSON("a" << 25 << "b" << 0));
+
+ string errMsg;
+ unique_ptr<CollectionMetadata> cloned(
+ getCollMetadata().clonePlusChunk(chunk, ChunkVersion(1, 0, OID()), &errMsg));
+ ASSERT(cloned == NULL);
+ ASSERT_FALSE(errMsg.empty());
+ ASSERT_EQUALS(2u, getCollMetadata().getNumChunks());
+}
+
+TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneMinusBasic) {
+ ChunkType chunk;
+ chunk.setMin(BSON("a" << 10 << "b" << 0));
+ chunk.setMax(BSON("a" << 20 << "b" << 0));
+
+ string errMsg;
+ ChunkVersion version(2, 0, OID());
+ unique_ptr<CollectionMetadata> cloned(getCollMetadata().cloneMigrate(chunk, version, &errMsg));
+
+ ASSERT(errMsg.empty());
+ ASSERT_EQUALS(2u, getCollMetadata().getNumChunks());
+ ASSERT_EQUALS(1u, cloned->getNumChunks());
+
+ // TODO: test maxShardVersion, maxCollVersion
+
+ ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 5 << "b" << 0)));
+ ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 15 << "b" << 0)));
+ ASSERT(cloned->keyBelongsToMe(BSON("a" << 30 << "b" << 0)));
+ ASSERT(cloned->keyBelongsToMe(BSON("a" << 35 << "b" << 0)));
+ ASSERT_FALSE(cloned->keyBelongsToMe(BSON("a" << 40 << "b" << 0)));
+}
+
+TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneMinusNonExisting) {
+ ChunkType chunk;
+ chunk.setMin(BSON("a" << 25 << "b" << 0));
+ chunk.setMax(BSON("a" << 28 << "b" << 0));
+
+ string errMsg;
+ unique_ptr<CollectionMetadata> cloned(
+ getCollMetadata().cloneMigrate(chunk, ChunkVersion(1, 0, OID()), &errMsg));
+ ASSERT(cloned == NULL);
+ ASSERT_FALSE(errMsg.empty());
+ ASSERT_EQUALS(2u, getCollMetadata().getNumChunks());
+}
+
+TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneSplitBasic) {
+ const BSONObj min(BSON("a" << 10 << "b" << 0));
+ const BSONObj max(BSON("a" << 20 << "b" << 0));
+
+ ChunkType chunk;
+ chunk.setMin(min);
+ chunk.setMax(max);
+
+ const BSONObj split1(BSON("a" << 15 << "b" << 0));
+ const BSONObj split2(BSON("a" << 18 << "b" << 0));
+ vector<BSONObj> splitKeys;
+ splitKeys.push_back(split1);
+ splitKeys.push_back(split2);
+ ChunkVersion version(1, 99, OID()); // first chunk 1|99 , second 1|100
+
+ string errMsg;
+ unique_ptr<CollectionMetadata> cloned(
+ getCollMetadata().cloneSplit(chunk, splitKeys, version, &errMsg));
+
+ version.incMinor(); /* second chunk 1|100, first split point */
+ version.incMinor(); /* third chunk 1|101, second split point */
+ ASSERT_EQUALS(cloned->getShardVersion().toLong(), version.toLong() /* 1|101 */);
+ ASSERT_EQUALS(cloned->getCollVersion().toLong(), version.toLong());
+ ASSERT_EQUALS(getCollMetadata().getNumChunks(), 2u);
+ ASSERT_EQUALS(cloned->getNumChunks(), 4u);
+ ASSERT(cloned->keyBelongsToMe(min));
+ ASSERT(cloned->keyBelongsToMe(split1));
+ ASSERT(cloned->keyBelongsToMe(split2));
+ ASSERT(!cloned->keyBelongsToMe(max));
+}
+
+TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneSplitOutOfRangeSplitPoint) {
+ ChunkType chunk;
+ chunk.setMin(BSON("a" << 10 << "b" << 0));
+ chunk.setMax(BSON("a" << 20 << "b" << 0));
+
+ vector<BSONObj> splitKeys;
+ splitKeys.push_back(BSON("a" << 5 << "b" << 0));
+
+ string errMsg;
+ unique_ptr<CollectionMetadata> cloned(
+ getCollMetadata().cloneSplit(chunk, splitKeys, ChunkVersion(1, 0, OID()), &errMsg));
+
+ ASSERT(cloned == NULL);
+ ASSERT_FALSE(errMsg.empty());
+ ASSERT_EQUALS(2u, getCollMetadata().getNumChunks());
+}
+
+TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneSplitBadChunkRange) {
+ const BSONObj min(BSON("a" << 10 << "b" << 0));
+ const BSONObj max(BSON("a" << 25 << "b" << 0));
+
+ ChunkType chunk;
+ chunk.setMin(BSON("a" << 10 << "b" << 0));
+ chunk.setMax(BSON("a" << 25 << "b" << 0));
+
+ vector<BSONObj> splitKeys;
+ splitKeys.push_back(BSON("a" << 15 << "b" << 0));
+
+ string errMsg;
+ unique_ptr<CollectionMetadata> cloned(
+ getCollMetadata().cloneSplit(chunk, splitKeys, ChunkVersion(1, 0, OID()), &errMsg));
+
+ ASSERT(cloned == NULL);
+ ASSERT_FALSE(errMsg.empty());
+ ASSERT_EQUALS(2u, getCollMetadata().getNumChunks());
+}
+
+TEST_F(TwoChunksWithGapCompoundKeyFixture, ChunkGapOrphanedDataRanges) {
+ KeyRange keyRange;
+ ASSERT(getCollMetadata().getNextOrphanRange(getCollMetadata().getMinKey(), &keyRange));
+ ASSERT(keyRange.minKey.woCompare(getCollMetadata().getMinKey()) == 0);
+ ASSERT(keyRange.maxKey.woCompare(BSON("a" << 10 << "b" << 0)) == 0);
+ ASSERT(keyRange.keyPattern.woCompare(getCollMetadata().getKeyPattern()) == 0);
+
+ ASSERT(getCollMetadata().getNextOrphanRange(keyRange.maxKey, &keyRange));
+ ASSERT(keyRange.minKey.woCompare(BSON("a" << 20 << "b" << 0)) == 0);
+ ASSERT(keyRange.maxKey.woCompare(BSON("a" << 30 << "b" << 0)) == 0);
+ ASSERT(keyRange.keyPattern.woCompare(getCollMetadata().getKeyPattern()) == 0);
+
+ ASSERT(getCollMetadata().getNextOrphanRange(keyRange.maxKey, &keyRange));
+ ASSERT(keyRange.minKey.woCompare(BSON("a" << 40 << "b" << 0)) == 0);
+ ASSERT(keyRange.maxKey.woCompare(getCollMetadata().getMaxKey()) == 0);
+ ASSERT(keyRange.keyPattern.woCompare(getCollMetadata().getKeyPattern()) == 0);
+
+ ASSERT(!getCollMetadata().getNextOrphanRange(keyRange.maxKey, &keyRange));
+}
+
+TEST_F(TwoChunksWithGapCompoundKeyFixture, ChunkGapAndPendingOrphanedDataRanges) {
+ string errMsg;
+ ChunkType chunk;
+ unique_ptr<CollectionMetadata> cloned;
+
+ chunk.setMin(BSON("a" << 20 << "b" << 0));
+ chunk.setMax(BSON("a" << 30 << "b" << 0));
+
+ cloned.reset(getCollMetadata().clonePlusPending(chunk, &errMsg));
+ ASSERT_EQUALS(errMsg, string(""));
+ ASSERT(cloned != NULL);
+
+ KeyRange keyRange;
+ ASSERT(cloned->getNextOrphanRange(cloned->getMinKey(), &keyRange));
+ ASSERT(keyRange.minKey.woCompare(cloned->getMinKey()) == 0);
+ ASSERT(keyRange.maxKey.woCompare(BSON("a" << 10 << "b" << 0)) == 0);
+ ASSERT(keyRange.keyPattern.woCompare(cloned->getKeyPattern()) == 0);
+
+ ASSERT(cloned->getNextOrphanRange(keyRange.maxKey, &keyRange));
+ ASSERT(keyRange.minKey.woCompare(BSON("a" << 40 << "b" << 0)) == 0);
+ ASSERT(keyRange.maxKey.woCompare(cloned->getMaxKey()) == 0);
+ ASSERT(keyRange.keyPattern.woCompare(cloned->getKeyPattern()) == 0);
+
+ ASSERT(!cloned->getNextOrphanRange(keyRange.maxKey, &keyRange));
+}
- OID epoch = OID::gen();
- ChunkVersion chunkVersion = ChunkVersion( 1, 0, epoch );
+/**
+ * Fixture with chunk containing:
+ * [min->10) , [10->20) , <gap> , [30->max)
+ */
+class ThreeChunkWithRangeGapFixture : public mongo::unittest::Test {
+protected:
+ void setUp() {
+ _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT));
+ mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook());
+ MockConnRegistry::get()->addServer(_dummyConfig.get());
+ OID epoch(OID::gen());
+
+ {
CollectionType collType;
- collType.setNs(NamespaceString{"test.foo"});
+ collType.setNs(NamespaceString{"x.y"});
collType.setKeyPattern(BSON("a" << 1));
collType.setUnique(false);
collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
collType.setEpoch(epoch);
_dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON());
-
- _dummyConfig->insert(
- ChunkType::ConfigNS,
- BSON(ChunkType::name("test.foo-a_10") <<
- ChunkType::ns("test.foo") <<
- ChunkType::min(BSON("a" << 10 << "b" << 0)) <<
- ChunkType::max(BSON("a" << 20 << "b" << 0)) <<
- ChunkType::DEPRECATED_lastmod(Date_t::fromMillisSinceEpoch(
- chunkVersion.toLong())) <<
- ChunkType::DEPRECATED_epoch(epoch) <<
- ChunkType::shard("shard0000")) );
-
- _dummyConfig->insert(
- ChunkType::ConfigNS,
- BSON(ChunkType::name("test.foo-a_10") <<
- ChunkType::ns("test.foo") <<
- ChunkType::min(BSON("a" << 30 << "b" << 0)) <<
- ChunkType::max(BSON("a" << 40 << "b" << 0)) <<
- ChunkType::DEPRECATED_lastmod(Date_t::fromMillisSinceEpoch(
- chunkVersion.toLong())) <<
- ChunkType::DEPRECATED_epoch(epoch) <<
- ChunkType::shard("shard0000")) );
-
- ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT));
- ASSERT(configLoc.isValid());
- CatalogManagerLegacy catalogManager;
- catalogManager.init(configLoc);
-
- MetadataLoader loader;
- Status status = loader.makeCollectionMetadata(&catalogManager,
- "test.foo",
- "shard0000",
- NULL,
- &_metadata);
- ASSERT_OK(status);
}
- void tearDown() {
- MockConnRegistry::get()->clear();
- }
-
- const CollectionMetadata& getCollMetadata() const {
- return _metadata;
- }
-
- private:
- unique_ptr<MockRemoteDBServer> _dummyConfig;
- CollectionMetadata _metadata;
- };
-
- TEST_F(TwoChunksWithGapCompoundKeyFixture, ClonePlusBasic) {
- ChunkType chunk;
- chunk.setMin( BSON("a" << 40 << "b" << 0) );
- chunk.setMax( BSON("a" << 50 << "b" << 0) );
-
- string errMsg;
- ChunkVersion version( 1, 0, getCollMetadata().getShardVersion().epoch() );
- unique_ptr<CollectionMetadata> cloned( getCollMetadata().clonePlusChunk( chunk,
- version,
- &errMsg ) );
-
- ASSERT( errMsg.empty() );
- ASSERT_EQUALS( 2u, getCollMetadata().getNumChunks() );
- ASSERT_EQUALS( 3u, cloned->getNumChunks() );
-
- // TODO: test maxShardVersion, maxCollVersion
-
- ASSERT_FALSE( cloned->keyBelongsToMe(BSON("a" << 25 << "b" << 0)) );
- ASSERT_FALSE( cloned->keyBelongsToMe(BSON("a" << 29 << "b" << 0)) );
- ASSERT( cloned->keyBelongsToMe(BSON("a" << 30 << "b" << 0)) );
- ASSERT( cloned->keyBelongsToMe(BSON("a" << 45 << "b" << 0)) );
- ASSERT( cloned->keyBelongsToMe(BSON("a" << 49 << "b" << 0)) );
- ASSERT_FALSE( cloned->keyBelongsToMe(BSON("a" << 50 << "b" << 0)) );
- }
-
- TEST_F(TwoChunksWithGapCompoundKeyFixture, ClonePlusOverlappingRange) {
- ChunkType chunk;
- chunk.setMin( BSON("a" << 15 << "b" << 0) );
- chunk.setMax( BSON("a" << 25 << "b" << 0) );
-
- string errMsg;
- unique_ptr<CollectionMetadata> cloned( getCollMetadata().clonePlusChunk( chunk,
- ChunkVersion( 1,
- 0,
- OID() ),
- &errMsg ) );
- ASSERT( cloned == NULL );
- ASSERT_FALSE( errMsg.empty() );
- ASSERT_EQUALS( 2u, getCollMetadata().getNumChunks() );
- }
-
- TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneMinusBasic) {
- ChunkType chunk;
- chunk.setMin( BSON("a" << 10 << "b" << 0) );
- chunk.setMax( BSON("a" << 20 << "b" << 0) );
-
- string errMsg;
- ChunkVersion version( 2, 0, OID() );
- unique_ptr<CollectionMetadata> cloned( getCollMetadata().cloneMigrate( chunk,
- version,
- &errMsg ) );
-
- ASSERT( errMsg.empty() );
- ASSERT_EQUALS( 2u, getCollMetadata().getNumChunks() );
- ASSERT_EQUALS( 1u, cloned->getNumChunks() );
-
- // TODO: test maxShardVersion, maxCollVersion
-
- ASSERT_FALSE( cloned->keyBelongsToMe(BSON("a" << 5 << "b" << 0)) );
- ASSERT_FALSE( cloned->keyBelongsToMe(BSON("a" << 15 << "b" << 0)) );
- ASSERT( cloned->keyBelongsToMe(BSON("a" << 30 << "b" << 0)) );
- ASSERT( cloned->keyBelongsToMe(BSON("a" << 35 << "b" << 0)) );
- ASSERT_FALSE( cloned->keyBelongsToMe(BSON("a" << 40 << "b" << 0)) );
- }
-
- TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneMinusNonExisting) {
- ChunkType chunk;
- chunk.setMin( BSON("a" << 25 << "b" << 0) );
- chunk.setMax( BSON("a" << 28 << "b" << 0) );
-
- string errMsg;
- unique_ptr<CollectionMetadata> cloned( getCollMetadata().cloneMigrate( chunk,
- ChunkVersion( 1,
- 0,
- OID() ),
- &errMsg ) );
- ASSERT( cloned == NULL );
- ASSERT_FALSE( errMsg.empty() );
- ASSERT_EQUALS( 2u, getCollMetadata().getNumChunks() );
- }
-
- TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneSplitBasic) {
- const BSONObj min( BSON("a" << 10 << "b" << 0) );
- const BSONObj max( BSON("a" << 20 << "b" << 0) );
-
- ChunkType chunk;
- chunk.setMin( min );
- chunk.setMax( max );
-
- const BSONObj split1( BSON("a" << 15 << "b" << 0) );
- const BSONObj split2( BSON("a" << 18 << "b" << 0) );
- vector<BSONObj> splitKeys;
- splitKeys.push_back( split1 );
- splitKeys.push_back( split2 );
- ChunkVersion version( 1, 99, OID() ); // first chunk 1|99 , second 1|100
-
- string errMsg;
- unique_ptr<CollectionMetadata> cloned( getCollMetadata().cloneSplit( chunk,
- splitKeys,
- version,
- &errMsg ) );
-
- version.incMinor(); /* second chunk 1|100, first split point */
- version.incMinor(); /* third chunk 1|101, second split point */
- ASSERT_EQUALS( cloned->getShardVersion().toLong(), version.toLong() /* 1|101 */);
- ASSERT_EQUALS( cloned->getCollVersion().toLong(), version.toLong() );
- ASSERT_EQUALS( getCollMetadata().getNumChunks(), 2u );
- ASSERT_EQUALS( cloned->getNumChunks(), 4u );
- ASSERT( cloned->keyBelongsToMe( min ) );
- ASSERT( cloned->keyBelongsToMe( split1 ) );
- ASSERT( cloned->keyBelongsToMe( split2 ) );
- ASSERT( !cloned->keyBelongsToMe( max ) );
- }
-
- TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneSplitOutOfRangeSplitPoint) {
- ChunkType chunk;
- chunk.setMin( BSON("a" << 10 << "b" << 0) );
- chunk.setMax( BSON("a" << 20 << "b" << 0) );
-
- vector<BSONObj> splitKeys;
- splitKeys.push_back( BSON("a" << 5 << "b" << 0) );
-
- string errMsg;
- unique_ptr<CollectionMetadata> cloned( getCollMetadata().cloneSplit( chunk,
- splitKeys,
- ChunkVersion( 1,
- 0,
- OID() ),
- &errMsg ) );
-
- ASSERT( cloned == NULL );
- ASSERT_FALSE( errMsg.empty() );
- ASSERT_EQUALS( 2u, getCollMetadata().getNumChunks() );
- }
-
- TEST_F(TwoChunksWithGapCompoundKeyFixture, CloneSplitBadChunkRange) {
- const BSONObj min( BSON("a" << 10 << "b" << 0) );
- const BSONObj max( BSON("a" << 25 << "b" << 0) );
-
- ChunkType chunk;
- chunk.setMin( BSON("a" << 10 << "b" << 0) );
- chunk.setMax( BSON("a" << 25 << "b" << 0) );
-
- vector<BSONObj> splitKeys;
- splitKeys.push_back( BSON("a" << 15 << "b" << 0) );
-
- string errMsg;
- unique_ptr<CollectionMetadata> cloned( getCollMetadata().cloneSplit( chunk,
- splitKeys,
- ChunkVersion( 1,
- 0,
- OID() ),
- &errMsg ) );
-
- ASSERT( cloned == NULL );
- ASSERT_FALSE( errMsg.empty() );
- ASSERT_EQUALS( 2u, getCollMetadata().getNumChunks() );
- }
-
- TEST_F(TwoChunksWithGapCompoundKeyFixture, ChunkGapOrphanedDataRanges) {
-
- KeyRange keyRange;
- ASSERT( getCollMetadata().getNextOrphanRange( getCollMetadata().getMinKey(), &keyRange ) );
- ASSERT( keyRange.minKey.woCompare( getCollMetadata().getMinKey() ) == 0 );
- ASSERT( keyRange.maxKey.woCompare( BSON( "a" << 10 << "b" << 0 ) ) == 0 );
- ASSERT( keyRange.keyPattern.woCompare( getCollMetadata().getKeyPattern() ) == 0 );
-
- ASSERT( getCollMetadata().getNextOrphanRange( keyRange.maxKey, &keyRange ) );
- ASSERT( keyRange.minKey.woCompare( BSON( "a" << 20 << "b" << 0 ) ) == 0 );
- ASSERT( keyRange.maxKey.woCompare( BSON( "a" << 30 << "b" << 0 ) ) == 0 );
- ASSERT( keyRange.keyPattern.woCompare( getCollMetadata().getKeyPattern() ) == 0 );
-
- ASSERT( getCollMetadata().getNextOrphanRange( keyRange.maxKey, &keyRange ) );
- ASSERT( keyRange.minKey.woCompare( BSON( "a" << 40 << "b" << 0 ) ) == 0 );
- ASSERT( keyRange.maxKey.woCompare( getCollMetadata().getMaxKey() ) == 0 );
- ASSERT( keyRange.keyPattern.woCompare( getCollMetadata().getKeyPattern() ) == 0 );
-
- ASSERT( !getCollMetadata().getNextOrphanRange( keyRange.maxKey, &keyRange ) );
- }
-
- TEST_F(TwoChunksWithGapCompoundKeyFixture, ChunkGapAndPendingOrphanedDataRanges) {
-
- string errMsg;
- ChunkType chunk;
- unique_ptr<CollectionMetadata> cloned;
-
- chunk.setMin( BSON( "a" << 20 << "b" << 0 ) );
- chunk.setMax( BSON( "a" << 30 << "b" << 0 ) );
-
- cloned.reset( getCollMetadata().clonePlusPending( chunk, &errMsg ) );
- ASSERT_EQUALS( errMsg, string("") );
- ASSERT( cloned != NULL );
-
- KeyRange keyRange;
- ASSERT( cloned->getNextOrphanRange( cloned->getMinKey(), &keyRange ) );
- ASSERT( keyRange.minKey.woCompare( cloned->getMinKey() ) == 0 );
- ASSERT( keyRange.maxKey.woCompare( BSON( "a" << 10 << "b" << 0 ) ) == 0 );
- ASSERT( keyRange.keyPattern.woCompare( cloned->getKeyPattern() ) == 0 );
-
- ASSERT( cloned->getNextOrphanRange( keyRange.maxKey, &keyRange ) );
- ASSERT( keyRange.minKey.woCompare( BSON( "a" << 40 << "b" << 0 ) ) == 0 );
- ASSERT( keyRange.maxKey.woCompare( cloned->getMaxKey() ) == 0 );
- ASSERT( keyRange.keyPattern.woCompare( cloned->getKeyPattern() ) == 0 );
-
- ASSERT( !cloned->getNextOrphanRange( keyRange.maxKey, &keyRange ) );
- }
-
- /**
- * Fixture with chunk containing:
- * [min->10) , [10->20) , <gap> , [30->max)
- */
- class ThreeChunkWithRangeGapFixture : public mongo::unittest::Test {
- protected:
- void setUp() {
- _dummyConfig.reset( new MockRemoteDBServer( CONFIG_HOST_PORT ) );
- mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() );
- MockConnRegistry::get()->addServer( _dummyConfig.get() );
-
- OID epoch(OID::gen());
-
- {
- CollectionType collType;
- collType.setNs(NamespaceString{"x.y"});
- collType.setKeyPattern(BSON("a" << 1));
- collType.setUnique(false);
- collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
- collType.setEpoch(epoch);
- _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON());
- }
-
- {
- ChunkVersion version( 1, 1, epoch );
- _dummyConfig->insert( ChunkType::ConfigNS, BSON(ChunkType::name("x.y-a_MinKey") <<
- ChunkType::ns("x.y") <<
- ChunkType::min(BSON("a" << MINKEY)) <<
- ChunkType::max(BSON("a" << 10)) <<
- ChunkType::DEPRECATED_lastmod(
- Date_t::fromMillisSinceEpoch(version.toLong())) <<
- ChunkType::DEPRECATED_epoch(version.epoch()) <<
- ChunkType::shard("shard0000")) );
- }
-
- {
- ChunkVersion version( 1, 3, epoch );
- _dummyConfig->insert( ChunkType::ConfigNS, BSON(ChunkType::name("x.y-a_10") <<
- ChunkType::ns("x.y") <<
- ChunkType::min(BSON("a" << 10)) <<
- ChunkType::max(BSON("a" << 20)) <<
- ChunkType::DEPRECATED_lastmod(
- Date_t::fromMillisSinceEpoch(version.toLong())) <<
- ChunkType::DEPRECATED_epoch(version.epoch()) <<
- ChunkType::shard("shard0000")) );
- }
-
- {
- ChunkVersion version( 1, 2, epoch );
- _dummyConfig->insert( ChunkType::ConfigNS, BSON(ChunkType::name("x.y-a_30") <<
- ChunkType::ns("x.y") <<
- ChunkType::min(BSON("a" << 30)) <<
- ChunkType::max(BSON("a" << MAXKEY)) <<
- ChunkType::DEPRECATED_lastmod(
- Date_t::fromMillisSinceEpoch(version.toLong())) <<
- ChunkType::DEPRECATED_epoch(version.epoch()) <<
- ChunkType::shard("shard0000")) );
- }
-
- ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT));
- ASSERT(configLoc.isValid());
- CatalogManagerLegacy catalogManager;
- catalogManager.init(configLoc);
-
- MetadataLoader loader;
- Status status = loader.makeCollectionMetadata(&catalogManager,
- "test.foo",
- "shard0000",
- NULL,
- &_metadata);
- ASSERT_OK(status);
+ {
+ ChunkVersion version(1, 1, epoch);
+ _dummyConfig->insert(ChunkType::ConfigNS,
+ BSON(ChunkType::name("x.y-a_MinKey")
+ << ChunkType::ns("x.y") << ChunkType::min(BSON("a" << MINKEY))
+ << ChunkType::max(BSON("a" << 10))
+ << ChunkType::DEPRECATED_lastmod(
+ Date_t::fromMillisSinceEpoch(version.toLong()))
+ << ChunkType::DEPRECATED_epoch(version.epoch())
+ << ChunkType::shard("shard0000")));
}
- void tearDown() {
- MockConnRegistry::get()->clear();
+ {
+ ChunkVersion version(1, 3, epoch);
+ _dummyConfig->insert(ChunkType::ConfigNS,
+ BSON(ChunkType::name("x.y-a_10")
+ << ChunkType::ns("x.y") << ChunkType::min(BSON("a" << 10))
+ << ChunkType::max(BSON("a" << 20))
+ << ChunkType::DEPRECATED_lastmod(
+ Date_t::fromMillisSinceEpoch(version.toLong()))
+ << ChunkType::DEPRECATED_epoch(version.epoch())
+ << ChunkType::shard("shard0000")));
}
- const CollectionMetadata& getCollMetadata() const {
- return _metadata;
+ {
+ ChunkVersion version(1, 2, epoch);
+ _dummyConfig->insert(ChunkType::ConfigNS,
+ BSON(ChunkType::name("x.y-a_30")
+ << ChunkType::ns("x.y") << ChunkType::min(BSON("a" << 30))
+ << ChunkType::max(BSON("a" << MAXKEY))
+ << ChunkType::DEPRECATED_lastmod(
+ Date_t::fromMillisSinceEpoch(version.toLong()))
+ << ChunkType::DEPRECATED_epoch(version.epoch())
+ << ChunkType::shard("shard0000")));
}
- private:
- unique_ptr<MockRemoteDBServer> _dummyConfig;
- CollectionMetadata _metadata;
- };
+ ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT));
+ ASSERT(configLoc.isValid());
+ CatalogManagerLegacy catalogManager;
+ catalogManager.init(configLoc);
- TEST_F(ThreeChunkWithRangeGapFixture, ShardOwnsDoc) {
- ASSERT( getCollMetadata().keyBelongsToMe(BSON("a" << 5)) );
- ASSERT( getCollMetadata().keyBelongsToMe(BSON("a" << 10)) );
- ASSERT( getCollMetadata().keyBelongsToMe(BSON("a" << 30)) );
- ASSERT( getCollMetadata().keyBelongsToMe(BSON("a" << 40)) );
+ MetadataLoader loader;
+ Status status = loader.makeCollectionMetadata(
+ &catalogManager, "test.foo", "shard0000", NULL, &_metadata);
+ ASSERT_OK(status);
}
- TEST_F(ThreeChunkWithRangeGapFixture, ShardDoesntOwnDoc) {
- ASSERT_FALSE( getCollMetadata().keyBelongsToMe(BSON("a" << 25)) );
- ASSERT_FALSE( getCollMetadata().keyBelongsToMe(BSON("a" << MAXKEY)) );
+ void tearDown() {
+ MockConnRegistry::get()->clear();
}
- TEST_F(ThreeChunkWithRangeGapFixture, GetNextFromEmpty) {
- ChunkType nextChunk;
- ASSERT( getCollMetadata().getNextChunk( getCollMetadata().getMinKey(), &nextChunk ) );
- ASSERT_EQUALS( 0, nextChunk.getMin().woCompare(BSON("a" << MINKEY)) );
- ASSERT_EQUALS( 0, nextChunk.getMax().woCompare(BSON("a" << 10)) );
- }
-
- TEST_F(ThreeChunkWithRangeGapFixture, GetNextFromMiddle) {
- ChunkType nextChunk;
- ASSERT( getCollMetadata().getNextChunk(BSON("a" << 20), &nextChunk) );
- ASSERT_EQUALS( 0, nextChunk.getMin().woCompare(BSON("a" << 30)) );
- ASSERT_EQUALS( 0, nextChunk.getMax().woCompare(BSON("a" << MAXKEY)) );
- }
-
- TEST_F(ThreeChunkWithRangeGapFixture, GetNextFromLast) {
- ChunkType nextChunk;
- ASSERT( getCollMetadata().getNextChunk(BSON("a" << 30), &nextChunk) );
- }
-
- TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkHoleInRange) {
-
- string errMsg;
- unique_ptr<CollectionMetadata> cloned;
-
- // Try to merge with hole in range
- ChunkVersion newShardVersion( 5, 0, getCollMetadata().getShardVersion().epoch() );
- cloned.reset( getCollMetadata().cloneMerge( BSON( "a" << 10 ),
- BSON( "a" << MAXKEY ),
- newShardVersion,
- &errMsg ) );
-
- ASSERT_NOT_EQUALS( errMsg, "" );
- ASSERT( cloned == NULL );
- }
-
- TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkDiffEndKey) {
-
- string errMsg;
- unique_ptr<CollectionMetadata> cloned;
-
- // Try to merge with different end key
- ChunkVersion newShardVersion( 5, 0, getCollMetadata().getShardVersion().epoch() );
- cloned.reset( getCollMetadata().cloneMerge( BSON( "a" << MINKEY ),
- BSON( "a" << 19 ),
- newShardVersion,
- &errMsg ) );
-
- ASSERT_NOT_EQUALS( errMsg, "" );
- ASSERT( cloned == NULL );
- }
-
- TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkMinKey) {
-
- string errMsg;
- unique_ptr<CollectionMetadata> cloned;
-
- ASSERT_EQUALS( getCollMetadata().getNumChunks(), 3u );
-
- // Try to merge lowest chunks together
- ChunkVersion newShardVersion( 5, 0, getCollMetadata().getShardVersion().epoch() );
- cloned.reset( getCollMetadata().cloneMerge( BSON( "a" << MINKEY ),
- BSON( "a" << 20 ),
- newShardVersion,
- &errMsg ) );
-
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
- ASSERT( cloned->keyBelongsToMe( BSON( "a" << 10 ) ) );
- ASSERT_EQUALS( cloned->getNumChunks(), 2u );
- ASSERT_EQUALS( cloned->getShardVersion().majorVersion(), 5 );
- }
-
- TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkMaxKey) {
-
- string errMsg;
- unique_ptr<CollectionMetadata> cloned;
- ChunkVersion newShardVersion( 5, 0, getCollMetadata().getShardVersion().epoch() );
-
- // Add one chunk to complete the range
- ChunkType chunk;
- chunk.setMin( BSON( "a" << 20 ) );
- chunk.setMax( BSON( "a" << 30 ) );
- cloned.reset( getCollMetadata().clonePlusChunk( chunk,
- newShardVersion,
- &errMsg ) );
- ASSERT_EQUALS( errMsg, "" );
- ASSERT_EQUALS( cloned->getNumChunks(), 4u );
- ASSERT( cloned != NULL );
-
- // Try to merge highest chunks together
- newShardVersion.incMajor();
- cloned.reset( cloned->cloneMerge( BSON( "a" << 20 ),
- BSON( "a" << MAXKEY ),
- newShardVersion,
- &errMsg ) );
-
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
- ASSERT( cloned->keyBelongsToMe( BSON( "a" << 30 ) ) );
- ASSERT_EQUALS( cloned->getNumChunks(), 3u );
- ASSERT_EQUALS( cloned->getShardVersion().majorVersion(), 6 );
- }
-
- TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkFullRange) {
-
- string errMsg;
- unique_ptr<CollectionMetadata> cloned;
- ChunkVersion newShardVersion( 5, 0, getCollMetadata().getShardVersion().epoch() );
-
- // Add one chunk to complete the range
- ChunkType chunk;
- chunk.setMin( BSON( "a" << 20 ) );
- chunk.setMax( BSON( "a" << 30 ) );
- cloned.reset( getCollMetadata().clonePlusChunk( chunk,
- newShardVersion,
- &errMsg ) );
- ASSERT_EQUALS( errMsg, "" );
- ASSERT_EQUALS( cloned->getNumChunks(), 4u );
- ASSERT( cloned != NULL );
-
- // Try to merge all chunks together
- newShardVersion.incMajor();
- cloned.reset( cloned->cloneMerge( BSON( "a" << MINKEY ),
- BSON( "a" << MAXKEY ),
- newShardVersion,
- &errMsg ) );
-
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
- ASSERT( cloned->keyBelongsToMe( BSON( "a" << 10 ) ) );
- ASSERT( cloned->keyBelongsToMe( BSON( "a" << 30 ) ) );
- ASSERT_EQUALS( cloned->getNumChunks(), 1u );
- ASSERT_EQUALS( cloned->getShardVersion().majorVersion(), 6 );
- }
-
- TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkMiddleRange) {
-
- string errMsg;
- unique_ptr<CollectionMetadata> cloned;
- ChunkVersion newShardVersion( 5, 0, getCollMetadata().getShardVersion().epoch() );
-
- // Add one chunk to complete the range
- ChunkType chunk;
- chunk.setMin( BSON( "a" << 20 ) );
- chunk.setMax( BSON( "a" << 30 ) );
- cloned.reset( getCollMetadata().clonePlusChunk( chunk,
- newShardVersion,
- &errMsg ) );
- ASSERT_EQUALS( errMsg, "" );
- ASSERT_EQUALS( cloned->getNumChunks(), 4u );
- ASSERT( cloned != NULL );
-
- // Try to merge middle two chunks
- newShardVersion.incMajor();
- cloned.reset( cloned->cloneMerge( BSON( "a" << 10 ),
- BSON( "a" << 30 ),
- newShardVersion,
- &errMsg ) );
-
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( cloned != NULL );
- ASSERT( cloned->keyBelongsToMe( BSON( "a" << 20 ) ) );
- ASSERT_EQUALS( cloned->getNumChunks(), 3u );
- ASSERT_EQUALS( cloned->getShardVersion().majorVersion(), 6 );
- }
-
- TEST_F(ThreeChunkWithRangeGapFixture, CannotMergeWithHole) {
-
- string errMsg;
- ChunkVersion newShardVersion( 5, 0, getCollMetadata().getShardVersion().epoch() );
-
- // Try to merge middle two chunks with a hole in the middle.
- newShardVersion.incMajor();
- CollectionMetadata* result = getCollMetadata().cloneMerge( BSON( "a" << 10 ),
- BSON( "a" << 30 ),
- newShardVersion,
- &errMsg );
- ASSERT( result == NULL );
- ASSERT( !errMsg.empty() );
+ const CollectionMetadata& getCollMetadata() const {
+ return _metadata;
}
-} // unnamed namespace
+private:
+ unique_ptr<MockRemoteDBServer> _dummyConfig;
+ CollectionMetadata _metadata;
+};
+
+TEST_F(ThreeChunkWithRangeGapFixture, ShardOwnsDoc) {
+ ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 5)));
+ ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 10)));
+ ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 30)));
+ ASSERT(getCollMetadata().keyBelongsToMe(BSON("a" << 40)));
+}
+
+TEST_F(ThreeChunkWithRangeGapFixture, ShardDoesntOwnDoc) {
+ ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << 25)));
+ ASSERT_FALSE(getCollMetadata().keyBelongsToMe(BSON("a" << MAXKEY)));
+}
+
+TEST_F(ThreeChunkWithRangeGapFixture, GetNextFromEmpty) {
+ ChunkType nextChunk;
+ ASSERT(getCollMetadata().getNextChunk(getCollMetadata().getMinKey(), &nextChunk));
+ ASSERT_EQUALS(0, nextChunk.getMin().woCompare(BSON("a" << MINKEY)));
+ ASSERT_EQUALS(0, nextChunk.getMax().woCompare(BSON("a" << 10)));
+}
+
+TEST_F(ThreeChunkWithRangeGapFixture, GetNextFromMiddle) {
+ ChunkType nextChunk;
+ ASSERT(getCollMetadata().getNextChunk(BSON("a" << 20), &nextChunk));
+ ASSERT_EQUALS(0, nextChunk.getMin().woCompare(BSON("a" << 30)));
+ ASSERT_EQUALS(0, nextChunk.getMax().woCompare(BSON("a" << MAXKEY)));
+}
+
+TEST_F(ThreeChunkWithRangeGapFixture, GetNextFromLast) {
+ ChunkType nextChunk;
+ ASSERT(getCollMetadata().getNextChunk(BSON("a" << 30), &nextChunk));
+}
+
+TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkHoleInRange) {
+ string errMsg;
+ unique_ptr<CollectionMetadata> cloned;
+
+ // Try to merge with hole in range
+ ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch());
+ cloned.reset(getCollMetadata().cloneMerge(
+ BSON("a" << 10), BSON("a" << MAXKEY), newShardVersion, &errMsg));
+
+ ASSERT_NOT_EQUALS(errMsg, "");
+ ASSERT(cloned == NULL);
+}
+
+TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkDiffEndKey) {
+ string errMsg;
+ unique_ptr<CollectionMetadata> cloned;
+
+ // Try to merge with different end key
+ ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch());
+ cloned.reset(getCollMetadata().cloneMerge(
+ BSON("a" << MINKEY), BSON("a" << 19), newShardVersion, &errMsg));
+
+ ASSERT_NOT_EQUALS(errMsg, "");
+ ASSERT(cloned == NULL);
+}
+
+TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkMinKey) {
+ string errMsg;
+ unique_ptr<CollectionMetadata> cloned;
+
+ ASSERT_EQUALS(getCollMetadata().getNumChunks(), 3u);
+
+ // Try to merge lowest chunks together
+ ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch());
+ cloned.reset(getCollMetadata().cloneMerge(
+ BSON("a" << MINKEY), BSON("a" << 20), newShardVersion, &errMsg));
+
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
+ ASSERT(cloned->keyBelongsToMe(BSON("a" << 10)));
+ ASSERT_EQUALS(cloned->getNumChunks(), 2u);
+ ASSERT_EQUALS(cloned->getShardVersion().majorVersion(), 5);
+}
+
+TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkMaxKey) {
+ string errMsg;
+ unique_ptr<CollectionMetadata> cloned;
+ ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch());
+
+ // Add one chunk to complete the range
+ ChunkType chunk;
+ chunk.setMin(BSON("a" << 20));
+ chunk.setMax(BSON("a" << 30));
+ cloned.reset(getCollMetadata().clonePlusChunk(chunk, newShardVersion, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_EQUALS(cloned->getNumChunks(), 4u);
+ ASSERT(cloned != NULL);
+
+ // Try to merge highest chunks together
+ newShardVersion.incMajor();
+ cloned.reset(
+ cloned->cloneMerge(BSON("a" << 20), BSON("a" << MAXKEY), newShardVersion, &errMsg));
+
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
+ ASSERT(cloned->keyBelongsToMe(BSON("a" << 30)));
+ ASSERT_EQUALS(cloned->getNumChunks(), 3u);
+ ASSERT_EQUALS(cloned->getShardVersion().majorVersion(), 6);
+}
+
+TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkFullRange) {
+ string errMsg;
+ unique_ptr<CollectionMetadata> cloned;
+ ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch());
+
+ // Add one chunk to complete the range
+ ChunkType chunk;
+ chunk.setMin(BSON("a" << 20));
+ chunk.setMax(BSON("a" << 30));
+ cloned.reset(getCollMetadata().clonePlusChunk(chunk, newShardVersion, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_EQUALS(cloned->getNumChunks(), 4u);
+ ASSERT(cloned != NULL);
+
+ // Try to merge all chunks together
+ newShardVersion.incMajor();
+ cloned.reset(
+ cloned->cloneMerge(BSON("a" << MINKEY), BSON("a" << MAXKEY), newShardVersion, &errMsg));
+
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
+ ASSERT(cloned->keyBelongsToMe(BSON("a" << 10)));
+ ASSERT(cloned->keyBelongsToMe(BSON("a" << 30)));
+ ASSERT_EQUALS(cloned->getNumChunks(), 1u);
+ ASSERT_EQUALS(cloned->getShardVersion().majorVersion(), 6);
+}
+
+TEST_F(ThreeChunkWithRangeGapFixture, MergeChunkMiddleRange) {
+ string errMsg;
+ unique_ptr<CollectionMetadata> cloned;
+ ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch());
+
+ // Add one chunk to complete the range
+ ChunkType chunk;
+ chunk.setMin(BSON("a" << 20));
+ chunk.setMax(BSON("a" << 30));
+ cloned.reset(getCollMetadata().clonePlusChunk(chunk, newShardVersion, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_EQUALS(cloned->getNumChunks(), 4u);
+ ASSERT(cloned != NULL);
+
+ // Try to merge middle two chunks
+ newShardVersion.incMajor();
+ cloned.reset(cloned->cloneMerge(BSON("a" << 10), BSON("a" << 30), newShardVersion, &errMsg));
+
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(cloned != NULL);
+ ASSERT(cloned->keyBelongsToMe(BSON("a" << 20)));
+ ASSERT_EQUALS(cloned->getNumChunks(), 3u);
+ ASSERT_EQUALS(cloned->getShardVersion().majorVersion(), 6);
+}
+
+TEST_F(ThreeChunkWithRangeGapFixture, CannotMergeWithHole) {
+ string errMsg;
+ ChunkVersion newShardVersion(5, 0, getCollMetadata().getShardVersion().epoch());
+
+ // Try to merge middle two chunks with a hole in the middle.
+ newShardVersion.incMajor();
+ CollectionMetadata* result =
+ getCollMetadata().cloneMerge(BSON("a" << 10), BSON("a" << 30), newShardVersion, &errMsg);
+ ASSERT(result == NULL);
+ ASSERT(!errMsg.empty());
+}
+
+} // unnamed namespace
diff --git a/src/mongo/s/commands/cluster_add_shard_cmd.cpp b/src/mongo/s/commands/cluster_add_shard_cmd.cpp
index 8fdeb3f5b4d..78c2a693846 100644
--- a/src/mongo/s/commands/cluster_add_shard_cmd.cpp
+++ b/src/mongo/s/commands/cluster_add_shard_cmd.cpp
@@ -41,105 +41,102 @@
namespace mongo {
- using std::string;
+using std::string;
namespace {
- class AddShardCmd : public Command {
- public:
- AddShardCmd() : Command("addShard", false, "addshard") { }
-
- virtual bool slaveOk() const {
- return true;
- }
-
- virtual bool adminOnly() const {
- return true;
- }
-
- virtual bool isWriteCommandForConfigServer() const {
+class AddShardCmd : public Command {
+public:
+ AddShardCmd() : Command("addShard", false, "addshard") {}
+
+ virtual bool slaveOk() const {
+ return true;
+ }
+
+ virtual bool adminOnly() const {
+ return true;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(std::stringstream& help) const {
+ help << "add a new shard to the system";
+ }
+
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::addShard);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
+
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ // get replica set component hosts
+ ConnectionString servers =
+ ConnectionString::parse(cmdObj.firstElement().valuestrsafe(), errmsg);
+ if (!errmsg.empty()) {
+ log() << "addshard request " << cmdObj << " failed: " << errmsg;
return false;
}
- virtual void help(std::stringstream& help) const {
- help << "add a new shard to the system";
- }
-
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
-
- ActionSet actions;
- actions.addAction(ActionType::addShard);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
- }
-
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
-
- // get replica set component hosts
- ConnectionString servers = ConnectionString::parse(
- cmdObj.firstElement().valuestrsafe(), errmsg);
- if (!errmsg.empty()) {
- log() << "addshard request " << cmdObj << " failed: " << errmsg;
+ // using localhost in server names implies every other process must use localhost addresses too
+ std::vector<HostAndPort> serverAddrs = servers.getServers();
+ for (size_t i = 0; i < serverAddrs.size(); i++) {
+ if (serverAddrs[i].isLocalHost() != grid.allowLocalHost()) {
+ errmsg = str::stream()
+ << "Can't use localhost as a shard since all shards need to"
+ << " communicate. Either use all shards and configdbs in localhost"
+ << " or all in actual IPs. host: " << serverAddrs[i].toString()
+ << " isLocalHost:" << serverAddrs[i].isLocalHost();
+
+ log() << "addshard request " << cmdObj
+ << " failed: attempt to mix localhosts and IPs";
return false;
}
- // using localhost in server names implies every other process must use localhost addresses too
- std::vector<HostAndPort> serverAddrs = servers.getServers();
- for (size_t i = 0; i < serverAddrs.size(); i++) {
- if (serverAddrs[i].isLocalHost() != grid.allowLocalHost()) {
- errmsg = str::stream() <<
- "Can't use localhost as a shard since all shards need to" <<
- " communicate. Either use all shards and configdbs in localhost" <<
- " or all in actual IPs. host: " << serverAddrs[i].toString() <<
- " isLocalHost:" << serverAddrs[i].isLocalHost();
-
- log() << "addshard request " << cmdObj
- << " failed: attempt to mix localhosts and IPs";
- return false;
- }
-
- // it's fine if mongods of a set all use default port
- if (!serverAddrs[i].hasPort()) {
- serverAddrs[i] = HostAndPort(serverAddrs[i].host(),
- ServerGlobalParams::ShardServerPort);
- }
+ // it's fine if mongods of a set all use default port
+ if (!serverAddrs[i].hasPort()) {
+ serverAddrs[i] =
+ HostAndPort(serverAddrs[i].host(), ServerGlobalParams::ShardServerPort);
}
+ }
- // name is optional; addShard will provide one if needed
- string name = "";
- if (cmdObj["name"].type() == String) {
- name = cmdObj["name"].valuestrsafe();
- }
+ // name is optional; addShard will provide one if needed
+ string name = "";
+ if (cmdObj["name"].type() == String) {
+ name = cmdObj["name"].valuestrsafe();
+ }
- // maxSize is the space usage cap in a shard in MBs
- long long maxSize = 0;
- if (cmdObj[ShardType::maxSizeMB()].isNumber()) {
- maxSize = cmdObj[ShardType::maxSizeMB()].numberLong();
- }
+ // maxSize is the space usage cap in a shard in MBs
+ long long maxSize = 0;
+ if (cmdObj[ShardType::maxSizeMB()].isNumber()) {
+ maxSize = cmdObj[ShardType::maxSizeMB()].numberLong();
+ }
- audit::logAddShard(ClientBasic::getCurrent(), name, servers.toString(), maxSize);
+ audit::logAddShard(ClientBasic::getCurrent(), name, servers.toString(), maxSize);
- StatusWith<string> addShardResult =
- grid.catalogManager()->addShard(name, servers, maxSize);
- if (!addShardResult.isOK()) {
- log() << "addShard request '" << cmdObj << "'"
- << " failed: " << addShardResult.getStatus().reason();
- return appendCommandStatus(result, addShardResult.getStatus());
- }
+ StatusWith<string> addShardResult = grid.catalogManager()->addShard(name, servers, maxSize);
+ if (!addShardResult.isOK()) {
+ log() << "addShard request '" << cmdObj << "'"
+ << " failed: " << addShardResult.getStatus().reason();
+ return appendCommandStatus(result, addShardResult.getStatus());
+ }
- result << "shardAdded" << addShardResult.getValue();
+ result << "shardAdded" << addShardResult.getValue();
- return true;
- }
+ return true;
+ }
- } addShard;
+} addShard;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_commands_common.cpp b/src/mongo/s/commands/cluster_commands_common.cpp
index 52afad182f5..f390a17d992 100644
--- a/src/mongo/s/commands/cluster_commands_common.cpp
+++ b/src/mongo/s/commands/cluster_commands_common.cpp
@@ -36,79 +36,76 @@
namespace mongo {
- int getUniqueCodeFromCommandResults(const std::vector<Strategy::CommandResult>& results) {
- int commonErrCode = -1;
- for (std::vector<Strategy::CommandResult>::const_iterator it = results.begin();
- it != results.end();
- ++it) {
-
- // Only look at shards with errors.
- if (!it->result["ok"].trueValue()) {
- int errCode = it->result["code"].numberInt();
-
- if (commonErrCode == -1) {
- commonErrCode = errCode;
- }
- else if (commonErrCode != errCode) {
- // At least two shards with errors disagree on the error code
- commonErrCode = 0;
- }
+int getUniqueCodeFromCommandResults(const std::vector<Strategy::CommandResult>& results) {
+ int commonErrCode = -1;
+ for (std::vector<Strategy::CommandResult>::const_iterator it = results.begin();
+ it != results.end();
+ ++it) {
+ // Only look at shards with errors.
+ if (!it->result["ok"].trueValue()) {
+ int errCode = it->result["code"].numberInt();
+
+ if (commonErrCode == -1) {
+ commonErrCode = errCode;
+ } else if (commonErrCode != errCode) {
+ // At least two shards with errors disagree on the error code
+ commonErrCode = 0;
}
}
+ }
- // If no error encountered or shards with errors disagree on the error code, return 0
- if (commonErrCode == -1 || commonErrCode == 0) {
- return 0;
- }
-
- // Otherwise, shards with errors agree on the error code; return that code
- return commonErrCode;
+ // If no error encountered or shards with errors disagree on the error code, return 0
+ if (commonErrCode == -1 || commonErrCode == 0) {
+ return 0;
}
- bool appendEmptyResultSet(BSONObjBuilder& result, Status status, const std::string& ns) {
- invariant(!status.isOK());
+ // Otherwise, shards with errors agree on the error code; return that code
+ return commonErrCode;
+}
- if (status == ErrorCodes::DatabaseNotFound) {
- // Old style reply
- result << "result" << BSONArray();
+bool appendEmptyResultSet(BSONObjBuilder& result, Status status, const std::string& ns) {
+ invariant(!status.isOK());
- // New (command) style reply
- appendCursorResponseObject(0LL, ns, BSONArray(), &result);
+ if (status == ErrorCodes::DatabaseNotFound) {
+ // Old style reply
+ result << "result" << BSONArray();
- return true;
- }
+ // New (command) style reply
+ appendCursorResponseObject(0LL, ns, BSONArray(), &result);
- return Command::appendCommandStatus(result, status);
+ return true;
}
- Status storePossibleCursor(const std::string& server, const BSONObj& cmdResult) {
- if (cmdResult["ok"].trueValue() && cmdResult.hasField("cursor")) {
- BSONElement cursorIdElt = cmdResult.getFieldDotted("cursor.id");
+ return Command::appendCommandStatus(result, status);
+}
+
+Status storePossibleCursor(const std::string& server, const BSONObj& cmdResult) {
+ if (cmdResult["ok"].trueValue() && cmdResult.hasField("cursor")) {
+ BSONElement cursorIdElt = cmdResult.getFieldDotted("cursor.id");
- if (cursorIdElt.type() != mongo::NumberLong) {
+ if (cursorIdElt.type() != mongo::NumberLong) {
+ return Status(ErrorCodes::TypeMismatch,
+ str::stream() << "expected \"cursor.id\" field from shard "
+ << "response to have NumberLong type, instead "
+ << "got: " << typeName(cursorIdElt.type()));
+ }
+
+ const long long cursorId = cursorIdElt.Long();
+ if (cursorId != 0) {
+ BSONElement cursorNsElt = cmdResult.getFieldDotted("cursor.ns");
+ if (cursorNsElt.type() != mongo::String) {
return Status(ErrorCodes::TypeMismatch,
- str::stream() << "expected \"cursor.id\" field from shard "
- << "response to have NumberLong type, instead "
- << "got: " << typeName(cursorIdElt.type()));
+ str::stream() << "expected \"cursor.ns\" field from "
+ << "shard response to have String type, "
+ << "instead got: " << typeName(cursorNsElt.type()));
}
- const long long cursorId = cursorIdElt.Long();
- if (cursorId != 0) {
- BSONElement cursorNsElt = cmdResult.getFieldDotted("cursor.ns");
- if (cursorNsElt.type() != mongo::String) {
- return Status(ErrorCodes::TypeMismatch,
- str::stream() << "expected \"cursor.ns\" field from "
- << "shard response to have String type, "
- << "instead got: "
- << typeName(cursorNsElt.type()));
- }
-
- const std::string cursorNs = cursorNsElt.String();
- cursorCache.storeRef(server, cursorId, cursorNs);
- }
+ const std::string cursorNs = cursorNsElt.String();
+ cursorCache.storeRef(server, cursorId, cursorNs);
}
-
- return Status::OK();
}
-} // namespace mongo
+ return Status::OK();
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_commands_common.h b/src/mongo/s/commands/cluster_commands_common.h
index bda035f84c6..d06e13c63f2 100644
--- a/src/mongo/s/commands/cluster_commands_common.h
+++ b/src/mongo/s/commands/cluster_commands_common.h
@@ -35,27 +35,27 @@
namespace mongo {
- class BSONObj;
-
- /**
- * Utility function to compute a single error code from a vector of command results.
- *
- * @return If there is an error code common to all of the error results, returns that error
- * code; otherwise, returns 0.
- */
- int getUniqueCodeFromCommandResults(const std::vector<Strategy::CommandResult>& results);
-
- /**
- * Utility function to return an empty result set from a command.
- */
- bool appendEmptyResultSet(BSONObjBuilder& result, Status status, const std::string& ns);
-
- /**
- * Utility function to parse a cursor command response and save the cursor in the CursorCache
- * "refs" container. Returns Status::OK() if the cursor was successfully saved or no cursor
- * was specified in the command response, and returns an error Status if a parsing error was
- * encountered.
- */
- Status storePossibleCursor(const std::string& server, const BSONObj& cmdResult);
-
-} // namespace mongo
+class BSONObj;
+
+/**
+ * Utility function to compute a single error code from a vector of command results.
+ *
+ * @return If there is an error code common to all of the error results, returns that error
+ * code; otherwise, returns 0.
+ */
+int getUniqueCodeFromCommandResults(const std::vector<Strategy::CommandResult>& results);
+
+/**
+ * Utility function to return an empty result set from a command.
+ */
+bool appendEmptyResultSet(BSONObjBuilder& result, Status status, const std::string& ns);
+
+/**
+ * Utility function to parse a cursor command response and save the cursor in the CursorCache
+ * "refs" container. Returns Status::OK() if the cursor was successfully saved or no cursor
+ * was specified in the command response, and returns an error Status if a parsing error was
+ * encountered.
+ */
+Status storePossibleCursor(const std::string& server, const BSONObj& cmdResult);
+
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_count_cmd.cpp b/src/mongo/s/commands/cluster_count_cmd.cpp
index 6d7d3f22186..9db1762b16a 100644
--- a/src/mongo/s/commands/cluster_count_cmd.cpp
+++ b/src/mongo/s/commands/cluster_count_cmd.cpp
@@ -38,210 +38,191 @@
namespace mongo {
- using std::string;
- using std::vector;
+using std::string;
+using std::vector;
namespace {
- long long applySkipLimit(long long num, const BSONObj& cmd) {
- BSONElement s = cmd["skip"];
- BSONElement l = cmd["limit"];
+long long applySkipLimit(long long num, const BSONObj& cmd) {
+ BSONElement s = cmd["skip"];
+ BSONElement l = cmd["limit"];
- if (s.isNumber()) {
- num = num - s.numberLong();
- if (num < 0) {
- num = 0;
- }
+ if (s.isNumber()) {
+ num = num - s.numberLong();
+ if (num < 0) {
+ num = 0;
}
+ }
- if (l.isNumber()) {
- long long limit = l.numberLong();
- if (limit < 0){
- limit = -limit;
- }
-
- // 0 limit means no limit
- if (limit < num && limit != 0) {
- num = limit;
- }
+ if (l.isNumber()) {
+ long long limit = l.numberLong();
+ if (limit < 0) {
+ limit = -limit;
}
- return num;
+ // 0 limit means no limit
+ if (limit < num && limit != 0) {
+ num = limit;
+ }
}
+ return num;
+}
- class ClusterCountCmd : public Command {
- public:
- ClusterCountCmd() : Command("count", false) { }
-
- virtual bool slaveOk() const {
- return true;
- }
-
- virtual bool adminOnly() const {
- return false;
- }
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
+class ClusterCountCmd : public Command {
+public:
+ ClusterCountCmd() : Command("count", false) {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
+ virtual bool slaveOk() const {
+ return true;
+ }
- ActionSet actions;
- actions.addAction(ActionType::find);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
- }
+ virtual bool adminOnly() const {
+ return false;
+ }
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- long long skip = 0;
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::find);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
- if (cmdObj["skip"].isNumber()) {
- skip = cmdObj["skip"].numberLong();
- if (skip < 0) {
- errmsg = "skip value is negative in count query";
- return false;
- }
- }
- else if (cmdObj["skip"].ok()) {
- errmsg = "skip value is not a valid number";
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ long long skip = 0;
+
+ if (cmdObj["skip"].isNumber()) {
+ skip = cmdObj["skip"].numberLong();
+ if (skip < 0) {
+ errmsg = "skip value is negative in count query";
return false;
}
+ } else if (cmdObj["skip"].ok()) {
+ errmsg = "skip value is not a valid number";
+ return false;
+ }
- const string collection = cmdObj.firstElement().valuestrsafe();
- const string fullns = dbname + "." + collection;
-
- BSONObjBuilder countCmdBuilder;
- countCmdBuilder.append("count", collection);
+ const string collection = cmdObj.firstElement().valuestrsafe();
+ const string fullns = dbname + "." + collection;
- BSONObj filter;
- if (cmdObj["query"].isABSONObj()) {
- countCmdBuilder.append("query", cmdObj["query"].Obj());
- filter = cmdObj["query"].Obj();
- }
+ BSONObjBuilder countCmdBuilder;
+ countCmdBuilder.append("count", collection);
- if (cmdObj["limit"].isNumber()) {
- long long limit = cmdObj["limit"].numberLong();
-
- // We only need to factor in the skip value when sending to the shards if we
- // have a value for limit, otherwise, we apply it only once we have collected all
- // counts.
- if (limit != 0 && cmdObj["skip"].isNumber()) {
- if (limit > 0)
- limit += skip;
- else
- limit -= skip;
- }
+ BSONObj filter;
+ if (cmdObj["query"].isABSONObj()) {
+ countCmdBuilder.append("query", cmdObj["query"].Obj());
+ filter = cmdObj["query"].Obj();
+ }
- countCmdBuilder.append("limit", limit);
+ if (cmdObj["limit"].isNumber()) {
+ long long limit = cmdObj["limit"].numberLong();
+
+ // We only need to factor in the skip value when sending to the shards if we
+ // have a value for limit, otherwise, we apply it only once we have collected all
+ // counts.
+ if (limit != 0 && cmdObj["skip"].isNumber()) {
+ if (limit > 0)
+ limit += skip;
+ else
+ limit -= skip;
}
- if (cmdObj.hasField("hint")) {
- countCmdBuilder.append(cmdObj["hint"]);
- }
+ countCmdBuilder.append("limit", limit);
+ }
- if (cmdObj.hasField("$queryOptions")) {
- countCmdBuilder.append(cmdObj["$queryOptions"]);
- }
+ if (cmdObj.hasField("hint")) {
+ countCmdBuilder.append(cmdObj["hint"]);
+ }
- if (cmdObj.hasField(LiteParsedQuery::cmdOptionMaxTimeMS)) {
- countCmdBuilder.append(cmdObj[LiteParsedQuery::cmdOptionMaxTimeMS]);
- }
+ if (cmdObj.hasField("$queryOptions")) {
+ countCmdBuilder.append(cmdObj["$queryOptions"]);
+ }
+
+ if (cmdObj.hasField(LiteParsedQuery::cmdOptionMaxTimeMS)) {
+ countCmdBuilder.append(cmdObj[LiteParsedQuery::cmdOptionMaxTimeMS]);
+ }
- vector<Strategy::CommandResult> countResult;
- Strategy::commandOp(dbname,
- countCmdBuilder.done(),
- options,
- fullns,
- filter,
- &countResult);
+ vector<Strategy::CommandResult> countResult;
+ Strategy::commandOp(dbname, countCmdBuilder.done(), options, fullns, filter, &countResult);
- long long total = 0;
- BSONObjBuilder shardSubTotal(result.subobjStart("shards"));
+ long long total = 0;
+ BSONObjBuilder shardSubTotal(result.subobjStart("shards"));
- for (vector<Strategy::CommandResult>::const_iterator iter = countResult.begin();
- iter != countResult.end();
- ++iter) {
+ for (vector<Strategy::CommandResult>::const_iterator iter = countResult.begin();
+ iter != countResult.end();
+ ++iter) {
+ const string& shardName = iter->shardTargetId;
- const string& shardName = iter->shardTargetId;
+ if (iter->result["ok"].trueValue()) {
+ long long shardCount = iter->result["n"].numberLong();
- if (iter->result["ok"].trueValue()) {
- long long shardCount = iter->result["n"].numberLong();
+ shardSubTotal.appendNumber(shardName, shardCount);
+ total += shardCount;
+ } else {
+ shardSubTotal.doneFast();
+ errmsg = "failed on : " + shardName;
+ result.append("cause", iter->result);
- shardSubTotal.appendNumber(shardName, shardCount);
- total += shardCount;
- }
- else {
- shardSubTotal.doneFast();
- errmsg = "failed on : " + shardName;
- result.append("cause", iter->result);
-
- // Add "code" to the top-level response, if the failure of the sharded command
- // can be accounted to a single error
- int code = getUniqueCodeFromCommandResults(countResult);
- if (code != 0) {
- result.append("code", code);
- }
-
- return false;
+ // Add "code" to the top-level response, if the failure of the sharded command
+ // can be accounted to a single error
+ int code = getUniqueCodeFromCommandResults(countResult);
+ if (code != 0) {
+ result.append("code", code);
}
- }
- shardSubTotal.doneFast();
- total = applySkipLimit(total, cmdObj);
- result.appendNumber("n", total);
-
- return true;
+ return false;
+ }
}
- virtual Status explain(OperationContext* txn,
- const std::string& dbname,
- const BSONObj& cmdObj,
- ExplainCommon::Verbosity verbosity,
- BSONObjBuilder* out) const {
+ shardSubTotal.doneFast();
+ total = applySkipLimit(total, cmdObj);
+ result.appendNumber("n", total);
- const string fullns = parseNs(dbname, cmdObj);
+ return true;
+ }
- // Extract the targeting query.
- BSONObj targetingQuery;
- if (Object == cmdObj["query"].type()) {
- targetingQuery = cmdObj["query"].Obj();
- }
+ virtual Status explain(OperationContext* txn,
+ const std::string& dbname,
+ const BSONObj& cmdObj,
+ ExplainCommon::Verbosity verbosity,
+ BSONObjBuilder* out) const {
+ const string fullns = parseNs(dbname, cmdObj);
+
+ // Extract the targeting query.
+ BSONObj targetingQuery;
+ if (Object == cmdObj["query"].type()) {
+ targetingQuery = cmdObj["query"].Obj();
+ }
- BSONObjBuilder explainCmdBob;
- ClusterExplain::wrapAsExplain(cmdObj, verbosity, &explainCmdBob);
+ BSONObjBuilder explainCmdBob;
+ ClusterExplain::wrapAsExplain(cmdObj, verbosity, &explainCmdBob);
- // We will time how long it takes to run the commands on the shards
- Timer timer;
+ // We will time how long it takes to run the commands on the shards
+ Timer timer;
- vector<Strategy::CommandResult> shardResults;
- Strategy::commandOp(dbname,
- explainCmdBob.obj(),
- 0,
- fullns,
- targetingQuery,
- &shardResults);
+ vector<Strategy::CommandResult> shardResults;
+ Strategy::commandOp(dbname, explainCmdBob.obj(), 0, fullns, targetingQuery, &shardResults);
- long long millisElapsed = timer.millis();
+ long long millisElapsed = timer.millis();
- const char* mongosStageName = ClusterExplain::getStageNameForReadOp(shardResults,
- cmdObj);
+ const char* mongosStageName = ClusterExplain::getStageNameForReadOp(shardResults, cmdObj);
- return ClusterExplain::buildExplainResult(shardResults,
- mongosStageName,
- millisElapsed,
- out);
- }
+ return ClusterExplain::buildExplainResult(
+ shardResults, mongosStageName, millisElapsed, out);
+ }
- } clusterCountCmd;
+} clusterCountCmd;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_current_op.cpp b/src/mongo/s/commands/cluster_current_op.cpp
index c4de4dcee82..28f1311ca40 100644
--- a/src/mongo/s/commands/cluster_current_op.cpp
+++ b/src/mongo/s/commands/cluster_current_op.cpp
@@ -46,181 +46,165 @@
namespace mongo {
namespace {
- const char kInprogFieldName[] = "inprog";
- const char kOpIdFieldName[] = "opid";
- const char kClientFieldName[] = "client";
- // awkward underscores used to make this visually distinct from kClientFieldName
- const char kClient_S_FieldName[] = "client_s";
- const char kLegacyInprogCollection[] = "$cmd.sys.inprog";
-
- const char kCommandName[] = "currentOp";
-
- class ClusterCurrentOpCommand : public RunOnAllShardsCommand {
- public:
-
- ClusterCurrentOpCommand() : RunOnAllShardsCommand(kCommandName) { }
-
- bool adminOnly() const final { return true; }
-
- Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) final {
-
-
- bool isAuthorized = AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
- ResourcePattern::forClusterResource(),
- ActionType::inprog);
-
- return isAuthorized ? Status::OK() : Status(ErrorCodes::Unauthorized, "Unauthorized");
- }
-
- // TODO remove after 3.2
- BSONObj specialErrorHandler(const std::string& server,
- const std::string& db,
- const BSONObj& cmdObj,
- const BSONObj& originalResult) const final {
-
- // it is unfortunate that this logic needs to be duplicated from
- // DBClientWithCommands::runPseudoCommand
- // but I don't see a better way to do it without performing heart surgery on
- // Future/CommandResponse.
-
- auto status = getStatusFromCommandResult(originalResult);
- invariant(!status.isOK());
-
- uassert(28629,
- str::stream() << "Received bad "
- << kCommandName
- << " response from server " << server
- << " got: " << originalResult,
- status != ErrorCodes::CommandResultSchemaViolation);
-
- // getStatusFromCommandResult handles cooercing "no such command" into the right
- // Status type
- if (status == ErrorCodes::CommandNotFound) {
- // fall back to the old inprog pseudo-command
- NamespaceString pseudoCommandNss("admin", kLegacyInprogCollection);
- BSONObj legacyResult;
-
- BSONObjBuilder legacyCommandBob;
-
- // need to exclude {currentOp: 1}
- for (auto&& cmdElem : cmdObj) {
- if (cmdElem.fieldNameStringData() != kCommandName) {
- legacyCommandBob.append(cmdElem);
- }
- }
- auto legacyCommand = legacyCommandBob.done();
-
- try {
- ScopedDbConnection conn(server);
- legacyResult =
- conn->findOne(pseudoCommandNss.ns(), legacyCommand);
-
- }
- catch (const DBException& ex) {
- // If there is a non-DBException exception the entire operation will be
- // terminated, as that would be a programmer error.
-
- // We convert the exception to a BSONObj so that the ordinary
- // failure path for RunOnAllShardsCommand will handle the failure
-
- // TODO: consider adding an exceptionToBSONObj utility?
- BSONObjBuilder b;
- b.append("errmsg", ex.toString());
- b.append("code", ex.getCode());
- return b.obj();
+const char kInprogFieldName[] = "inprog";
+const char kOpIdFieldName[] = "opid";
+const char kClientFieldName[] = "client";
+// awkward underscores used to make this visually distinct from kClientFieldName
+const char kClient_S_FieldName[] = "client_s";
+const char kLegacyInprogCollection[] = "$cmd.sys.inprog";
+
+const char kCommandName[] = "currentOp";
+
+class ClusterCurrentOpCommand : public RunOnAllShardsCommand {
+public:
+ ClusterCurrentOpCommand() : RunOnAllShardsCommand(kCommandName) {}
+
+ bool adminOnly() const final {
+ return true;
+ }
+
+ Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) final {
+ bool isAuthorized = AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forClusterResource(), ActionType::inprog);
+
+ return isAuthorized ? Status::OK() : Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+
+ // TODO remove after 3.2
+ BSONObj specialErrorHandler(const std::string& server,
+ const std::string& db,
+ const BSONObj& cmdObj,
+ const BSONObj& originalResult) const final {
+ // it is unfortunate that this logic needs to be duplicated from
+ // DBClientWithCommands::runPseudoCommand
+ // but I don't see a better way to do it without performing heart surgery on
+ // Future/CommandResponse.
+
+ auto status = getStatusFromCommandResult(originalResult);
+ invariant(!status.isOK());
+
+ uassert(28629,
+ str::stream() << "Received bad " << kCommandName << " response from server "
+ << server << " got: " << originalResult,
+ status != ErrorCodes::CommandResultSchemaViolation);
+
+ // getStatusFromCommandResult handles cooercing "no such command" into the right
+ // Status type
+ if (status == ErrorCodes::CommandNotFound) {
+ // fall back to the old inprog pseudo-command
+ NamespaceString pseudoCommandNss("admin", kLegacyInprogCollection);
+ BSONObj legacyResult;
+
+ BSONObjBuilder legacyCommandBob;
+
+ // need to exclude {currentOp: 1}
+ for (auto&& cmdElem : cmdObj) {
+ if (cmdElem.fieldNameStringData() != kCommandName) {
+ legacyCommandBob.append(cmdElem);
}
- return legacyResult;
}
- // if the command failed for another reason then we don't retry it.
- return originalResult;
- }
-
- void aggregateResults(const std::vector<ShardAndReply>& results,
- BSONObjBuilder& output) final {
- // Each shard responds with a document containing an array of subdocuments.
- // Each subdocument represents an operation running on that shard.
- // We merge the responses into a single document containg an array
- // of the operations from all shards.
-
- // There are two modifications we make.
- // 1) we prepend the shardid (with a colon separator) to the opid of each operation.
- // This allows users to pass the value of the opid field directly to killOp.
+ auto legacyCommand = legacyCommandBob.done();
- // 2) we change the field name of "client" to "client_s". This is because each
- // client is actually a mongos.
+ try {
+ ScopedDbConnection conn(server);
+ legacyResult = conn->findOne(pseudoCommandNss.ns(), legacyCommand);
- // TODO: failpoint for a shard response being invalid.
+ } catch (const DBException& ex) {
+ // If there is a non-DBException exception the entire operation will be
+ // terminated, as that would be a programmer error.
- // Error handling - we maintain the same behavior as legacy currentOp/inprog
- // that is, if any shard replies with an invalid response (i.e. it does not
- // contain a field 'inprog' that is an array), we ignore it.
- //
- // If there is a lower level error (i.e. the command fails, network error, etc)
- // RunOnAllShardsCommand will handle returning an error to the user.
- BSONArrayBuilder aggregatedOpsBab(output.subarrayStart(kInprogFieldName));
+ // We convert the exception to a BSONObj so that the ordinary
+ // failure path for RunOnAllShardsCommand will handle the failure
- for (auto&& shardResponse : results) {
-
- StringData shardName;
- BSONObj shardResponseObj;
- std::tie(shardName, shardResponseObj) = shardResponse;
+ // TODO: consider adding an exceptionToBSONObj utility?
+ BSONObjBuilder b;
+ b.append("errmsg", ex.toString());
+ b.append("code", ex.getCode());
+ return b.obj();
+ }
+ return legacyResult;
+ }
+ // if the command failed for another reason then we don't retry it.
+ return originalResult;
+ }
+
+ void aggregateResults(const std::vector<ShardAndReply>& results, BSONObjBuilder& output) final {
+ // Each shard responds with a document containing an array of subdocuments.
+ // Each subdocument represents an operation running on that shard.
+ // We merge the responses into a single document containg an array
+ // of the operations from all shards.
+
+ // There are two modifications we make.
+ // 1) we prepend the shardid (with a colon separator) to the opid of each operation.
+ // This allows users to pass the value of the opid field directly to killOp.
+
+ // 2) we change the field name of "client" to "client_s". This is because each
+ // client is actually a mongos.
+
+ // TODO: failpoint for a shard response being invalid.
+
+ // Error handling - we maintain the same behavior as legacy currentOp/inprog
+ // that is, if any shard replies with an invalid response (i.e. it does not
+ // contain a field 'inprog' that is an array), we ignore it.
+ //
+ // If there is a lower level error (i.e. the command fails, network error, etc)
+ // RunOnAllShardsCommand will handle returning an error to the user.
+ BSONArrayBuilder aggregatedOpsBab(output.subarrayStart(kInprogFieldName));
+
+ for (auto&& shardResponse : results) {
+ StringData shardName;
+ BSONObj shardResponseObj;
+ std::tie(shardName, shardResponseObj) = shardResponse;
+
+ auto shardOps = shardResponseObj[kInprogFieldName];
+
+ // legacy behavior
+ if (!shardOps.isABSONObj()) {
+ warning() << "invalid currentOp response from shard " << shardName
+ << ", got: " << shardOps;
+ continue;
+ }
- auto shardOps = shardResponseObj[kInprogFieldName];
+ for (auto&& shardOp : shardOps.Obj()) {
+ BSONObjBuilder modifiedShardOpBob;
- // legacy behavior
- if (!shardOps.isABSONObj()) {
- warning() << "invalid currentOp response from shard "
- << shardName
- << ", got: "
- << shardOps;
+ // maintain legacy behavior
+ // but log it first
+ if (!shardOp.isABSONObj()) {
+ warning() << "invalid currentOp response from shard " << shardName
+ << ", got: " << shardOp;
continue;
}
- for (auto&& shardOp : shardOps.Obj()) {
- BSONObjBuilder modifiedShardOpBob;
-
- // maintain legacy behavior
- // but log it first
- if (!shardOp.isABSONObj()) {
- warning() << "invalid currentOp response from shard "
- << shardName
- << ", got: "
- << shardOp;
- continue;
- }
-
- for (auto&& shardOpElement : shardOp.Obj()) {
- auto fieldName = shardOpElement.fieldNameStringData();
- if (fieldName == kOpIdFieldName) {
- uassert(28630,
- str::stream() << "expected numeric opid from currentOp response"
- << " from shard " << shardName
- << ", got: " << shardOpElement,
- shardOpElement.isNumber());
-
- modifiedShardOpBob.append(kOpIdFieldName,
- str::stream() << shardName
- << ":"
- << shardOpElement.numberInt());
- }
- else if (fieldName == kClientFieldName) {
- modifiedShardOpBob.appendAs(shardOpElement, kClient_S_FieldName);
- }
- else {
- modifiedShardOpBob.append(shardOpElement);
- }
+ for (auto&& shardOpElement : shardOp.Obj()) {
+ auto fieldName = shardOpElement.fieldNameStringData();
+ if (fieldName == kOpIdFieldName) {
+ uassert(28630,
+ str::stream() << "expected numeric opid from currentOp response"
+ << " from shard " << shardName
+ << ", got: " << shardOpElement,
+ shardOpElement.isNumber());
+
+ modifiedShardOpBob.append(kOpIdFieldName,
+ str::stream() << shardName << ":"
+ << shardOpElement.numberInt());
+ } else if (fieldName == kClientFieldName) {
+ modifiedShardOpBob.appendAs(shardOpElement, kClient_S_FieldName);
+ } else {
+ modifiedShardOpBob.append(shardOpElement);
}
- modifiedShardOpBob.done();
- // append the modified document to the output array
- aggregatedOpsBab.append(modifiedShardOpBob.obj());
}
+ modifiedShardOpBob.done();
+ // append the modified document to the output array
+ aggregatedOpsBab.append(modifiedShardOpBob.obj());
}
- aggregatedOpsBab.done();
}
+ aggregatedOpsBab.done();
+ }
- } clusterCurrentOpCmd;
+} clusterCurrentOpCmd;
} // namespace
} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_db_stats_cmd.cpp b/src/mongo/s/commands/cluster_db_stats_cmd.cpp
index 33f804175aa..a5a50c867cf 100644
--- a/src/mongo/s/commands/cluster_db_stats_cmd.cpp
+++ b/src/mongo/s/commands/cluster_db_stats_cmd.cpp
@@ -35,74 +35,72 @@
namespace mongo {
namespace {
- using std::vector;
-
- class DBStatsCmd : public RunOnAllShardsCommand {
- public:
- DBStatsCmd() : RunOnAllShardsCommand("dbStats", "dbstats") {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::dbStats);
- out->push_back(Privilege(ResourcePattern::forDatabaseName(dbname), actions));
- }
-
- virtual void aggregateResults(const vector<ShardAndReply>& results,
- BSONObjBuilder& output) {
- long long objects = 0;
- long long unscaledDataSize = 0;
- long long dataSize = 0;
- long long storageSize = 0;
- long long numExtents = 0;
- long long indexes = 0;
- long long indexSize = 0;
- long long fileSize = 0;
-
- long long freeListNum = 0;
- long long freeListSize = 0;
-
- for (const ShardAndReply& shardAndReply : results) {
- const BSONObj& b = std::get<1>(shardAndReply);
-
- objects += b["objects"].numberLong();
- unscaledDataSize += b["avgObjSize"].numberLong() * b["objects"].numberLong();
- dataSize += b["dataSize"].numberLong();
- storageSize += b["storageSize"].numberLong();
- numExtents += b["numExtents"].numberLong();
- indexes += b["indexes"].numberLong();
- indexSize += b["indexSize"].numberLong();
- fileSize += b["fileSize"].numberLong();
-
- if (b["extentFreeList"].isABSONObj()) {
- freeListNum += b["extentFreeList"].Obj()["num"].numberLong();
- freeListSize += b["extentFreeList"].Obj()["totalSize"].numberLong();
- }
+using std::vector;
+
+class DBStatsCmd : public RunOnAllShardsCommand {
+public:
+ DBStatsCmd() : RunOnAllShardsCommand("dbStats", "dbstats") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::dbStats);
+ out->push_back(Privilege(ResourcePattern::forDatabaseName(dbname), actions));
+ }
+
+ virtual void aggregateResults(const vector<ShardAndReply>& results, BSONObjBuilder& output) {
+ long long objects = 0;
+ long long unscaledDataSize = 0;
+ long long dataSize = 0;
+ long long storageSize = 0;
+ long long numExtents = 0;
+ long long indexes = 0;
+ long long indexSize = 0;
+ long long fileSize = 0;
+
+ long long freeListNum = 0;
+ long long freeListSize = 0;
+
+ for (const ShardAndReply& shardAndReply : results) {
+ const BSONObj& b = std::get<1>(shardAndReply);
+
+ objects += b["objects"].numberLong();
+ unscaledDataSize += b["avgObjSize"].numberLong() * b["objects"].numberLong();
+ dataSize += b["dataSize"].numberLong();
+ storageSize += b["storageSize"].numberLong();
+ numExtents += b["numExtents"].numberLong();
+ indexes += b["indexes"].numberLong();
+ indexSize += b["indexSize"].numberLong();
+ fileSize += b["fileSize"].numberLong();
+
+ if (b["extentFreeList"].isABSONObj()) {
+ freeListNum += b["extentFreeList"].Obj()["num"].numberLong();
+ freeListSize += b["extentFreeList"].Obj()["totalSize"].numberLong();
}
+ }
- // result.appendNumber( "collections" , ncollections ); //TODO: need to find a good way to get this
- output.appendNumber("objects", objects);
-
- // avgObjSize on mongod is not scaled based on the argument to db.stats(), so we use
- // unscaledDataSize here for consistency. See SERVER-7347.
- output.append("avgObjSize", objects == 0 ? 0 :
- double(unscaledDataSize) / double(objects));
- output.appendNumber("dataSize", dataSize);
- output.appendNumber("storageSize", storageSize);
- output.appendNumber("numExtents", numExtents);
- output.appendNumber("indexes", indexes);
- output.appendNumber("indexSize", indexSize);
- output.appendNumber("fileSize", fileSize);
-
- {
- BSONObjBuilder extentFreeList(output.subobjStart("extentFreeList"));
- extentFreeList.appendNumber("num", freeListNum);
- extentFreeList.appendNumber("totalSize", freeListSize);
- extentFreeList.done();
- }
+ // result.appendNumber( "collections" , ncollections ); //TODO: need to find a good way to get this
+ output.appendNumber("objects", objects);
+
+ // avgObjSize on mongod is not scaled based on the argument to db.stats(), so we use
+ // unscaledDataSize here for consistency. See SERVER-7347.
+ output.append("avgObjSize", objects == 0 ? 0 : double(unscaledDataSize) / double(objects));
+ output.appendNumber("dataSize", dataSize);
+ output.appendNumber("storageSize", storageSize);
+ output.appendNumber("numExtents", numExtents);
+ output.appendNumber("indexes", indexes);
+ output.appendNumber("indexSize", indexSize);
+ output.appendNumber("fileSize", fileSize);
+
+ {
+ BSONObjBuilder extentFreeList(output.subobjStart("extentFreeList"));
+ extentFreeList.appendNumber("num", freeListNum);
+ extentFreeList.appendNumber("totalSize", freeListSize);
+ extentFreeList.done();
}
+ }
- } clusterDBStatsCmd;
+} clusterDBStatsCmd;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_drop_database_cmd.cpp b/src/mongo/s/commands/cluster_drop_database_cmd.cpp
index 0cf6eec2f20..8ac6a6351d2 100644
--- a/src/mongo/s/commands/cluster_drop_database_cmd.cpp
+++ b/src/mongo/s/commands/cluster_drop_database_cmd.cpp
@@ -40,85 +40,83 @@
namespace mongo {
- using std::shared_ptr;
+using std::shared_ptr;
namespace {
- class DropDatabaseCmd : public Command {
- public:
- DropDatabaseCmd() : Command("dropDatabase") { }
-
- virtual bool slaveOk() const {
- return true;
+class DropDatabaseCmd : public Command {
+public:
+ DropDatabaseCmd() : Command("dropDatabase") {}
+
+ virtual bool slaveOk() const {
+ return true;
+ }
+
+ virtual bool adminOnly() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::dropDatabase);
+ out->push_back(Privilege(ResourcePattern::forDatabaseName(dbname), actions));
+ }
+
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ // Disallow dropping the config database from mongos
+ if (dbname == "config") {
+ return appendCommandStatus(
+ result, Status(ErrorCodes::IllegalOperation, "Cannot drop the config database"));
}
- virtual bool adminOnly() const {
- return false;
- }
+ BSONElement e = cmdObj.firstElement();
- virtual bool isWriteCommandForConfigServer() const {
- return false;
+ if (!e.isNumber() || e.number() != 1) {
+ errmsg = "invalid params";
+ return 0;
}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
-
- ActionSet actions;
- actions.addAction(ActionType::dropDatabase);
- out->push_back(Privilege(ResourcePattern::forDatabaseName(dbname), actions));
- }
-
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
-
- // Disallow dropping the config database from mongos
- if (dbname == "config") {
- return appendCommandStatus(result, Status(ErrorCodes::IllegalOperation,
- "Cannot drop the config database"));
- }
-
- BSONElement e = cmdObj.firstElement();
+ // Refresh the database metadata
+ grid.catalogCache()->invalidate(dbname);
- if (!e.isNumber() || e.number() != 1) {
- errmsg = "invalid params";
- return 0;
+ auto status = grid.catalogCache()->getDatabase(dbname);
+ if (!status.isOK()) {
+ if (status == ErrorCodes::DatabaseNotFound) {
+ result.append("info", "database does not exist");
+ return true;
}
- // Refresh the database metadata
- grid.catalogCache()->invalidate(dbname);
-
- auto status = grid.catalogCache()->getDatabase(dbname);
- if (!status.isOK()) {
- if (status == ErrorCodes::DatabaseNotFound) {
- result.append("info", "database does not exist");
- return true;
- }
-
- return appendCommandStatus(result, status.getStatus());
- }
-
- log() << "DROP DATABASE: " << dbname;
+ return appendCommandStatus(result, status.getStatus());
+ }
- shared_ptr<DBConfig> conf = status.getValue();
+ log() << "DROP DATABASE: " << dbname;
- // TODO: Make dropping logic saner and more tolerant of partial drops. This is
- // particularly important since a database drop can be aborted by *any* collection
- // with a distributed namespace lock taken (migrates/splits)
+ shared_ptr<DBConfig> conf = status.getValue();
- if (!conf->dropDatabase(errmsg)) {
- return false;
- }
+ // TODO: Make dropping logic saner and more tolerant of partial drops. This is
+ // particularly important since a database drop can be aborted by *any* collection
+ // with a distributed namespace lock taken (migrates/splits)
- result.append("dropped", dbname);
- return true;
+ if (!conf->dropDatabase(errmsg)) {
+ return false;
}
- } clusterDropDatabaseCmd;
+ result.append("dropped", dbname);
+ return true;
+ }
+
+} clusterDropDatabaseCmd;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_enable_sharding_cmd.cpp b/src/mongo/s/commands/cluster_enable_sharding_cmd.cpp
index 3c17bc7433e..62749bfbab2 100644
--- a/src/mongo/s/commands/cluster_enable_sharding_cmd.cpp
+++ b/src/mongo/s/commands/cluster_enable_sharding_cmd.cpp
@@ -48,77 +48,74 @@
namespace mongo {
namespace {
- class EnableShardingCmd : public Command {
- public:
- EnableShardingCmd() : Command("enableSharding", false, "enablesharding") { }
-
- virtual bool slaveOk() const {
- return true;
- }
-
- virtual bool adminOnly() const {
- return true;
- }
-
- virtual bool isWriteCommandForConfigServer() const {
- return false;
+class EnableShardingCmd : public Command {
+public:
+ EnableShardingCmd() : Command("enableSharding", false, "enablesharding") {}
+
+ virtual bool slaveOk() const {
+ return true;
+ }
+
+ virtual bool adminOnly() const {
+ return true;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(std::stringstream& help) const {
+ help << "Enable sharding for a database. "
+ << "(Use 'shardcollection' command afterwards.)\n"
+ << " { enablesharding : \"<dbname>\" }\n";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forDatabaseName(parseNs(dbname, cmdObj)),
+ ActionType::enableSharding)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
}
- virtual void help(std::stringstream& help) const {
- help << "Enable sharding for a database. "
- << "(Use 'shardcollection' command afterwards.)\n"
- << " { enablesharding : \"<dbname>\" }\n";
- }
+ return Status::OK();
+ }
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
+ virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
+ return cmdObj.firstElement().str();
+ }
- if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
- ResourcePattern::forDatabaseName(
- parseNs(dbname, cmdObj)),
- ActionType::enableSharding)) {
- return Status(ErrorCodes::Unauthorized, "Unauthorized");
- }
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname_unused,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ const std::string dbname = parseNs("", cmdObj);
- return Status::OK();
+ if (dbname.empty() || !nsIsDbOnly(dbname)) {
+ errmsg = "invalid db name specified: " + dbname;
+ return false;
}
- virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
- return cmdObj.firstElement().str();
+ if (dbname == "admin" || dbname == "config" || dbname == "local") {
+ errmsg = "can't shard " + dbname + " database";
+ return false;
}
- virtual bool run(OperationContext* txn,
- const std::string& dbname_unused,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
-
- const std::string dbname = parseNs("", cmdObj);
-
- if (dbname.empty() || !nsIsDbOnly(dbname)) {
- errmsg = "invalid db name specified: " + dbname;
- return false;
- }
-
- if (dbname == "admin" || dbname == "config" || dbname == "local") {
- errmsg = "can't shard " + dbname + " database";
- return false;
- }
-
- Status status = grid.catalogManager()->enableSharding(dbname);
- if (status.isOK()) {
- audit::logEnableSharding(ClientBasic::getCurrent(), dbname);
- }
+ Status status = grid.catalogManager()->enableSharding(dbname);
+ if (status.isOK()) {
+ audit::logEnableSharding(ClientBasic::getCurrent(), dbname);
+ }
- // Make sure to force update of any stale metadata
- grid.catalogCache()->invalidate(dbname);
+ // Make sure to force update of any stale metadata
+ grid.catalogCache()->invalidate(dbname);
- return appendCommandStatus(result, status);
- }
+ return appendCommandStatus(result, status);
+ }
- } enableShardingCmd;
+} enableShardingCmd;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_explain_cmd.cpp b/src/mongo/s/commands/cluster_explain_cmd.cpp
index 4fa094affed..9cb8bae6b9c 100644
--- a/src/mongo/s/commands/cluster_explain_cmd.cpp
+++ b/src/mongo/s/commands/cluster_explain_cmd.cpp
@@ -33,98 +33,107 @@
namespace mongo {
+/**
+ * Implements the explain command on mongos.
+ *
+ * "Old-style" explains (i.e. queries which have the $explain flag set), do not run
+ * through this path. Such explains will be supported for backwards compatibility,
+ * and must succeed in multiversion clusters.
+ *
+ * "New-style" explains use the explain command. When the explain command is routed
+ * through mongos, it is forwarded to all relevant shards. If *any* shard does not
+ * support a new-style explain, then the entire explain will fail (i.e. new-style
+ * explains cannot be used in multiversion clusters).
+ */
+class ClusterExplainCmd : public Command {
+ MONGO_DISALLOW_COPYING(ClusterExplainCmd);
+
+public:
+ ClusterExplainCmd() : Command("explain") {}
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
/**
- * Implements the explain command on mongos.
- *
- * "Old-style" explains (i.e. queries which have the $explain flag set), do not run
- * through this path. Such explains will be supported for backwards compatibility,
- * and must succeed in multiversion clusters.
- *
- * "New-style" explains use the explain command. When the explain command is routed
- * through mongos, it is forwarded to all relevant shards. If *any* shard does not
- * support a new-style explain, then the entire explain will fail (i.e. new-style
- * explains cannot be used in multiversion clusters).
+ * Running an explain on a secondary requires explicitly setting slaveOk.
*/
- class ClusterExplainCmd : public Command {
- MONGO_DISALLOW_COPYING(ClusterExplainCmd);
- public:
- ClusterExplainCmd() : Command("explain") { }
+ virtual bool slaveOk() const {
+ return false;
+ }
+ virtual bool slaveOverrideOk() const {
+ return true;
+ }
- virtual bool isWriteCommandForConfigServer() const { return false; }
+ virtual bool maintenanceOk() const {
+ return false;
+ }
- /**
- * Running an explain on a secondary requires explicitly setting slaveOk.
- */
- virtual bool slaveOk() const { return false; }
- virtual bool slaveOverrideOk() const { return true; }
+ virtual bool adminOnly() const {
+ return false;
+ }
- virtual bool maintenanceOk() const { return false; }
+ virtual void help(std::stringstream& help) const {
+ help << "explain database reads and writes";
+ }
- virtual bool adminOnly() const { return false; }
+ /**
+ * You are authorized to run an explain if you are authorized to run
+ * the command that you are explaining. The auth check is performed recursively
+ * on the nested command.
+ */
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ if (Object != cmdObj.firstElement().type()) {
+ return Status(ErrorCodes::BadValue, "explain command requires a nested object");
+ }
- virtual void help(std::stringstream& help) const {
- help << "explain database reads and writes";
+ BSONObj explainObj = cmdObj.firstElement().Obj();
+
+ Command* commToExplain = Command::findCommand(explainObj.firstElementFieldName());
+ if (NULL == commToExplain) {
+ mongoutils::str::stream ss;
+ ss << "unknown command: " << explainObj.firstElementFieldName();
+ return Status(ErrorCodes::CommandNotFound, ss);
}
- /**
- * You are authorized to run an explain if you are authorized to run
- * the command that you are explaining. The auth check is performed recursively
- * on the nested command.
- */
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
-
- if (Object != cmdObj.firstElement().type()) {
- return Status(ErrorCodes::BadValue, "explain command requires a nested object");
- }
-
- BSONObj explainObj = cmdObj.firstElement().Obj();
-
- Command* commToExplain = Command::findCommand(explainObj.firstElementFieldName());
- if (NULL == commToExplain) {
- mongoutils::str::stream ss;
- ss << "unknown command: " << explainObj.firstElementFieldName();
- return Status(ErrorCodes::CommandNotFound, ss);
- }
-
- return commToExplain->checkAuthForCommand(client, dbname, explainObj);
+ return commToExplain->checkAuthForCommand(client, dbname, explainObj);
+ }
+
+ virtual bool run(OperationContext* txn,
+ const std::string& dbName,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ ExplainCommon::Verbosity verbosity;
+ Status parseStatus = ExplainCommon::parseCmdBSON(cmdObj, &verbosity);
+ if (!parseStatus.isOK()) {
+ return appendCommandStatus(result, parseStatus);
}
- virtual bool run(OperationContext* txn,
- const std::string& dbName,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
-
- ExplainCommon::Verbosity verbosity;
- Status parseStatus = ExplainCommon::parseCmdBSON(cmdObj, &verbosity);
- if (!parseStatus.isOK()) {
- return appendCommandStatus(result, parseStatus);
- }
-
- // This is the nested command which we are explaining.
- BSONObj explainObj = cmdObj.firstElement().Obj();
-
- const std::string cmdName = explainObj.firstElementFieldName();
- Command* commToExplain = Command::findCommand(cmdName);
- if (NULL == commToExplain) {
- mongoutils::str::stream ss;
- ss << "Explain failed due to unknown command: " << cmdName;
- Status explainStatus(ErrorCodes::CommandNotFound, ss);
- return appendCommandStatus(result, explainStatus);
- }
-
- // Actually call the nested command's explain(...) method.
- Status explainStatus = commToExplain->explain(txn, dbName, explainObj, verbosity, &result);
- if (!explainStatus.isOK()) {
- return appendCommandStatus(result, explainStatus);
- }
-
- return true;
+ // This is the nested command which we are explaining.
+ BSONObj explainObj = cmdObj.firstElement().Obj();
+
+ const std::string cmdName = explainObj.firstElementFieldName();
+ Command* commToExplain = Command::findCommand(cmdName);
+ if (NULL == commToExplain) {
+ mongoutils::str::stream ss;
+ ss << "Explain failed due to unknown command: " << cmdName;
+ Status explainStatus(ErrorCodes::CommandNotFound, ss);
+ return appendCommandStatus(result, explainStatus);
}
- } cmdExplainCluster;
+ // Actually call the nested command's explain(...) method.
+ Status explainStatus = commToExplain->explain(txn, dbName, explainObj, verbosity, &result);
+ if (!explainStatus.isOK()) {
+ return appendCommandStatus(result, explainStatus);
+ }
+
+ return true;
+ }
+
+} cmdExplainCluster;
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp b/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp
index 8fcff46f1cd..4f14f66fe2c 100644
--- a/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp
+++ b/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp
@@ -49,192 +49,181 @@
namespace mongo {
namespace {
- using std::shared_ptr;
- using std::string;
- using std::vector;
+using std::shared_ptr;
+using std::string;
+using std::vector;
- class FindAndModifyCmd : public Command {
- public:
- FindAndModifyCmd() : Command("findAndModify", false, "findandmodify") { }
+class FindAndModifyCmd : public Command {
+public:
+ FindAndModifyCmd() : Command("findAndModify", false, "findandmodify") {}
- virtual bool slaveOk() const {
- return true;
- }
-
- virtual bool adminOnly() const {
- return false;
- }
-
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
-
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
-
- find_and_modify::addPrivilegesRequiredForFindAndModify(this, dbname, cmdObj, out);
- }
-
- virtual Status explain(OperationContext* txn,
- const std::string& dbName,
- const BSONObj& cmdObj,
- ExplainCommon::Verbosity verbosity,
- BSONObjBuilder* out) const {
-
- const string ns = parseNsCollectionRequired(dbName, cmdObj);
-
- auto status = grid.catalogCache()->getDatabase(dbName);
- uassertStatusOK(status);
+ virtual bool slaveOk() const {
+ return true;
+ }
- shared_ptr<DBConfig> conf = status.getValue();
+ virtual bool adminOnly() const {
+ return false;
+ }
- shared_ptr<Shard> shard;
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- if (!conf->isShardingEnabled() || !conf->isSharded(ns)) {
- shard = grid.shardRegistry()->getShard(conf->getPrimaryId());
- }
- else {
- shared_ptr<ChunkManager> chunkMgr = _getChunkManager(conf, ns);
-
- const BSONObj query = cmdObj.getObjectField("query");
-
- StatusWith<BSONObj> status = _getShardKey(chunkMgr, query);
- if (!status.isOK()) {
- return status.getStatus();
- }
-
- BSONObj shardKey = status.getValue();
- ChunkPtr chunk = chunkMgr->findIntersectingChunk(shardKey);
-
- shard = grid.shardRegistry()->getShard(chunk->getShardId());
- }
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ find_and_modify::addPrivilegesRequiredForFindAndModify(this, dbname, cmdObj, out);
+ }
- BSONObjBuilder explainCmd;
- ClusterExplain::wrapAsExplain(cmdObj, verbosity, &explainCmd);
+ virtual Status explain(OperationContext* txn,
+ const std::string& dbName,
+ const BSONObj& cmdObj,
+ ExplainCommon::Verbosity verbosity,
+ BSONObjBuilder* out) const {
+ const string ns = parseNsCollectionRequired(dbName, cmdObj);
- // Time how long it takes to run the explain command on the shard.
- Timer timer;
+ auto status = grid.catalogCache()->getDatabase(dbName);
+ uassertStatusOK(status);
- BSONObjBuilder result;
- bool ok = _runCommand(conf, shard->getId(), ns, explainCmd.obj(), result);
- long long millisElapsed = timer.millis();
+ shared_ptr<DBConfig> conf = status.getValue();
- if (!ok) {
- BSONObj res = result.obj();
- return Status(ErrorCodes::OperationFailed,
- str::stream() << "Explain for findAndModify failed: " << res);
- }
-
- Strategy::CommandResult cmdResult;
- cmdResult.shardTargetId = shard->getId();
- cmdResult.target = shard->getConnString();
- cmdResult.result = result.obj();
-
- vector<Strategy::CommandResult> shardResults;
- shardResults.push_back(cmdResult);
-
- return ClusterExplain::buildExplainResult(shardResults,
- ClusterExplain::kSingleShard,
- millisElapsed,
- out);
- }
-
- virtual bool run(OperationContext* txn,
- const std::string& dbName,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
-
- const string ns = parseNsCollectionRequired(dbName, cmdObj);
-
- // findAndModify should only be creating database if upsert is true, but this would
- // require that the parsing be pulled into this function.
- auto conf = uassertStatusOK(grid.implicitCreateDb(dbName));
- if (!conf->isShardingEnabled() || !conf->isSharded(ns)) {
- return _runCommand(conf, conf->getPrimaryId(), ns, cmdObj, result);
- }
+ shared_ptr<Shard> shard;
+ if (!conf->isShardingEnabled() || !conf->isSharded(ns)) {
+ shard = grid.shardRegistry()->getShard(conf->getPrimaryId());
+ } else {
shared_ptr<ChunkManager> chunkMgr = _getChunkManager(conf, ns);
const BSONObj query = cmdObj.getObjectField("query");
StatusWith<BSONObj> status = _getShardKey(chunkMgr, query);
if (!status.isOK()) {
- // Bad query
- return appendCommandStatus(result, status.getStatus());
+ return status.getStatus();
}
BSONObj shardKey = status.getValue();
ChunkPtr chunk = chunkMgr->findIntersectingChunk(shardKey);
- bool ok = _runCommand(conf, chunk->getShardId(), ns, cmdObj, result);
- if (ok) {
- // check whether split is necessary (using update object for size heuristic)
- if (Chunk::ShouldAutoSplit) {
- chunk->splitIfShould(cmdObj.getObjectField("update").objsize());
- }
- }
-
- return ok;
+ shard = grid.shardRegistry()->getShard(chunk->getShardId());
}
- private:
- shared_ptr<ChunkManager> _getChunkManager(shared_ptr<DBConfig> conf,
- const string& ns) const {
+ BSONObjBuilder explainCmd;
+ ClusterExplain::wrapAsExplain(cmdObj, verbosity, &explainCmd);
- shared_ptr<ChunkManager> chunkMgr = conf->getChunkManager(ns);
- massert(13002, "shard internal error chunk manager should never be null", chunkMgr);
+ // Time how long it takes to run the explain command on the shard.
+ Timer timer;
- return chunkMgr;
+ BSONObjBuilder result;
+ bool ok = _runCommand(conf, shard->getId(), ns, explainCmd.obj(), result);
+ long long millisElapsed = timer.millis();
+
+ if (!ok) {
+ BSONObj res = result.obj();
+ return Status(ErrorCodes::OperationFailed,
+ str::stream() << "Explain for findAndModify failed: " << res);
}
- StatusWith<BSONObj> _getShardKey(shared_ptr<ChunkManager> chunkMgr,
- const BSONObj& query) const {
+ Strategy::CommandResult cmdResult;
+ cmdResult.shardTargetId = shard->getId();
+ cmdResult.target = shard->getConnString();
+ cmdResult.result = result.obj();
+
+ vector<Strategy::CommandResult> shardResults;
+ shardResults.push_back(cmdResult);
+
+ return ClusterExplain::buildExplainResult(
+ shardResults, ClusterExplain::kSingleShard, millisElapsed, out);
+ }
+
+ virtual bool run(OperationContext* txn,
+ const std::string& dbName,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ const string ns = parseNsCollectionRequired(dbName, cmdObj);
+
+ // findAndModify should only be creating database if upsert is true, but this would
+ // require that the parsing be pulled into this function.
+ auto conf = uassertStatusOK(grid.implicitCreateDb(dbName));
+ if (!conf->isShardingEnabled() || !conf->isSharded(ns)) {
+ return _runCommand(conf, conf->getPrimaryId(), ns, cmdObj, result);
+ }
- // Verify that the query has an equality predicate using the shard key
- StatusWith<BSONObj> status =
- chunkMgr->getShardKeyPattern().extractShardKeyFromQuery(query);
+ shared_ptr<ChunkManager> chunkMgr = _getChunkManager(conf, ns);
- if (!status.isOK()) {
- return status;
- }
+ const BSONObj query = cmdObj.getObjectField("query");
- BSONObj shardKey = status.getValue();
+ StatusWith<BSONObj> status = _getShardKey(chunkMgr, query);
+ if (!status.isOK()) {
+ // Bad query
+ return appendCommandStatus(result, status.getStatus());
+ }
- if (shardKey.isEmpty()) {
- return Status(ErrorCodes::ShardKeyNotFound,
- "query for sharded findAndModify must have shardkey");
- }
+ BSONObj shardKey = status.getValue();
+ ChunkPtr chunk = chunkMgr->findIntersectingChunk(shardKey);
- return shardKey;
+ bool ok = _runCommand(conf, chunk->getShardId(), ns, cmdObj, result);
+ if (ok) {
+ // check whether split is necessary (using update object for size heuristic)
+ if (Chunk::ShouldAutoSplit) {
+ chunk->splitIfShould(cmdObj.getObjectField("update").objsize());
+ }
}
- bool _runCommand(DBConfigPtr conf,
- const ShardId& shardId,
- const string& ns,
- const BSONObj& cmdObj,
- BSONObjBuilder& result) const {
+ return ok;
+ }
- BSONObj res;
+private:
+ shared_ptr<ChunkManager> _getChunkManager(shared_ptr<DBConfig> conf, const string& ns) const {
+ shared_ptr<ChunkManager> chunkMgr = conf->getChunkManager(ns);
+ massert(13002, "shard internal error chunk manager should never be null", chunkMgr);
- const auto shard = grid.shardRegistry()->getShard(shardId);
- ShardConnection conn(shard->getConnString(), ns);
- bool ok = conn->runCommand(conf->name(), cmdObj, res);
- conn.done();
+ return chunkMgr;
+ }
- // RecvStaleConfigCode is the code for RecvStaleConfigException.
- if (!ok && res.getIntField("code") == RecvStaleConfigCode) {
- // Command code traps this exception and re-runs
- throw RecvStaleConfigException("FindAndModify", res);
- }
+ StatusWith<BSONObj> _getShardKey(shared_ptr<ChunkManager> chunkMgr,
+ const BSONObj& query) const {
+ // Verify that the query has an equality predicate using the shard key
+ StatusWith<BSONObj> status = chunkMgr->getShardKeyPattern().extractShardKeyFromQuery(query);
+
+ if (!status.isOK()) {
+ return status;
+ }
+
+ BSONObj shardKey = status.getValue();
- result.appendElements(res);
- return ok;
+ if (shardKey.isEmpty()) {
+ return Status(ErrorCodes::ShardKeyNotFound,
+ "query for sharded findAndModify must have shardkey");
}
- } findAndModifyCmd;
+ return shardKey;
+ }
+
+ bool _runCommand(DBConfigPtr conf,
+ const ShardId& shardId,
+ const string& ns,
+ const BSONObj& cmdObj,
+ BSONObjBuilder& result) const {
+ BSONObj res;
+
+ const auto shard = grid.shardRegistry()->getShard(shardId);
+ ShardConnection conn(shard->getConnString(), ns);
+ bool ok = conn->runCommand(conf->name(), cmdObj, res);
+ conn.done();
+
+ // RecvStaleConfigCode is the code for RecvStaleConfigException.
+ if (!ok && res.getIntField("code") == RecvStaleConfigCode) {
+ // Command code traps this exception and re-runs
+ throw RecvStaleConfigException("FindAndModify", res);
+ }
+
+ result.appendElements(res);
+ return ok;
+ }
+
+} findAndModifyCmd;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_find_cmd.cpp b/src/mongo/s/commands/cluster_find_cmd.cpp
index f85b65fe39e..b40e919ca3c 100644
--- a/src/mongo/s/commands/cluster_find_cmd.cpp
+++ b/src/mongo/s/commands/cluster_find_cmd.cpp
@@ -37,111 +37,118 @@
namespace mongo {
namespace {
- using std::unique_ptr;
- using std::string;
- using std::vector;
+using std::unique_ptr;
+using std::string;
+using std::vector;
- /**
- * Implements the find command on mongos.
- *
- * TODO: this is just a placeholder. It needs to be implemented for real under SERVER-15176.
- */
- class ClusterFindCmd : public Command {
- MONGO_DISALLOW_COPYING(ClusterFindCmd);
- public:
- ClusterFindCmd() : Command("find") { }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
+/**
+ * Implements the find command on mongos.
+ *
+ * TODO: this is just a placeholder. It needs to be implemented for real under SERVER-15176.
+ */
+class ClusterFindCmd : public Command {
+ MONGO_DISALLOW_COPYING(ClusterFindCmd);
- virtual bool slaveOk() const { return false; }
+public:
+ ClusterFindCmd() : Command("find") {}
- virtual bool slaveOverrideOk() const { return true; }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- virtual bool maintenanceOk() const { return false; }
+ virtual bool slaveOk() const {
+ return false;
+ }
- virtual bool adminOnly() const { return false; }
+ virtual bool slaveOverrideOk() const {
+ return true;
+ }
- virtual void help(std::stringstream& help) const {
- help << "query for documents";
- }
+ virtual bool maintenanceOk() const {
+ return false;
+ }
- /**
- * In order to run the find command, you must be authorized for the "find" action
- * type on the collection.
- */
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
+ virtual bool adminOnly() const {
+ return false;
+ }
- AuthorizationSession* authzSession = AuthorizationSession::get(client);
- ResourcePattern pattern = parseResourcePattern(dbname, cmdObj);
+ virtual void help(std::stringstream& help) const {
+ help << "query for documents";
+ }
- if (authzSession->isAuthorizedForActionsOnResource(pattern, ActionType::find)) {
- return Status::OK();
- }
+ /**
+ * In order to run the find command, you must be authorized for the "find" action
+ * type on the collection.
+ */
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ AuthorizationSession* authzSession = AuthorizationSession::get(client);
+ ResourcePattern pattern = parseResourcePattern(dbname, cmdObj);
+
+ if (authzSession->isAuthorizedForActionsOnResource(pattern, ActionType::find)) {
+ return Status::OK();
+ }
- return Status(ErrorCodes::Unauthorized, "unauthorized");
+ return Status(ErrorCodes::Unauthorized, "unauthorized");
+ }
+
+ virtual Status explain(OperationContext* txn,
+ const std::string& dbname,
+ const BSONObj& cmdObj,
+ ExplainCommon::Verbosity verbosity,
+ BSONObjBuilder* out) const {
+ const string fullns = parseNs(dbname, cmdObj);
+ const NamespaceString nss(fullns);
+ if (!nss.isValid()) {
+ return {ErrorCodes::InvalidNamespace,
+ str::stream() << "Invalid collection name: " << nss.ns()};
}
- virtual Status explain(OperationContext* txn,
- const std::string& dbname,
- const BSONObj& cmdObj,
- ExplainCommon::Verbosity verbosity,
- BSONObjBuilder* out) const {
-
- const string fullns = parseNs(dbname, cmdObj);
- const NamespaceString nss(fullns);
- if (!nss.isValid()) {
- return {ErrorCodes::InvalidNamespace,
- str::stream() << "Invalid collection name: " << nss.ns()};
- }
-
- // Parse the command BSON to a LiteParsedQuery.
- bool isExplain = true;
- auto lpqStatus = LiteParsedQuery::makeFromFindCommand(nss, cmdObj, isExplain);
- if (!lpqStatus.isOK()) {
- return lpqStatus.getStatus();
- }
-
- auto& lpq = lpqStatus.getValue();
-
- BSONObjBuilder explainCmdBob;
- ClusterExplain::wrapAsExplain(cmdObj, verbosity, &explainCmdBob);
-
- // We will time how long it takes to run the commands on the shards.
- Timer timer;
-
- vector<Strategy::CommandResult> shardResults;
- Strategy::commandOp(dbname,
- explainCmdBob.obj(),
- lpq->getOptions(),
- fullns,
- lpq->getFilter(),
- &shardResults);
-
- long long millisElapsed = timer.millis();
-
- const char* mongosStageName = ClusterExplain::getStageNameForReadOp(shardResults, cmdObj);
-
- return ClusterExplain::buildExplainResult(shardResults,
- mongosStageName,
- millisElapsed,
- out);
+ // Parse the command BSON to a LiteParsedQuery.
+ bool isExplain = true;
+ auto lpqStatus = LiteParsedQuery::makeFromFindCommand(nss, cmdObj, isExplain);
+ if (!lpqStatus.isOK()) {
+ return lpqStatus.getStatus();
}
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj, int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
+ auto& lpq = lpqStatus.getValue();
- // Currently only explains of finds run through the find command. Queries that are not
- // explained use the legacy OP_QUERY path.
- errmsg = "find command not yet implemented";
- return false;
- }
+ BSONObjBuilder explainCmdBob;
+ ClusterExplain::wrapAsExplain(cmdObj, verbosity, &explainCmdBob);
+
+ // We will time how long it takes to run the commands on the shards.
+ Timer timer;
+
+ vector<Strategy::CommandResult> shardResults;
+ Strategy::commandOp(dbname,
+ explainCmdBob.obj(),
+ lpq->getOptions(),
+ fullns,
+ lpq->getFilter(),
+ &shardResults);
+
+ long long millisElapsed = timer.millis();
+
+ const char* mongosStageName = ClusterExplain::getStageNameForReadOp(shardResults, cmdObj);
+
+ return ClusterExplain::buildExplainResult(
+ shardResults, mongosStageName, millisElapsed, out);
+ }
+
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ // Currently only explains of finds run through the find command. Queries that are not
+ // explained use the legacy OP_QUERY path.
+ errmsg = "find command not yet implemented";
+ return false;
+ }
- } cmdFindCluster;
+} cmdFindCluster;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_flush_router_config_cmd.cpp b/src/mongo/s/commands/cluster_flush_router_config_cmd.cpp
index 19477622b64..dbb52870602 100644
--- a/src/mongo/s/commands/cluster_flush_router_config_cmd.cpp
+++ b/src/mongo/s/commands/cluster_flush_router_config_cmd.cpp
@@ -36,48 +36,47 @@
namespace mongo {
namespace {
- class FlushRouterConfigCmd : public Command {
- public:
- FlushRouterConfigCmd() : Command("flushRouterConfig", false, "flushrouterconfig") { }
+class FlushRouterConfigCmd : public Command {
+public:
+ FlushRouterConfigCmd() : Command("flushRouterConfig", false, "flushrouterconfig") {}
- virtual bool slaveOk() const {
- return true;
- }
+ virtual bool slaveOk() const {
+ return true;
+ }
- virtual bool adminOnly() const {
- return true;
- }
+ virtual bool adminOnly() const {
+ return true;
+ }
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- virtual void help(std::stringstream& help) const {
- help << "flush all router config";
- }
+ virtual void help(std::stringstream& help) const {
+ help << "flush all router config";
+ }
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::flushRouterConfig);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
- }
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::flushRouterConfig);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ grid.catalogCache()->invalidateAll();
- grid.catalogCache()->invalidateAll();
+ result.appendBool("flushed", true);
+ return true;
+ }
- result.appendBool("flushed", true);
- return true;
- }
+} flushRouterConfigCmd;
- } flushRouterConfigCmd;
-
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_fsync_cmd.cpp b/src/mongo/s/commands/cluster_fsync_cmd.cpp
index 99ae1cf8fea..f9dc78ee9d7 100644
--- a/src/mongo/s/commands/cluster_fsync_cmd.cpp
+++ b/src/mongo/s/commands/cluster_fsync_cmd.cpp
@@ -36,77 +36,76 @@
namespace mongo {
namespace {
- class FsyncCommand : public Command {
- public:
- FsyncCommand() : Command("fsync", false, "fsync") { }
-
- virtual bool slaveOk() const {
- return true;
- }
-
- virtual bool adminOnly() const {
- return true;
- }
-
- virtual bool isWriteCommandForConfigServer() const {
+class FsyncCommand : public Command {
+public:
+ FsyncCommand() : Command("fsync", false, "fsync") {}
+
+ virtual bool slaveOk() const {
+ return true;
+ }
+
+ virtual bool adminOnly() const {
+ return true;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(std::stringstream& help) const {
+ help << "invoke fsync on all shards belonging to the cluster";
+ }
+
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::fsync);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
+
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ if (cmdObj["lock"].trueValue()) {
+ errmsg = "can't do lock through mongos";
return false;
}
- virtual void help(std::stringstream& help) const {
- help << "invoke fsync on all shards belonging to the cluster";
- }
+ BSONObjBuilder sub;
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::fsync);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
- }
+ bool ok = true;
+ int numFiles = 0;
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
+ std::vector<ShardId> shardIds;
+ grid.shardRegistry()->getAllShardIds(&shardIds);
- if (cmdObj["lock"].trueValue()) {
- errmsg = "can't do lock through mongos";
- return false;
+ for (const ShardId& shardId : shardIds) {
+ const auto s = grid.shardRegistry()->getShard(shardId);
+ if (!s) {
+ continue;
}
- BSONObjBuilder sub;
+ BSONObj x = s->runCommand("admin", "fsync");
+ sub.append(s->getId(), x);
- bool ok = true;
- int numFiles = 0;
-
- std::vector<ShardId> shardIds;
- grid.shardRegistry()->getAllShardIds(&shardIds);
-
- for (const ShardId& shardId : shardIds) {
- const auto s = grid.shardRegistry()->getShard(shardId);
- if (!s) {
- continue;
- }
-
- BSONObj x = s->runCommand("admin", "fsync");
- sub.append(s->getId(), x);
-
- if (!x["ok"].trueValue()) {
- ok = false;
- errmsg = x["errmsg"].String();
- }
-
- numFiles += x["numFiles"].numberInt();
+ if (!x["ok"].trueValue()) {
+ ok = false;
+ errmsg = x["errmsg"].String();
}
- result.append("numFiles", numFiles);
- result.append("all", sub.obj());
- return ok;
+ numFiles += x["numFiles"].numberInt();
}
- } fsyncCmd;
+ result.append("numFiles", numFiles);
+ result.append("all", sub.obj());
+ return ok;
+ }
+
+} fsyncCmd;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_get_last_error_cmd.cpp b/src/mongo/s/commands/cluster_get_last_error_cmd.cpp
index 45a1cc9792e..1e219d77114 100644
--- a/src/mongo/s/commands/cluster_get_last_error_cmd.cpp
+++ b/src/mongo/s/commands/cluster_get_last_error_cmd.cpp
@@ -41,174 +41,163 @@
namespace mongo {
namespace {
- class GetLastErrorCmd : public Command {
- public:
- GetLastErrorCmd() : Command("getLastError", false, "getlasterror") { }
-
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
-
- virtual bool slaveOk() const {
- return true;
- }
-
- virtual void help(std::stringstream& help) const {
- help << "check for an error on the last command executed";
- }
-
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
-
- // No auth required for getlasterror
+class GetLastErrorCmd : public Command {
+public:
+ GetLastErrorCmd() : Command("getLastError", false, "getlasterror") {}
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual bool slaveOk() const {
+ return true;
+ }
+
+ virtual void help(std::stringstream& help) const {
+ help << "check for an error on the last command executed";
+ }
+
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ // No auth required for getlasterror
+ }
+
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ // Mongos GLE - finicky.
+ //
+ // To emulate mongod, we first append any write errors we had, then try to append
+ // write concern error if there was no write error. We need to contact the previous
+ // shards regardless to maintain 2.4 behavior.
+ //
+ // If there are any unexpected or connectivity errors when calling GLE, fail the
+ // command.
+ //
+ // Finally, report the write concern errors IF we don't already have an error.
+ // If we only get one write concern error back, report that, otherwise report an
+ // aggregated error.
+ //
+ // TODO: Do we need to contact the prev shards regardless - do we care that much
+ // about 2.4 behavior?
+ //
+
+ LastError* le = &LastError::get(cc());
+ le->disable();
+
+
+ // Write commands always have the error stored in the mongos last error
+ bool errorOccurred = false;
+ if (le->getNPrev() == 1) {
+ errorOccurred = le->appendSelf(result, false);
}
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
-
- // Mongos GLE - finicky.
- //
- // To emulate mongod, we first append any write errors we had, then try to append
- // write concern error if there was no write error. We need to contact the previous
- // shards regardless to maintain 2.4 behavior.
- //
- // If there are any unexpected or connectivity errors when calling GLE, fail the
- // command.
- //
- // Finally, report the write concern errors IF we don't already have an error.
- // If we only get one write concern error back, report that, otherwise report an
- // aggregated error.
- //
- // TODO: Do we need to contact the prev shards regardless - do we care that much
- // about 2.4 behavior?
- //
-
- LastError *le = &LastError::get(cc());
- le->disable();
-
-
- // Write commands always have the error stored in the mongos last error
- bool errorOccurred = false;
- if (le->getNPrev() == 1) {
- errorOccurred = le->appendSelf(result, false);
- }
-
- // For compatibility with 2.4 sharded GLE, we always enforce the write concern
- // across all shards.
- const HostOpTimeMap hostOpTimes(ClusterLastErrorInfo::get(cc()).getPrevHostOpTimes());
- HostOpTimeMap resolvedHostOpTimes;
-
- Status status(Status::OK());
- for (HostOpTimeMap::const_iterator it = hostOpTimes.begin();
- it != hostOpTimes.end();
- ++it) {
-
- const ConnectionString& shardEndpoint = it->first;
- const HostOpTime& hot = it->second;
+ // For compatibility with 2.4 sharded GLE, we always enforce the write concern
+ // across all shards.
+ const HostOpTimeMap hostOpTimes(ClusterLastErrorInfo::get(cc()).getPrevHostOpTimes());
+ HostOpTimeMap resolvedHostOpTimes;
- ConnectionString resolvedHost;
- status = DBClientShardResolver::findMaster(shardEndpoint, &resolvedHost);
- if (!status.isOK()) {
- break;
- }
-
- resolvedHostOpTimes[resolvedHost] = hot;
- }
-
- DBClientMultiCommand dispatcher;
- std::vector<LegacyWCResponse> wcResponses;
- if (status.isOK()) {
- status = enforceLegacyWriteConcern(&dispatcher,
- dbname,
- cmdObj,
- resolvedHostOpTimes,
- &wcResponses);
- }
-
- // Don't forget about our last hosts, reset the client info
- ClusterLastErrorInfo::get(cc()).disableForCommand();
-
- // We're now done contacting all remote servers, just report results
+ Status status(Status::OK());
+ for (HostOpTimeMap::const_iterator it = hostOpTimes.begin(); it != hostOpTimes.end();
+ ++it) {
+ const ConnectionString& shardEndpoint = it->first;
+ const HostOpTime& hot = it->second;
+ ConnectionString resolvedHost;
+ status = DBClientShardResolver::findMaster(shardEndpoint, &resolvedHost);
if (!status.isOK()) {
- // Return immediately if we failed to contact a shard, unexpected GLE issue
- // Can't return code, since it may have been set above (2.4 compatibility)
- result.append("errmsg", status.reason());
- return false;
+ break;
}
- // Go through all the write concern responses and find errors
- BSONArrayBuilder shards;
- BSONObjBuilder shardRawGLE;
- BSONArrayBuilder errors;
- BSONArrayBuilder errorRawGLE;
+ resolvedHostOpTimes[resolvedHost] = hot;
+ }
- int numWCErrors = 0;
- const LegacyWCResponse* lastErrResponse = NULL;
+ DBClientMultiCommand dispatcher;
+ std::vector<LegacyWCResponse> wcResponses;
+ if (status.isOK()) {
+ status = enforceLegacyWriteConcern(
+ &dispatcher, dbname, cmdObj, resolvedHostOpTimes, &wcResponses);
+ }
- for (std::vector<LegacyWCResponse>::const_iterator it = wcResponses.begin();
- it != wcResponses.end();
- ++it) {
+ // Don't forget about our last hosts, reset the client info
+ ClusterLastErrorInfo::get(cc()).disableForCommand();
- const LegacyWCResponse& wcResponse = *it;
+ // We're now done contacting all remote servers, just report results
- shards.append(wcResponse.shardHost);
- shardRawGLE.append(wcResponse.shardHost, wcResponse.gleResponse);
+ if (!status.isOK()) {
+ // Return immediately if we failed to contact a shard, unexpected GLE issue
+ // Can't return code, since it may have been set above (2.4 compatibility)
+ result.append("errmsg", status.reason());
+ return false;
+ }
- if (!wcResponse.errToReport.empty()) {
- numWCErrors++;
- lastErrResponse = &wcResponse;
- errors.append(wcResponse.errToReport);
- errorRawGLE.append(wcResponse.gleResponse);
- }
- }
+ // Go through all the write concern responses and find errors
+ BSONArrayBuilder shards;
+ BSONObjBuilder shardRawGLE;
+ BSONArrayBuilder errors;
+ BSONArrayBuilder errorRawGLE;
- // Always report what we found to match 2.4 behavior and for debugging
- if (wcResponses.size() == 1u) {
- result.append("singleShard", wcResponses.front().shardHost);
- }
- else {
- result.append("shards", shards.arr());
- result.append("shardRawGLE", shardRawGLE.obj());
- }
+ int numWCErrors = 0;
+ const LegacyWCResponse* lastErrResponse = NULL;
- // Suppress write concern errors if a write error occurred, to match mongod behavior
- if (errorOccurred || numWCErrors == 0) {
- // Still need to return err
- if (!errorOccurred) {
- result.appendNull("err");
- }
+ for (std::vector<LegacyWCResponse>::const_iterator it = wcResponses.begin();
+ it != wcResponses.end();
+ ++it) {
+ const LegacyWCResponse& wcResponse = *it;
- return true;
- }
+ shards.append(wcResponse.shardHost);
+ shardRawGLE.append(wcResponse.shardHost, wcResponse.gleResponse);
- if (numWCErrors == 1) {
- // Return the single write concern error we found, err should be set or not
- // from gle response
- result.appendElements(lastErrResponse->gleResponse);
- return lastErrResponse->gleResponse["ok"].trueValue();
+ if (!wcResponse.errToReport.empty()) {
+ numWCErrors++;
+ lastErrResponse = &wcResponse;
+ errors.append(wcResponse.errToReport);
+ errorRawGLE.append(wcResponse.gleResponse);
}
- else {
+ }
- // Return a generic combined WC error message
- result.append("errs", errors.arr());
- result.append("errObjects", errorRawGLE.arr());
+ // Always report what we found to match 2.4 behavior and for debugging
+ if (wcResponses.size() == 1u) {
+ result.append("singleShard", wcResponses.front().shardHost);
+ } else {
+ result.append("shards", shards.arr());
+ result.append("shardRawGLE", shardRawGLE.obj());
+ }
- // Need to always return err
+ // Suppress write concern errors if a write error occurred, to match mongod behavior
+ if (errorOccurred || numWCErrors == 0) {
+ // Still need to return err
+ if (!errorOccurred) {
result.appendNull("err");
-
- return appendCommandStatus(result,
- Status(ErrorCodes::WriteConcernFailed,
- "multiple write concern errors occurred"));
}
+
+ return true;
+ }
+
+ if (numWCErrors == 1) {
+ // Return the single write concern error we found, err should be set or not
+ // from gle response
+ result.appendElements(lastErrResponse->gleResponse);
+ return lastErrResponse->gleResponse["ok"].trueValue();
+ } else {
+ // Return a generic combined WC error message
+ result.append("errs", errors.arr());
+ result.append("errObjects", errorRawGLE.arr());
+
+ // Need to always return err
+ result.appendNull("err");
+
+ return appendCommandStatus(
+ result,
+ Status(ErrorCodes::WriteConcernFailed, "multiple write concern errors occurred"));
}
+ }
- } cmdGetLastError;
+} cmdGetLastError;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_get_prev_error_cmd.cpp b/src/mongo/s/commands/cluster_get_prev_error_cmd.cpp
index 5e04c0471ea..c0dcdfc618c 100644
--- a/src/mongo/s/commands/cluster_get_prev_error_cmd.cpp
+++ b/src/mongo/s/commands/cluster_get_prev_error_cmd.cpp
@@ -38,41 +38,39 @@
namespace mongo {
namespace {
- class GetPrevErrorCmd : public Command {
- public:
- GetPrevErrorCmd() : Command("getPrevError", false, "getpreverror") { }
+class GetPrevErrorCmd : public Command {
+public:
+ GetPrevErrorCmd() : Command("getPrevError", false, "getpreverror") {}
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- virtual bool slaveOk() const {
- return true;
- }
+ virtual bool slaveOk() const {
+ return true;
+ }
- virtual void help(std::stringstream& help) const {
- help << "get previous error (since last reseterror command)";
- }
+ virtual void help(std::stringstream& help) const {
+ help << "get previous error (since last reseterror command)";
+ }
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ // No auth required
+ }
- // No auth required
- }
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ errmsg += "getpreverror not supported for sharded environments";
+ return false;
+ }
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
+} cmdGetPrevError;
- errmsg += "getpreverror not supported for sharded environments";
- return false;
- }
-
- } cmdGetPrevError;
-
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_get_shard_map_cmd.cpp b/src/mongo/s/commands/cluster_get_shard_map_cmd.cpp
index cd5ee8608fe..0fcdc77d6fc 100644
--- a/src/mongo/s/commands/cluster_get_shard_map_cmd.cpp
+++ b/src/mongo/s/commands/cluster_get_shard_map_cmd.cpp
@@ -38,50 +38,51 @@
namespace mongo {
namespace {
- class CmdGetShardMap : public Command {
- public:
- CmdGetShardMap() : Command("getShardMap") { }
+class CmdGetShardMap : public Command {
+public:
+ CmdGetShardMap() : Command("getShardMap") {}
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- virtual bool slaveOk() const {
- return true;
- }
+ virtual bool slaveOk() const {
+ return true;
+ }
- virtual void help(std::stringstream& help) const {
- help << "lists the set of shards known to this instance";
- }
+ virtual void help(std::stringstream& help) const {
+ help << "lists the set of shards known to this instance";
+ }
- virtual bool adminOnly() const { return true; }
+ virtual bool adminOnly() const {
+ return true;
+ }
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::getShardMap);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
- }
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::getShardMap);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
-
- // MongoD instances do not know that they are part of a sharded cluster until they
- // receive a setShardVersion command and that's when the catalog manager and the shard
- // registry get initialized.
- if (grid.shardRegistry()) {
- grid.shardRegistry()->toBSON(&result);
- }
-
- return true;
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ // MongoD instances do not know that they are part of a sharded cluster until they
+ // receive a setShardVersion command and that's when the catalog manager and the shard
+ // registry get initialized.
+ if (grid.shardRegistry()) {
+ grid.shardRegistry()->toBSON(&result);
}
- } getShardMapCmd;
+ return true;
+ }
+
+} getShardMapCmd;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_get_shard_version_cmd.cpp b/src/mongo/s/commands/cluster_get_shard_version_cmd.cpp
index 486906aaf4a..54d88f7d6bb 100644
--- a/src/mongo/s/commands/cluster_get_shard_version_cmd.cpp
+++ b/src/mongo/s/commands/cluster_get_shard_version_cmd.cpp
@@ -42,86 +42,83 @@
namespace mongo {
- using std::shared_ptr;
+using std::shared_ptr;
namespace {
- class GetShardVersion : public Command {
- public:
- GetShardVersion() : Command("getShardVersion", false, "getshardversion") { }
-
- virtual bool slaveOk() const {
- return true;
+class GetShardVersion : public Command {
+public:
+ GetShardVersion() : Command("getShardVersion", false, "getshardversion") {}
+
+ virtual bool slaveOk() const {
+ return true;
+ }
+
+ virtual bool adminOnly() const {
+ return true;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(std::stringstream& help) const {
+ help << " example: { getShardVersion : 'alleyinsider.foo' } ";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname, cmdObj))),
+ ActionType::getShardVersion)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
}
- virtual bool adminOnly() const {
- return true;
+ return Status::OK();
+ }
+
+ virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
+ return parseNsFullyQualified(dbname, cmdObj);
+ }
+
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ const NamespaceString nss(parseNs(dbname, cmdObj));
+ if (nss.size() == 0) {
+ return appendCommandStatus(
+ result, Status(ErrorCodes::InvalidNamespace, "no namespace specified"));
}
- virtual bool isWriteCommandForConfigServer() const {
- return false;
+ auto status = grid.catalogCache()->getDatabase(nss.db().toString());
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status.getStatus());
}
- virtual void help(std::stringstream& help) const {
- help << " example: { getShardVersion : 'alleyinsider.foo' } ";
+ std::shared_ptr<DBConfig> config = status.getValue();
+ if (!config->isSharded(nss.ns())) {
+ return appendCommandStatus(
+ result,
+ Status(ErrorCodes::NamespaceNotSharded, "ns [" + nss.ns() + " is not sharded."));
}
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
-
- if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
- ResourcePattern::forExactNamespace(
- NamespaceString(parseNs(dbname,
- cmdObj))),
- ActionType::getShardVersion)) {
- return Status(ErrorCodes::Unauthorized, "Unauthorized");
- }
-
- return Status::OK();
+ ChunkManagerPtr cm = config->getChunkManagerIfExists(nss.ns());
+ if (!cm) {
+ errmsg = "no chunk manager?";
+ return false;
}
- virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
- return parseNsFullyQualified(dbname, cmdObj);
- }
+ cm->_printChunks();
+ cm->getVersion().addToBSON(result);
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
-
- const NamespaceString nss(parseNs(dbname, cmdObj));
- if (nss.size() == 0) {
- return appendCommandStatus(result, Status(ErrorCodes::InvalidNamespace,
- "no namespace specified"));
- }
-
- auto status = grid.catalogCache()->getDatabase(nss.db().toString());
- if (!status.isOK()) {
- return appendCommandStatus(result, status.getStatus());
- }
-
- std::shared_ptr<DBConfig> config = status.getValue();
- if (!config->isSharded(nss.ns())) {
- return appendCommandStatus(result, Status(ErrorCodes::NamespaceNotSharded,
- "ns [" + nss.ns() + " is not sharded."));
- }
-
- ChunkManagerPtr cm = config->getChunkManagerIfExists(nss.ns());
- if (!cm) {
- errmsg = "no chunk manager?";
- return false;
- }
-
- cm->_printChunks();
- cm->getVersion().addToBSON(result);
-
- return true;
- }
+ return true;
+ }
- } getShardVersionCmd;
+} getShardVersionCmd;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_index_filter_cmd.cpp b/src/mongo/s/commands/cluster_index_filter_cmd.cpp
index 323ad646a33..d3465a4bb48 100644
--- a/src/mongo/s/commands/cluster_index_filter_cmd.cpp
+++ b/src/mongo/s/commands/cluster_index_filter_cmd.cpp
@@ -38,137 +38,135 @@
namespace mongo {
- using std::string;
- using std::stringstream;
- using std::vector;
+using std::string;
+using std::stringstream;
+using std::vector;
- /**
- * Base class for mongos index filter commands.
- * Cluster index filter commands don't do much more than
- * forwarding the commands to all shards and combining the results.
- */
- class ClusterIndexFilterCmd : public Command {
+/**
+ * Base class for mongos index filter commands.
+ * Cluster index filter commands don't do much more than
+ * forwarding the commands to all shards and combining the results.
+ */
+class ClusterIndexFilterCmd : public Command {
MONGO_DISALLOW_COPYING(ClusterIndexFilterCmd);
- public:
- virtual ~ClusterIndexFilterCmd() {
- }
+public:
+ virtual ~ClusterIndexFilterCmd() {}
- bool slaveOk() const {
- return false;
- }
+ bool slaveOk() const {
+ return false;
+ }
- bool slaveOverrideOk() const {
- return true;
- }
+ bool slaveOverrideOk() const {
+ return true;
+ }
- virtual bool isWriteCommandForConfigServer() const { return false; }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- void help(stringstream& ss) const {
- ss << _helpText;
- }
+ void help(stringstream& ss) const {
+ ss << _helpText;
+ }
- Status checkAuthForCommand( ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj ) {
- AuthorizationSession* authzSession = AuthorizationSession::get(client);
- ResourcePattern pattern = parseResourcePattern(dbname, cmdObj);
-
- if (authzSession->isAuthorizedForActionsOnResource(pattern,
- ActionType::planCacheIndexFilter)) {
- return Status::OK();
- }
-
- return Status(ErrorCodes::Unauthorized, "unauthorized");
- }
+ Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ AuthorizationSession* authzSession = AuthorizationSession::get(client);
+ ResourcePattern pattern = parseResourcePattern(dbname, cmdObj);
- // Cluster plan cache command entry point.
- bool run(OperationContext* txn, const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result);
-
- public:
-
- /**
- * Instantiates a command that can be invoked by "name", which will be described by
- * "helpText", and will require privilege "actionType" to run.
- */
- ClusterIndexFilterCmd( const std::string& name, const std::string& helpText) :
- Command( name ), _helpText( helpText ) {
+ if (authzSession->isAuthorizedForActionsOnResource(pattern,
+ ActionType::planCacheIndexFilter)) {
+ return Status::OK();
}
- private:
-
- std::string _helpText;
- };
-
- //
- // Cluster index filter command implementation(s) below
- //
-
- bool ClusterIndexFilterCmd::run(OperationContext* txn, const std::string& dbName,
- BSONObj& cmdObj,
- int options,
- std::string& errMsg,
- BSONObjBuilder& result) {
- const std::string fullns = parseNs(dbName, cmdObj);
- NamespaceString nss(fullns);
-
- // Dispatch command to all the shards.
- // Targeted shard commands are generally data-dependent but index filter
- // commands are tied to query shape (data has no effect on query shape).
- vector<Strategy::CommandResult> results;
- Strategy::commandOp(dbName, cmdObj, options, nss.ns(), BSONObj(), &results);
-
- // Set value of first shard result's "ok" field.
- bool clusterCmdResult = true;
-
- for (vector<Strategy::CommandResult>::const_iterator i = results.begin();
- i != results.end(); ++i) {
- const Strategy::CommandResult& cmdResult = *i;
-
- // XXX: In absence of sensible aggregation strategy,
- // promote first shard's result to top level.
- if (i == results.begin()) {
- result.appendElements(cmdResult.result);
- clusterCmdResult = cmdResult.result["ok"].trueValue();
- }
-
- // Append shard result as a sub object.
- // Name the field after the shard.
- result.append(cmdResult.shardTargetId, cmdResult.result);
+ return Status(ErrorCodes::Unauthorized, "unauthorized");
+ }
+
+ // Cluster plan cache command entry point.
+ bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result);
+
+public:
+ /**
+ * Instantiates a command that can be invoked by "name", which will be described by
+ * "helpText", and will require privilege "actionType" to run.
+ */
+ ClusterIndexFilterCmd(const std::string& name, const std::string& helpText)
+ : Command(name), _helpText(helpText) {}
+
+private:
+ std::string _helpText;
+};
+
+//
+// Cluster index filter command implementation(s) below
+//
+
+bool ClusterIndexFilterCmd::run(OperationContext* txn,
+ const std::string& dbName,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errMsg,
+ BSONObjBuilder& result) {
+ const std::string fullns = parseNs(dbName, cmdObj);
+ NamespaceString nss(fullns);
+
+ // Dispatch command to all the shards.
+ // Targeted shard commands are generally data-dependent but index filter
+ // commands are tied to query shape (data has no effect on query shape).
+ vector<Strategy::CommandResult> results;
+ Strategy::commandOp(dbName, cmdObj, options, nss.ns(), BSONObj(), &results);
+
+ // Set value of first shard result's "ok" field.
+ bool clusterCmdResult = true;
+
+ for (vector<Strategy::CommandResult>::const_iterator i = results.begin(); i != results.end();
+ ++i) {
+ const Strategy::CommandResult& cmdResult = *i;
+
+ // XXX: In absence of sensible aggregation strategy,
+ // promote first shard's result to top level.
+ if (i == results.begin()) {
+ result.appendElements(cmdResult.result);
+ clusterCmdResult = cmdResult.result["ok"].trueValue();
}
- return clusterCmdResult;
+ // Append shard result as a sub object.
+ // Name the field after the shard.
+ result.append(cmdResult.shardTargetId, cmdResult.result);
}
- //
- // Register index filter commands at startup
- //
+ return clusterCmdResult;
+}
- namespace {
+//
+// Register index filter commands at startup
+//
- MONGO_INITIALIZER(RegisterIndexFilterCommands)(InitializerContext* context) {
- // Leaked intentionally: a Command registers itself when constructed.
+namespace {
- new ClusterIndexFilterCmd(
- "planCacheListFilters",
- "Displays index filters for all query shapes in a collection." );
+MONGO_INITIALIZER(RegisterIndexFilterCommands)(InitializerContext* context) {
+ // Leaked intentionally: a Command registers itself when constructed.
- new ClusterIndexFilterCmd(
- "planCacheClearFilters",
- "Clears index filter for a single query shape or, "
- "if the query shape is omitted, all filters for the collection." );
+ new ClusterIndexFilterCmd("planCacheListFilters",
+ "Displays index filters for all query shapes in a collection.");
- new ClusterIndexFilterCmd(
- "planCacheSetFilter",
- "Sets index filter for a query shape. Overrides existing index filter." );
+ new ClusterIndexFilterCmd("planCacheClearFilters",
+ "Clears index filter for a single query shape or, "
+ "if the query shape is omitted, all filters for the collection.");
- return Status::OK();
- }
+ new ClusterIndexFilterCmd(
+ "planCacheSetFilter",
+ "Sets index filter for a query shape. Overrides existing index filter.");
+
+ return Status::OK();
+}
- } // namespace
+} // namespace
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_is_db_grid_cmd.cpp b/src/mongo/s/commands/cluster_is_db_grid_cmd.cpp
index d4c958582ac..3c16419807d 100644
--- a/src/mongo/s/commands/cluster_is_db_grid_cmd.cpp
+++ b/src/mongo/s/commands/cluster_is_db_grid_cmd.cpp
@@ -34,38 +34,36 @@
namespace mongo {
namespace {
- class IsDbGridCmd : public Command {
- public:
- IsDbGridCmd() : Command("isdbgrid") { }
+class IsDbGridCmd : public Command {
+public:
+ IsDbGridCmd() : Command("isdbgrid") {}
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- virtual bool slaveOk() const {
- return true;
- }
+ virtual bool slaveOk() const {
+ return true;
+ }
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ // No auth required
+ }
- // No auth required
- }
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ result.append("isdbgrid", 1);
+ result.append("hostname", getHostNameCached());
+ return true;
+ }
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
+} isdbGrid;
- result.append("isdbgrid", 1);
- result.append("hostname", getHostNameCached());
- return true;
- }
-
- } isdbGrid;
-
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_is_master_cmd.cpp b/src/mongo/s/commands/cluster_is_master_cmd.cpp
index 5f926811a27..75dde8f15f9 100644
--- a/src/mongo/s/commands/cluster_is_master_cmd.cpp
+++ b/src/mongo/s/commands/cluster_is_master_cmd.cpp
@@ -35,52 +35,50 @@
namespace mongo {
namespace {
- class CmdIsMaster : public Command {
- public:
- CmdIsMaster() : Command("isMaster", false, "ismaster") { }
+class CmdIsMaster : public Command {
+public:
+ CmdIsMaster() : Command("isMaster", false, "ismaster") {}
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- virtual bool slaveOk() const {
- return true;
- }
+ virtual bool slaveOk() const {
+ return true;
+ }
- virtual void help(std::stringstream& help) const {
- help << "test if this is master half of a replica pair";
- }
+ virtual void help(std::stringstream& help) const {
+ help << "test if this is master half of a replica pair";
+ }
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ // No auth required
+ }
- // No auth required
- }
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ result.appendBool("ismaster", true);
+ result.append("msg", "isdbgrid");
+ result.appendNumber("maxBsonObjectSize", BSONObjMaxUserSize);
+ result.appendNumber("maxMessageSizeBytes", MaxMessageSizeBytes);
+ result.appendNumber("maxWriteBatchSize", BatchedCommandRequest::kMaxWriteBatchSize);
+ result.appendDate("localTime", jsTime());
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
+ // Mongos tries to keep exactly the same version range of the server for which
+ // it is compiled.
+ result.append("maxWireVersion", maxWireVersion);
+ result.append("minWireVersion", minWireVersion);
- result.appendBool("ismaster", true);
- result.append("msg", "isdbgrid");
- result.appendNumber("maxBsonObjectSize", BSONObjMaxUserSize);
- result.appendNumber("maxMessageSizeBytes", MaxMessageSizeBytes);
- result.appendNumber("maxWriteBatchSize", BatchedCommandRequest::kMaxWriteBatchSize);
- result.appendDate("localTime", jsTime());
+ return true;
+ }
- // Mongos tries to keep exactly the same version range of the server for which
- // it is compiled.
- result.append("maxWireVersion", maxWireVersion);
- result.append("minWireVersion", minWireVersion);
+} isMaster;
- return true;
- }
-
- } isMaster;
-
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_kill_op.cpp b/src/mongo/s/commands/cluster_kill_op.cpp
index e6f3a127b56..056f4b7ff60 100644
--- a/src/mongo/s/commands/cluster_kill_op.cpp
+++ b/src/mongo/s/commands/cluster_kill_op.cpp
@@ -50,81 +50,84 @@
namespace mongo {
namespace {
- class ClusterKillOpCommand : public Command {
- public:
- ClusterKillOpCommand() : Command("killOp") {}
-
- bool isWriteCommandForConfigServer() const final { return false; }
+class ClusterKillOpCommand : public Command {
+public:
+ ClusterKillOpCommand() : Command("killOp") {}
+
+ bool isWriteCommandForConfigServer() const final {
+ return false;
+ }
+
+ bool slaveOk() const final {
+ return true;
+ }
+
+ bool adminOnly() const final {
+ return true;
+ }
+
+ Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) final {
+ bool isAuthorized = AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forClusterResource(), ActionType::killop);
+ return isAuthorized ? Status::OK() : Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+
+ bool run(OperationContext* txn,
+ const std::string& db,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) final {
+ // The format of op is shardid:opid
+ // This is different than the format passed to the mongod killOp command.
+ std::string opToKill;
+ uassertStatusOK(bsonExtractStringField(cmdObj, "op", &opToKill));
+
+ const auto opSepPos = opToKill.find(':');
+
+ uassert(28625,
+ str::stream() << "The op argument to killOp must be of the format shardid:opid"
+ << " but found \"" << opToKill << '"',
+ (opToKill.size() >= 3) && // must have at least N:N
+ (opSepPos != std::string::npos) && // must have ':' as separator
+ (opSepPos != 0) && // can't be :NN
+ (opSepPos != (opToKill.size() - 1))); // can't be NN:
+
+ auto shardIdent = opToKill.substr(0, opSepPos);
+ log() << "want to kill op: " << opToKill;
+
+ // Will throw if shard id is not found
+ auto shard = grid.shardRegistry()->getShard(shardIdent);
+ if (!shard) {
+ return appendCommandStatus(
+ result,
+ Status(ErrorCodes::ShardNotFound,
+ str::stream() << "shard " << shardIdent << " does not exist"));
+ }
- bool slaveOk() const final { return true; }
+ auto opId = std::stoi(opToKill.substr(opSepPos + 1));
- bool adminOnly() const final { return true; }
+ // shardid is actually the opid - keeping for backwards compatibility.
+ result.append("shard", shardIdent);
+ result.append("shardid", opId);
- Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) final {
+ ScopedDbConnection conn(shard->getConnString());
+ BSONObj cmdRes;
+ BSONObjBuilder argsBob;
+ argsBob.append("op", opId);
+ auto args = argsBob.done();
+ // intentionally ignore return value - that is how legacy killOp worked.
+ conn->runPseudoCommand("admin", "killOp", "$cmd.sys.killop", args, cmdRes);
+ conn.done();
- bool isAuthorized = AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
- ResourcePattern::forClusterResource(),
- ActionType::killop);
- return isAuthorized ? Status::OK() : Status(ErrorCodes::Unauthorized, "Unauthorized");
- }
-
- bool run(OperationContext* txn,
- const std::string& db,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) final {
-
- // The format of op is shardid:opid
- // This is different than the format passed to the mongod killOp command.
- std::string opToKill;
- uassertStatusOK(bsonExtractStringField(cmdObj, "op", &opToKill));
-
- const auto opSepPos = opToKill.find(':');
-
- uassert(28625,
- str::stream() << "The op argument to killOp must be of the format shardid:opid"
- << " but found \"" << opToKill << '"',
- (opToKill.size() >= 3) && // must have at least N:N
- (opSepPos != std::string::npos) && // must have ':' as separator
- (opSepPos != 0) && // can't be :NN
- (opSepPos != (opToKill.size() - 1))); // can't be NN:
-
- auto shardIdent = opToKill.substr(0, opSepPos);
- log() << "want to kill op: " << opToKill;
-
- // Will throw if shard id is not found
- auto shard = grid.shardRegistry()->getShard(shardIdent);
- if (!shard) {
- return appendCommandStatus(result,
- Status(ErrorCodes::ShardNotFound,
- str::stream() << "shard " << shardIdent
- << " does not exist"));
- }
-
- auto opId = std::stoi(opToKill.substr(opSepPos + 1));
-
- // shardid is actually the opid - keeping for backwards compatibility.
- result.append("shard", shardIdent);
- result.append("shardid", opId);
-
- ScopedDbConnection conn(shard->getConnString());
- BSONObj cmdRes;
- BSONObjBuilder argsBob;
- argsBob.append("op", opId);
- auto args = argsBob.done();
- // intentionally ignore return value - that is how legacy killOp worked.
- conn->runPseudoCommand("admin", "killOp", "$cmd.sys.killop", args, cmdRes);
- conn.done();
-
- // The original behavior of killOp on mongos is to always return success, regardless of
- // whether the shard reported success or not.
- return true;
- }
+ // The original behavior of killOp on mongos is to always return success, regardless of
+ // whether the shard reported success or not.
+ return true;
+ }
- } clusterKillOpCommand;
+} clusterKillOpCommand;
} // namespace
} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_list_databases_cmd.cpp b/src/mongo/s/commands/cluster_list_databases_cmd.cpp
index 1fc52781cce..248fb79f488 100644
--- a/src/mongo/s/commands/cluster_list_databases_cmd.cpp
+++ b/src/mongo/s/commands/cluster_list_databases_cmd.cpp
@@ -41,173 +41,169 @@
namespace mongo {
- using std::unique_ptr;
- using std::map;
- using std::string;
- using std::vector;
+using std::unique_ptr;
+using std::map;
+using std::string;
+using std::vector;
namespace {
- class ListDatabasesCmd : public Command {
- public:
- ListDatabasesCmd() : Command("listDatabases", true, "listdatabases") { }
-
- virtual bool slaveOk() const {
- return true;
- }
-
- virtual bool slaveOverrideOk() const {
- return true;
- }
-
- virtual bool adminOnly() const {
- return true;
- }
-
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
-
- virtual void help(std::stringstream& help) const {
- help << "list databases in a cluster";
- }
-
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::listDatabases);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
- }
-
- virtual bool run(OperationContext* txn,
- const std::string& dbname_unused,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
-
- map<string, long long> sizes;
- map<string, unique_ptr<BSONObjBuilder> > dbShardInfo;
-
- vector<ShardId> shardIds;
- grid.shardRegistry()->getAllShardIds(&shardIds);
-
- for (const ShardId& shardId : shardIds) {
- const auto s = grid.shardRegistry()->getShard(shardId);
- if (!s) {
- continue;
- }
-
- BSONObj x = s->runCommand("admin", "listDatabases");
+class ListDatabasesCmd : public Command {
+public:
+ ListDatabasesCmd() : Command("listDatabases", true, "listdatabases") {}
+
+ virtual bool slaveOk() const {
+ return true;
+ }
+
+ virtual bool slaveOverrideOk() const {
+ return true;
+ }
+
+ virtual bool adminOnly() const {
+ return true;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(std::stringstream& help) const {
+ help << "list databases in a cluster";
+ }
+
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::listDatabases);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
+
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname_unused,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ map<string, long long> sizes;
+ map<string, unique_ptr<BSONObjBuilder>> dbShardInfo;
+
+ vector<ShardId> shardIds;
+ grid.shardRegistry()->getAllShardIds(&shardIds);
+
+ for (const ShardId& shardId : shardIds) {
+ const auto s = grid.shardRegistry()->getShard(shardId);
+ if (!s) {
+ continue;
+ }
- BSONObjIterator j(x["databases"].Obj());
- while (j.more()) {
- BSONObj dbObj = j.next().Obj();
+ BSONObj x = s->runCommand("admin", "listDatabases");
- const string name = dbObj["name"].String();
- const long long size = dbObj["sizeOnDisk"].numberLong();
+ BSONObjIterator j(x["databases"].Obj());
+ while (j.more()) {
+ BSONObj dbObj = j.next().Obj();
- long long& totalSize = sizes[name];
- if (size == 1) {
- if (totalSize <= 1) {
- totalSize = 1;
- }
- }
- else {
- totalSize += size;
- }
+ const string name = dbObj["name"].String();
+ const long long size = dbObj["sizeOnDisk"].numberLong();
- unique_ptr<BSONObjBuilder>& bb = dbShardInfo[name];
- if (!bb.get()) {
- bb.reset(new BSONObjBuilder());
+ long long& totalSize = sizes[name];
+ if (size == 1) {
+ if (totalSize <= 1) {
+ totalSize = 1;
}
+ } else {
+ totalSize += size;
+ }
- bb->appendNumber(s->getId(), size);
+ unique_ptr<BSONObjBuilder>& bb = dbShardInfo[name];
+ if (!bb.get()) {
+ bb.reset(new BSONObjBuilder());
}
+ bb->appendNumber(s->getId(), size);
}
+ }
- long long totalSize = 0;
+ long long totalSize = 0;
- BSONArrayBuilder bb(result.subarrayStart("databases"));
- for (map<string, long long>::iterator i = sizes.begin(); i != sizes.end(); ++i) {
- const string name = i->first;
+ BSONArrayBuilder bb(result.subarrayStart("databases"));
+ for (map<string, long long>::iterator i = sizes.begin(); i != sizes.end(); ++i) {
+ const string name = i->first;
- if (name == "local") {
- // We don't return local, since all shards have their own independent local
- continue;
- }
+ if (name == "local") {
+ // We don't return local, since all shards have their own independent local
+ continue;
+ }
- if (name == "config" || name == "admin") {
- // Always get this from the config servers
- continue;
- }
+ if (name == "config" || name == "admin") {
+ // Always get this from the config servers
+ continue;
+ }
- long long size = i->second;
- totalSize += size;
+ long long size = i->second;
+ totalSize += size;
- BSONObjBuilder temp;
- temp.append("name", name);
- temp.appendNumber("sizeOnDisk", size);
- temp.appendBool("empty", size == 1);
- temp.append("shards", dbShardInfo[name]->obj());
+ BSONObjBuilder temp;
+ temp.append("name", name);
+ temp.appendNumber("sizeOnDisk", size);
+ temp.appendBool("empty", size == 1);
+ temp.append("shards", dbShardInfo[name]->obj());
- bb.append(temp.obj());
- }
+ bb.append(temp.obj());
+ }
- // Obtain the cached config shard
- const auto configShard = grid.shardRegistry()->getShard("config");
-
- {
- // get config db from the config servers (first one)
- BSONObj x;
- if (configShard->runCommand("config", "dbstats", x)) {
- BSONObjBuilder b;
- b.append("name", "config");
- b.appendBool("empty", false);
- if (x["fileSize"].type())
- b.appendAs(x["fileSize"], "sizeOnDisk");
- else
- b.append("sizeOnDisk", 1);
- bb.append(b.obj());
- }
- else {
- bb.append(BSON("name" << "config"));
- }
+ // Obtain the cached config shard
+ const auto configShard = grid.shardRegistry()->getShard("config");
+
+ {
+ // get config db from the config servers (first one)
+ BSONObj x;
+ if (configShard->runCommand("config", "dbstats", x)) {
+ BSONObjBuilder b;
+ b.append("name", "config");
+ b.appendBool("empty", false);
+ if (x["fileSize"].type())
+ b.appendAs(x["fileSize"], "sizeOnDisk");
+ else
+ b.append("sizeOnDisk", 1);
+ bb.append(b.obj());
+ } else {
+ bb.append(BSON("name"
+ << "config"));
}
+ }
- {
- // get admin db from the config servers (first one)
- BSONObj x;
- if (configShard->runCommand("admin", "dbstats", x)) {
- BSONObjBuilder b;
- b.append("name", "admin");
- b.appendBool("empty", false);
-
- if (x["fileSize"].type()) {
- b.appendAs(x["fileSize"], "sizeOnDisk");
- }
- else {
- b.append("sizeOnDisk", 1);
- }
-
- bb.append(b.obj());
- }
- else {
- bb.append(BSON("name" << "admin"));
+ {
+ // get admin db from the config servers (first one)
+ BSONObj x;
+ if (configShard->runCommand("admin", "dbstats", x)) {
+ BSONObjBuilder b;
+ b.append("name", "admin");
+ b.appendBool("empty", false);
+
+ if (x["fileSize"].type()) {
+ b.appendAs(x["fileSize"], "sizeOnDisk");
+ } else {
+ b.append("sizeOnDisk", 1);
}
+
+ bb.append(b.obj());
+ } else {
+ bb.append(BSON("name"
+ << "admin"));
}
+ }
- bb.done();
+ bb.done();
- result.appendNumber("totalSize", totalSize);
- result.appendNumber("totalSizeMb", totalSize / (1024 * 1024));
+ result.appendNumber("totalSize", totalSize);
+ result.appendNumber("totalSizeMb", totalSize / (1024 * 1024));
- return 1;
- }
+ return 1;
+ }
- } cmdListDatabases;
+} cmdListDatabases;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_list_shards_cmd.cpp b/src/mongo/s/commands/cluster_list_shards_cmd.cpp
index 3cb365710e5..16eccd3c8e1 100644
--- a/src/mongo/s/commands/cluster_list_shards_cmd.cpp
+++ b/src/mongo/s/commands/cluster_list_shards_cmd.cpp
@@ -39,59 +39,55 @@
namespace mongo {
namespace {
- class ListShardsCmd : public Command {
- public:
- ListShardsCmd() : Command("listShards", false, "listshards") { }
-
- virtual bool slaveOk() const {
- return true;
- }
-
- virtual bool adminOnly() const {
- return true;
- }
-
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
-
- virtual void help(std::stringstream& help) const {
- help << "list all shards of the system";
+class ListShardsCmd : public Command {
+public:
+ ListShardsCmd() : Command("listShards", false, "listshards") {}
+
+ virtual bool slaveOk() const {
+ return true;
+ }
+
+ virtual bool adminOnly() const {
+ return true;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(std::stringstream& help) const {
+ help << "list all shards of the system";
+ }
+
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::listShards);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
+
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ std::vector<ShardType> shards;
+ Status status = grid.catalogManager()->getAllShards(&shards);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status);
}
-
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
-
- ActionSet actions;
- actions.addAction(ActionType::listShards);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ std::vector<BSONObj> shardsObj;
+ for (std::vector<ShardType>::const_iterator it = shards.begin(); it != shards.end(); it++) {
+ shardsObj.push_back(it->toBSON());
}
+ result.append("shards", shardsObj);
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
-
- std::vector<ShardType> shards;
- Status status = grid.catalogManager()->getAllShards(&shards);
- if (!status.isOK()) {
- return appendCommandStatus(result, status);
- }
- std::vector<BSONObj> shardsObj;
- for (std::vector<ShardType>::const_iterator it = shards.begin();
- it != shards.end();
- it++) {
- shardsObj.push_back(it->toBSON());
- }
- result.append("shards", shardsObj);
-
- return true;
- }
+ return true;
+ }
- } listShards;
+} listShards;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_map_reduce_cmd.cpp b/src/mongo/s/commands/cluster_map_reduce_cmd.cpp
index f4a0347de5c..78ecd3ae927 100644
--- a/src/mongo/s/commands/cluster_map_reduce_cmd.cpp
+++ b/src/mongo/s/commands/cluster_map_reduce_cmd.cpp
@@ -55,564 +55,536 @@
namespace mongo {
- using std::shared_ptr;
- using std::map;
- using std::set;
- using std::string;
- using std::vector;
+using std::shared_ptr;
+using std::map;
+using std::set;
+using std::string;
+using std::vector;
namespace {
- AtomicUInt32 JOB_NUMBER;
+AtomicUInt32 JOB_NUMBER;
- /**
- * Generates a unique name for the temporary M/R output collection.
- */
- string getTmpName(const string& coll) {
- StringBuilder sb;
- sb << "tmp.mrs." << coll << "_" << time(0) << "_" << JOB_NUMBER.fetchAndAdd(1);
- return sb.str();
- }
+/**
+ * Generates a unique name for the temporary M/R output collection.
+ */
+string getTmpName(const string& coll) {
+ StringBuilder sb;
+ sb << "tmp.mrs." << coll << "_" << time(0) << "_" << JOB_NUMBER.fetchAndAdd(1);
+ return sb.str();
+}
- /**
- * Given an input map/reduce command, this call generates the matching command which should
- * be sent to the shards as part of the first phase of map/reduce.
- */
- BSONObj fixForShards(const BSONObj& orig,
- const string& output,
- string& badShardedField,
- int maxChunkSizeBytes) {
-
- BSONObjBuilder b;
- BSONObjIterator i(orig);
- while (i.more()) {
- BSONElement e = i.next();
- const string fn = e.fieldName();
-
- if (fn == bypassDocumentValidationCommandOption() ||
- fn == "map" ||
- fn == "mapreduce" ||
- fn == "mapReduce" ||
- fn == "mapparams" ||
- fn == "reduce" ||
- fn == "query" ||
- fn == "sort" ||
- fn == "scope" ||
- fn == "verbose" ||
- fn == "$queryOptions" ||
- fn == LiteParsedQuery::cmdOptionMaxTimeMS) {
-
- b.append(e);
- }
- else if (fn == "out" || fn == "finalize") {
- // We don't want to copy these
- }
- else {
- badShardedField = fn;
- return BSONObj();
- }
+/**
+ * Given an input map/reduce command, this call generates the matching command which should
+ * be sent to the shards as part of the first phase of map/reduce.
+ */
+BSONObj fixForShards(const BSONObj& orig,
+ const string& output,
+ string& badShardedField,
+ int maxChunkSizeBytes) {
+ BSONObjBuilder b;
+ BSONObjIterator i(orig);
+ while (i.more()) {
+ BSONElement e = i.next();
+ const string fn = e.fieldName();
+
+ if (fn == bypassDocumentValidationCommandOption() || fn == "map" || fn == "mapreduce" ||
+ fn == "mapReduce" || fn == "mapparams" || fn == "reduce" || fn == "query" ||
+ fn == "sort" || fn == "scope" || fn == "verbose" || fn == "$queryOptions" ||
+ fn == LiteParsedQuery::cmdOptionMaxTimeMS) {
+ b.append(e);
+ } else if (fn == "out" || fn == "finalize") {
+ // We don't want to copy these
+ } else {
+ badShardedField = fn;
+ return BSONObj();
}
+ }
- b.append("out", output);
- b.append("shardedFirstPass", true);
+ b.append("out", output);
+ b.append("shardedFirstPass", true);
- if (maxChunkSizeBytes > 0) {
- // Will need to figure out chunks, ask shards for points
- b.append("splitInfo", maxChunkSizeBytes);
- }
+ if (maxChunkSizeBytes > 0) {
+ // Will need to figure out chunks, ask shards for points
+ b.append("splitInfo", maxChunkSizeBytes);
+ }
+
+ return b.obj();
+}
+
+
+/**
+ * Outline for sharded map reduce for sharded output, $out replace:
+ *
+ * ============= mongos =============
+ * 1. Send map reduce command to all relevant shards with some extra info like the value for
+ * the chunkSize and the name of the temporary output collection.
+ *
+ * ============= shard =============
+ * 2. Does normal map reduce.
+ *
+ * 3. Calls splitVector on itself against the output collection and puts the results into the
+ * response object.
+ *
+ * ============= mongos =============
+ * 4. If the output collection is *not* sharded, uses the information from splitVector to
+ * create a pre-split sharded collection.
+ *
+ * 5. Grabs the distributed lock for the final output collection.
+ *
+ * 6. Sends mapReduce.shardedfinish.
+ *
+ * ============= shard =============
+ * 7. Extracts the list of shards from the mapReduce.shardedfinish and performs a broadcast
+ * query against all of them to obtain all documents that this shard owns.
+ *
+ * 8. Performs the reduce operation against every document from step #7 and outputs them to
+ * another temporary collection. Also keeps track of the BSONObject size of every "reduced"
+ * document for each chunk range.
+ *
+ * 9. Atomically drops the old output collection and renames the temporary collection to the
+ * output collection.
+ *
+ * ============= mongos =============
+ * 10. Releases the distributed lock acquired at step #5.
+ *
+ * 11. Inspects the BSONObject size from step #8 and determines if it needs to split.
+ */
+class MRCmd : public Command {
+public:
+ MRCmd() : Command("mapReduce", false, "mapreduce") {}
- return b.obj();
+ virtual bool slaveOk() const {
+ return true;
}
+ virtual bool adminOnly() const {
+ return false;
+ }
- /**
- * Outline for sharded map reduce for sharded output, $out replace:
- *
- * ============= mongos =============
- * 1. Send map reduce command to all relevant shards with some extra info like the value for
- * the chunkSize and the name of the temporary output collection.
- *
- * ============= shard =============
- * 2. Does normal map reduce.
- *
- * 3. Calls splitVector on itself against the output collection and puts the results into the
- * response object.
- *
- * ============= mongos =============
- * 4. If the output collection is *not* sharded, uses the information from splitVector to
- * create a pre-split sharded collection.
- *
- * 5. Grabs the distributed lock for the final output collection.
- *
- * 6. Sends mapReduce.shardedfinish.
- *
- * ============= shard =============
- * 7. Extracts the list of shards from the mapReduce.shardedfinish and performs a broadcast
- * query against all of them to obtain all documents that this shard owns.
- *
- * 8. Performs the reduce operation against every document from step #7 and outputs them to
- * another temporary collection. Also keeps track of the BSONObject size of every "reduced"
- * document for each chunk range.
- *
- * 9. Atomically drops the old output collection and renames the temporary collection to the
- * output collection.
- *
- * ============= mongos =============
- * 10. Releases the distributed lock acquired at step #5.
- *
- * 11. Inspects the BSONObject size from step #8 and determines if it needs to split.
- */
- class MRCmd : public Command {
- public:
- MRCmd() : Command("mapReduce", false, "mapreduce") { }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- virtual bool slaveOk() const {
- return true;
- }
+ virtual void help(std::stringstream& help) const {
+ help << "Runs the sharded map/reduce command";
+ }
- virtual bool adminOnly() const {
- return false;
- }
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ mr::addPrivilegesRequiredForMapReduce(this, dbname, cmdObj, out);
+ }
- virtual bool isWriteCommandForConfigServer() const {
- return false;
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ Timer t;
+
+ const string collection = cmdObj.firstElement().valuestrsafe();
+ const string fullns = dbname + "." + collection;
+ const string shardResultCollection = getTmpName(collection);
+
+ BSONObj customOut;
+ string finalColShort;
+ string finalColLong;
+ bool customOutDB = false;
+
+ string outDB = dbname;
+
+ BSONElement outElmt = cmdObj.getField("out");
+ if (outElmt.type() == Object) {
+ // Check if there is a custom output
+ BSONObj out = outElmt.embeddedObject();
+ customOut = out;
+
+ // Mode must be 1st element
+ finalColShort = out.firstElement().str();
+ if (customOut.hasField("db")) {
+ customOutDB = true;
+ outDB = customOut.getField("db").str();
+ }
+
+ finalColLong = outDB + "." + finalColShort;
}
- virtual void help(std::stringstream& help) const {
- help << "Runs the sharded map/reduce command";
+ // Ensure the input database exists
+ auto status = grid.catalogCache()->getDatabase(dbname);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status.getStatus());
}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
+ shared_ptr<DBConfig> confIn = status.getValue();
- mr::addPrivilegesRequiredForMapReduce(this, dbname, cmdObj, out);
+ shared_ptr<DBConfig> confOut;
+ if (customOutDB) {
+ // Create the output database implicitly, since we have a custom output requested
+ confOut = uassertStatusOK(grid.implicitCreateDb(outDB));
+ } else {
+ confOut = confIn;
}
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
-
- Timer t;
-
- const string collection = cmdObj.firstElement().valuestrsafe();
- const string fullns = dbname + "." + collection;
- const string shardResultCollection = getTmpName(collection);
-
- BSONObj customOut;
- string finalColShort;
- string finalColLong;
- bool customOutDB = false;
-
- string outDB = dbname;
-
- BSONElement outElmt = cmdObj.getField("out");
- if (outElmt.type() == Object) {
- // Check if there is a custom output
- BSONObj out = outElmt.embeddedObject();
- customOut = out;
-
- // Mode must be 1st element
- finalColShort = out.firstElement().str();
- if (customOut.hasField("db")) {
- customOutDB = true;
- outDB = customOut.getField("db").str();
- }
+ const bool shardedInput =
+ confIn && confIn->isShardingEnabled() && confIn->isSharded(fullns);
+ const bool shardedOutput = customOut.getBoolField("sharded");
- finalColLong = outDB + "." + finalColShort;
- }
-
- // Ensure the input database exists
- auto status = grid.catalogCache()->getDatabase(dbname);
- if (!status.isOK()) {
- return appendCommandStatus(result, status.getStatus());
- }
+ if (!shardedOutput) {
+ uassert(15920,
+ "Cannot output to a non-sharded collection because "
+ "sharded collection exists already",
+ !confOut->isSharded(finalColLong));
- shared_ptr<DBConfig> confIn = status.getValue();
+ // TODO: Should we also prevent going from non-sharded to sharded? During the
+ // transition client may see partial data.
+ }
- shared_ptr<DBConfig> confOut;
- if (customOutDB) {
- // Create the output database implicitly, since we have a custom output requested
- confOut = uassertStatusOK(grid.implicitCreateDb(outDB));
- }
- else {
- confOut = confIn;
+ int64_t maxChunkSizeBytes = 0;
+ if (shardedOutput) {
+ // Will need to figure out chunks, ask shards for points
+ maxChunkSizeBytes = cmdObj["maxChunkSizeBytes"].numberLong();
+ if (maxChunkSizeBytes == 0) {
+ maxChunkSizeBytes = Chunk::MaxChunkSize;
}
- const bool shardedInput = confIn &&
- confIn->isShardingEnabled() &&
- confIn->isSharded(fullns);
- const bool shardedOutput = customOut.getBoolField("sharded");
+ // maxChunkSizeBytes is sent as int BSON field
+ invariant(maxChunkSizeBytes < std::numeric_limits<int>::max());
+ }
- if (!shardedOutput) {
- uassert(15920,
- "Cannot output to a non-sharded collection because "
- "sharded collection exists already",
- !confOut->isSharded(finalColLong));
+ if (customOut.hasField("inline") && shardedOutput) {
+ errmsg = "cannot specify inline and sharded output at the same time";
+ return false;
+ }
- // TODO: Should we also prevent going from non-sharded to sharded? During the
- // transition client may see partial data.
- }
+ // modify command to run on shards with output to tmp collection
+ string badShardedField;
+ BSONObj shardedCommand =
+ fixForShards(cmdObj, shardResultCollection, badShardedField, maxChunkSizeBytes);
- int64_t maxChunkSizeBytes = 0;
- if (shardedOutput) {
- // Will need to figure out chunks, ask shards for points
- maxChunkSizeBytes = cmdObj["maxChunkSizeBytes"].numberLong();
- if (maxChunkSizeBytes == 0) {
- maxChunkSizeBytes = Chunk::MaxChunkSize;
- }
+ if (!shardedInput && !shardedOutput && !customOutDB) {
+ LOG(1) << "simple MR, just passthrough";
- // maxChunkSizeBytes is sent as int BSON field
- invariant(maxChunkSizeBytes < std::numeric_limits<int>::max());
- }
+ const auto shard = grid.shardRegistry()->getShard(confIn->getPrimaryId());
+ ShardConnection conn(shard->getConnString(), "");
- if (customOut.hasField("inline") && shardedOutput) {
- errmsg = "cannot specify inline and sharded output at the same time";
- return false;
- }
+ BSONObj res;
+ bool ok = conn->runCommand(dbname, cmdObj, res);
+ conn.done();
- // modify command to run on shards with output to tmp collection
- string badShardedField;
- BSONObj shardedCommand = fixForShards(cmdObj,
- shardResultCollection,
- badShardedField,
- maxChunkSizeBytes);
+ result.appendElements(res);
+ return ok;
+ }
- if (!shardedInput && !shardedOutput && !customOutDB) {
- LOG(1) << "simple MR, just passthrough";
+ if (badShardedField.size()) {
+ errmsg = str::stream() << "unknown m/r field for sharding: " << badShardedField;
+ return false;
+ }
- const auto shard = grid.shardRegistry()->getShard(confIn->getPrimaryId());
- ShardConnection conn(shard->getConnString(), "");
+ BSONObj q;
+ if (cmdObj["query"].type() == Object) {
+ q = cmdObj["query"].embeddedObjectUserCheck();
+ }
- BSONObj res;
- bool ok = conn->runCommand(dbname, cmdObj, res);
- conn.done();
+ set<string> servers;
+ vector<Strategy::CommandResult> mrCommandResults;
- result.appendElements(res);
- return ok;
- }
+ BSONObjBuilder shardResultsB;
+ BSONObjBuilder shardCountsB;
+ map<string, int64_t> countsMap;
+ set<BSONObj> splitPts;
- if (badShardedField.size()) {
- errmsg = str::stream() << "unknown m/r field for sharding: " << badShardedField;
- return false;
- }
+ {
+ bool ok = true;
- BSONObj q;
- if (cmdObj["query"].type() == Object) {
- q = cmdObj["query"].embeddedObjectUserCheck();
- }
+ // TODO: take distributed lock to prevent split / migration?
- set<string> servers;
- vector<Strategy::CommandResult> mrCommandResults;
+ try {
+ Strategy::commandOp(dbname, shardedCommand, 0, fullns, q, &mrCommandResults);
+ } catch (DBException& e) {
+ e.addContext(str::stream() << "could not run map command on all shards for ns "
+ << fullns << " and query " << q);
+ throw;
+ }
- BSONObjBuilder shardResultsB;
- BSONObjBuilder shardCountsB;
- map<string, int64_t> countsMap;
- set< BSONObj > splitPts;
+ for (const auto& mrResult : mrCommandResults) {
+ // Need to gather list of all servers even if an error happened
+ string server;
+ {
+ const auto shard = grid.shardRegistry()->getShard(mrResult.shardTargetId);
+ server = shard->getConnString().toString();
+ }
+ servers.insert(server);
- {
- bool ok = true;
+ if (!ok) {
+ continue;
+ }
- // TODO: take distributed lock to prevent split / migration?
+ BSONObj singleResult = mrResult.result;
+ ok = singleResult["ok"].trueValue();
- try {
- Strategy::commandOp(dbname, shardedCommand, 0, fullns, q, &mrCommandResults);
- }
- catch (DBException& e){
- e.addContext(str::stream() << "could not run map command on all shards for ns "
- << fullns << " and query " << q);
- throw;
+ if (!ok) {
+ // At this point we will return
+ errmsg = str::stream()
+ << "MR parallel processing failed: " << singleResult.toString();
+ continue;
}
- for (const auto& mrResult : mrCommandResults) {
- // Need to gather list of all servers even if an error happened
- string server;
- {
- const auto shard =
- grid.shardRegistry()->getShard(mrResult.shardTargetId);
- server = shard->getConnString().toString();
- }
- servers.insert(server);
+ shardResultsB.append(server, singleResult);
- if (!ok) {
- continue;
- }
+ BSONObj counts = singleResult["counts"].embeddedObjectUserCheck();
+ shardCountsB.append(server, counts);
- BSONObj singleResult = mrResult.result;
- ok = singleResult["ok"].trueValue();
+ // Add up the counts for each shard. Some of them will be fixed later like
+ // output and reduce.
+ BSONObjIterator j(counts);
+ while (j.more()) {
+ BSONElement temp = j.next();
+ countsMap[temp.fieldName()] += temp.numberLong();
+ }
- if (!ok) {
- // At this point we will return
- errmsg = str::stream() << "MR parallel processing failed: "
- << singleResult.toString();
- continue;
+ if (singleResult.hasField("splitKeys")) {
+ BSONElement splitKeys = singleResult.getField("splitKeys");
+ vector<BSONElement> pts = splitKeys.Array();
+ for (vector<BSONElement>::iterator it = pts.begin(); it != pts.end(); ++it) {
+ splitPts.insert(it->Obj().getOwned());
}
+ }
+ }
- shardResultsB.append(server, singleResult);
+ if (!ok) {
+ _cleanUp(servers, dbname, shardResultCollection);
- BSONObj counts = singleResult["counts"].embeddedObjectUserCheck();
- shardCountsB.append(server, counts);
+ // Add "code" to the top-level response, if the failure of the sharded command
+ // can be accounted to a single error.
+ int code = getUniqueCodeFromCommandResults(mrCommandResults);
+ if (code != 0) {
+ result.append("code", code);
+ }
- // Add up the counts for each shard. Some of them will be fixed later like
- // output and reduce.
- BSONObjIterator j(counts);
- while (j.more()) {
- BSONElement temp = j.next();
- countsMap[temp.fieldName()] += temp.numberLong();
- }
+ return false;
+ }
+ }
- if (singleResult.hasField("splitKeys")) {
- BSONElement splitKeys = singleResult.getField("splitKeys");
- vector<BSONElement> pts = splitKeys.Array();
- for (vector<BSONElement>::iterator it = pts.begin(); it != pts.end(); ++it) {
- splitPts.insert(it->Obj().getOwned());
- }
- }
- }
+ // Build the sharded finish command
+ BSONObjBuilder finalCmd;
+ finalCmd.append("mapreduce.shardedfinish", cmdObj);
+ finalCmd.append("inputDB", dbname);
+ finalCmd.append("shardedOutputCollection", shardResultCollection);
+ finalCmd.append("shards", shardResultsB.done());
- if (!ok) {
- _cleanUp(servers, dbname, shardResultCollection);
+ BSONObj shardCounts = shardCountsB.done();
+ finalCmd.append("shardCounts", shardCounts);
- // Add "code" to the top-level response, if the failure of the sharded command
- // can be accounted to a single error.
- int code = getUniqueCodeFromCommandResults(mrCommandResults);
- if (code != 0) {
- result.append("code", code);
- }
+ BSONObjBuilder timingBuilder;
+ timingBuilder.append("shardProcessing", t.millis());
- return false;
- }
- }
+ BSONObjBuilder aggCountsB;
+ for (const auto& countEntry : countsMap) {
+ aggCountsB.append(countEntry.first, static_cast<long long>(countEntry.second));
+ }
- // Build the sharded finish command
- BSONObjBuilder finalCmd;
- finalCmd.append("mapreduce.shardedfinish", cmdObj);
- finalCmd.append("inputDB", dbname);
- finalCmd.append("shardedOutputCollection", shardResultCollection);
- finalCmd.append("shards", shardResultsB.done());
+ BSONObj aggCounts = aggCountsB.done();
+ finalCmd.append("counts", aggCounts);
- BSONObj shardCounts = shardCountsB.done();
- finalCmd.append("shardCounts", shardCounts);
+ if (auto elem = cmdObj[LiteParsedQuery::cmdOptionMaxTimeMS])
+ finalCmd.append(elem);
+ if (auto elem = cmdObj[bypassDocumentValidationCommandOption()])
+ finalCmd.append(elem);
- BSONObjBuilder timingBuilder;
- timingBuilder.append("shardProcessing", t.millis());
+ Timer t2;
- BSONObjBuilder aggCountsB;
- for (const auto& countEntry : countsMap) {
- aggCountsB.append(countEntry.first, static_cast<long long>(countEntry.second));
- }
+ long long reduceCount = 0;
+ long long outputCount = 0;
+ BSONObjBuilder postCountsB;
- BSONObj aggCounts = aggCountsB.done();
- finalCmd.append("counts", aggCounts);
+ bool ok = true;
+ BSONObj singleResult;
- if (auto elem = cmdObj[LiteParsedQuery::cmdOptionMaxTimeMS]) finalCmd.append(elem);
- if (auto elem = cmdObj[bypassDocumentValidationCommandOption()]) finalCmd.append(elem);
+ if (!shardedOutput) {
+ const auto shard = grid.shardRegistry()->getShard(confOut->getPrimaryId());
+ LOG(1) << "MR with single shard output, NS=" << finalColLong
+ << " primary=" << shard->toString();
- Timer t2;
+ ShardConnection conn(shard->getConnString(), finalColLong);
+ ok = conn->runCommand(outDB, finalCmd.obj(), singleResult);
- long long reduceCount = 0;
- long long outputCount = 0;
- BSONObjBuilder postCountsB;
+ BSONObj counts = singleResult.getObjectField("counts");
+ postCountsB.append(conn->getServerAddress(), counts);
+ reduceCount = counts.getIntField("reduce");
+ outputCount = counts.getIntField("output");
- bool ok = true;
- BSONObj singleResult;
+ conn.done();
+ } else {
+ LOG(1) << "MR with sharded output, NS=" << finalColLong;
- if (!shardedOutput) {
- const auto shard = grid.shardRegistry()->getShard(confOut->getPrimaryId());
- LOG(1) << "MR with single shard output, NS=" << finalColLong
- << " primary=" << shard->toString();
+ // Create the sharded collection if needed
+ if (!confOut->isSharded(finalColLong)) {
+ // Enable sharding on db
+ confOut->enableSharding();
- ShardConnection conn(shard->getConnString(), finalColLong);
- ok = conn->runCommand(outDB, finalCmd.obj(), singleResult);
+ // Shard collection according to split points
+ vector<BSONObj> sortedSplitPts;
- BSONObj counts = singleResult.getObjectField("counts");
- postCountsB.append(conn->getServerAddress(), counts);
- reduceCount = counts.getIntField("reduce");
- outputCount = counts.getIntField("output");
+ // Points will be properly sorted using the set
+ for (const auto& splitPt : splitPts) {
+ sortedSplitPts.push_back(splitPt);
+ }
- conn.done();
+ // Pre-split the collection onto all the shards for this database. Note that
+ // it's not completely safe to pre-split onto non-primary shards using the
+ // shardcollection method (a conflict may result if multiple map-reduces are
+ // writing to the same output collection, for instance).
+ //
+ // TODO: pre-split mapReduce output in a safer way.
+
+ set<ShardId> outShardIds;
+ confOut->getAllShardIds(&outShardIds);
+
+ BSONObj sortKey = BSON("_id" << 1);
+ ShardKeyPattern sortKeyPattern(sortKey);
+ Status status = grid.catalogManager()->shardCollection(
+ finalColLong, sortKeyPattern, true, &sortedSplitPts, &outShardIds);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status);
+ }
}
- else {
- LOG(1) << "MR with sharded output, NS=" << finalColLong;
-
- // Create the sharded collection if needed
- if (!confOut->isSharded(finalColLong)) {
- // Enable sharding on db
- confOut->enableSharding();
- // Shard collection according to split points
- vector<BSONObj> sortedSplitPts;
+ map<BSONObj, int> chunkSizes;
+ {
+ // Take distributed lock to prevent split / migration.
+ auto scopedDistLock = grid.catalogManager()->getDistLockManager()->lock(
+ finalColLong,
+ "mr-post-process",
+ stdx::chrono::milliseconds(-1), // retry indefinitely
+ stdx::chrono::milliseconds(100));
+
+ if (!scopedDistLock.isOK()) {
+ return appendCommandStatus(result, scopedDistLock.getStatus());
+ }
- // Points will be properly sorted using the set
- for (const auto& splitPt : splitPts) {
- sortedSplitPts.push_back(splitPt);
- }
+ BSONObj finalCmdObj = finalCmd.obj();
+ mrCommandResults.clear();
- // Pre-split the collection onto all the shards for this database. Note that
- // it's not completely safe to pre-split onto non-primary shards using the
- // shardcollection method (a conflict may result if multiple map-reduces are
- // writing to the same output collection, for instance).
- //
- // TODO: pre-split mapReduce output in a safer way.
-
- set<ShardId> outShardIds;
- confOut->getAllShardIds(&outShardIds);
-
- BSONObj sortKey = BSON("_id" << 1);
- ShardKeyPattern sortKeyPattern(sortKey);
- Status status = grid.catalogManager()->shardCollection(finalColLong,
- sortKeyPattern,
- true,
- &sortedSplitPts,
- &outShardIds);
- if (!status.isOK()) {
- return appendCommandStatus(result, status);
- }
+ try {
+ Strategy::commandOp(
+ outDB, finalCmdObj, 0, finalColLong, BSONObj(), &mrCommandResults);
+ ok = true;
+ } catch (DBException& e) {
+ e.addContext(str::stream() << "could not run final reduce on all shards for "
+ << fullns << ", output " << finalColLong);
+ throw;
}
- map<BSONObj, int> chunkSizes;
- {
- // Take distributed lock to prevent split / migration.
- auto scopedDistLock = grid.catalogManager()->getDistLockManager()->lock(
- finalColLong,
- "mr-post-process",
- stdx::chrono::milliseconds(-1), // retry indefinitely
- stdx::chrono::milliseconds(100));
-
- if (!scopedDistLock.isOK()) {
- return appendCommandStatus(result, scopedDistLock.getStatus());
+ for (const auto& mrResult : mrCommandResults) {
+ string server;
+ {
+ const auto shard = grid.shardRegistry()->getShard(mrResult.shardTargetId);
+ server = shard->getConnString().toString();
}
+ singleResult = mrResult.result;
- BSONObj finalCmdObj = finalCmd.obj();
- mrCommandResults.clear();
-
- try {
- Strategy::commandOp(outDB, finalCmdObj, 0, finalColLong, BSONObj(), &mrCommandResults);
- ok = true;
- }
- catch (DBException& e){
- e.addContext(str::stream() << "could not run final reduce on all shards for "
- << fullns << ", output " << finalColLong);
- throw;
+ ok = singleResult["ok"].trueValue();
+ if (!ok) {
+ break;
}
- for (const auto& mrResult : mrCommandResults) {
- string server;
- {
- const auto shard =
- grid.shardRegistry()->getShard(mrResult.shardTargetId);
- server = shard->getConnString().toString();
- }
- singleResult = mrResult.result;
-
- ok = singleResult["ok"].trueValue();
- if (!ok) {
- break;
- }
-
- BSONObj counts = singleResult.getObjectField("counts");
- reduceCount += counts.getIntField("reduce");
- outputCount += counts.getIntField("output");
- postCountsB.append(server, counts);
-
- // get the size inserted for each chunk
- // split cannot be called here since we already have the distributed lock
- if (singleResult.hasField("chunkSizes")) {
- vector<BSONElement> sizes = singleResult.getField("chunkSizes").Array();
- for (unsigned int i = 0; i < sizes.size(); i += 2) {
- BSONObj key = sizes[i].Obj().getOwned();
- const long long size = sizes[i + 1].numberLong();
-
- invariant(size < std::numeric_limits<int>::max());
- chunkSizes[key] = static_cast<int>(size);
- }
+ BSONObj counts = singleResult.getObjectField("counts");
+ reduceCount += counts.getIntField("reduce");
+ outputCount += counts.getIntField("output");
+ postCountsB.append(server, counts);
+
+ // get the size inserted for each chunk
+ // split cannot be called here since we already have the distributed lock
+ if (singleResult.hasField("chunkSizes")) {
+ vector<BSONElement> sizes = singleResult.getField("chunkSizes").Array();
+ for (unsigned int i = 0; i < sizes.size(); i += 2) {
+ BSONObj key = sizes[i].Obj().getOwned();
+ const long long size = sizes[i + 1].numberLong();
+
+ invariant(size < std::numeric_limits<int>::max());
+ chunkSizes[key] = static_cast<int>(size);
}
}
}
+ }
- // Do the splitting round
- ChunkManagerPtr cm = confOut->getChunkManagerIfExists(finalColLong);
- for (const auto& chunkSize : chunkSizes) {
- BSONObj key = chunkSize.first;
- const int size = chunkSize.second;
- invariant(size < std::numeric_limits<int>::max());
-
- // key reported should be the chunk's minimum
- ChunkPtr c = cm->findIntersectingChunk(key);
- if (!c) {
- warning() << "Mongod reported " << size << " bytes inserted for key "
- << key << " but can't find chunk";
- }
- else {
- c->splitIfShould(size);
- }
+ // Do the splitting round
+ ChunkManagerPtr cm = confOut->getChunkManagerIfExists(finalColLong);
+ for (const auto& chunkSize : chunkSizes) {
+ BSONObj key = chunkSize.first;
+ const int size = chunkSize.second;
+ invariant(size < std::numeric_limits<int>::max());
+
+ // key reported should be the chunk's minimum
+ ChunkPtr c = cm->findIntersectingChunk(key);
+ if (!c) {
+ warning() << "Mongod reported " << size << " bytes inserted for key " << key
+ << " but can't find chunk";
+ } else {
+ c->splitIfShould(size);
}
}
+ }
- _cleanUp(servers, dbname, shardResultCollection);
-
- if (!ok) {
- errmsg = str::stream() << "MR post processing failed: " << singleResult.toString();
- return 0;
- }
+ _cleanUp(servers, dbname, shardResultCollection);
- // copy some elements from a single result
- // annoying that we have to copy all results for inline, but no way around it
- if (singleResult.hasField("result")) {
- result.append(singleResult.getField("result"));
- }
- else if (singleResult.hasField("results")) {
- result.append(singleResult.getField("results"));
- }
+ if (!ok) {
+ errmsg = str::stream() << "MR post processing failed: " << singleResult.toString();
+ return 0;
+ }
- BSONObjBuilder countsB(32);
- // input stat is determined by aggregate MR job
- countsB.append("input", aggCounts.getField("input").numberLong());
- countsB.append("emit", aggCounts.getField("emit").numberLong());
+ // copy some elements from a single result
+ // annoying that we have to copy all results for inline, but no way around it
+ if (singleResult.hasField("result")) {
+ result.append(singleResult.getField("result"));
+ } else if (singleResult.hasField("results")) {
+ result.append(singleResult.getField("results"));
+ }
- // reduce count is sum of all reduces that happened
- countsB.append("reduce", aggCounts.getField("reduce").numberLong() + reduceCount);
+ BSONObjBuilder countsB(32);
+ // input stat is determined by aggregate MR job
+ countsB.append("input", aggCounts.getField("input").numberLong());
+ countsB.append("emit", aggCounts.getField("emit").numberLong());
- // ouput is determined by post processing on each shard
- countsB.append("output", outputCount);
- result.append("counts", countsB.done());
+ // reduce count is sum of all reduces that happened
+ countsB.append("reduce", aggCounts.getField("reduce").numberLong() + reduceCount);
- timingBuilder.append("postProcessing", t2.millis());
+ // ouput is determined by post processing on each shard
+ countsB.append("output", outputCount);
+ result.append("counts", countsB.done());
- result.append("timeMillis", t.millis());
- result.append("timing", timingBuilder.done());
- result.append("shardCounts", shardCounts);
- result.append("postProcessCounts", postCountsB.done());
+ timingBuilder.append("postProcessing", t2.millis());
- return true;
- }
+ result.append("timeMillis", t.millis());
+ result.append("timing", timingBuilder.done());
+ result.append("shardCounts", shardCounts);
+ result.append("postProcessCounts", postCountsB.done());
- private:
+ return true;
+ }
- /**
- * Drops the temporary results collections from each shard.
- */
- void _cleanUp(const set<string>& servers, string dbName, string shardResultCollection) {
- try {
- // drop collections with tmp results on each shard
- for (const auto& server : servers) {
- ScopedDbConnection conn(server);
- conn->dropCollection(dbName + "." + shardResultCollection);
- conn.done();
- }
- }
- catch (const DBException& e) {
- warning() << "Cannot cleanup shard results" << e.toString();
- }
- catch (const std::exception& e) {
- severe() << "Cannot cleanup shard results" << causedBy(e);
+private:
+ /**
+ * Drops the temporary results collections from each shard.
+ */
+ void _cleanUp(const set<string>& servers, string dbName, string shardResultCollection) {
+ try {
+ // drop collections with tmp results on each shard
+ for (const auto& server : servers) {
+ ScopedDbConnection conn(server);
+ conn->dropCollection(dbName + "." + shardResultCollection);
+ conn.done();
}
+ } catch (const DBException& e) {
+ warning() << "Cannot cleanup shard results" << e.toString();
+ } catch (const std::exception& e) {
+ severe() << "Cannot cleanup shard results" << causedBy(e);
}
+ }
- } clusterMapReduceCmd;
+} clusterMapReduceCmd;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_merge_chunks_cmd.cpp b/src/mongo/s/commands/cluster_merge_chunks_cmd.cpp
index fba40110919..c6173244fbb 100644
--- a/src/mongo/s/commands/cluster_merge_chunks_cmd.cpp
+++ b/src/mongo/s/commands/cluster_merge_chunks_cmd.cpp
@@ -45,160 +45,168 @@
namespace mongo {
- using std::shared_ptr;
- using std::string;
- using std::stringstream;
- using std::vector;
+using std::shared_ptr;
+using std::string;
+using std::stringstream;
+using std::vector;
namespace {
- /**
- * Mongos-side command for merging chunks, passes command to appropriate shard.
- */
- class ClusterMergeChunksCommand : public Command {
- public:
- ClusterMergeChunksCommand() : Command("mergeChunks") {}
+/**
+ * Mongos-side command for merging chunks, passes command to appropriate shard.
+ */
+class ClusterMergeChunksCommand : public Command {
+public:
+ ClusterMergeChunksCommand() : Command("mergeChunks") {}
+
+ virtual void help(stringstream& h) const {
+ h << "Merge Chunks command\n"
+ << "usage: { mergeChunks : <ns>, bounds : [ <min key>, <max key> ] }";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname, cmdObj))),
+ ActionType::splitChunk)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+ return Status::OK();
+ }
+
+ virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
+ return parseNsFullyQualified(dbname, cmdObj);
+ }
+
+ virtual bool adminOnly() const {
+ return true;
+ }
+ virtual bool slaveOk() const {
+ return false;
+ }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ // Required
+ static BSONField<string> nsField;
+ static BSONField<vector<BSONObj>> boundsField;
+
+ // Used to send sharding state
+ static BSONField<string> shardNameField;
+ static BSONField<string> configField;
+
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ vector<BSONObj> bounds;
+ if (!FieldParser::extract(cmdObj, boundsField, &bounds, &errmsg)) {
+ return false;
+ }
+
+ if (bounds.size() == 0) {
+ errmsg = "no bounds were specified";
+ return false;
+ }
- virtual void help(stringstream& h) const {
- h << "Merge Chunks command\n"
- << "usage: { mergeChunks : <ns>, bounds : [ <min key>, <max key> ] }";
+ if (bounds.size() != 2) {
+ errmsg = "only a min and max bound may be specified";
+ return false;
}
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
- ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname, cmdObj))),
- ActionType::splitChunk)) {
- return Status(ErrorCodes::Unauthorized, "Unauthorized");
- }
- return Status::OK();
+ BSONObj minKey = bounds[0];
+ BSONObj maxKey = bounds[1];
+
+ if (minKey.isEmpty()) {
+ errmsg = "no min key specified";
+ return false;
}
- virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
- return parseNsFullyQualified(dbname, cmdObj);
+ if (maxKey.isEmpty()) {
+ errmsg = "no max key specified";
+ return false;
}
- virtual bool adminOnly() const { return true; }
- virtual bool slaveOk() const { return false; }
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- // Required
- static BSONField<string> nsField;
- static BSONField<vector<BSONObj> > boundsField;
-
- // Used to send sharding state
- static BSONField<string> shardNameField;
- static BSONField<string> configField;
-
-
- bool run(OperationContext* txn, const string& dbname,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
-
- vector<BSONObj> bounds;
- if ( !FieldParser::extract( cmdObj, boundsField, &bounds, &errmsg ) ) {
- return false;
- }
-
- if ( bounds.size() == 0 ) {
- errmsg = "no bounds were specified";
- return false;
- }
-
- if ( bounds.size() != 2 ) {
- errmsg = "only a min and max bound may be specified";
- return false;
- }
-
- BSONObj minKey = bounds[0];
- BSONObj maxKey = bounds[1];
-
- if ( minKey.isEmpty() ) {
- errmsg = "no min key specified";
- return false;
- }
-
- if ( maxKey.isEmpty() ) {
- errmsg = "no max key specified";
- return false;
- }
-
- const NamespaceString nss(parseNs(dbname, cmdObj));
- if (nss.size() == 0) {
- return appendCommandStatus(result, Status(ErrorCodes::InvalidNamespace,
- "no namespace specified"));
- }
-
- auto status = grid.catalogCache()->getDatabase(nss.db().toString());
- if (!status.isOK()) {
- return appendCommandStatus(result, status.getStatus());
- }
-
- std::shared_ptr<DBConfig> config = status.getValue();
- if (!config->isSharded(nss.ns())) {
- return appendCommandStatus(result, Status(ErrorCodes::NamespaceNotSharded,
- "ns [" + nss.ns() + " is not sharded."));
- }
-
- // This refreshes the chunk metadata if stale.
- ChunkManagerPtr manager = config->getChunkManagerIfExists(nss, true);
- if (!manager) {
- return appendCommandStatus(result, Status(ErrorCodes::NamespaceNotSharded,
- "ns [" + nss.ns() + " is not sharded."));
- }
-
- if (!manager->getShardKeyPattern().isShardKey(minKey)
- || !manager->getShardKeyPattern().isShardKey(maxKey)) {
- errmsg = stream() << "shard key bounds " << "[" << minKey << "," << maxKey << ")"
- << " are not valid for shard key pattern "
- << manager->getShardKeyPattern().toBSON();
- return false;
- }
-
- minKey = manager->getShardKeyPattern().normalizeShardKey(minKey);
- maxKey = manager->getShardKeyPattern().normalizeShardKey(maxKey);
-
- ChunkPtr firstChunk = manager->findIntersectingChunk(minKey);
- verify(firstChunk);
-
- BSONObjBuilder remoteCmdObjB;
- remoteCmdObjB.append( cmdObj[ ClusterMergeChunksCommand::nsField() ] );
- remoteCmdObjB.append( cmdObj[ ClusterMergeChunksCommand::boundsField() ] );
- remoteCmdObjB.append( ClusterMergeChunksCommand::configField(),
- grid.catalogManager()->connectionString().toString() );
- remoteCmdObjB.append( ClusterMergeChunksCommand::shardNameField(),
- firstChunk->getShardId() );
-
- BSONObj remoteResult;
-
- // Throws, but handled at level above. Don't want to rewrap to preserve exception
- // formatting.
- const auto shard = grid.shardRegistry()->getShard(firstChunk->getShardId());
- if (!shard) {
- return appendCommandStatus(result,
- Status(ErrorCodes::ShardNotFound,
- str::stream() << "Can't find shard for chunk: "
- << firstChunk->toString()));
- }
-
- ScopedDbConnection conn(shard->getConnString());
- bool ok = conn->runCommand( "admin", remoteCmdObjB.obj(), remoteResult );
- conn.done();
-
- result.appendElements( remoteResult );
- return ok;
+ const NamespaceString nss(parseNs(dbname, cmdObj));
+ if (nss.size() == 0) {
+ return appendCommandStatus(
+ result, Status(ErrorCodes::InvalidNamespace, "no namespace specified"));
}
- } clusterMergeChunksCommand;
+ auto status = grid.catalogCache()->getDatabase(nss.db().toString());
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status.getStatus());
+ }
+
+ std::shared_ptr<DBConfig> config = status.getValue();
+ if (!config->isSharded(nss.ns())) {
+ return appendCommandStatus(
+ result,
+ Status(ErrorCodes::NamespaceNotSharded, "ns [" + nss.ns() + " is not sharded."));
+ }
+
+ // This refreshes the chunk metadata if stale.
+ ChunkManagerPtr manager = config->getChunkManagerIfExists(nss, true);
+ if (!manager) {
+ return appendCommandStatus(
+ result,
+ Status(ErrorCodes::NamespaceNotSharded, "ns [" + nss.ns() + " is not sharded."));
+ }
+
+ if (!manager->getShardKeyPattern().isShardKey(minKey) ||
+ !manager->getShardKeyPattern().isShardKey(maxKey)) {
+ errmsg = stream() << "shard key bounds "
+ << "[" << minKey << "," << maxKey << ")"
+ << " are not valid for shard key pattern "
+ << manager->getShardKeyPattern().toBSON();
+ return false;
+ }
+
+ minKey = manager->getShardKeyPattern().normalizeShardKey(minKey);
+ maxKey = manager->getShardKeyPattern().normalizeShardKey(maxKey);
+
+ ChunkPtr firstChunk = manager->findIntersectingChunk(minKey);
+ verify(firstChunk);
+
+ BSONObjBuilder remoteCmdObjB;
+ remoteCmdObjB.append(cmdObj[ClusterMergeChunksCommand::nsField()]);
+ remoteCmdObjB.append(cmdObj[ClusterMergeChunksCommand::boundsField()]);
+ remoteCmdObjB.append(ClusterMergeChunksCommand::configField(),
+ grid.catalogManager()->connectionString().toString());
+ remoteCmdObjB.append(ClusterMergeChunksCommand::shardNameField(), firstChunk->getShardId());
+
+ BSONObj remoteResult;
+
+ // Throws, but handled at level above. Don't want to rewrap to preserve exception
+ // formatting.
+ const auto shard = grid.shardRegistry()->getShard(firstChunk->getShardId());
+ if (!shard) {
+ return appendCommandStatus(
+ result,
+ Status(ErrorCodes::ShardNotFound,
+ str::stream() << "Can't find shard for chunk: " << firstChunk->toString()));
+ }
+
+ ScopedDbConnection conn(shard->getConnString());
+ bool ok = conn->runCommand("admin", remoteCmdObjB.obj(), remoteResult);
+ conn.done();
+
+ result.appendElements(remoteResult);
+ return ok;
+ }
+
+} clusterMergeChunksCommand;
- BSONField<string> ClusterMergeChunksCommand::nsField( "mergeChunks" );
- BSONField<vector<BSONObj> > ClusterMergeChunksCommand::boundsField( "bounds" );
+BSONField<string> ClusterMergeChunksCommand::nsField("mergeChunks");
+BSONField<vector<BSONObj>> ClusterMergeChunksCommand::boundsField("bounds");
- BSONField<string> ClusterMergeChunksCommand::configField( "config" );
- BSONField<string> ClusterMergeChunksCommand::shardNameField( "shardName" );
+BSONField<string> ClusterMergeChunksCommand::configField("config");
+BSONField<string> ClusterMergeChunksCommand::shardNameField("shardName");
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_move_chunk_cmd.cpp b/src/mongo/s/commands/cluster_move_chunk_cmd.cpp
index 602f539ec55..45b9c63cdec 100644
--- a/src/mongo/s/commands/cluster_move_chunk_cmd.cpp
+++ b/src/mongo/s/commands/cluster_move_chunk_cmd.cpp
@@ -50,230 +50,216 @@
namespace mongo {
- using std::shared_ptr;
- using std::unique_ptr;
- using std::string;
+using std::shared_ptr;
+using std::unique_ptr;
+using std::string;
namespace {
- class MoveChunkCmd : public Command {
- public:
- MoveChunkCmd() : Command("moveChunk", false, "movechunk") { }
-
- virtual bool slaveOk() const {
- return true;
- }
-
- virtual bool adminOnly() const {
- return true;
+class MoveChunkCmd : public Command {
+public:
+ MoveChunkCmd() : Command("moveChunk", false, "movechunk") {}
+
+ virtual bool slaveOk() const {
+ return true;
+ }
+
+ virtual bool adminOnly() const {
+ return true;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(std::stringstream& help) const {
+ help << "Example: move chunk that contains the doc {num : 7} to shard001\n"
+ << " { movechunk : 'test.foo' , find : { num : 7 } , to : 'shard0001' }\n"
+ << "Example: move chunk with lower bound 0 and upper bound 10 to shard001\n"
+ << " { movechunk : 'test.foo' , bounds : [ { num : 0 } , { num : 10 } ] "
+ << " , to : 'shard001' }\n";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname, cmdObj))),
+ ActionType::moveChunk)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
}
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
-
- virtual void help(std::stringstream& help) const {
- help << "Example: move chunk that contains the doc {num : 7} to shard001\n"
- << " { movechunk : 'test.foo' , find : { num : 7 } , to : 'shard0001' }\n"
- << "Example: move chunk with lower bound 0 and upper bound 10 to shard001\n"
- << " { movechunk : 'test.foo' , bounds : [ { num : 0 } , { num : 10 } ] "
- << " , to : 'shard001' }\n";
- }
+ return Status::OK();
+ }
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
-
- if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
- ResourcePattern::forExactNamespace(
- NamespaceString(parseNs(dbname,
- cmdObj))),
- ActionType::moveChunk)) {
- return Status(ErrorCodes::Unauthorized, "Unauthorized");
- }
-
- return Status::OK();
- }
-
- virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
- return parseNsFullyQualified(dbname, cmdObj);
- }
+ virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
+ return parseNsFullyQualified(dbname, cmdObj);
+ }
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ ShardConnection::sync();
- ShardConnection::sync();
+ Timer t;
- Timer t;
+ const NamespaceString nss(parseNs(dbname, cmdObj));
- const NamespaceString nss(parseNs(dbname, cmdObj));
+ std::shared_ptr<DBConfig> config;
- std::shared_ptr<DBConfig> config;
-
- {
- if (nss.size() == 0) {
- return appendCommandStatus(result, Status(ErrorCodes::InvalidNamespace,
- "no namespace specified"));
- }
-
- auto status = grid.catalogCache()->getDatabase(nss.db().toString());
- if (!status.isOK()) {
- return appendCommandStatus(result, status.getStatus());
- }
-
- config = status.getValue();
+ {
+ if (nss.size() == 0) {
+ return appendCommandStatus(
+ result, Status(ErrorCodes::InvalidNamespace, "no namespace specified"));
}
- if (!config->isSharded(nss.ns())) {
- config->reload();
-
- if (!config->isSharded(nss.ns())) {
- return appendCommandStatus(result,
- Status(ErrorCodes::NamespaceNotSharded,
- "ns [" + nss.ns() + " is not sharded."));
- }
+ auto status = grid.catalogCache()->getDatabase(nss.db().toString());
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status.getStatus());
}
- string toString = cmdObj["to"].valuestrsafe();
- if (!toString.size()) {
- errmsg = "you have to specify where you want to move the chunk";
- return false;
- }
+ config = status.getValue();
+ }
+
+ if (!config->isSharded(nss.ns())) {
+ config->reload();
- const auto to = grid.shardRegistry()->getShard(toString);
- if (!to) {
- string msg(str::stream() <<
- "Could not move chunk in '" << nss.ns() <<
- "' to shard '" << toString <<
- "' because that shard does not exist");
- log() << msg;
+ if (!config->isSharded(nss.ns())) {
return appendCommandStatus(result,
- Status(ErrorCodes::ShardNotFound, msg));
+ Status(ErrorCodes::NamespaceNotSharded,
+ "ns [" + nss.ns() + " is not sharded."));
}
+ }
- // so far, chunk size serves test purposes; it may or may not become a supported parameter
- long long maxChunkSizeBytes = cmdObj["maxChunkSizeBytes"].numberLong();
- if (maxChunkSizeBytes == 0) {
- maxChunkSizeBytes = Chunk::MaxChunkSize;
- }
+ string toString = cmdObj["to"].valuestrsafe();
+ if (!toString.size()) {
+ errmsg = "you have to specify where you want to move the chunk";
+ return false;
+ }
- BSONObj find = cmdObj.getObjectField("find");
- BSONObj bounds = cmdObj.getObjectField("bounds");
+ const auto to = grid.shardRegistry()->getShard(toString);
+ if (!to) {
+ string msg(str::stream() << "Could not move chunk in '" << nss.ns() << "' to shard '"
+ << toString << "' because that shard does not exist");
+ log() << msg;
+ return appendCommandStatus(result, Status(ErrorCodes::ShardNotFound, msg));
+ }
- // check that only one of the two chunk specification methods is used
- if (find.isEmpty() == bounds.isEmpty()) {
- errmsg = "need to specify either a find query, or both lower and upper bounds.";
- return false;
- }
+ // so far, chunk size serves test purposes; it may or may not become a supported parameter
+ long long maxChunkSizeBytes = cmdObj["maxChunkSizeBytes"].numberLong();
+ if (maxChunkSizeBytes == 0) {
+ maxChunkSizeBytes = Chunk::MaxChunkSize;
+ }
- // This refreshes the chunk metadata if stale.
- ChunkManagerPtr info = config->getChunkManager(nss.ns(), true);
- ChunkPtr chunk;
+ BSONObj find = cmdObj.getObjectField("find");
+ BSONObj bounds = cmdObj.getObjectField("bounds");
- if (!find.isEmpty()) {
+ // check that only one of the two chunk specification methods is used
+ if (find.isEmpty() == bounds.isEmpty()) {
+ errmsg = "need to specify either a find query, or both lower and upper bounds.";
+ return false;
+ }
- StatusWith<BSONObj> status =
- info->getShardKeyPattern().extractShardKeyFromQuery(find);
+ // This refreshes the chunk metadata if stale.
+ ChunkManagerPtr info = config->getChunkManager(nss.ns(), true);
+ ChunkPtr chunk;
- // Bad query
- if (!status.isOK())
- return appendCommandStatus(result, status.getStatus());
+ if (!find.isEmpty()) {
+ StatusWith<BSONObj> status = info->getShardKeyPattern().extractShardKeyFromQuery(find);
- BSONObj shardKey = status.getValue();
+ // Bad query
+ if (!status.isOK())
+ return appendCommandStatus(result, status.getStatus());
- if (shardKey.isEmpty()) {
- errmsg = str::stream() << "no shard key found in chunk query " << find;
- return false;
- }
+ BSONObj shardKey = status.getValue();
- chunk = info->findIntersectingChunk(shardKey);
- verify(chunk.get());
- }
- else {
-
- // Bounds
- if (!info->getShardKeyPattern().isShardKey(bounds[0].Obj())
- || !info->getShardKeyPattern().isShardKey(bounds[1].Obj())) {
- errmsg = str::stream() << "shard key bounds " << "[" << bounds[0].Obj() << ","
- << bounds[1].Obj() << ")"
- << " are not valid for shard key pattern "
- << info->getShardKeyPattern().toBSON();
- return false;
- }
-
- BSONObj minKey = info->getShardKeyPattern().normalizeShardKey(bounds[0].Obj());
- BSONObj maxKey = info->getShardKeyPattern().normalizeShardKey(bounds[1].Obj());
-
- chunk = info->findIntersectingChunk(minKey);
- verify(chunk.get());
-
- if (chunk->getMin().woCompare(minKey) != 0
- || chunk->getMax().woCompare(maxKey) != 0) {
-
- errmsg = str::stream() << "no chunk found with the shard key bounds " << "["
- << minKey << "," << maxKey << ")";
- return false;
- }
+ if (shardKey.isEmpty()) {
+ errmsg = str::stream() << "no shard key found in chunk query " << find;
+ return false;
}
- {
- const auto from = grid.shardRegistry()->getShard(chunk->getShardId());
- if (from->getId() == to->getId()) {
- errmsg = "that chunk is already on that shard";
- return false;
- }
+ chunk = info->findIntersectingChunk(shardKey);
+ verify(chunk.get());
+ } else {
+ // Bounds
+ if (!info->getShardKeyPattern().isShardKey(bounds[0].Obj()) ||
+ !info->getShardKeyPattern().isShardKey(bounds[1].Obj())) {
+ errmsg = str::stream() << "shard key bounds "
+ << "[" << bounds[0].Obj() << "," << bounds[1].Obj() << ")"
+ << " are not valid for shard key pattern "
+ << info->getShardKeyPattern().toBSON();
+ return false;
}
- LOG(0) << "CMD: movechunk: " << cmdObj;
+ BSONObj minKey = info->getShardKeyPattern().normalizeShardKey(bounds[0].Obj());
+ BSONObj maxKey = info->getShardKeyPattern().normalizeShardKey(bounds[1].Obj());
- StatusWith<int> maxTimeMS = LiteParsedQuery::parseMaxTimeMSCommand(cmdObj);
+ chunk = info->findIntersectingChunk(minKey);
+ verify(chunk.get());
- if (!maxTimeMS.isOK()) {
- errmsg = maxTimeMS.getStatus().reason();
+ if (chunk->getMin().woCompare(minKey) != 0 || chunk->getMax().woCompare(maxKey) != 0) {
+ errmsg = str::stream() << "no chunk found with the shard key bounds "
+ << "[" << minKey << "," << maxKey << ")";
return false;
}
+ }
- unique_ptr<WriteConcernOptions> writeConcern(new WriteConcernOptions());
-
- Status status = writeConcern->parseSecondaryThrottle(cmdObj, NULL);
- if (!status.isOK()){
- if (status.code() != ErrorCodes::WriteConcernNotDefined) {
- errmsg = status.toString();
- return false;
- }
-
- // Let the shard decide what write concern to use.
- writeConcern.reset();
+ {
+ const auto from = grid.shardRegistry()->getShard(chunk->getShardId());
+ if (from->getId() == to->getId()) {
+ errmsg = "that chunk is already on that shard";
+ return false;
}
+ }
- BSONObj res;
- if (!chunk->moveAndCommit(to->getId(),
- maxChunkSizeBytes,
- writeConcern.get(),
- cmdObj["_waitForDelete"].trueValue(),
- maxTimeMS.getValue(),
- res)) {
+ LOG(0) << "CMD: movechunk: " << cmdObj;
- errmsg = "move failed";
- result.append("cause", res);
+ StatusWith<int> maxTimeMS = LiteParsedQuery::parseMaxTimeMSCommand(cmdObj);
+
+ if (!maxTimeMS.isOK()) {
+ errmsg = maxTimeMS.getStatus().reason();
+ return false;
+ }
- if (!res["code"].eoo()) {
- result.append(res["code"]);
- }
+ unique_ptr<WriteConcernOptions> writeConcern(new WriteConcernOptions());
+ Status status = writeConcern->parseSecondaryThrottle(cmdObj, NULL);
+ if (!status.isOK()) {
+ if (status.code() != ErrorCodes::WriteConcernNotDefined) {
+ errmsg = status.toString();
return false;
}
- result.append("millis", t.millis());
+ // Let the shard decide what write concern to use.
+ writeConcern.reset();
+ }
+
+ BSONObj res;
+ if (!chunk->moveAndCommit(to->getId(),
+ maxChunkSizeBytes,
+ writeConcern.get(),
+ cmdObj["_waitForDelete"].trueValue(),
+ maxTimeMS.getValue(),
+ res)) {
+ errmsg = "move failed";
+ result.append("cause", res);
+
+ if (!res["code"].eoo()) {
+ result.append(res["code"]);
+ }
- return true;
+ return false;
}
- } moveChunk;
+ result.append("millis", t.millis());
+
+ return true;
+ }
+
+} moveChunk;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_move_primary_cmd.cpp b/src/mongo/s/commands/cluster_move_primary_cmd.cpp
index 227830f410d..f9d401a26b6 100644
--- a/src/mongo/s/commands/cluster_move_primary_cmd.cpp
+++ b/src/mongo/s/commands/cluster_move_primary_cmd.cpp
@@ -51,242 +51,225 @@
namespace mongo {
- using std::shared_ptr;
- using std::set;
- using std::string;
+using std::shared_ptr;
+using std::set;
+using std::string;
namespace {
- class MoveDatabasePrimaryCommand : public Command {
- public:
- MoveDatabasePrimaryCommand() : Command("movePrimary", false, "moveprimary") { }
+class MoveDatabasePrimaryCommand : public Command {
+public:
+ MoveDatabasePrimaryCommand() : Command("movePrimary", false, "moveprimary") {}
- virtual bool slaveOk() const {
- return true;
- }
+ virtual bool slaveOk() const {
+ return true;
+ }
- virtual bool adminOnly() const {
- return true;
- }
+ virtual bool adminOnly() const {
+ return true;
+ }
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(std::stringstream& help) const {
+ help << " example: { moveprimary : 'foo' , to : 'localhost:9999' }";
+ }
- virtual void help(std::stringstream& help) const {
- help << " example: { moveprimary : 'foo' , to : 'localhost:9999' }";
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forDatabaseName(parseNs(dbname, cmdObj)), ActionType::moveChunk)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
}
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
+ return Status::OK();
+ }
- if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
- ResourcePattern::forDatabaseName(
- parseNs(dbname, cmdObj)),
- ActionType::moveChunk)) {
- return Status(ErrorCodes::Unauthorized, "Unauthorized");
- }
+ virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
+ return cmdObj.firstElement().str();
+ }
+
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname_unused,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ const string dbname = parseNs("", cmdObj);
- return Status::OK();
+ if (dbname.empty() || !nsIsDbOnly(dbname)) {
+ errmsg = "invalid db name specified: " + dbname;
+ return false;
}
- virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
- return cmdObj.firstElement().str();
+ if (dbname == "admin" || dbname == "config" || dbname == "local") {
+ errmsg = "can't move primary for " + dbname + " database";
+ return false;
}
- virtual bool run(OperationContext* txn,
- const std::string& dbname_unused,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
+ // Flush all cached information. This can't be perfect, but it's better than nothing.
+ grid.catalogCache()->invalidate(dbname);
- const string dbname = parseNs("", cmdObj);
+ auto status = grid.catalogCache()->getDatabase(dbname);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status.getStatus());
+ }
- if (dbname.empty() || !nsIsDbOnly(dbname)) {
- errmsg = "invalid db name specified: " + dbname;
- return false;
- }
+ shared_ptr<DBConfig> config = status.getValue();
- if (dbname == "admin" || dbname == "config" || dbname == "local") {
- errmsg = "can't move primary for " + dbname + " database";
- return false;
- }
+ const string to = cmdObj["to"].valuestrsafe();
+ if (!to.size()) {
+ errmsg = "you have to specify where you want to move it";
+ return false;
+ }
- // Flush all cached information. This can't be perfect, but it's better than nothing.
- grid.catalogCache()->invalidate(dbname);
+ shared_ptr<Shard> toShard = grid.shardRegistry()->getShard(to);
+ if (!toShard) {
+ string msg(str::stream() << "Could not move database '" << dbname << "' to shard '"
+ << to << "' because the shard does not exist");
+ log() << msg;
+ return appendCommandStatus(result, Status(ErrorCodes::ShardNotFound, msg));
+ }
- auto status = grid.catalogCache()->getDatabase(dbname);
- if (!status.isOK()) {
- return appendCommandStatus(result, status.getStatus());
- }
+ shared_ptr<Shard> fromShard = grid.shardRegistry()->getShard(config->getPrimaryId());
+ invariant(fromShard);
- shared_ptr<DBConfig> config = status.getValue();
+ if (fromShard->getConnString().sameLogicalEndpoint(toShard->getConnString())) {
+ errmsg = "it is already the primary";
+ return false;
+ }
- const string to = cmdObj["to"].valuestrsafe();
- if (!to.size()) {
- errmsg = "you have to specify where you want to move it";
- return false;
- }
+ if (!grid.catalogManager()->isShardHost(toShard->getConnString())) {
+ errmsg = "that server isn't known to me";
+ return false;
+ }
- shared_ptr<Shard> toShard = grid.shardRegistry()->getShard(to);
- if (!toShard) {
- string msg(str::stream() << "Could not move database '" << dbname
- << "' to shard '" << to
- << "' because the shard does not exist");
- log() << msg;
- return appendCommandStatus(result,
- Status(ErrorCodes::ShardNotFound, msg));
- }
+ log() << "Moving " << dbname << " primary from: " << fromShard->toString()
+ << " to: " << toShard->toString();
- shared_ptr<Shard> fromShard =
- grid.shardRegistry()->getShard(config->getPrimaryId());
- invariant(fromShard);
+ string whyMessage(str::stream() << "Moving primary shard of " << dbname);
+ auto scopedDistLock =
+ grid.catalogManager()->getDistLockManager()->lock(dbname + "-movePrimary", whyMessage);
- if (fromShard->getConnString().sameLogicalEndpoint(toShard->getConnString())) {
- errmsg = "it is already the primary";
- return false;
- }
+ if (!scopedDistLock.isOK()) {
+ return appendCommandStatus(result, scopedDistLock.getStatus());
+ }
- if (!grid.catalogManager()->isShardHost(toShard->getConnString())) {
- errmsg = "that server isn't known to me";
- return false;
- }
+ set<string> shardedColls;
+ config->getAllShardedCollections(shardedColls);
- log() << "Moving " << dbname << " primary from: "
- << fromShard->toString() << " to: " << toShard->toString();
+ // Record start in changelog
+ BSONObj moveStartDetails =
+ _buildMoveEntry(dbname, fromShard->toString(), toShard->toString(), shardedColls);
- string whyMessage(str::stream() << "Moving primary shard of " << dbname);
- auto scopedDistLock = grid.catalogManager()->getDistLockManager()->lock(
- dbname + "-movePrimary", whyMessage);
+ grid.catalogManager()->logChange(txn, "movePrimary.start", dbname, moveStartDetails);
- if (!scopedDistLock.isOK()) {
- return appendCommandStatus(result, scopedDistLock.getStatus());
- }
+ BSONArrayBuilder barr;
+ barr.append(shardedColls);
- set<string> shardedColls;
- config->getAllShardedCollections(shardedColls);
-
- // Record start in changelog
- BSONObj moveStartDetails = _buildMoveEntry(dbname,
- fromShard->toString(),
- toShard->toString(),
- shardedColls);
-
- grid.catalogManager()->logChange(txn, "movePrimary.start", dbname, moveStartDetails);
-
- BSONArrayBuilder barr;
- barr.append(shardedColls);
-
- ScopedDbConnection toconn(toShard->getConnString());
-
- // TODO ERH - we need a clone command which replays operations from clone start to now
- // can just use local.oplog.$main
- BSONObj cloneRes;
- bool worked = toconn->runCommand(
- dbname.c_str(),
- BSON("clone" << fromShard->getConnString().toString()
- << "collsToIgnore" << barr.arr()
- << bypassDocumentValidationCommandOption() << true),
- cloneRes);
- toconn.done();
-
- if (!worked) {
- log() << "clone failed" << cloneRes;
- errmsg = "clone failed";
- return false;
- }
+ ScopedDbConnection toconn(toShard->getConnString());
- const string oldPrimary = fromShard->getConnString().toString();
+ // TODO ERH - we need a clone command which replays operations from clone start to now
+ // can just use local.oplog.$main
+ BSONObj cloneRes;
+ bool worked = toconn->runCommand(
+ dbname.c_str(),
+ BSON("clone" << fromShard->getConnString().toString() << "collsToIgnore" << barr.arr()
+ << bypassDocumentValidationCommandOption() << true),
+ cloneRes);
+ toconn.done();
- ScopedDbConnection fromconn(fromShard->getConnString());
+ if (!worked) {
+ log() << "clone failed" << cloneRes;
+ errmsg = "clone failed";
+ return false;
+ }
- config->setPrimary(toShard->getConnString().toString());
+ const string oldPrimary = fromShard->getConnString().toString();
- if (shardedColls.empty()){
+ ScopedDbConnection fromconn(fromShard->getConnString());
- // TODO: Collections can be created in the meantime, and we should handle in the future.
- log() << "movePrimary dropping database on " << oldPrimary
- << ", no sharded collections in " << dbname;
+ config->setPrimary(toShard->getConnString().toString());
- try {
- fromconn->dropDatabase(dbname.c_str());
- }
- catch (DBException& e){
- e.addContext(str::stream() << "movePrimary could not drop the database "
- << dbname << " on " << oldPrimary);
- throw;
- }
+ if (shardedColls.empty()) {
+ // TODO: Collections can be created in the meantime, and we should handle in the future.
+ log() << "movePrimary dropping database on " << oldPrimary
+ << ", no sharded collections in " << dbname;
+ try {
+ fromconn->dropDatabase(dbname.c_str());
+ } catch (DBException& e) {
+ e.addContext(str::stream() << "movePrimary could not drop the database " << dbname
+ << " on " << oldPrimary);
+ throw;
}
- else if (cloneRes["clonedColls"].type() != Array) {
- // Legacy behavior from old mongod with sharded collections, *do not* delete
- // database, but inform user they can drop manually (or ignore).
- warning() << "movePrimary legacy mongod behavior detected. "
- << "User must manually remove unsharded collections in database "
- << dbname << " on " << oldPrimary;
- }
- else {
- // We moved some unsharded collections, but not all
- BSONObjIterator it(cloneRes["clonedColls"].Obj());
-
- while (it.more()){
- BSONElement el = it.next();
- if (el.type() == String){
- try {
- log() << "movePrimary dropping cloned collection " << el.String()
- << " on " << oldPrimary;
- fromconn->dropCollection(el.String());
- }
- catch (DBException& e){
- e.addContext(str::stream() << "movePrimary could not drop the cloned collection "
- << el.String() << " on " << oldPrimary);
- throw;
- }
+ } else if (cloneRes["clonedColls"].type() != Array) {
+ // Legacy behavior from old mongod with sharded collections, *do not* delete
+ // database, but inform user they can drop manually (or ignore).
+ warning() << "movePrimary legacy mongod behavior detected. "
+ << "User must manually remove unsharded collections in database " << dbname
+ << " on " << oldPrimary;
+
+ } else {
+ // We moved some unsharded collections, but not all
+ BSONObjIterator it(cloneRes["clonedColls"].Obj());
+
+ while (it.more()) {
+ BSONElement el = it.next();
+ if (el.type() == String) {
+ try {
+ log() << "movePrimary dropping cloned collection " << el.String() << " on "
+ << oldPrimary;
+ fromconn->dropCollection(el.String());
+ } catch (DBException& e) {
+ e.addContext(str::stream()
+ << "movePrimary could not drop the cloned collection "
+ << el.String() << " on " << oldPrimary);
+ throw;
}
}
}
-
- fromconn.done();
-
- result << "primary" << toShard->toString();
-
- // Record finish in changelog
- BSONObj moveFinishDetails = _buildMoveEntry(dbname,
- oldPrimary,
- toShard->toString(),
- shardedColls);
-
- grid.catalogManager()->logChange(txn, "movePrimary", dbname, moveFinishDetails);
- return true;
}
- private:
- static BSONObj _buildMoveEntry(const string db,
- const string from,
- const string to,
- set<string> shardedColls) {
-
- BSONObjBuilder details;
- details.append("database", db);
- details.append("from", from);
- details.append("to", to);
-
- BSONArrayBuilder collB(details.subarrayStart("shardedCollections"));
- {
- set<string>::iterator it;
- for (it = shardedColls.begin(); it != shardedColls.end(); ++it) {
- collB.append(*it);
- }
+ fromconn.done();
+
+ result << "primary" << toShard->toString();
+
+ // Record finish in changelog
+ BSONObj moveFinishDetails =
+ _buildMoveEntry(dbname, oldPrimary, toShard->toString(), shardedColls);
+
+ grid.catalogManager()->logChange(txn, "movePrimary", dbname, moveFinishDetails);
+ return true;
+ }
+
+private:
+ static BSONObj _buildMoveEntry(const string db,
+ const string from,
+ const string to,
+ set<string> shardedColls) {
+ BSONObjBuilder details;
+ details.append("database", db);
+ details.append("from", from);
+ details.append("to", to);
+
+ BSONArrayBuilder collB(details.subarrayStart("shardedCollections"));
+ {
+ set<string>::iterator it;
+ for (it = shardedColls.begin(); it != shardedColls.end(); ++it) {
+ collB.append(*it);
}
- collB.done();
-
- return details.obj();
}
+ collB.done();
+
+ return details.obj();
+ }
- } movePrimary;
+} movePrimary;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_netstat_cmd.cpp b/src/mongo/s/commands/cluster_netstat_cmd.cpp
index ca1bf2fd9af..df4c158a8b7 100644
--- a/src/mongo/s/commands/cluster_netstat_cmd.cpp
+++ b/src/mongo/s/commands/cluster_netstat_cmd.cpp
@@ -35,47 +35,46 @@
namespace mongo {
namespace {
- class NetStatCmd : public Command {
- public:
- NetStatCmd() : Command("netstat", false, "netstat") { }
+class NetStatCmd : public Command {
+public:
+ NetStatCmd() : Command("netstat", false, "netstat") {}
- virtual bool slaveOk() const {
- return true;
- }
+ virtual bool slaveOk() const {
+ return true;
+ }
- virtual bool adminOnly() const {
- return true;
- }
+ virtual bool adminOnly() const {
+ return true;
+ }
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- virtual void help(std::stringstream& help) const {
- help << " shows status/reachability of servers in the cluster";
- }
+ virtual void help(std::stringstream& help) const {
+ help << " shows status/reachability of servers in the cluster";
+ }
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::netstat);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
- }
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::netstat);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ result.append("configserver", grid.catalogManager()->connectionString().toString());
+ result.append("isdbgrid", 1);
+ return true;
+ }
- result.append("configserver", grid.catalogManager()->connectionString().toString());
- result.append("isdbgrid", 1);
- return true;
- }
+} netstat;
- } netstat;
-
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_pipeline_cmd.cpp b/src/mongo/s/commands/cluster_pipeline_cmd.cpp
index 60f7512d172..1d1fe933410 100644
--- a/src/mongo/s/commands/cluster_pipeline_cmd.cpp
+++ b/src/mongo/s/commands/cluster_pipeline_cmd.cpp
@@ -53,372 +53,345 @@
namespace mongo {
- using boost::intrusive_ptr;
- using std::unique_ptr;
- using std::shared_ptr;
- using std::string;
- using std::vector;
+using boost::intrusive_ptr;
+using std::unique_ptr;
+using std::shared_ptr;
+using std::string;
+using std::vector;
namespace {
- /**
- * Implements the aggregation (pipeline command for sharding).
- */
- class PipelineCommand : public Command {
- public:
- PipelineCommand() : Command(Pipeline::commandName, false) { }
+/**
+ * Implements the aggregation (pipeline command for sharding).
+ */
+class PipelineCommand : public Command {
+public:
+ PipelineCommand() : Command(Pipeline::commandName, false) {}
- virtual bool slaveOk() const {
- return true;
- }
+ virtual bool slaveOk() const {
+ return true;
+ }
- virtual bool adminOnly() const {
- return false;
- }
+ virtual bool adminOnly() const {
+ return false;
+ }
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- virtual void help(std::stringstream& help) const {
- help << "Runs the sharded aggregation command";
- }
+ virtual void help(std::stringstream& help) const {
+ help << "Runs the sharded aggregation command";
+ }
- // virtuals from Command
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
+ // virtuals from Command
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ Pipeline::addRequiredPrivileges(this, dbname, cmdObj, out);
+ }
- Pipeline::addRequiredPrivileges(this, dbname, cmdObj, out);
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ const string fullns = parseNs(dbname, cmdObj);
+
+ auto status = grid.catalogCache()->getDatabase(dbname);
+ if (!status.isOK()) {
+ return appendEmptyResultSet(result, status.getStatus(), fullns);
}
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
-
- const string fullns = parseNs(dbname, cmdObj);
+ shared_ptr<DBConfig> conf = status.getValue();
- auto status = grid.catalogCache()->getDatabase(dbname);
- if (!status.isOK()) {
- return appendEmptyResultSet(result, status.getStatus(), fullns);
- }
-
- shared_ptr<DBConfig> conf = status.getValue();
-
- // If the system isn't running sharded, or the target collection isn't sharded, pass
- // this on to a mongod.
- if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
- return aggPassthrough(conf, cmdObj, result, options);
- }
+ // If the system isn't running sharded, or the target collection isn't sharded, pass
+ // this on to a mongod.
+ if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
+ return aggPassthrough(conf, cmdObj, result, options);
+ }
- intrusive_ptr<ExpressionContext> mergeCtx =
- new ExpressionContext(txn, NamespaceString(fullns));
- mergeCtx->inRouter = true;
- // explicitly *not* setting mergeCtx->tempDir
+ intrusive_ptr<ExpressionContext> mergeCtx =
+ new ExpressionContext(txn, NamespaceString(fullns));
+ mergeCtx->inRouter = true;
+ // explicitly *not* setting mergeCtx->tempDir
- // Parse the pipeline specification
- intrusive_ptr<Pipeline> pipeline(Pipeline::parseCommand(errmsg, cmdObj, mergeCtx));
- if (!pipeline.get()) {
- // There was some parsing error
- return false;
- }
+ // Parse the pipeline specification
+ intrusive_ptr<Pipeline> pipeline(Pipeline::parseCommand(errmsg, cmdObj, mergeCtx));
+ if (!pipeline.get()) {
+ // There was some parsing error
+ return false;
+ }
- // If the first $match stage is an exact match on the shard key, we only have to send it
- // to one shard, so send the command to that shard.
- BSONObj firstMatchQuery = pipeline->getInitialQuery();
- ChunkManagerPtr chunkMgr = conf->getChunkManager(fullns);
- BSONObj shardKeyMatches = uassertStatusOK(
- chunkMgr->getShardKeyPattern().extractShardKeyFromQuery(firstMatchQuery));
-
- // Don't need to split pipeline if the first $match is an exact match on shard key, but
- // we can't send the entire pipeline to one shard if there is a $out stage, since that
- // shard may not be the primary shard for the database.
- bool needSplit = shardKeyMatches.isEmpty() || pipeline->hasOutStage();
-
- // Split the pipeline into pieces for mongod(s) and this mongos. If needSplit is true,
- // 'pipeline' will become the merger side.
- intrusive_ptr<Pipeline> shardPipeline(needSplit ? pipeline->splitForSharded()
- : pipeline);
-
- // Create the command for the shards. The 'fromRouter' field means produce output to
- // be merged.
- MutableDocument commandBuilder(shardPipeline->serialize());
- if (needSplit) {
- commandBuilder.setField("fromRouter", Value(true));
- commandBuilder.setField("cursor", Value(DOC("batchSize" << 0)));
- }
- else {
- commandBuilder.setField("cursor", Value(cmdObj["cursor"]));
- }
+ // If the first $match stage is an exact match on the shard key, we only have to send it
+ // to one shard, so send the command to that shard.
+ BSONObj firstMatchQuery = pipeline->getInitialQuery();
+ ChunkManagerPtr chunkMgr = conf->getChunkManager(fullns);
+ BSONObj shardKeyMatches = uassertStatusOK(
+ chunkMgr->getShardKeyPattern().extractShardKeyFromQuery(firstMatchQuery));
+
+ // Don't need to split pipeline if the first $match is an exact match on shard key, but
+ // we can't send the entire pipeline to one shard if there is a $out stage, since that
+ // shard may not be the primary shard for the database.
+ bool needSplit = shardKeyMatches.isEmpty() || pipeline->hasOutStage();
+
+ // Split the pipeline into pieces for mongod(s) and this mongos. If needSplit is true,
+ // 'pipeline' will become the merger side.
+ intrusive_ptr<Pipeline> shardPipeline(needSplit ? pipeline->splitForSharded() : pipeline);
+
+ // Create the command for the shards. The 'fromRouter' field means produce output to
+ // be merged.
+ MutableDocument commandBuilder(shardPipeline->serialize());
+ if (needSplit) {
+ commandBuilder.setField("fromRouter", Value(true));
+ commandBuilder.setField("cursor", Value(DOC("batchSize" << 0)));
+ } else {
+ commandBuilder.setField("cursor", Value(cmdObj["cursor"]));
+ }
- if (cmdObj.hasField("$queryOptions")) {
- commandBuilder.setField("$queryOptions", Value(cmdObj["$queryOptions"]));
- }
+ if (cmdObj.hasField("$queryOptions")) {
+ commandBuilder.setField("$queryOptions", Value(cmdObj["$queryOptions"]));
+ }
- if (cmdObj.hasField(LiteParsedQuery::cmdOptionMaxTimeMS)) {
- commandBuilder.setField(LiteParsedQuery::cmdOptionMaxTimeMS,
- Value(cmdObj[LiteParsedQuery::cmdOptionMaxTimeMS]));
- }
+ if (cmdObj.hasField(LiteParsedQuery::cmdOptionMaxTimeMS)) {
+ commandBuilder.setField(LiteParsedQuery::cmdOptionMaxTimeMS,
+ Value(cmdObj[LiteParsedQuery::cmdOptionMaxTimeMS]));
+ }
- BSONObj shardedCommand = commandBuilder.freeze().toBson();
- BSONObj shardQuery = shardPipeline->getInitialQuery();
-
- // Run the command on the shards
- // TODO need to make sure cursors are killed if a retry is needed
- vector<Strategy::CommandResult> shardResults;
- Strategy::commandOp(dbname,
- shardedCommand,
- options,
- fullns,
- shardQuery,
- &shardResults);
-
- if (pipeline->isExplain()) {
- // This must be checked before we start modifying result.
- uassertAllShardsSupportExplain(shardResults);
-
- if (needSplit) {
- result << "splitPipeline"
- << DOC("shardsPart" << shardPipeline->writeExplainOps()
- << "mergerPart" << pipeline->writeExplainOps());
- }
- else {
- result << "splitPipeline" << BSONNULL;
- }
+ BSONObj shardedCommand = commandBuilder.freeze().toBson();
+ BSONObj shardQuery = shardPipeline->getInitialQuery();
- BSONObjBuilder shardExplains(result.subobjStart("shards"));
- for (size_t i = 0; i < shardResults.size(); i++) {
- shardExplains.append(shardResults[i].shardTargetId,
- BSON("host" << shardResults[i].target.toString() <<
- "stages" << shardResults[i].result["stages"]));
- }
+ // Run the command on the shards
+ // TODO need to make sure cursors are killed if a retry is needed
+ vector<Strategy::CommandResult> shardResults;
+ Strategy::commandOp(dbname, shardedCommand, options, fullns, shardQuery, &shardResults);
- return true;
- }
+ if (pipeline->isExplain()) {
+ // This must be checked before we start modifying result.
+ uassertAllShardsSupportExplain(shardResults);
- if (!needSplit) {
- invariant(shardResults.size() == 1);
- const auto& reply = shardResults[0].result;
- storePossibleCursor(shardResults[0].target.toString(), reply);
- result.appendElements(reply);
- return reply["ok"].trueValue();
+ if (needSplit) {
+ result << "splitPipeline"
+ << DOC("shardsPart" << shardPipeline->writeExplainOps() << "mergerPart"
+ << pipeline->writeExplainOps());
+ } else {
+ result << "splitPipeline" << BSONNULL;
}
- DocumentSourceMergeCursors::CursorIds cursorIds = parseCursors(shardResults, fullns);
- pipeline->addInitialSource(DocumentSourceMergeCursors::create(cursorIds, mergeCtx));
-
- MutableDocument mergeCmd(pipeline->serialize());
- mergeCmd["cursor"] = Value(cmdObj["cursor"]);
-
- if (cmdObj.hasField("$queryOptions")) {
- mergeCmd["$queryOptions"] = Value(cmdObj["$queryOptions"]);
+ BSONObjBuilder shardExplains(result.subobjStart("shards"));
+ for (size_t i = 0; i < shardResults.size(); i++) {
+ shardExplains.append(shardResults[i].shardTargetId,
+ BSON("host" << shardResults[i].target.toString() << "stages"
+ << shardResults[i].result["stages"]));
}
- if (cmdObj.hasField(LiteParsedQuery::cmdOptionMaxTimeMS)) {
- mergeCmd[LiteParsedQuery::cmdOptionMaxTimeMS]
- = Value(cmdObj[LiteParsedQuery::cmdOptionMaxTimeMS]);
- }
+ return true;
+ }
- string outputNsOrEmpty;
- if (DocumentSourceOut* out = dynamic_cast<DocumentSourceOut*>(pipeline->output())) {
- outputNsOrEmpty = out->getOutputNs().ns();
- }
+ if (!needSplit) {
+ invariant(shardResults.size() == 1);
+ const auto& reply = shardResults[0].result;
+ storePossibleCursor(shardResults[0].target.toString(), reply);
+ result.appendElements(reply);
+ return reply["ok"].trueValue();
+ }
- // Run merging command on primary shard of database. Need to use ShardConnection so
- // that the merging mongod is sent the config servers on connection init.
- const auto shard = grid.shardRegistry()->getShard(conf->getPrimaryId());
- ShardConnection conn(shard->getConnString(), outputNsOrEmpty);
- BSONObj mergedResults = aggRunCommand(conn.get(),
- dbname,
- mergeCmd.freeze().toBson(),
- options);
- conn.done();
+ DocumentSourceMergeCursors::CursorIds cursorIds = parseCursors(shardResults, fullns);
+ pipeline->addInitialSource(DocumentSourceMergeCursors::create(cursorIds, mergeCtx));
- // Copy output from merging (primary) shard to the output object from our command.
- // Also, propagates errmsg and code if ok == false.
- result.appendElements(mergedResults);
+ MutableDocument mergeCmd(pipeline->serialize());
+ mergeCmd["cursor"] = Value(cmdObj["cursor"]);
- return mergedResults["ok"].trueValue();
+ if (cmdObj.hasField("$queryOptions")) {
+ mergeCmd["$queryOptions"] = Value(cmdObj["$queryOptions"]);
}
- private:
- DocumentSourceMergeCursors::CursorIds parseCursors(
- const vector<Strategy::CommandResult>& shardResults,
- const string& fullns);
-
- void killAllCursors(const vector<Strategy::CommandResult>& shardResults);
- void uassertAllShardsSupportExplain(const vector<Strategy::CommandResult>& shardResults);
-
- // These are temporary hacks because the runCommand method doesn't report the exact
- // host the command was run on which is necessary for cursor support. The exact host
- // could be different from conn->getServerAddress() for connections that map to
- // multiple servers such as for replica sets. These also take care of registering
- // returned cursors with mongos's cursorCache.
- BSONObj aggRunCommand(DBClientBase* conn,
- const string& db,
- BSONObj cmd,
- int queryOptions);
-
- bool aggPassthrough(DBConfigPtr conf,
- BSONObj cmd,
- BSONObjBuilder& result,
- int queryOptions);
- } clusterPipelineCmd;
-
- DocumentSourceMergeCursors::CursorIds PipelineCommand::parseCursors(
- const vector<Strategy::CommandResult>& shardResults,
- const string& fullns) {
- try {
- DocumentSourceMergeCursors::CursorIds cursors;
-
- for (size_t i = 0; i < shardResults.size(); i++) {
- BSONObj result = shardResults[i].result;
-
- if (!result["ok"].trueValue()) {
- // If the failure of the sharded command can be accounted to a single error,
- // throw a UserException with that error code; otherwise, throw with a
- // location uassert code.
- int errCode = getUniqueCodeFromCommandResults(shardResults);
- if (errCode == 0) {
- errCode = 17022;
- }
-
- invariant(errCode == result["code"].numberInt() || errCode == 17022);
- uasserted(errCode, str::stream()
- << "sharded pipeline failed on shard "
- << shardResults[i].shardTargetId << ": "
- << result.toString());
- }
+ if (cmdObj.hasField(LiteParsedQuery::cmdOptionMaxTimeMS)) {
+ mergeCmd[LiteParsedQuery::cmdOptionMaxTimeMS] =
+ Value(cmdObj[LiteParsedQuery::cmdOptionMaxTimeMS]);
+ }
- BSONObj cursor = result["cursor"].Obj();
+ string outputNsOrEmpty;
+ if (DocumentSourceOut* out = dynamic_cast<DocumentSourceOut*>(pipeline->output())) {
+ outputNsOrEmpty = out->getOutputNs().ns();
+ }
- massert(17023,
- str::stream() << "shard " << shardResults[i].shardTargetId
- << " returned non-empty first batch",
- cursor["firstBatch"].Obj().isEmpty());
+ // Run merging command on primary shard of database. Need to use ShardConnection so
+ // that the merging mongod is sent the config servers on connection init.
+ const auto shard = grid.shardRegistry()->getShard(conf->getPrimaryId());
+ ShardConnection conn(shard->getConnString(), outputNsOrEmpty);
+ BSONObj mergedResults =
+ aggRunCommand(conn.get(), dbname, mergeCmd.freeze().toBson(), options);
+ conn.done();
- massert(17024,
- str::stream() << "shard " << shardResults[i].shardTargetId
- << " returned cursorId 0",
- cursor["id"].Long() != 0);
+ // Copy output from merging (primary) shard to the output object from our command.
+ // Also, propagates errmsg and code if ok == false.
+ result.appendElements(mergedResults);
- massert(17025,
- str::stream() << "shard " << shardResults[i].shardTargetId
- << " returned different ns: " << cursor["ns"],
- cursor["ns"].String() == fullns);
+ return mergedResults["ok"].trueValue();
+ }
- cursors.push_back(std::make_pair(shardResults[i].target, cursor["id"].Long()));
- }
+private:
+ DocumentSourceMergeCursors::CursorIds parseCursors(
+ const vector<Strategy::CommandResult>& shardResults, const string& fullns);
- return cursors;
- }
- catch (...) {
- // Need to clean up any cursors we successfully created on the shards
- killAllCursors(shardResults);
- throw;
- }
- }
+ void killAllCursors(const vector<Strategy::CommandResult>& shardResults);
+ void uassertAllShardsSupportExplain(const vector<Strategy::CommandResult>& shardResults);
- void PipelineCommand::uassertAllShardsSupportExplain(
- const vector<Strategy::CommandResult>& shardResults) {
+ // These are temporary hacks because the runCommand method doesn't report the exact
+ // host the command was run on which is necessary for cursor support. The exact host
+ // could be different from conn->getServerAddress() for connections that map to
+ // multiple servers such as for replica sets. These also take care of registering
+ // returned cursors with mongos's cursorCache.
+ BSONObj aggRunCommand(DBClientBase* conn, const string& db, BSONObj cmd, int queryOptions);
- for (size_t i = 0; i < shardResults.size(); i++) {
- uassert(17403,
- str::stream() << "Shard " << shardResults[i].target.toString()
- << " failed: " << shardResults[i].result,
- shardResults[i].result["ok"].trueValue());
-
- uassert(17404,
- str::stream() << "Shard " << shardResults[i].target.toString()
- << " does not support $explain",
- shardResults[i].result.hasField("stages"));
- }
- }
+ bool aggPassthrough(DBConfigPtr conf, BSONObj cmd, BSONObjBuilder& result, int queryOptions);
+} clusterPipelineCmd;
- void PipelineCommand::killAllCursors(const vector<Strategy::CommandResult>& shardResults) {
- // This function must ignore and log all errors. Callers expect a best-effort attempt at
- // cleanup without exceptions. If any cursors aren't cleaned up here, they will be cleaned
- // up automatically on the shard after 10 minutes anyway.
+DocumentSourceMergeCursors::CursorIds PipelineCommand::parseCursors(
+ const vector<Strategy::CommandResult>& shardResults, const string& fullns) {
+ try {
+ DocumentSourceMergeCursors::CursorIds cursors;
for (size_t i = 0; i < shardResults.size(); i++) {
- try {
- BSONObj result = shardResults[i].result;
- if (!result["ok"].trueValue()) {
- continue;
- }
-
- const long long cursor = result["cursor"]["id"].Long();
- if (!cursor) {
- continue;
+ BSONObj result = shardResults[i].result;
+
+ if (!result["ok"].trueValue()) {
+ // If the failure of the sharded command can be accounted to a single error,
+ // throw a UserException with that error code; otherwise, throw with a
+ // location uassert code.
+ int errCode = getUniqueCodeFromCommandResults(shardResults);
+ if (errCode == 0) {
+ errCode = 17022;
}
- ScopedDbConnection conn(shardResults[i].target);
- conn->killCursor(cursor);
- conn.done();
- }
- catch (const DBException& e) {
- log() << "Couldn't kill aggregation cursor on shard: " << shardResults[i].target
- << " due to DBException: " << e.toString();
- }
- catch (const std::exception& e) {
- log() << "Couldn't kill aggregation cursor on shard: " << shardResults[i].target
- << " due to std::exception: " << e.what();
+ invariant(errCode == result["code"].numberInt() || errCode == 17022);
+ uasserted(errCode,
+ str::stream() << "sharded pipeline failed on shard "
+ << shardResults[i].shardTargetId << ": "
+ << result.toString());
}
- catch (...) {
- log() << "Couldn't kill aggregation cursor on shard: " << shardResults[i].target
- << " due to non-exception";
- }
- }
- }
- BSONObj PipelineCommand::aggRunCommand(DBClientBase* conn,
- const string& db,
- BSONObj cmd,
- int queryOptions) {
+ BSONObj cursor = result["cursor"].Obj();
- // Temporary hack. See comment on declaration for details.
+ massert(17023,
+ str::stream() << "shard " << shardResults[i].shardTargetId
+ << " returned non-empty first batch",
+ cursor["firstBatch"].Obj().isEmpty());
- massert(17016,
- "should only be running an aggregate command here",
- str::equals(cmd.firstElementFieldName(), "aggregate"));
+ massert(17024,
+ str::stream() << "shard " << shardResults[i].shardTargetId
+ << " returned cursorId 0",
+ cursor["id"].Long() != 0);
- auto cursor = conn->query(db + ".$cmd",
- cmd,
- -1, // nToReturn
- 0, // nToSkip
- NULL, // fieldsToReturn
- queryOptions);
- massert(17014,
- str::stream() << "aggregate command didn't return results on host: "
- << conn->toString(),
- cursor && cursor->more());
+ massert(17025,
+ str::stream() << "shard " << shardResults[i].shardTargetId
+ << " returned different ns: " << cursor["ns"],
+ cursor["ns"].String() == fullns);
- BSONObj result = cursor->nextSafe().getOwned();
-
- if (ErrorCodes::SendStaleConfig == getStatusFromCommandResult(result)) {
- throw RecvStaleConfigException("command failed because of stale config", result);
+ cursors.push_back(std::make_pair(shardResults[i].target, cursor["id"].Long()));
}
- uassertStatusOK(storePossibleCursor(cursor->originalHost(), result));
- return result;
+ return cursors;
+ } catch (...) {
+ // Need to clean up any cursors we successfully created on the shards
+ killAllCursors(shardResults);
+ throw;
+ }
+}
+
+void PipelineCommand::uassertAllShardsSupportExplain(
+ const vector<Strategy::CommandResult>& shardResults) {
+ for (size_t i = 0; i < shardResults.size(); i++) {
+ uassert(17403,
+ str::stream() << "Shard " << shardResults[i].target.toString()
+ << " failed: " << shardResults[i].result,
+ shardResults[i].result["ok"].trueValue());
+
+ uassert(17404,
+ str::stream() << "Shard " << shardResults[i].target.toString()
+ << " does not support $explain",
+ shardResults[i].result.hasField("stages"));
}
+}
- bool PipelineCommand::aggPassthrough(DBConfigPtr conf,
- BSONObj cmd,
- BSONObjBuilder& out,
- int queryOptions) {
+void PipelineCommand::killAllCursors(const vector<Strategy::CommandResult>& shardResults) {
+ // This function must ignore and log all errors. Callers expect a best-effort attempt at
+ // cleanup without exceptions. If any cursors aren't cleaned up here, they will be cleaned
+ // up automatically on the shard after 10 minutes anyway.
- // Temporary hack. See comment on declaration for details.
- const auto shard = grid.shardRegistry()->getShard(conf->getPrimaryId());
- ShardConnection conn(shard->getConnString(), "");
- BSONObj result = aggRunCommand(conn.get(), conf->name(), cmd, queryOptions);
- conn.done();
- out.appendElements(result);
- return result["ok"].trueValue();
+ for (size_t i = 0; i < shardResults.size(); i++) {
+ try {
+ BSONObj result = shardResults[i].result;
+ if (!result["ok"].trueValue()) {
+ continue;
+ }
+
+ const long long cursor = result["cursor"]["id"].Long();
+ if (!cursor) {
+ continue;
+ }
+
+ ScopedDbConnection conn(shardResults[i].target);
+ conn->killCursor(cursor);
+ conn.done();
+ } catch (const DBException& e) {
+ log() << "Couldn't kill aggregation cursor on shard: " << shardResults[i].target
+ << " due to DBException: " << e.toString();
+ } catch (const std::exception& e) {
+ log() << "Couldn't kill aggregation cursor on shard: " << shardResults[i].target
+ << " due to std::exception: " << e.what();
+ } catch (...) {
+ log() << "Couldn't kill aggregation cursor on shard: " << shardResults[i].target
+ << " due to non-exception";
+ }
+ }
+}
+
+BSONObj PipelineCommand::aggRunCommand(DBClientBase* conn,
+ const string& db,
+ BSONObj cmd,
+ int queryOptions) {
+ // Temporary hack. See comment on declaration for details.
+
+ massert(17016,
+ "should only be running an aggregate command here",
+ str::equals(cmd.firstElementFieldName(), "aggregate"));
+
+ auto cursor = conn->query(db + ".$cmd",
+ cmd,
+ -1, // nToReturn
+ 0, // nToSkip
+ NULL, // fieldsToReturn
+ queryOptions);
+ massert(
+ 17014,
+ str::stream() << "aggregate command didn't return results on host: " << conn->toString(),
+ cursor && cursor->more());
+
+ BSONObj result = cursor->nextSafe().getOwned();
+
+ if (ErrorCodes::SendStaleConfig == getStatusFromCommandResult(result)) {
+ throw RecvStaleConfigException("command failed because of stale config", result);
}
-} // namespace
-} // namespace mongo
+ uassertStatusOK(storePossibleCursor(cursor->originalHost(), result));
+ return result;
+}
+
+bool PipelineCommand::aggPassthrough(DBConfigPtr conf,
+ BSONObj cmd,
+ BSONObjBuilder& out,
+ int queryOptions) {
+ // Temporary hack. See comment on declaration for details.
+ const auto shard = grid.shardRegistry()->getShard(conf->getPrimaryId());
+ ShardConnection conn(shard->getConnString(), "");
+ BSONObj result = aggRunCommand(conn.get(), conf->name(), cmd, queryOptions);
+ conn.done();
+ out.appendElements(result);
+ return result["ok"].trueValue();
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_plan_cache_cmd.cpp b/src/mongo/s/commands/cluster_plan_cache_cmd.cpp
index bb66ce55998..1411f5bc3b1 100644
--- a/src/mongo/s/commands/cluster_plan_cache_cmd.cpp
+++ b/src/mongo/s/commands/cluster_plan_cache_cmd.cpp
@@ -38,141 +38,137 @@
namespace mongo {
- using std::string;
- using std::stringstream;
- using std::vector;
+using std::string;
+using std::stringstream;
+using std::vector;
- /**
- * Base class for mongos plan cache commands.
- * Cluster plan cache commands don't do much more than
- * forwarding the commands to all shards and combining the results.
- */
- class ClusterPlanCacheCmd : public Command {
+/**
+ * Base class for mongos plan cache commands.
+ * Cluster plan cache commands don't do much more than
+ * forwarding the commands to all shards and combining the results.
+ */
+class ClusterPlanCacheCmd : public Command {
MONGO_DISALLOW_COPYING(ClusterPlanCacheCmd);
- public:
- virtual ~ClusterPlanCacheCmd() {
- }
+public:
+ virtual ~ClusterPlanCacheCmd() {}
- bool slaveOk() const {
- return false;
- }
-
- bool slaveOverrideOk() const {
- return true;
- }
+ bool slaveOk() const {
+ return false;
+ }
- virtual bool isWriteCommandForConfigServer() const { return false; }
+ bool slaveOverrideOk() const {
+ return true;
+ }
- void help(stringstream& ss) const {
- ss << _helpText;
- }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- Status checkAuthForCommand( ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj ) {
- AuthorizationSession* authzSession = AuthorizationSession::get(client);
- ResourcePattern pattern = parseResourcePattern(dbname, cmdObj);
+ void help(stringstream& ss) const {
+ ss << _helpText;
+ }
- if (authzSession->isAuthorizedForActionsOnResource(pattern, _actionType)) {
- return Status::OK();
- }
+ Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ AuthorizationSession* authzSession = AuthorizationSession::get(client);
+ ResourcePattern pattern = parseResourcePattern(dbname, cmdObj);
- return Status(ErrorCodes::Unauthorized, "unauthorized");
+ if (authzSession->isAuthorizedForActionsOnResource(pattern, _actionType)) {
+ return Status::OK();
}
- // Cluster plan cache command entry point.
- bool run(OperationContext* txn, const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result);
-
- public:
-
- /**
- * Instantiates a command that can be invoked by "name", which will be described by
- * "helpText", and will require privilege "actionType" to run.
- */
- ClusterPlanCacheCmd( const std::string& name, const std::string& helpText,
- ActionType actionType ) :
- Command( name ), _helpText( helpText ), _actionType( actionType ) {
- }
+ return Status(ErrorCodes::Unauthorized, "unauthorized");
+ }
+
+ // Cluster plan cache command entry point.
+ bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result);
- private:
-
- std::string _helpText;
- ActionType _actionType;
- };
-
- //
- // Cluster plan cache command implementation(s) below
- //
-
- bool ClusterPlanCacheCmd::run(OperationContext* txn, const std::string& dbName,
- BSONObj& cmdObj,
- int options,
- std::string& errMsg,
- BSONObjBuilder& result) {
- const std::string fullns = parseNs(dbName, cmdObj);
- NamespaceString nss(fullns);
-
- // Dispatch command to all the shards.
- // Targeted shard commands are generally data-dependent but plan cache
- // commands are tied to query shape (data has no effect on query shape).
- vector<Strategy::CommandResult> results;
- Strategy::commandOp(dbName, cmdObj, options, nss.ns(), BSONObj(), &results);
-
- // Set value of first shard result's "ok" field.
- bool clusterCmdResult = true;
-
- for (vector<Strategy::CommandResult>::const_iterator i = results.begin();
- i != results.end(); ++i) {
- const Strategy::CommandResult& cmdResult = *i;
-
- // XXX: In absence of sensible aggregation strategy,
- // promote first shard's result to top level.
- if (i == results.begin()) {
- result.appendElements(cmdResult.result);
- clusterCmdResult = cmdResult.result["ok"].trueValue();
- }
-
- // Append shard result as a sub object.
- // Name the field after the shard.
- string shardName = cmdResult.shardTargetId;
- result.append(shardName, cmdResult.result);
+public:
+ /**
+ * Instantiates a command that can be invoked by "name", which will be described by
+ * "helpText", and will require privilege "actionType" to run.
+ */
+ ClusterPlanCacheCmd(const std::string& name, const std::string& helpText, ActionType actionType)
+ : Command(name), _helpText(helpText), _actionType(actionType) {}
+
+private:
+ std::string _helpText;
+ ActionType _actionType;
+};
+
+//
+// Cluster plan cache command implementation(s) below
+//
+
+bool ClusterPlanCacheCmd::run(OperationContext* txn,
+ const std::string& dbName,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errMsg,
+ BSONObjBuilder& result) {
+ const std::string fullns = parseNs(dbName, cmdObj);
+ NamespaceString nss(fullns);
+
+ // Dispatch command to all the shards.
+ // Targeted shard commands are generally data-dependent but plan cache
+ // commands are tied to query shape (data has no effect on query shape).
+ vector<Strategy::CommandResult> results;
+ Strategy::commandOp(dbName, cmdObj, options, nss.ns(), BSONObj(), &results);
+
+ // Set value of first shard result's "ok" field.
+ bool clusterCmdResult = true;
+
+ for (vector<Strategy::CommandResult>::const_iterator i = results.begin(); i != results.end();
+ ++i) {
+ const Strategy::CommandResult& cmdResult = *i;
+
+ // XXX: In absence of sensible aggregation strategy,
+ // promote first shard's result to top level.
+ if (i == results.begin()) {
+ result.appendElements(cmdResult.result);
+ clusterCmdResult = cmdResult.result["ok"].trueValue();
}
- return clusterCmdResult;
+ // Append shard result as a sub object.
+ // Name the field after the shard.
+ string shardName = cmdResult.shardTargetId;
+ result.append(shardName, cmdResult.result);
}
- //
- // Register plan cache commands at startup
- //
+ return clusterCmdResult;
+}
- namespace {
+//
+// Register plan cache commands at startup
+//
- MONGO_INITIALIZER(RegisterPlanCacheCommands)(InitializerContext* context) {
- // Leaked intentionally: a Command registers itself when constructed.
+namespace {
- new ClusterPlanCacheCmd(
- "planCacheListQueryShapes",
- "Displays all query shapes in a collection.",
- ActionType::planCacheRead );
+MONGO_INITIALIZER(RegisterPlanCacheCommands)(InitializerContext* context) {
+ // Leaked intentionally: a Command registers itself when constructed.
- new ClusterPlanCacheCmd(
- "planCacheClear",
- "Drops one or all cached queries in a collection.",
- ActionType::planCacheWrite );
+ new ClusterPlanCacheCmd("planCacheListQueryShapes",
+ "Displays all query shapes in a collection.",
+ ActionType::planCacheRead);
- new ClusterPlanCacheCmd(
- "planCacheListPlans",
- "Displays the cached plans for a query shape.",
- ActionType::planCacheRead );
+ new ClusterPlanCacheCmd("planCacheClear",
+ "Drops one or all cached queries in a collection.",
+ ActionType::planCacheWrite);
- return Status::OK();
- }
+ new ClusterPlanCacheCmd("planCacheListPlans",
+ "Displays the cached plans for a query shape.",
+ ActionType::planCacheRead);
+
+ return Status::OK();
+}
- } // namespace
+} // namespace
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_profile_cmd.cpp b/src/mongo/s/commands/cluster_profile_cmd.cpp
index e4084def78b..ca619d80fbe 100644
--- a/src/mongo/s/commands/cluster_profile_cmd.cpp
+++ b/src/mongo/s/commands/cluster_profile_cmd.cpp
@@ -33,43 +33,41 @@
namespace mongo {
namespace {
- class ProfileCmd : public Command {
- public:
- ProfileCmd() : Command("profile", false) { }
+class ProfileCmd : public Command {
+public:
+ ProfileCmd() : Command("profile", false) {}
- virtual bool slaveOk() const {
- return true;
- }
+ virtual bool slaveOk() const {
+ return true;
+ }
- virtual bool adminOnly() const {
- return false;
- }
+ virtual bool adminOnly() const {
+ return false;
+ }
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::enableProfiler);
+ out->push_back(Privilege(ResourcePattern::forDatabaseName(dbname), actions));
+ }
- ActionSet actions;
- actions.addAction(ActionType::enableProfiler);
- out->push_back(Privilege(ResourcePattern::forDatabaseName(dbname), actions));
- }
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ errmsg = "profile currently not supported via mongos";
+ return false;
+ }
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
+} profileCmd;
- errmsg = "profile currently not supported via mongos";
- return false;
- }
-
- } profileCmd;
-
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_remove_shard_cmd.cpp b/src/mongo/s/commands/cluster_remove_shard_cmd.cpp
index e936e926d3b..14aeaf6bf42 100644
--- a/src/mongo/s/commands/cluster_remove_shard_cmd.cpp
+++ b/src/mongo/s/commands/cluster_remove_shard_cmd.cpp
@@ -45,90 +45,84 @@
namespace mongo {
- using std::string;
- using std::vector;
+using std::string;
+using std::vector;
namespace {
- class RemoveShardCmd : public Command {
- public:
- RemoveShardCmd() : Command("removeShard", false, "removeshard") { }
-
- virtual bool slaveOk() const {
- return true;
- }
-
- virtual bool adminOnly() const {
- return true;
- }
-
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
-
- virtual void help(std::stringstream& help) const {
- help << "remove a shard from the system.";
+class RemoveShardCmd : public Command {
+public:
+ RemoveShardCmd() : Command("removeShard", false, "removeshard") {}
+
+ virtual bool slaveOk() const {
+ return true;
+ }
+
+ virtual bool adminOnly() const {
+ return true;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(std::stringstream& help) const {
+ help << "remove a shard from the system.";
+ }
+
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::removeShard);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
+
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ const string target = cmdObj.firstElement().valuestrsafe();
+
+ const auto s = grid.shardRegistry()->getShard(target);
+ if (!s) {
+ string msg(str::stream() << "Could not drop shard '" << target
+ << "' because it does not exist");
+ log() << msg;
+ return appendCommandStatus(result, Status(ErrorCodes::ShardNotFound, msg));
}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
-
- ActionSet actions;
- actions.addAction(ActionType::removeShard);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ StatusWith<ShardDrainingStatus> removeShardResult =
+ grid.catalogManager()->removeShard(txn, s->getId());
+ if (!removeShardResult.isOK()) {
+ return appendCommandStatus(result, removeShardResult.getStatus());
}
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
-
- const string target = cmdObj.firstElement().valuestrsafe();
-
- const auto s = grid.shardRegistry()->getShard(target);
- if (!s) {
- string msg(str::stream() <<
- "Could not drop shard '" << target <<
- "' because it does not exist");
- log() << msg;
- return appendCommandStatus(result,
- Status(ErrorCodes::ShardNotFound, msg));
- }
-
- StatusWith<ShardDrainingStatus> removeShardResult =
- grid.catalogManager()->removeShard(txn, s->getId());
- if (!removeShardResult.isOK()) {
- return appendCommandStatus(result, removeShardResult.getStatus());
- }
-
- vector<string> databases;
- grid.catalogManager()->getDatabasesForShard(s->getId(), &databases);
-
- // Get BSONObj containing:
- // 1) note about moving or dropping databases in a shard
- // 2) list of databases (excluding 'local' database) that need to be moved
- BSONObj dbInfo;
- {
- BSONObjBuilder dbInfoBuilder;
- dbInfoBuilder.append("note",
- "you need to drop or movePrimary these databases");
- BSONArrayBuilder dbs(dbInfoBuilder.subarrayStart("dbsToMove"));
- for (vector<string>::const_iterator it = databases.begin();
- it != databases.end();
- it++) {
- if (*it != "local") {
- dbs.append(*it);
- }
+ vector<string> databases;
+ grid.catalogManager()->getDatabasesForShard(s->getId(), &databases);
+
+ // Get BSONObj containing:
+ // 1) note about moving or dropping databases in a shard
+ // 2) list of databases (excluding 'local' database) that need to be moved
+ BSONObj dbInfo;
+ {
+ BSONObjBuilder dbInfoBuilder;
+ dbInfoBuilder.append("note", "you need to drop or movePrimary these databases");
+ BSONArrayBuilder dbs(dbInfoBuilder.subarrayStart("dbsToMove"));
+ for (vector<string>::const_iterator it = databases.begin(); it != databases.end();
+ it++) {
+ if (*it != "local") {
+ dbs.append(*it);
}
- dbs.doneFast();
- dbInfo = dbInfoBuilder.obj();
}
+ dbs.doneFast();
+ dbInfo = dbInfoBuilder.obj();
+ }
- // TODO: Standardize/Seperate how we append to the result object
- switch (removeShardResult.getValue()) {
+ // TODO: Standardize/Seperate how we append to the result object
+ switch (removeShardResult.getValue()) {
case ShardDrainingStatus::STARTED:
result.append("msg", "draining started successfully");
result.append("state", "started");
@@ -137,10 +131,10 @@ namespace {
break;
case ShardDrainingStatus::ONGOING: {
vector<ChunkType> chunks;
- Status status = grid.catalogManager()->getChunks(
- Query(BSON(ChunkType::shard(s->getId()))),
- 0, // return all
- &chunks);
+ Status status =
+ grid.catalogManager()->getChunks(Query(BSON(ChunkType::shard(s->getId()))),
+ 0, // return all
+ &chunks);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
@@ -161,12 +155,12 @@ namespace {
result.append("msg", "removeshard completed successfully");
result.append("state", "completed");
result.append("shard", s->getId());
- }
-
- return true;
}
- } removeShardCmd;
+ return true;
+ }
+
+} removeShardCmd;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_repair_database_cmd.cpp b/src/mongo/s/commands/cluster_repair_database_cmd.cpp
index 33c849ecde6..5383b5688bf 100644
--- a/src/mongo/s/commands/cluster_repair_database_cmd.cpp
+++ b/src/mongo/s/commands/cluster_repair_database_cmd.cpp
@@ -33,19 +33,19 @@
namespace mongo {
namespace {
- class ClusterRepairDatabaseCmd : public RunOnAllShardsCommand {
- public:
- ClusterRepairDatabaseCmd() : RunOnAllShardsCommand("repairDatabase") {}
+class ClusterRepairDatabaseCmd : public RunOnAllShardsCommand {
+public:
+ ClusterRepairDatabaseCmd() : RunOnAllShardsCommand("repairDatabase") {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::repairDatabase);
- out->push_back(Privilege(ResourcePattern::forDatabaseName(dbname), actions));
- }
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::repairDatabase);
+ out->push_back(Privilege(ResourcePattern::forDatabaseName(dbname), actions));
+ }
- } clusterRepairDatabaseCmd;
+} clusterRepairDatabaseCmd;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_repl_set_get_status_cmd.cpp b/src/mongo/s/commands/cluster_repl_set_get_status_cmd.cpp
index 0164fe3cc15..bd8c0332bb1 100644
--- a/src/mongo/s/commands/cluster_repl_set_get_status_cmd.cpp
+++ b/src/mongo/s/commands/cluster_repl_set_get_status_cmd.cpp
@@ -36,53 +36,51 @@
namespace mongo {
namespace {
- class CmdReplSetGetStatus : public Command {
- public:
- CmdReplSetGetStatus() : Command("replSetGetStatus") { }
-
- virtual bool slaveOk() const {
- return true;
- }
-
- virtual bool adminOnly() const {
- return true;
- }
-
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
-
- virtual void help(std::stringstream& help) const {
- help << "Not supported through mongos";
+class CmdReplSetGetStatus : public Command {
+public:
+ CmdReplSetGetStatus() : Command("replSetGetStatus") {}
+
+ virtual bool slaveOk() const {
+ return true;
+ }
+
+ virtual bool adminOnly() const {
+ return true;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(std::stringstream& help) const {
+ help << "Not supported through mongos";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ // Require no auth since this command isn't supported in mongos
+ return Status::OK();
+ }
+
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ if (cmdObj["forShell"].trueValue()) {
+ LastError::get(cc()).disable();
+ ClusterLastErrorInfo::get(cc()).disableForCommand();
}
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
+ errmsg = "replSetGetStatus is not supported through mongos";
+ result.append("info", "mongos");
- // Require no auth since this command isn't supported in mongos
- return Status::OK();
- }
-
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
-
- if (cmdObj["forShell"].trueValue()) {
- LastError::get(cc()).disable();
- ClusterLastErrorInfo::get(cc()).disableForCommand();
- }
-
- errmsg = "replSetGetStatus is not supported through mongos";
- result.append("info", "mongos");
-
- return false;
- }
+ return false;
+ }
- } cmdReplSetGetStatus;
+} cmdReplSetGetStatus;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_reset_error_cmd.cpp b/src/mongo/s/commands/cluster_reset_error_cmd.cpp
index efa56f524b3..e82b43db1ec 100644
--- a/src/mongo/s/commands/cluster_reset_error_cmd.cpp
+++ b/src/mongo/s/commands/cluster_reset_error_cmd.cpp
@@ -40,56 +40,50 @@
namespace mongo {
namespace {
- class CmdShardingResetError : public Command {
- public:
- CmdShardingResetError() : Command("resetError", false, "reseterror") { }
+class CmdShardingResetError : public Command {
+public:
+ CmdShardingResetError() : Command("resetError", false, "reseterror") {}
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
-
- virtual bool slaveOk() const {
- return true;
- }
-
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- // No auth required
- }
-
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
+ virtual bool slaveOk() const {
+ return true;
+ }
- LastError::get(cc()).reset();
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ // No auth required
+ }
- const std::set<std::string>* shards =
- ClusterLastErrorInfo::get(cc()).getPrevShardHosts();
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ LastError::get(cc()).reset();
- for (std::set<std::string>::const_iterator i = shards->begin();
- i != shards->end();
- i++) {
+ const std::set<std::string>* shards = ClusterLastErrorInfo::get(cc()).getPrevShardHosts();
- const std::string shardName = *i;
+ for (std::set<std::string>::const_iterator i = shards->begin(); i != shards->end(); i++) {
+ const std::string shardName = *i;
- ShardConnection conn(ConnectionString(shardName, ConnectionString::SET), "");
+ ShardConnection conn(ConnectionString(shardName, ConnectionString::SET), "");
- BSONObj res;
+ BSONObj res;
- // Don't care about result from shards.
- conn->runCommand(dbname, cmdObj, res);
- conn.done();
- }
-
- return true;
+ // Don't care about result from shards.
+ conn->runCommand(dbname, cmdObj, res);
+ conn.done();
}
- } cmdShardingResetError;
+ return true;
+ }
+
+} cmdShardingResetError;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_shard_collection_cmd.cpp b/src/mongo/s/commands/cluster_shard_collection_cmd.cpp
index ccc0edd67f8..6e7c6f2cb55 100644
--- a/src/mongo/s/commands/cluster_shard_collection_cmd.cpp
+++ b/src/mongo/s/commands/cluster_shard_collection_cmd.cpp
@@ -55,464 +55,436 @@
namespace mongo {
- using std::shared_ptr;
- using std::list;
- using std::set;
- using std::string;
- using std::vector;
+using std::shared_ptr;
+using std::list;
+using std::set;
+using std::string;
+using std::vector;
namespace {
- class ShardCollectionCmd : public Command {
- public:
- ShardCollectionCmd() : Command("shardCollection", false, "shardcollection") { }
-
- virtual bool slaveOk() const {
- return true;
- }
-
- virtual bool adminOnly() const {
- return true;
+class ShardCollectionCmd : public Command {
+public:
+ ShardCollectionCmd() : Command("shardCollection", false, "shardcollection") {}
+
+ virtual bool slaveOk() const {
+ return true;
+ }
+
+ virtual bool adminOnly() const {
+ return true;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(std::stringstream& help) const {
+ help << "Shard a collection. Requires key. Optional unique."
+ << " Sharding must already be enabled for the database.\n"
+ << " { enablesharding : \"<dbname>\" }\n";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname, cmdObj))),
+ ActionType::enableSharding)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
}
- virtual bool isWriteCommandForConfigServer() const {
+ return Status::OK();
+ }
+
+ virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
+ return parseNsFullyQualified(dbname, cmdObj);
+ }
+
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ const string ns = parseNs(dbname, cmdObj);
+ if (ns.size() == 0) {
+ errmsg = "no ns";
return false;
}
- virtual void help(std::stringstream& help) const {
- help << "Shard a collection. Requires key. Optional unique."
- << " Sharding must already be enabled for the database.\n"
- << " { enablesharding : \"<dbname>\" }\n";
+ const NamespaceString nsStr(ns);
+ if (!nsStr.isValid()) {
+ return appendCommandStatus(
+ result,
+ Status(ErrorCodes::InvalidNamespace, "invalid collection namespace [" + ns + "]"));
}
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
-
- if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
- ResourcePattern::forExactNamespace(
- NamespaceString(parseNs(dbname,
- cmdObj))),
- ActionType::enableSharding)) {
- return Status(ErrorCodes::Unauthorized, "Unauthorized");
- }
-
- return Status::OK();
+ auto config = uassertStatusOK(grid.catalogCache()->getDatabase(nsStr.db().toString()));
+ if (!config->isShardingEnabled()) {
+ return appendCommandStatus(
+ result,
+ Status(ErrorCodes::IllegalOperation,
+ str::stream() << "sharding not enabled for db " << nsStr.db()));
}
- virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
- return parseNsFullyQualified(dbname, cmdObj);
+ if (config->isSharded(ns)) {
+ return appendCommandStatus(
+ result,
+ Status(ErrorCodes::IllegalOperation,
+ str::stream() << "sharding already enabled for collection " << ns));
}
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
-
- const string ns = parseNs(dbname, cmdObj);
- if (ns.size() == 0) {
- errmsg = "no ns";
- return false;
- }
-
- const NamespaceString nsStr(ns);
- if (!nsStr.isValid()) {
- return appendCommandStatus(
- result,
- Status(ErrorCodes::InvalidNamespace,
- "invalid collection namespace [" + ns + "]"));
- }
-
- auto config = uassertStatusOK(grid.catalogCache()->getDatabase(nsStr.db().toString()));
- if (!config->isShardingEnabled()) {
- return appendCommandStatus(
- result,
- Status(ErrorCodes::IllegalOperation,
- str::stream() << "sharding not enabled for db " << nsStr.db()));
- }
-
- if (config->isSharded(ns)) {
- return appendCommandStatus(
- result,
- Status(ErrorCodes::IllegalOperation,
- str::stream() << "sharding already enabled for collection " << ns));
- }
+ // NOTE: We *must* take ownership of the key here - otherwise the shared BSONObj
+ // becomes corrupt as soon as the command ends.
+ BSONObj proposedKey = cmdObj.getObjectField("key").getOwned();
+ if (proposedKey.isEmpty()) {
+ errmsg = "no shard key";
+ return false;
+ }
- // NOTE: We *must* take ownership of the key here - otherwise the shared BSONObj
- // becomes corrupt as soon as the command ends.
- BSONObj proposedKey = cmdObj.getObjectField("key").getOwned();
- if (proposedKey.isEmpty()) {
- errmsg = "no shard key";
- return false;
- }
+ ShardKeyPattern proposedKeyPattern(proposedKey);
+ if (!proposedKeyPattern.isValid()) {
+ errmsg = str::stream() << "Unsupported shard key pattern. Pattern must"
+ << " either be a single hashed field, or a list"
+ << " of ascending fields.";
+ return false;
+ }
- ShardKeyPattern proposedKeyPattern(proposedKey);
- if (!proposedKeyPattern.isValid()) {
- errmsg = str::stream() << "Unsupported shard key pattern. Pattern must"
- << " either be a single hashed field, or a list"
- << " of ascending fields.";
- return false;
- }
+ bool isHashedShardKey = proposedKeyPattern.isHashedPattern();
- bool isHashedShardKey = proposedKeyPattern.isHashedPattern();
+ if (isHashedShardKey && cmdObj["unique"].trueValue()) {
+ dassert(proposedKey.nFields() == 1);
- if (isHashedShardKey && cmdObj["unique"].trueValue()) {
- dassert(proposedKey.nFields() == 1);
+ // it's possible to ensure uniqueness on the hashed field by
+ // declaring an additional (non-hashed) unique index on the field,
+ // but the hashed shard key itself should not be declared unique
+ errmsg = "hashed shard keys cannot be declared unique.";
+ return false;
+ }
- // it's possible to ensure uniqueness on the hashed field by
- // declaring an additional (non-hashed) unique index on the field,
- // but the hashed shard key itself should not be declared unique
- errmsg = "hashed shard keys cannot be declared unique.";
- return false;
- }
+ if (ns.find(".system.") != string::npos) {
+ errmsg = "can't shard system namespaces";
+ return false;
+ }
- if (ns.find(".system.") != string::npos) {
- errmsg = "can't shard system namespaces";
- return false;
+ // The rest of the checks require a connection to the primary db
+ ConnectionString shardConnString;
+ {
+ const auto shard = grid.shardRegistry()->getShard(config->getPrimaryId());
+ shardConnString = shard->getConnString();
+ }
+ ScopedDbConnection conn(shardConnString);
+
+ // check that collection is not capped
+ BSONObj res;
+ {
+ list<BSONObj> all = conn->getCollectionInfos(
+ config->name(), BSON("name" << nsToCollectionSubstring(ns)));
+ if (!all.empty()) {
+ res = all.front().getOwned();
}
+ }
- // The rest of the checks require a connection to the primary db
- ConnectionString shardConnString;
- {
- const auto shard = grid.shardRegistry()->getShard(config->getPrimaryId());
- shardConnString = shard->getConnString();
- }
- ScopedDbConnection conn(shardConnString);
-
- //check that collection is not capped
- BSONObj res;
- {
- list<BSONObj> all = conn->getCollectionInfos(
- config->name(),
- BSON("name" << nsToCollectionSubstring(ns)));
- if (!all.empty()) {
- res = all.front().getOwned();
- }
- }
+ if (res["options"].type() == Object &&
+ res["options"].embeddedObject()["capped"].trueValue()) {
+ errmsg = "can't shard capped collection";
+ conn.done();
+ return false;
+ }
- if (res["options"].type() == Object &&
- res["options"].embeddedObject()["capped"].trueValue()) {
- errmsg = "can't shard capped collection";
+ // The proposed shard key must be validated against the set of existing indexes.
+ // In particular, we must ensure the following constraints
+ //
+ // 1. All existing unique indexes, except those which start with the _id index,
+ // must contain the proposed key as a prefix (uniqueness of the _id index is
+ // ensured by the _id generation process or guaranteed by the user).
+ //
+ // 2. If the collection is not empty, there must exist at least one index that
+ // is "useful" for the proposed key. A "useful" index is defined as follows
+ // Useful Index:
+ // i. contains proposedKey as a prefix
+ // ii. is not a sparse index or partial index
+ // iii. contains no null values
+ // iv. is not multikey (maybe lift this restriction later)
+ // v. if a hashed index, has default seed (lift this restriction later)
+ //
+ // 3. If the proposed shard key is specified as unique, there must exist a useful,
+ // unique index exactly equal to the proposedKey (not just a prefix).
+ //
+ // After validating these constraint:
+ //
+ // 4. If there is no useful index, and the collection is non-empty, we
+ // must fail.
+ //
+ // 5. If the collection is empty, and it's still possible to create an index
+ // on the proposed key, we go ahead and do so.
+
+ list<BSONObj> indexes = conn->getIndexSpecs(ns);
+
+ // 1. Verify consistency with existing unique indexes
+ ShardKeyPattern proposedShardKey(proposedKey);
+ for (list<BSONObj>::iterator it = indexes.begin(); it != indexes.end(); ++it) {
+ BSONObj idx = *it;
+ BSONObj currentKey = idx["key"].embeddedObject();
+ bool isUnique = idx["unique"].trueValue();
+
+ if (isUnique && !proposedShardKey.isUniqueIndexCompatible(currentKey)) {
+ errmsg = str::stream() << "can't shard collection '" << ns << "' "
+ << "with unique index on " << currentKey << " "
+ << "and proposed shard key " << proposedKey << ". "
+ << "Uniqueness can't be maintained unless "
+ << "shard key is a prefix";
conn.done();
return false;
}
+ }
- // The proposed shard key must be validated against the set of existing indexes.
- // In particular, we must ensure the following constraints
- //
- // 1. All existing unique indexes, except those which start with the _id index,
- // must contain the proposed key as a prefix (uniqueness of the _id index is
- // ensured by the _id generation process or guaranteed by the user).
- //
- // 2. If the collection is not empty, there must exist at least one index that
- // is "useful" for the proposed key. A "useful" index is defined as follows
- // Useful Index:
- // i. contains proposedKey as a prefix
- // ii. is not a sparse index or partial index
- // iii. contains no null values
- // iv. is not multikey (maybe lift this restriction later)
- // v. if a hashed index, has default seed (lift this restriction later)
- //
- // 3. If the proposed shard key is specified as unique, there must exist a useful,
- // unique index exactly equal to the proposedKey (not just a prefix).
- //
- // After validating these constraint:
- //
- // 4. If there is no useful index, and the collection is non-empty, we
- // must fail.
- //
- // 5. If the collection is empty, and it's still possible to create an index
- // on the proposed key, we go ahead and do so.
-
- list<BSONObj> indexes = conn->getIndexSpecs(ns);
-
- // 1. Verify consistency with existing unique indexes
- ShardKeyPattern proposedShardKey(proposedKey);
- for (list<BSONObj>::iterator it = indexes.begin(); it != indexes.end(); ++it) {
- BSONObj idx = *it;
- BSONObj currentKey = idx["key"].embeddedObject();
- bool isUnique = idx["unique"].trueValue();
-
- if (isUnique && !proposedShardKey.isUniqueIndexCompatible(currentKey)) {
- errmsg = str::stream() << "can't shard collection '" << ns << "' "
- << "with unique index on " << currentKey << " "
- << "and proposed shard key " << proposedKey << ". "
- << "Uniqueness can't be maintained unless "
- << "shard key is a prefix";
+ // 2. Check for a useful index
+ bool hasUsefulIndexForKey = false;
+
+ for (list<BSONObj>::iterator it = indexes.begin(); it != indexes.end(); ++it) {
+ BSONObj idx = *it;
+ BSONObj currentKey = idx["key"].embeddedObject();
+ // Check 2.i. and 2.ii.
+ if (!idx["sparse"].trueValue() && idx["filter"].eoo() &&
+ proposedKey.isPrefixOf(currentKey)) {
+ // We can't currently use hashed indexes with a non-default hash seed
+ // Check v.
+ // Note that this means that, for sharding, we only support one hashed index
+ // per field per collection.
+ if (isHashedShardKey && !idx["seed"].eoo() &&
+ idx["seed"].numberInt() != BSONElementHasher::DEFAULT_HASH_SEED) {
+ errmsg = str::stream() << "can't shard collection " << ns
+ << " with hashed shard key " << proposedKey
+ << " because the hashed index uses a non-default"
+ << " seed of " << idx["seed"].numberInt();
conn.done();
return false;
}
+
+ hasUsefulIndexForKey = true;
}
+ }
- // 2. Check for a useful index
- bool hasUsefulIndexForKey = false;
+ // 3. If proposed key is required to be unique, additionally check for exact match.
+ bool careAboutUnique = cmdObj["unique"].trueValue();
+ if (hasUsefulIndexForKey && careAboutUnique) {
+ BSONObj eqQuery = BSON("ns" << ns << "key" << proposedKey);
+ BSONObj eqQueryResult;
for (list<BSONObj>::iterator it = indexes.begin(); it != indexes.end(); ++it) {
BSONObj idx = *it;
- BSONObj currentKey = idx["key"].embeddedObject();
- // Check 2.i. and 2.ii.
- if (!idx["sparse"].trueValue() &&
- idx["filter"].eoo() &&
- proposedKey.isPrefixOf(currentKey)) {
-
- // We can't currently use hashed indexes with a non-default hash seed
- // Check v.
- // Note that this means that, for sharding, we only support one hashed index
- // per field per collection.
- if (isHashedShardKey &&
- !idx["seed"].eoo() &&
- idx["seed"].numberInt() != BSONElementHasher::DEFAULT_HASH_SEED) {
-
- errmsg = str::stream() << "can't shard collection " << ns
- << " with hashed shard key " << proposedKey
- << " because the hashed index uses a non-default"
- << " seed of " << idx["seed"].numberInt();
- conn.done();
- return false;
- }
-
- hasUsefulIndexForKey = true;
- }
- }
-
- // 3. If proposed key is required to be unique, additionally check for exact match.
- bool careAboutUnique = cmdObj["unique"].trueValue();
- if (hasUsefulIndexForKey && careAboutUnique) {
- BSONObj eqQuery = BSON("ns" << ns << "key" << proposedKey);
- BSONObj eqQueryResult;
-
- for (list<BSONObj>::iterator it = indexes.begin(); it != indexes.end(); ++it) {
- BSONObj idx = *it;
- if (idx["key"].embeddedObject() == proposedKey) {
- eqQueryResult = idx;
- break;
- }
- }
-
- if (eqQueryResult.isEmpty()) {
- // If no exact match, index not useful, but still possible to create one later
- hasUsefulIndexForKey = false;
- }
- else {
- bool isExplicitlyUnique = eqQueryResult["unique"].trueValue();
- BSONObj currKey = eqQueryResult["key"].embeddedObject();
- bool isCurrentID = str::equals(currKey.firstElementFieldName(), "_id");
-
- if (!isExplicitlyUnique && !isCurrentID) {
- errmsg = str::stream() << "can't shard collection " << ns << ", "
- << proposedKey << " index not unique, "
- << "and unique index explicitly specified";
- conn.done();
- return false;
- }
+ if (idx["key"].embeddedObject() == proposedKey) {
+ eqQueryResult = idx;
+ break;
}
}
- if (hasUsefulIndexForKey) {
- // Check 2.iii and 2.iv. Make sure no null entries in the sharding index
- // and that there is a useful, non-multikey index available
- BSONObjBuilder checkShardingIndexCmd;
- checkShardingIndexCmd.append("checkShardingIndex", ns);
- checkShardingIndexCmd.append("keyPattern", proposedKey);
-
- if (!conn.get()->runCommand("admin", checkShardingIndexCmd.obj(), res)) {
- errmsg = res["errmsg"].str();
+ if (eqQueryResult.isEmpty()) {
+ // If no exact match, index not useful, but still possible to create one later
+ hasUsefulIndexForKey = false;
+ } else {
+ bool isExplicitlyUnique = eqQueryResult["unique"].trueValue();
+ BSONObj currKey = eqQueryResult["key"].embeddedObject();
+ bool isCurrentID = str::equals(currKey.firstElementFieldName(), "_id");
+
+ if (!isExplicitlyUnique && !isCurrentID) {
+ errmsg = str::stream() << "can't shard collection " << ns << ", " << proposedKey
+ << " index not unique, "
+ << "and unique index explicitly specified";
conn.done();
return false;
}
}
- else if (conn->count(ns) != 0) {
- // 4. if no useful index, and collection is non-empty, fail
- errmsg = str::stream() << "please create an index that starts with the "
- << "shard key before sharding.";
- result.append("proposedKey", proposedKey);
- result.append("curIndexes", indexes);
+ }
+
+ if (hasUsefulIndexForKey) {
+ // Check 2.iii and 2.iv. Make sure no null entries in the sharding index
+ // and that there is a useful, non-multikey index available
+ BSONObjBuilder checkShardingIndexCmd;
+ checkShardingIndexCmd.append("checkShardingIndex", ns);
+ checkShardingIndexCmd.append("keyPattern", proposedKey);
+
+ if (!conn.get()->runCommand("admin", checkShardingIndexCmd.obj(), res)) {
+ errmsg = res["errmsg"].str();
conn.done();
return false;
}
- else {
- // 5. If no useful index exists, and collection empty, create one on proposedKey.
- // Only need to call ensureIndex on primary shard, since indexes get copied to
- // receiving shard whenever a migrate occurs.
- Status status = clusterCreateIndex(ns, proposedKey, careAboutUnique, NULL);
- if (!status.isOK()) {
- errmsg = str::stream() << "ensureIndex failed to create index on "
- << "primary shard: " << status.reason();
- conn.done();
- return false;
- }
+ } else if (conn->count(ns) != 0) {
+ // 4. if no useful index, and collection is non-empty, fail
+ errmsg = str::stream() << "please create an index that starts with the "
+ << "shard key before sharding.";
+ result.append("proposedKey", proposedKey);
+ result.append("curIndexes", indexes);
+ conn.done();
+ return false;
+ } else {
+ // 5. If no useful index exists, and collection empty, create one on proposedKey.
+ // Only need to call ensureIndex on primary shard, since indexes get copied to
+ // receiving shard whenever a migrate occurs.
+ Status status = clusterCreateIndex(ns, proposedKey, careAboutUnique, NULL);
+ if (!status.isOK()) {
+ errmsg = str::stream() << "ensureIndex failed to create index on "
+ << "primary shard: " << status.reason();
+ conn.done();
+ return false;
}
+ }
- bool isEmpty = (conn->count(ns) == 0);
-
- conn.done();
+ bool isEmpty = (conn->count(ns) == 0);
- // Pre-splitting:
- // For new collections which use hashed shard keys, we can can pre-split the
- // range of possible hashes into a large number of chunks, and distribute them
- // evenly at creation time. Until we design a better initialization scheme, the
- // safest way to pre-split is to
- // 1. make one big chunk for each shard
- // 2. move them one at a time
- // 3. split the big chunks to achieve the desired total number of initial chunks
-
- vector<ShardId> shardIds;
- grid.shardRegistry()->getAllShardIds(&shardIds);
- int numShards = shardIds.size();
-
- vector<BSONObj> initSplits; // there will be at most numShards-1 of these
- vector<BSONObj> allSplits; // all of the initial desired split points
-
- // only pre-split when using a hashed shard key and collection is still empty
- if (isHashedShardKey && isEmpty){
- int numChunks = cmdObj["numInitialChunks"].numberInt();
- if (numChunks <= 0) {
- // default number of initial chunks
- numChunks = 2 * numShards;
- }
+ conn.done();
- // hashes are signed, 64-bit ints. So we divide the range (-MIN long, +MAX long)
- // into intervals of size (2^64/numChunks) and create split points at the
- // boundaries. The logic below ensures that initial chunks are all
- // symmetric around 0.
- long long intervalSize = (std::numeric_limits<long long>::max() / numChunks) * 2;
- long long current = 0;
+ // Pre-splitting:
+ // For new collections which use hashed shard keys, we can can pre-split the
+ // range of possible hashes into a large number of chunks, and distribute them
+ // evenly at creation time. Until we design a better initialization scheme, the
+ // safest way to pre-split is to
+ // 1. make one big chunk for each shard
+ // 2. move them one at a time
+ // 3. split the big chunks to achieve the desired total number of initial chunks
- if (numChunks % 2 == 0){
- allSplits.push_back(BSON(proposedKey.firstElementFieldName() << current));
- current += intervalSize;
- }
- else {
- current += intervalSize / 2;
- }
+ vector<ShardId> shardIds;
+ grid.shardRegistry()->getAllShardIds(&shardIds);
+ int numShards = shardIds.size();
- for (int i = 0; i < (numChunks - 1) / 2; i++){
- allSplits.push_back(BSON(proposedKey.firstElementFieldName() << current));
- allSplits.push_back(BSON(proposedKey.firstElementFieldName() << -current));
- current += intervalSize;
- }
+ vector<BSONObj> initSplits; // there will be at most numShards-1 of these
+ vector<BSONObj> allSplits; // all of the initial desired split points
- sort(allSplits.begin(), allSplits.end());
+ // only pre-split when using a hashed shard key and collection is still empty
+ if (isHashedShardKey && isEmpty) {
+ int numChunks = cmdObj["numInitialChunks"].numberInt();
+ if (numChunks <= 0) {
+ // default number of initial chunks
+ numChunks = 2 * numShards;
+ }
- // 1. the initial splits define the "big chunks" that we will subdivide later
- int lastIndex = -1;
- for (int i = 1; i < numShards; i++) {
- if (lastIndex < (i*numChunks) / numShards - 1) {
- lastIndex = (i*numChunks) / numShards - 1;
- initSplits.push_back(allSplits[lastIndex]);
- }
- }
+ // hashes are signed, 64-bit ints. So we divide the range (-MIN long, +MAX long)
+ // into intervals of size (2^64/numChunks) and create split points at the
+ // boundaries. The logic below ensures that initial chunks are all
+ // symmetric around 0.
+ long long intervalSize = (std::numeric_limits<long long>::max() / numChunks) * 2;
+ long long current = 0;
+
+ if (numChunks % 2 == 0) {
+ allSplits.push_back(BSON(proposedKey.firstElementFieldName() << current));
+ current += intervalSize;
+ } else {
+ current += intervalSize / 2;
}
- LOG(0) << "CMD: shardcollection: " << cmdObj;
+ for (int i = 0; i < (numChunks - 1) / 2; i++) {
+ allSplits.push_back(BSON(proposedKey.firstElementFieldName() << current));
+ allSplits.push_back(BSON(proposedKey.firstElementFieldName() << -current));
+ current += intervalSize;
+ }
- audit::logShardCollection(ClientBasic::getCurrent(),
- ns,
- proposedKey,
- careAboutUnique);
+ sort(allSplits.begin(), allSplits.end());
- Status status = grid.catalogManager()->shardCollection(ns,
- proposedShardKey,
- careAboutUnique,
- &initSplits);
- if (!status.isOK()) {
- return appendCommandStatus(result, status);
+ // 1. the initial splits define the "big chunks" that we will subdivide later
+ int lastIndex = -1;
+ for (int i = 1; i < numShards; i++) {
+ if (lastIndex < (i * numChunks) / numShards - 1) {
+ lastIndex = (i * numChunks) / numShards - 1;
+ initSplits.push_back(allSplits[lastIndex]);
+ }
}
+ }
- result << "collectionsharded" << ns;
-
- // Only initially move chunks when using a hashed shard key
- if (isHashedShardKey && isEmpty) {
- // Reload the new config info. If we created more than one initial chunk, then
- // we need to move them around to balance.
- ChunkManagerPtr chunkManager = config->getChunkManager(ns, true);
- ChunkMap chunkMap = chunkManager->getChunkMap();
-
- // 2. Move and commit each "big chunk" to a different shard.
- int i = 0;
- for (ChunkMap::const_iterator c = chunkMap.begin(); c != chunkMap.end(); ++c, ++i){
- const ShardId& shardId = shardIds[i % numShards];
- const auto to = grid.shardRegistry()->getShard(shardId);
- if (!to) {
- continue;
- }
+ LOG(0) << "CMD: shardcollection: " << cmdObj;
- ChunkPtr chunk = c->second;
+ audit::logShardCollection(ClientBasic::getCurrent(), ns, proposedKey, careAboutUnique);
- // can't move chunk to shard it's already on
- if (to->getId() == chunk->getShardId()) {
- continue;
- }
+ Status status = grid.catalogManager()->shardCollection(
+ ns, proposedShardKey, careAboutUnique, &initSplits);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status);
+ }
- BSONObj moveResult;
- WriteConcernOptions noThrottle;
- if (!chunk->moveAndCommit(to->getId(),
- Chunk::MaxChunkSize,
- &noThrottle,
- true,
- 0,
- moveResult)) {
-
- warning() << "couldn't move chunk " << chunk->toString()
- << " to shard " << *to
- << " while sharding collection " << ns << "."
- << " Reason: " << moveResult;
- }
+ result << "collectionsharded" << ns;
+
+ // Only initially move chunks when using a hashed shard key
+ if (isHashedShardKey && isEmpty) {
+ // Reload the new config info. If we created more than one initial chunk, then
+ // we need to move them around to balance.
+ ChunkManagerPtr chunkManager = config->getChunkManager(ns, true);
+ ChunkMap chunkMap = chunkManager->getChunkMap();
+
+ // 2. Move and commit each "big chunk" to a different shard.
+ int i = 0;
+ for (ChunkMap::const_iterator c = chunkMap.begin(); c != chunkMap.end(); ++c, ++i) {
+ const ShardId& shardId = shardIds[i % numShards];
+ const auto to = grid.shardRegistry()->getShard(shardId);
+ if (!to) {
+ continue;
+ }
+
+ ChunkPtr chunk = c->second;
+
+ // can't move chunk to shard it's already on
+ if (to->getId() == chunk->getShardId()) {
+ continue;
}
- if (allSplits.empty()) {
- return true;
+ BSONObj moveResult;
+ WriteConcernOptions noThrottle;
+ if (!chunk->moveAndCommit(
+ to->getId(), Chunk::MaxChunkSize, &noThrottle, true, 0, moveResult)) {
+ warning() << "couldn't move chunk " << chunk->toString() << " to shard " << *to
+ << " while sharding collection " << ns << "."
+ << " Reason: " << moveResult;
}
+ }
- // Reload the config info, after all the migrations
- chunkManager = config->getChunkManager(ns, true);
-
- // 3. Subdivide the big chunks by splitting at each of the points in "allSplits"
- // that we haven't already split by.
- ChunkPtr currentChunk = chunkManager->findIntersectingChunk(allSplits[0]);
-
- vector<BSONObj> subSplits;
- for (unsigned i = 0; i <= allSplits.size(); i++){
- if (i == allSplits.size() || !currentChunk->containsKey(allSplits[i])) {
- if (!subSplits.empty()){
- Status status = currentChunk->multiSplit(subSplits, NULL);
- if (!status.isOK()){
- warning() << "couldn't split chunk "
- << currentChunk->toString()
- << " while sharding collection " << ns
- << causedBy(status);
- }
-
- subSplits.clear();
- }
+ if (allSplits.empty()) {
+ return true;
+ }
- if (i < allSplits.size()) {
- currentChunk = chunkManager->findIntersectingChunk(allSplits[i]);
+ // Reload the config info, after all the migrations
+ chunkManager = config->getChunkManager(ns, true);
+
+ // 3. Subdivide the big chunks by splitting at each of the points in "allSplits"
+ // that we haven't already split by.
+ ChunkPtr currentChunk = chunkManager->findIntersectingChunk(allSplits[0]);
+
+ vector<BSONObj> subSplits;
+ for (unsigned i = 0; i <= allSplits.size(); i++) {
+ if (i == allSplits.size() || !currentChunk->containsKey(allSplits[i])) {
+ if (!subSplits.empty()) {
+ Status status = currentChunk->multiSplit(subSplits, NULL);
+ if (!status.isOK()) {
+ warning() << "couldn't split chunk " << currentChunk->toString()
+ << " while sharding collection " << ns << causedBy(status);
}
+
+ subSplits.clear();
}
- else {
- BSONObj splitPoint(allSplits[i]);
- // Do not split on the boundaries
- if (currentChunk->getMin().woCompare(splitPoint) == 0) {
- continue;
- }
+ if (i < allSplits.size()) {
+ currentChunk = chunkManager->findIntersectingChunk(allSplits[i]);
+ }
+ } else {
+ BSONObj splitPoint(allSplits[i]);
- subSplits.push_back(splitPoint);
+ // Do not split on the boundaries
+ if (currentChunk->getMin().woCompare(splitPoint) == 0) {
+ continue;
}
- }
- // Proactively refresh the chunk manager. Not really necessary, but this way it's
- // immediately up-to-date the next time it's used.
- config->getChunkManager(ns, true);
+ subSplits.push_back(splitPoint);
+ }
}
- return true;
+ // Proactively refresh the chunk manager. Not really necessary, but this way it's
+ // immediately up-to-date the next time it's used.
+ config->getChunkManager(ns, true);
}
- } shardCollectionCmd;
+ return true;
+ }
+
+} shardCollectionCmd;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_shutdown_cmd.cpp b/src/mongo/s/commands/cluster_shutdown_cmd.cpp
index 58fe1d47483..72f0fd71e6f 100644
--- a/src/mongo/s/commands/cluster_shutdown_cmd.cpp
+++ b/src/mongo/s/commands/cluster_shutdown_cmd.cpp
@@ -34,26 +34,25 @@
namespace mongo {
namespace {
- class ClusterShutdownCmd : public CmdShutdown {
- public:
- virtual void help(std::stringstream& help) const {
- help << "shutdown the database. must be ran against admin db and "
- << "either (1) ran from localhost or (2) authenticated.";
- }
-
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
-
- // Never returns
- shutdownHelper();
- return true;
- }
-
- } clusterShutdownCmd;
-
-} // namespace
-} // namespace mongo
+class ClusterShutdownCmd : public CmdShutdown {
+public:
+ virtual void help(std::stringstream& help) const {
+ help << "shutdown the database. must be ran against admin db and "
+ << "either (1) ran from localhost or (2) authenticated.";
+ }
+
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ // Never returns
+ shutdownHelper();
+ return true;
+ }
+
+} clusterShutdownCmd;
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_split_collection_cmd.cpp b/src/mongo/s/commands/cluster_split_collection_cmd.cpp
index acd99d32c7b..fd5f462518c 100644
--- a/src/mongo/s/commands/cluster_split_collection_cmd.cpp
+++ b/src/mongo/s/commands/cluster_split_collection_cmd.cpp
@@ -49,244 +49,231 @@
namespace mongo {
- using std::shared_ptr;
- using std::string;
- using std::vector;
+using std::shared_ptr;
+using std::string;
+using std::vector;
namespace {
- class SplitCollectionCmd : public Command {
- public:
- SplitCollectionCmd() : Command("split", false, "split") { }
+class SplitCollectionCmd : public Command {
+public:
+ SplitCollectionCmd() : Command("split", false, "split") {}
+
+ virtual bool slaveOk() const {
+ return true;
+ }
+
+ virtual bool adminOnly() const {
+ return true;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(std::stringstream& help) const {
+ help << " example: - split the shard that contains give key\n"
+ << " { split : 'alleyinsider.blog.posts' , find : { ts : 1 } }\n"
+ << " example: - split the shard that contains the key with this as the middle\n"
+ << " { split : 'alleyinsider.blog.posts' , middle : { ts : 1 } }\n"
+ << " NOTE: this does not move the chunks, it just creates a logical separation.";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname, cmdObj))),
+ ActionType::splitChunk)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+ return Status::OK();
+ }
+
+ virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
+ return parseNsFullyQualified(dbname, cmdObj);
+ }
+
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ ShardConnection::sync();
+
+ const NamespaceString nss(parseNs(dbname, cmdObj));
+ if (nss.size() == 0) {
+ return appendCommandStatus(
+ result, Status(ErrorCodes::InvalidNamespace, "no namespace specified"));
+ }
- virtual bool slaveOk() const {
- return true;
+ auto status = grid.catalogCache()->getDatabase(nss.db().toString());
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status.getStatus());
}
- virtual bool adminOnly() const {
- return true;
+ std::shared_ptr<DBConfig> config = status.getValue();
+ if (!config->isSharded(nss.ns())) {
+ config->reload();
+
+ if (!config->isSharded(nss.ns())) {
+ return appendCommandStatus(result,
+ Status(ErrorCodes::NamespaceNotSharded,
+ "ns [" + nss.ns() + " is not sharded."));
+ }
}
- virtual bool isWriteCommandForConfigServer() const {
+ const BSONField<BSONObj> findField("find", BSONObj());
+ const BSONField<BSONArray> boundsField("bounds", BSONArray());
+ const BSONField<BSONObj> middleField("middle", BSONObj());
+
+ BSONObj find;
+ if (FieldParser::extract(cmdObj, findField, &find, &errmsg) == FieldParser::FIELD_INVALID) {
return false;
}
- virtual void help(std::stringstream& help) const {
- help << " example: - split the shard that contains give key\n"
- << " { split : 'alleyinsider.blog.posts' , find : { ts : 1 } }\n"
- << " example: - split the shard that contains the key with this as the middle\n"
- << " { split : 'alleyinsider.blog.posts' , middle : { ts : 1 } }\n"
- << " NOTE: this does not move the chunks, it just creates a logical separation.";
+ BSONArray bounds;
+ if (FieldParser::extract(cmdObj, boundsField, &bounds, &errmsg) ==
+ FieldParser::FIELD_INVALID) {
+ return false;
}
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
+ if (!bounds.isEmpty()) {
+ if (!bounds.hasField("0")) {
+ errmsg = "lower bound not specified";
+ return false;
+ }
- if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
- ResourcePattern::forExactNamespace(
- NamespaceString(parseNs(dbname,
- cmdObj))),
- ActionType::splitChunk)) {
- return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ if (!bounds.hasField("1")) {
+ errmsg = "upper bound not specified";
+ return false;
}
- return Status::OK();
}
- virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
- return parseNsFullyQualified(dbname, cmdObj);
+ if (!find.isEmpty() && !bounds.isEmpty()) {
+ errmsg = "cannot specify bounds and find at the same time";
+ return false;
}
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
+ BSONObj middle;
+ if (FieldParser::extract(cmdObj, middleField, &middle, &errmsg) ==
+ FieldParser::FIELD_INVALID) {
+ return false;
+ }
- ShardConnection::sync();
+ if (find.isEmpty() && bounds.isEmpty() && middle.isEmpty()) {
+ errmsg = "need to specify find/bounds or middle";
+ return false;
+ }
- const NamespaceString nss(parseNs(dbname, cmdObj));
- if (nss.size() == 0) {
- return appendCommandStatus(result, Status(ErrorCodes::InvalidNamespace,
- "no namespace specified"));
- }
+ if (!find.isEmpty() && !middle.isEmpty()) {
+ errmsg = "cannot specify find and middle together";
+ return false;
+ }
- auto status = grid.catalogCache()->getDatabase(nss.db().toString());
- if (!status.isOK()) {
- return appendCommandStatus(result, status.getStatus());
- }
+ if (!bounds.isEmpty() && !middle.isEmpty()) {
+ errmsg = "cannot specify bounds and middle together";
+ return false;
+ }
- std::shared_ptr<DBConfig> config = status.getValue();
- if (!config->isSharded(nss.ns())) {
- config->reload();
+ // This refreshes the chunk metadata if stale.
+ ChunkManagerPtr info = config->getChunkManager(nss.ns(), true);
+ ChunkPtr chunk;
- if (!config->isSharded(nss.ns())) {
- return appendCommandStatus(result,
- Status(ErrorCodes::NamespaceNotSharded,
- "ns [" + nss.ns() + " is not sharded."));
- }
- }
+ if (!find.isEmpty()) {
+ StatusWith<BSONObj> status = info->getShardKeyPattern().extractShardKeyFromQuery(find);
- const BSONField<BSONObj> findField("find", BSONObj());
- const BSONField<BSONArray> boundsField("bounds", BSONArray());
- const BSONField<BSONObj> middleField("middle", BSONObj());
+ // Bad query
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status.getStatus());
+ }
- BSONObj find;
- if (FieldParser::extract(cmdObj, findField, &find, &errmsg) ==
- FieldParser::FIELD_INVALID) {
+ BSONObj shardKey = status.getValue();
+ if (shardKey.isEmpty()) {
+ errmsg = stream() << "no shard key found in chunk query " << find;
return false;
}
- BSONArray bounds;
- if (FieldParser::extract(cmdObj, boundsField, &bounds, &errmsg) ==
- FieldParser::FIELD_INVALID) {
+ chunk = info->findIntersectingChunk(shardKey);
+ invariant(chunk.get());
+ } else if (!bounds.isEmpty()) {
+ if (!info->getShardKeyPattern().isShardKey(bounds[0].Obj()) ||
+ !info->getShardKeyPattern().isShardKey(bounds[1].Obj())) {
+ errmsg = stream() << "shard key bounds "
+ << "[" << bounds[0].Obj() << "," << bounds[1].Obj() << ")"
+ << " are not valid for shard key pattern "
+ << info->getShardKeyPattern().toBSON();
return false;
}
- if (!bounds.isEmpty()) {
- if (!bounds.hasField("0")) {
- errmsg = "lower bound not specified";
- return false;
- }
+ BSONObj minKey = info->getShardKeyPattern().normalizeShardKey(bounds[0].Obj());
+ BSONObj maxKey = info->getShardKeyPattern().normalizeShardKey(bounds[1].Obj());
- if (!bounds.hasField("1")) {
- errmsg = "upper bound not specified";
- return false;
- }
- }
+ chunk = info->findIntersectingChunk(minKey);
+ invariant(chunk.get());
- if (!find.isEmpty() && !bounds.isEmpty()) {
- errmsg = "cannot specify bounds and find at the same time";
+ if (chunk->getMin().woCompare(minKey) != 0 || chunk->getMax().woCompare(maxKey) != 0) {
+ errmsg = stream() << "no chunk found with the shard key bounds "
+ << "[" << minKey << "," << maxKey << ")";
return false;
}
-
- BSONObj middle;
- if (FieldParser::extract(cmdObj, middleField, &middle, &errmsg) ==
- FieldParser::FIELD_INVALID) {
+ } else {
+ // Middle
+ if (!info->getShardKeyPattern().isShardKey(middle)) {
+ errmsg = stream() << "new split key " << middle
+ << " is not valid for shard key pattern "
+ << info->getShardKeyPattern().toBSON();
return false;
}
- if (find.isEmpty() && bounds.isEmpty() && middle.isEmpty()) {
- errmsg = "need to specify find/bounds or middle";
- return false;
- }
+ middle = info->getShardKeyPattern().normalizeShardKey(middle);
- if (!find.isEmpty() && !middle.isEmpty()) {
- errmsg = "cannot specify find and middle together";
- return false;
+ // Check shard key size when manually provided
+ Status status = ShardKeyPattern::checkShardKeySize(middle);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status);
}
- if (!bounds.isEmpty() && !middle.isEmpty()) {
- errmsg = "cannot specify bounds and middle together";
+ chunk = info->findIntersectingChunk(middle);
+ invariant(chunk.get());
+
+ if (chunk->getMin().woCompare(middle) == 0 || chunk->getMax().woCompare(middle) == 0) {
+ errmsg = stream() << "new split key " << middle
+ << " is a boundary key of existing chunk "
+ << "[" << chunk->getMin() << "," << chunk->getMax() << ")";
return false;
}
+ }
- // This refreshes the chunk metadata if stale.
- ChunkManagerPtr info = config->getChunkManager(nss.ns(), true);
- ChunkPtr chunk;
-
- if (!find.isEmpty()) {
- StatusWith<BSONObj> status =
- info->getShardKeyPattern().extractShardKeyFromQuery(find);
-
- // Bad query
- if (!status.isOK()) {
- return appendCommandStatus(result, status.getStatus());
- }
-
- BSONObj shardKey = status.getValue();
- if (shardKey.isEmpty()) {
- errmsg = stream() << "no shard key found in chunk query " << find;
- return false;
- }
+ invariant(chunk.get());
+ log() << "splitting chunk [" << chunk->getMin() << "," << chunk->getMax() << ")"
+ << " in collection " << nss.ns() << " on shard " << chunk->getShardId();
- chunk = info->findIntersectingChunk(shardKey);
- invariant(chunk.get());
- }
- else if (!bounds.isEmpty()) {
-
- if (!info->getShardKeyPattern().isShardKey(bounds[0].Obj())
- || !info->getShardKeyPattern().isShardKey(bounds[1].Obj())) {
- errmsg = stream() << "shard key bounds " << "[" << bounds[0].Obj() << ","
- << bounds[1].Obj() << ")"
- << " are not valid for shard key pattern "
- << info->getShardKeyPattern().toBSON();
- return false;
- }
-
- BSONObj minKey = info->getShardKeyPattern().normalizeShardKey(bounds[0].Obj());
- BSONObj maxKey = info->getShardKeyPattern().normalizeShardKey(bounds[1].Obj());
-
- chunk = info->findIntersectingChunk(minKey);
- invariant(chunk.get());
-
- if (chunk->getMin().woCompare(minKey) != 0
- || chunk->getMax().woCompare(maxKey) != 0) {
- errmsg = stream() << "no chunk found with the shard key bounds " << "["
- << minKey << "," << maxKey << ")";
- return false;
- }
- }
- else {
- // Middle
- if (!info->getShardKeyPattern().isShardKey(middle)) {
- errmsg = stream() << "new split key " << middle
- << " is not valid for shard key pattern "
- << info->getShardKeyPattern().toBSON();
- return false;
- }
-
- middle = info->getShardKeyPattern().normalizeShardKey(middle);
-
- // Check shard key size when manually provided
- Status status = ShardKeyPattern::checkShardKeySize(middle);
- if (!status.isOK()) {
- return appendCommandStatus(result, status);
- }
-
- chunk = info->findIntersectingChunk(middle);
- invariant(chunk.get());
-
- if (chunk->getMin().woCompare(middle) == 0
- || chunk->getMax().woCompare(middle) == 0) {
- errmsg = stream() << "new split key " << middle
- << " is a boundary key of existing chunk " << "["
- << chunk->getMin() << "," << chunk->getMax() << ")";
- return false;
- }
+ BSONObj res;
+ if (middle.isEmpty()) {
+ Status status = chunk->split(Chunk::atMedian, NULL, NULL);
+ if (!status.isOK()) {
+ errmsg = "split failed";
+ result.append("cause", status.toString());
+ return false;
}
+ } else {
+ vector<BSONObj> splitPoints;
+ splitPoints.push_back(middle);
- invariant(chunk.get());
- log() << "splitting chunk [" << chunk->getMin() << "," << chunk->getMax() << ")"
- << " in collection " << nss.ns()
- << " on shard " << chunk->getShardId();
-
- BSONObj res;
- if (middle.isEmpty()) {
- Status status = chunk->split(Chunk::atMedian, NULL, NULL);
- if (!status.isOK()) {
- errmsg = "split failed";
- result.append("cause", status.toString());
- return false;
- }
- }
- else {
- vector<BSONObj> splitPoints;
- splitPoints.push_back(middle);
-
- Status status = chunk->multiSplit(splitPoints, NULL);
- if (!status.isOK()) {
- errmsg = "split failed";
- result.append("cause", status.toString());
- return false;
- }
+ Status status = chunk->multiSplit(splitPoints, NULL);
+ if (!status.isOK()) {
+ errmsg = "split failed";
+ result.append("cause", status.toString());
+ return false;
}
-
- return true;
}
- } splitCollectionCmd;
+ return true;
+ }
+
+} splitCollectionCmd;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_user_management_commands.cpp b/src/mongo/s/commands/cluster_user_management_commands.cpp
index 2c781ac0e91..d2a6ab667d4 100644
--- a/src/mongo/s/commands/cluster_user_management_commands.cpp
+++ b/src/mongo/s/commands/cluster_user_management_commands.cpp
@@ -46,746 +46,761 @@
namespace mongo {
- using std::string;
- using std::stringstream;
- using std::vector;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+class CmdCreateUser : public Command {
+public:
+ CmdCreateUser() : Command("createUser") {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Adds a user to the system";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForCreateUserCommand(client, dbname, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ return grid.catalogManager()->runUserManagementWriteCommand(
+ this->name, dbname, cmdObj, &result);
+ }
+
+ virtual void redactForLogging(mutablebson::Document* cmdObj) {
+ auth::redactPasswordData(cmdObj->root());
+ }
+
+} cmdCreateUser;
+
+class CmdUpdateUser : public Command {
+public:
+ CmdUpdateUser() : Command("updateUser") {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Used to update a user, for example to change its password";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForUpdateUserCommand(client, dbname, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ auth::CreateOrUpdateUserArgs args;
+ Status status = auth::parseCreateOrUpdateUserCommands(cmdObj, this->name, dbname, &args);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status);
+ }
+ const bool ok = grid.catalogManager()->runUserManagementWriteCommand(
+ this->name, dbname, cmdObj, &result);
+
+ AuthorizationManager* authzManager = getGlobalAuthorizationManager();
+ invariant(authzManager);
+ authzManager->invalidateUserByName(args.userName);
+
+ return ok;
+ }
+
+ virtual void redactForLogging(mutablebson::Document* cmdObj) {
+ auth::redactPasswordData(cmdObj->root());
+ }
+
+} cmdUpdateUser;
+
+class CmdDropUser : public Command {
+public:
+ CmdDropUser() : Command("dropUser") {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Drops a single user.";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForDropUserCommand(client, dbname, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ UserName userName;
+ BSONObj unusedWriteConcern;
+ Status status =
+ auth::parseAndValidateDropUserCommand(cmdObj, dbname, &userName, &unusedWriteConcern);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status);
+ }
+ const bool ok = grid.catalogManager()->runUserManagementWriteCommand(
+ this->name, dbname, cmdObj, &result);
+
+ AuthorizationManager* authzManager = getGlobalAuthorizationManager();
+ invariant(authzManager);
+ authzManager->invalidateUserByName(userName);
+
+ return ok;
+ }
+
+} cmdDropUser;
+
+class CmdDropAllUsersFromDatabase : public Command {
+public:
+ CmdDropAllUsersFromDatabase() : Command("dropAllUsersFromDatabase") {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Drops all users for a single database.";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForDropAllUsersFromDatabaseCommand(client, dbname);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const bool ok = grid.catalogManager()->runUserManagementWriteCommand(
+ this->name, dbname, cmdObj, &result);
+
+ AuthorizationManager* authzManager = getGlobalAuthorizationManager();
+ invariant(authzManager);
+ authzManager->invalidateUsersFromDB(dbname);
+
+ return ok;
+ }
+
+} cmdDropAllUsersFromDatabase;
+
+class CmdGrantRolesToUser : public Command {
+public:
+ CmdGrantRolesToUser() : Command("grantRolesToUser") {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Grants roles to a user.";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForGrantRolesToUserCommand(client, dbname, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ string userNameString;
+ vector<RoleName> roles;
+ BSONObj unusedWriteConcern;
+ Status status = auth::parseRolePossessionManipulationCommands(
+ cmdObj, this->name, dbname, &userNameString, &roles, &unusedWriteConcern);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status);
+ }
+ const bool ok = grid.catalogManager()->runUserManagementWriteCommand(
+ this->name, dbname, cmdObj, &result);
+
+ AuthorizationManager* authzManager = getGlobalAuthorizationManager();
+ invariant(authzManager);
+ authzManager->invalidateUserByName(UserName(userNameString, dbname));
+
+ return ok;
+ }
+
+} cmdGrantRolesToUser;
+
+class CmdRevokeRolesFromUser : public Command {
+public:
+ CmdRevokeRolesFromUser() : Command("revokeRolesFromUser") {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Revokes roles from a user.";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForRevokeRolesFromUserCommand(client, dbname, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ string userNameString;
+ vector<RoleName> unusedRoles;
+ BSONObj unusedWriteConcern;
+ Status status = auth::parseRolePossessionManipulationCommands(
+ cmdObj, this->name, dbname, &userNameString, &unusedRoles, &unusedWriteConcern);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status);
+ }
+ const bool ok = grid.catalogManager()->runUserManagementWriteCommand(
+ this->name, dbname, cmdObj, &result);
+
+ AuthorizationManager* authzManager = getGlobalAuthorizationManager();
+ invariant(authzManager);
+ authzManager->invalidateUserByName(UserName(userNameString, dbname));
+
+ return ok;
+ }
+
+} cmdRevokeRolesFromUser;
+
+class CmdUsersInfo : public Command {
+public:
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool slaveOverrideOk() const {
+ return true;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ CmdUsersInfo() : Command("usersInfo") {}
+
+ virtual void help(stringstream& ss) const {
+ ss << "Returns information about users.";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForUsersInfoCommand(client, dbname, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ return grid.catalogManager()->runUserManagementReadCommand(dbname, cmdObj, &result);
+ }
+
+} cmdUsersInfo;
+
+class CmdCreateRole : public Command {
+public:
+ CmdCreateRole() : Command("createRole") {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Adds a role to the system";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForCreateRoleCommand(client, dbname, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ return grid.catalogManager()->runUserManagementWriteCommand(
+ this->name, dbname, cmdObj, &result);
+ }
+
+} cmdCreateRole;
+
+class CmdUpdateRole : public Command {
+public:
+ CmdUpdateRole() : Command("updateRole") {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Used to update a role";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForUpdateRoleCommand(client, dbname, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const bool ok = grid.catalogManager()->runUserManagementWriteCommand(
+ this->name, dbname, cmdObj, &result);
+
+ AuthorizationManager* authzManager = getGlobalAuthorizationManager();
+ invariant(authzManager);
+ authzManager->invalidateUserCache();
+
+ return ok;
+ }
+
+} cmdUpdateRole;
+
+class CmdGrantPrivilegesToRole : public Command {
+public:
+ CmdGrantPrivilegesToRole() : Command("grantPrivilegesToRole") {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Grants privileges to a role";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForGrantPrivilegesToRoleCommand(client, dbname, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const bool ok = grid.catalogManager()->runUserManagementWriteCommand(
+ this->name, dbname, cmdObj, &result);
+
+ AuthorizationManager* authzManager = getGlobalAuthorizationManager();
+ invariant(authzManager);
+ authzManager->invalidateUserCache();
+
+ return ok;
+ }
+
+} cmdGrantPrivilegesToRole;
+
+class CmdRevokePrivilegesFromRole : public Command {
+public:
+ CmdRevokePrivilegesFromRole() : Command("revokePrivilegesFromRole") {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Revokes privileges from a role";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForRevokePrivilegesFromRoleCommand(client, dbname, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const bool ok = grid.catalogManager()->runUserManagementWriteCommand(
+ this->name, dbname, cmdObj, &result);
+
+ AuthorizationManager* authzManager = getGlobalAuthorizationManager();
+ invariant(authzManager);
+ authzManager->invalidateUserCache();
+
+ return ok;
+ }
+
+} cmdRevokePrivilegesFromRole;
+
+class CmdGrantRolesToRole : public Command {
+public:
+ CmdGrantRolesToRole() : Command("grantRolesToRole") {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Grants roles to another role.";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForGrantRolesToRoleCommand(client, dbname, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const bool ok = grid.catalogManager()->runUserManagementWriteCommand(
+ this->name, dbname, cmdObj, &result);
+
+ AuthorizationManager* authzManager = getGlobalAuthorizationManager();
+ invariant(authzManager);
+ authzManager->invalidateUserCache();
+
+ return ok;
+ }
+
+} cmdGrantRolesToRole;
+
+class CmdRevokeRolesFromRole : public Command {
+public:
+ CmdRevokeRolesFromRole() : Command("revokeRolesFromRole") {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Revokes roles from another role.";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForRevokeRolesFromRoleCommand(client, dbname, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const bool ok = grid.catalogManager()->runUserManagementWriteCommand(
+ this->name, dbname, cmdObj, &result);
+
+ AuthorizationManager* authzManager = getGlobalAuthorizationManager();
+ invariant(authzManager);
+ authzManager->invalidateUserCache();
+
+ return ok;
+ }
+
+} cmdRevokeRolesFromRole;
+
+class CmdDropRole : public Command {
+public:
+ CmdDropRole() : Command("dropRole") {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Drops a single role. Before deleting the role completely it must remove it "
+ "from any users or roles that reference it. If any errors occur in the middle "
+ "of that process it's possible to be left in a state where the role has been "
+ "removed from some user/roles but otherwise still exists.";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForDropRoleCommand(client, dbname, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const bool ok = grid.catalogManager()->runUserManagementWriteCommand(
+ this->name, dbname, cmdObj, &result);
+
+ AuthorizationManager* authzManager = getGlobalAuthorizationManager();
+ invariant(authzManager);
+ authzManager->invalidateUserCache();
+
+ return ok;
+ }
+
+} cmdDropRole;
+
+class CmdDropAllRolesFromDatabase : public Command {
+public:
+ CmdDropAllRolesFromDatabase() : Command("dropAllRolesFromDatabase") {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Drops all roles from the given database. Before deleting the roles completely "
+ "it must remove them from any users or other roles that reference them. If any "
+ "errors occur in the middle of that process it's possible to be left in a state "
+ "where the roles have been removed from some user/roles but otherwise still "
+ "exist.";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForDropAllRolesFromDatabaseCommand(client, dbname);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const bool ok = grid.catalogManager()->runUserManagementWriteCommand(
+ this->name, dbname, cmdObj, &result);
+
+ AuthorizationManager* authzManager = getGlobalAuthorizationManager();
+ invariant(authzManager);
+ authzManager->invalidateUserCache();
+
+ return ok;
+ }
+
+} cmdDropAllRolesFromDatabase;
+
+class CmdRolesInfo : public Command {
+public:
+ CmdRolesInfo() : Command("rolesInfo") {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool slaveOverrideOk() const {
+ return true;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Returns information about roles.";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForRolesInfoCommand(client, dbname, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ return grid.catalogManager()->runUserManagementReadCommand(dbname, cmdObj, &result);
+ }
+
+} cmdRolesInfo;
+
+class CmdInvalidateUserCache : public Command {
+public:
+ CmdInvalidateUserCache() : Command("invalidateUserCache") {}
+
+ virtual bool slaveOk() const {
+ return true;
+ }
+
+ virtual bool adminOnly() const {
+ return true;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Invalidates the in-memory cache of user information";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForInvalidateUserCacheCommand(client);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ AuthorizationManager* authzManager = getGlobalAuthorizationManager();
+ invariant(authzManager);
+ authzManager->invalidateUserCache();
+ return true;
+ }
+
+} cmdInvalidateUserCache;
- class CmdCreateUser : public Command {
- public:
-
- CmdCreateUser() : Command("createUser") {}
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void help(stringstream& ss) const {
- ss << "Adds a user to the system";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForCreateUserCommand(client, dbname, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- return grid.catalogManager()->runUserManagementWriteCommand(this->name,
- dbname,
- cmdObj,
- &result);
- }
-
- virtual void redactForLogging(mutablebson::Document* cmdObj) {
- auth::redactPasswordData(cmdObj->root());
- }
-
- } cmdCreateUser;
-
- class CmdUpdateUser : public Command {
- public:
-
- CmdUpdateUser() : Command("updateUser") {}
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void help(stringstream& ss) const {
- ss << "Used to update a user, for example to change its password";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForUpdateUserCommand(client, dbname, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- auth::CreateOrUpdateUserArgs args;
- Status status = auth::parseCreateOrUpdateUserCommands(cmdObj,
- this->name,
- dbname,
- &args);
- if (!status.isOK()) {
- return appendCommandStatus(result, status);
- }
- const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name,
- dbname,
- cmdObj,
- &result);
-
- AuthorizationManager* authzManager = getGlobalAuthorizationManager();
- invariant(authzManager);
- authzManager->invalidateUserByName(args.userName);
-
- return ok;
- }
-
- virtual void redactForLogging(mutablebson::Document* cmdObj) {
- auth::redactPasswordData(cmdObj->root());
- }
-
- } cmdUpdateUser;
-
- class CmdDropUser : public Command {
- public:
-
- CmdDropUser() : Command("dropUser") {}
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void help(stringstream& ss) const {
- ss << "Drops a single user.";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForDropUserCommand(client, dbname, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- UserName userName;
- BSONObj unusedWriteConcern;
- Status status = auth::parseAndValidateDropUserCommand(cmdObj,
- dbname,
- &userName,
- &unusedWriteConcern);
- if (!status.isOK()) {
- return appendCommandStatus(result, status);
- }
- const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name,
- dbname,
- cmdObj,
- &result);
-
- AuthorizationManager* authzManager = getGlobalAuthorizationManager();
- invariant(authzManager);
- authzManager->invalidateUserByName(userName);
-
- return ok;
- }
-
- } cmdDropUser;
-
- class CmdDropAllUsersFromDatabase : public Command {
- public:
-
- CmdDropAllUsersFromDatabase() : Command("dropAllUsersFromDatabase") {}
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void help(stringstream& ss) const {
- ss << "Drops all users for a single database.";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForDropAllUsersFromDatabaseCommand(client, dbname);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name,
- dbname,
- cmdObj,
- &result);
-
- AuthorizationManager* authzManager = getGlobalAuthorizationManager();
- invariant(authzManager);
- authzManager->invalidateUsersFromDB(dbname);
-
- return ok;
- }
-
- } cmdDropAllUsersFromDatabase;
-
- class CmdGrantRolesToUser: public Command {
- public:
-
- CmdGrantRolesToUser() : Command("grantRolesToUser") {}
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void help(stringstream& ss) const {
- ss << "Grants roles to a user.";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForGrantRolesToUserCommand(client, dbname, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- string userNameString;
- vector<RoleName> roles;
- BSONObj unusedWriteConcern;
- Status status = auth::parseRolePossessionManipulationCommands(cmdObj,
- this->name,
- dbname,
- &userNameString,
- &roles,
- &unusedWriteConcern);
- if (!status.isOK()) {
- return appendCommandStatus(result, status);
- }
- const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name,
- dbname,
- cmdObj,
- &result);
-
- AuthorizationManager* authzManager = getGlobalAuthorizationManager();
- invariant(authzManager);
- authzManager->invalidateUserByName(UserName(userNameString, dbname));
-
- return ok;
- }
-
- } cmdGrantRolesToUser;
-
- class CmdRevokeRolesFromUser: public Command {
- public:
-
- CmdRevokeRolesFromUser() : Command("revokeRolesFromUser") {}
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void help(stringstream& ss) const {
- ss << "Revokes roles from a user.";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForRevokeRolesFromUserCommand(client, dbname, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- string userNameString;
- vector<RoleName> unusedRoles;
- BSONObj unusedWriteConcern;
- Status status = auth::parseRolePossessionManipulationCommands(cmdObj,
- this->name,
- dbname,
- &userNameString,
- &unusedRoles,
- &unusedWriteConcern);
- if (!status.isOK()) {
- return appendCommandStatus(result, status);
- }
- const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name,
- dbname,
- cmdObj,
- &result);
-
- AuthorizationManager* authzManager = getGlobalAuthorizationManager();
- invariant(authzManager);
- authzManager->invalidateUserByName(UserName(userNameString, dbname));
-
- return ok;
- }
-
- } cmdRevokeRolesFromUser;
-
- class CmdUsersInfo: public Command {
- public:
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool slaveOverrideOk() const { return true; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- CmdUsersInfo() : Command("usersInfo") {}
-
- virtual void help(stringstream& ss) const {
- ss << "Returns information about users.";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForUsersInfoCommand(client, dbname, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- return grid.catalogManager()->runUserManagementReadCommand(dbname,
- cmdObj,
- &result);
- }
-
- } cmdUsersInfo;
-
- class CmdCreateRole: public Command {
- public:
-
- CmdCreateRole() : Command("createRole") {}
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void help(stringstream& ss) const {
- ss << "Adds a role to the system";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForCreateRoleCommand(client, dbname, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- return grid.catalogManager()->runUserManagementWriteCommand(this->name,
- dbname,
- cmdObj,
- &result);
- }
-
- } cmdCreateRole;
-
- class CmdUpdateRole: public Command {
- public:
-
- CmdUpdateRole() : Command("updateRole") {}
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void help(stringstream& ss) const {
- ss << "Used to update a role";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForUpdateRoleCommand(client, dbname, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name,
- dbname,
- cmdObj,
- &result);
-
- AuthorizationManager* authzManager = getGlobalAuthorizationManager();
- invariant(authzManager);
- authzManager->invalidateUserCache();
-
- return ok;
- }
-
- } cmdUpdateRole;
-
- class CmdGrantPrivilegesToRole: public Command {
- public:
-
- CmdGrantPrivilegesToRole() : Command("grantPrivilegesToRole") {}
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void help(stringstream& ss) const {
- ss << "Grants privileges to a role";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForGrantPrivilegesToRoleCommand(client, dbname, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name,
- dbname,
- cmdObj,
- &result);
-
- AuthorizationManager* authzManager = getGlobalAuthorizationManager();
- invariant(authzManager);
- authzManager->invalidateUserCache();
-
- return ok;
- }
-
- } cmdGrantPrivilegesToRole;
-
- class CmdRevokePrivilegesFromRole: public Command {
- public:
-
- CmdRevokePrivilegesFromRole() : Command("revokePrivilegesFromRole") {}
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void help(stringstream& ss) const {
- ss << "Revokes privileges from a role";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForRevokePrivilegesFromRoleCommand(client, dbname, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name,
- dbname,
- cmdObj,
- &result);
-
- AuthorizationManager* authzManager = getGlobalAuthorizationManager();
- invariant(authzManager);
- authzManager->invalidateUserCache();
-
- return ok;
- }
-
- } cmdRevokePrivilegesFromRole;
-
- class CmdGrantRolesToRole: public Command {
- public:
-
- CmdGrantRolesToRole() : Command("grantRolesToRole") {}
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void help(stringstream& ss) const {
- ss << "Grants roles to another role.";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForGrantRolesToRoleCommand(client, dbname, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name,
- dbname,
- cmdObj,
- &result);
-
- AuthorizationManager* authzManager = getGlobalAuthorizationManager();
- invariant(authzManager);
- authzManager->invalidateUserCache();
-
- return ok;
- }
-
- } cmdGrantRolesToRole;
-
- class CmdRevokeRolesFromRole: public Command {
- public:
-
- CmdRevokeRolesFromRole() : Command("revokeRolesFromRole") {}
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void help(stringstream& ss) const {
- ss << "Revokes roles from another role.";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForRevokeRolesFromRoleCommand(client, dbname, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name,
- dbname,
- cmdObj,
- &result);
-
- AuthorizationManager* authzManager = getGlobalAuthorizationManager();
- invariant(authzManager);
- authzManager->invalidateUserCache();
-
- return ok;
- }
-
- } cmdRevokeRolesFromRole;
-
- class CmdDropRole: public Command {
- public:
-
- CmdDropRole() : Command("dropRole") {}
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void help(stringstream& ss) const {
- ss << "Drops a single role. Before deleting the role completely it must remove it "
- "from any users or roles that reference it. If any errors occur in the middle "
- "of that process it's possible to be left in a state where the role has been "
- "removed from some user/roles but otherwise still exists.";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForDropRoleCommand(client, dbname, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name,
- dbname,
- cmdObj,
- &result);
-
- AuthorizationManager* authzManager = getGlobalAuthorizationManager();
- invariant(authzManager);
- authzManager->invalidateUserCache();
-
- return ok;
- }
-
- } cmdDropRole;
-
- class CmdDropAllRolesFromDatabase: public Command {
- public:
-
- CmdDropAllRolesFromDatabase() : Command("dropAllRolesFromDatabase") {}
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void help(stringstream& ss) const {
- ss << "Drops all roles from the given database. Before deleting the roles completely "
- "it must remove them from any users or other roles that reference them. If any "
- "errors occur in the middle of that process it's possible to be left in a state "
- "where the roles have been removed from some user/roles but otherwise still "
- "exist.";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForDropAllRolesFromDatabaseCommand(client, dbname);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name,
- dbname,
- cmdObj,
- &result);
-
- AuthorizationManager* authzManager = getGlobalAuthorizationManager();
- invariant(authzManager);
- authzManager->invalidateUserCache();
-
- return ok;
- }
-
- } cmdDropAllRolesFromDatabase;
-
- class CmdRolesInfo: public Command {
- public:
-
- CmdRolesInfo() : Command("rolesInfo") {}
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool slaveOverrideOk() const { return true; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void help(stringstream& ss) const {
- ss << "Returns information about roles.";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForRolesInfoCommand(client, dbname, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- return grid.catalogManager()->runUserManagementReadCommand(dbname,
- cmdObj,
- &result);
- }
-
- } cmdRolesInfo;
-
- class CmdInvalidateUserCache: public Command {
- public:
-
- CmdInvalidateUserCache() : Command("invalidateUserCache") {}
-
- virtual bool slaveOk() const { return true; }
-
- virtual bool adminOnly() const { return true; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void help(stringstream& ss) const {
- ss << "Invalidates the in-memory cache of user information";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForInvalidateUserCacheCommand(client);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- AuthorizationManager* authzManager = getGlobalAuthorizationManager();
- invariant(authzManager);
- authzManager->invalidateUserCache();
- return true;
- }
-
- } cmdInvalidateUserCache;
-
- /**
- * This command is used only by mongorestore to handle restoring users/roles. We do this so
- * that mongorestore doesn't do direct inserts into the admin.system.users and
- * admin.system.roles, which would bypass the authzUpdateLock and allow multiple concurrent
- * modifications to users/roles. What mongorestore now does instead is it inserts all user/role
- * definitions it wants to restore into temporary collections, then this command moves those
- * user/role definitions into their proper place in admin.system.users and admin.system.roles.
- * It either adds the users/roles to the existing ones or replaces the existing ones, depending
- * on whether the "drop" argument is true or false.
- */
- class CmdMergeAuthzCollections : public Command {
- public:
-
- CmdMergeAuthzCollections() : Command("_mergeAuthzCollections") {}
-
- virtual bool slaveOk() const { return false; }
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual bool adminOnly() const { return true; }
-
- virtual void help(stringstream& ss) const {
- ss << "Internal command used by mongorestore for updating user/role data";
- }
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return auth::checkAuthForMergeAuthzCollectionsCommand(client, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- return grid.catalogManager()->runUserManagementWriteCommand(this->name,
- dbname,
- cmdObj,
- &result);
- }
-
- } cmdMergeAuthzCollections;
-
-} // namespace mongo
+/**
+ * This command is used only by mongorestore to handle restoring users/roles. We do this so
+ * that mongorestore doesn't do direct inserts into the admin.system.users and
+ * admin.system.roles, which would bypass the authzUpdateLock and allow multiple concurrent
+ * modifications to users/roles. What mongorestore now does instead is it inserts all user/role
+ * definitions it wants to restore into temporary collections, then this command moves those
+ * user/role definitions into their proper place in admin.system.users and admin.system.roles.
+ * It either adds the users/roles to the existing ones or replaces the existing ones, depending
+ * on whether the "drop" argument is true or false.
+ */
+class CmdMergeAuthzCollections : public Command {
+public:
+ CmdMergeAuthzCollections() : Command("_mergeAuthzCollections") {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual bool adminOnly() const {
+ return true;
+ }
+
+ virtual void help(stringstream& ss) const {
+ ss << "Internal command used by mongorestore for updating user/role data";
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return auth::checkAuthForMergeAuthzCollectionsCommand(client, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ return grid.catalogManager()->runUserManagementWriteCommand(
+ this->name, dbname, cmdObj, &result);
+ }
+
+} cmdMergeAuthzCollections;
+
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_whats_my_uri_cmd.cpp b/src/mongo/s/commands/cluster_whats_my_uri_cmd.cpp
index ad4c957e598..6b10eb476e6 100644
--- a/src/mongo/s/commands/cluster_whats_my_uri_cmd.cpp
+++ b/src/mongo/s/commands/cluster_whats_my_uri_cmd.cpp
@@ -35,41 +35,39 @@
namespace mongo {
namespace {
- class WhatsMyUriCmd : public Command {
- public:
- WhatsMyUriCmd() : Command("whatsmyuri") { }
+class WhatsMyUriCmd : public Command {
+public:
+ WhatsMyUriCmd() : Command("whatsmyuri") {}
- virtual bool slaveOk() const {
- return true;
- }
+ virtual bool slaveOk() const {
+ return true;
+ }
- virtual bool isWriteCommandForConfigServer() const {
- return false;
- }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- virtual void help(std::stringstream &help) const {
- help << "{whatsmyuri:1}";
- }
+ virtual void help(std::stringstream& help) const {
+ help << "{whatsmyuri:1}";
+ }
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ // No auth required
+ }
- // No auth required
- }
+ virtual bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) {
+ result << "you" << cc().getRemote().toString();
+ return true;
+ }
- virtual bool run(OperationContext* txn,
- const std::string& dbname,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& result) {
+} whatsMyUriCmd;
- result << "you" << cc().getRemote().toString();
- return true;
- }
-
- } whatsMyUriCmd;
-
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/cluster_write_cmd.cpp b/src/mongo/s/commands/cluster_write_cmd.cpp
index a22dd2c7dc7..e499d708ec3 100644
--- a/src/mongo/s/commands/cluster_write_cmd.cpp
+++ b/src/mongo/s/commands/cluster_write_cmd.cpp
@@ -27,7 +27,7 @@
*/
#include "mongo/platform/basic.h"
-
+
#include "mongo/base/error_codes.h"
#include "mongo/db/client.h"
#include "mongo/db/client_basic.h"
@@ -46,234 +46,209 @@
namespace mongo {
- using std::string;
- using std::stringstream;
- using std::vector;
+using std::string;
+using std::stringstream;
+using std::vector;
namespace {
- /**
- * Base class for mongos write commands. Cluster write commands support batch writes and write
- * concern, and return per-item error information. All cluster write commands use the entry
- * point ClusterWriteCmd::run().
- *
- * Batch execution (targeting and dispatching) is performed by the BatchWriteExec class.
- */
- class ClusterWriteCmd : public Command {
- public:
- virtual ~ClusterWriteCmd() {
-
- }
-
- virtual bool slaveOk() const {
- return false;
- }
-
- virtual bool isWriteCommandForConfigServer() const {
- return false;
+/**
+ * Base class for mongos write commands. Cluster write commands support batch writes and write
+ * concern, and return per-item error information. All cluster write commands use the entry
+ * point ClusterWriteCmd::run().
+ *
+ * Batch execution (targeting and dispatching) is performed by the BatchWriteExec class.
+ */
+class ClusterWriteCmd : public Command {
+public:
+ virtual ~ClusterWriteCmd() {}
+
+ virtual bool slaveOk() const {
+ return false;
+ }
+
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ Status status = auth::checkAuthForWriteCommand(AuthorizationSession::get(client),
+ _writeType,
+ NamespaceString(parseNs(dbname, cmdObj)),
+ cmdObj);
+
+ // TODO: Remove this when we standardize GLE reporting from commands
+ if (!status.isOK()) {
+ LastError::get(client).setLastError(status.code(), status.reason());
}
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
+ return status;
+ }
- Status status = auth::checkAuthForWriteCommand(AuthorizationSession::get(client),
- _writeType,
- NamespaceString(parseNs(dbname,
- cmdObj)),
- cmdObj);
+ virtual Status explain(OperationContext* txn,
+ const std::string& dbname,
+ const BSONObj& cmdObj,
+ ExplainCommon::Verbosity verbosity,
+ BSONObjBuilder* out) const {
+ BatchedCommandRequest request(_writeType);
- // TODO: Remove this when we standardize GLE reporting from commands
- if (!status.isOK()) {
- LastError::get(client).setLastError(status.code(), status.reason());
- }
-
- return status;
+ string errMsg;
+ if (!request.parseBSON(cmdObj, &errMsg) || !request.isValid(&errMsg)) {
+ return Status(ErrorCodes::FailedToParse, errMsg);
}
- virtual Status explain(OperationContext* txn,
- const std::string& dbname,
- const BSONObj& cmdObj,
- ExplainCommon::Verbosity verbosity,
- BSONObjBuilder* out) const {
+ // Fixup the namespace to be a full ns internally
+ const NamespaceString nss(dbname, request.getNS());
+ request.setNS(nss.ns());
- BatchedCommandRequest request(_writeType);
+ // We can only explain write batches of size 1.
+ if (request.sizeWriteOps() != 1U) {
+ return Status(ErrorCodes::InvalidLength, "explained write batches must be of size 1");
+ }
- string errMsg;
- if (!request.parseBSON(cmdObj, &errMsg) || !request.isValid(&errMsg)) {
- return Status(ErrorCodes::FailedToParse, errMsg);
- }
+ BSONObjBuilder explainCmdBob;
+ ClusterExplain::wrapAsExplain(cmdObj, verbosity, &explainCmdBob);
- // Fixup the namespace to be a full ns internally
- const NamespaceString nss(dbname, request.getNS());
- request.setNS(nss.ns());
+ // We will time how long it takes to run the commands on the shards.
+ Timer timer;
- // We can only explain write batches of size 1.
- if (request.sizeWriteOps() != 1U) {
- return Status(ErrorCodes::InvalidLength,
- "explained write batches must be of size 1");
- }
+ // Target the command to the shards based on the singleton batch item.
+ BatchItemRef targetingBatchItem(&request, 0);
+ vector<Strategy::CommandResult> shardResults;
+ Status status = Strategy::commandOpWrite(
+ dbname, explainCmdBob.obj(), targetingBatchItem, &shardResults);
+ if (!status.isOK()) {
+ return status;
+ }
- BSONObjBuilder explainCmdBob;
- ClusterExplain::wrapAsExplain(cmdObj, verbosity, &explainCmdBob);
-
- // We will time how long it takes to run the commands on the shards.
- Timer timer;
-
- // Target the command to the shards based on the singleton batch item.
- BatchItemRef targetingBatchItem(&request, 0);
- vector<Strategy::CommandResult> shardResults;
- Status status = Strategy::commandOpWrite(dbname,
- explainCmdBob.obj(),
- targetingBatchItem,
- &shardResults);
- if (!status.isOK()) {
- return status;
+ return ClusterExplain::buildExplainResult(
+ shardResults, ClusterExplain::kWriteOnShards, timer.millis(), out);
+ }
+
+ virtual bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ BatchedCommandRequest request(_writeType);
+ BatchedCommandResponse response;
+
+ ClusterWriter writer(true, 0);
+
+ LastError* cmdLastError = &LastError::get(cc());
+
+ {
+ // Disable the last error object for the duration of the write
+ LastError::Disabled disableLastError(cmdLastError);
+
+ // TODO: if we do namespace parsing, push this to the type
+ if (!request.parseBSON(cmdObj, &errmsg) || !request.isValid(&errmsg)) {
+ // Batch parse failure
+ response.setOk(false);
+ response.setErrCode(ErrorCodes::FailedToParse);
+ response.setErrMessage(errmsg);
+ } else {
+ // Fixup the namespace to be a full ns internally
+ const NamespaceString nss(dbname, request.getNS());
+ request.setNSS(nss);
+
+ writer.write(request, &response);
}
- return ClusterExplain::buildExplainResult(shardResults,
- ClusterExplain::kWriteOnShards,
- timer.millis(),
- out);
+ dassert(response.isValid(NULL));
}
- virtual bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
-
- BatchedCommandRequest request(_writeType);
- BatchedCommandResponse response;
-
- ClusterWriter writer(true, 0);
-
- LastError* cmdLastError = &LastError::get(cc());
-
- {
- // Disable the last error object for the duration of the write
- LastError::Disabled disableLastError(cmdLastError);
+ // Populate the lastError object based on the write response
+ cmdLastError->reset();
+ batchErrorToLastError(request, response, cmdLastError);
- // TODO: if we do namespace parsing, push this to the type
- if (!request.parseBSON(cmdObj, &errmsg) || !request.isValid(&errmsg)) {
- // Batch parse failure
- response.setOk(false);
- response.setErrCode(ErrorCodes::FailedToParse);
- response.setErrMessage(errmsg);
- }
- else {
- // Fixup the namespace to be a full ns internally
- const NamespaceString nss(dbname, request.getNS());
- request.setNSS(nss);
+ size_t numAttempts;
- writer.write(request, &response);
- }
-
- dassert(response.isValid(NULL));
- }
-
- // Populate the lastError object based on the write response
- cmdLastError->reset();
- batchErrorToLastError(request, response, cmdLastError);
-
- size_t numAttempts;
-
- if (!response.getOk()) {
- numAttempts = 0;
- }
- else if (request.getOrdered() && response.isErrDetailsSet()) {
- // Add one failed attempt
- numAttempts = response.getErrDetailsAt(0)->getIndex() + 1;
- }
- else {
- numAttempts = request.sizeWriteOps();
- }
+ if (!response.getOk()) {
+ numAttempts = 0;
+ } else if (request.getOrdered() && response.isErrDetailsSet()) {
+ // Add one failed attempt
+ numAttempts = response.getErrDetailsAt(0)->getIndex() + 1;
+ } else {
+ numAttempts = request.sizeWriteOps();
+ }
- // TODO: increase opcounters by more than one
- if (_writeType == BatchedCommandRequest::BatchType_Insert) {
- for (size_t i = 0; i < numAttempts; ++i) {
- globalOpCounters.gotInsert();
- }
+ // TODO: increase opcounters by more than one
+ if (_writeType == BatchedCommandRequest::BatchType_Insert) {
+ for (size_t i = 0; i < numAttempts; ++i) {
+ globalOpCounters.gotInsert();
}
- else if (_writeType == BatchedCommandRequest::BatchType_Update) {
- for (size_t i = 0; i < numAttempts; ++i) {
- globalOpCounters.gotUpdate();
- }
+ } else if (_writeType == BatchedCommandRequest::BatchType_Update) {
+ for (size_t i = 0; i < numAttempts; ++i) {
+ globalOpCounters.gotUpdate();
}
- else if (_writeType == BatchedCommandRequest::BatchType_Delete) {
- for (size_t i = 0; i < numAttempts; ++i) {
- globalOpCounters.gotDelete();
- }
- }
-
- // Save the last opTimes written on each shard for this client, to allow GLE to work
- if (haveClient() && writer.getStats().hasShardStats()) {
- ClusterLastErrorInfo::get(cc()).addHostOpTimes(
- writer.getStats().getShardStats().getWriteOpTimes());
+ } else if (_writeType == BatchedCommandRequest::BatchType_Delete) {
+ for (size_t i = 0; i < numAttempts; ++i) {
+ globalOpCounters.gotDelete();
}
-
- // TODO
- // There's a pending issue about how to report response here. If we use
- // the command infra-structure, we should reuse the 'errmsg' field. But
- // we have already filed that message inside the BatchCommandResponse.
- // return response.getOk();
- result.appendElements(response.toBSON());
- return true;
}
- protected:
- /**
- * Instantiates a command that can be invoked by "name", which will be capable of issuing
- * write batches of type "writeType", and will require privilege "action" to run.
- */
- ClusterWriteCmd(StringData name, BatchedCommandRequest::BatchType writeType)
- : Command(name),
- _writeType(writeType) {
-
+ // Save the last opTimes written on each shard for this client, to allow GLE to work
+ if (haveClient() && writer.getStats().hasShardStats()) {
+ ClusterLastErrorInfo::get(cc())
+ .addHostOpTimes(writer.getStats().getShardStats().getWriteOpTimes());
}
- private:
- // Type of batch (e.g. insert, update).
- const BatchedCommandRequest::BatchType _writeType;
- };
+ // TODO
+ // There's a pending issue about how to report response here. If we use
+ // the command infra-structure, we should reuse the 'errmsg' field. But
+ // we have already filed that message inside the BatchCommandResponse.
+ // return response.getOk();
+ result.appendElements(response.toBSON());
+ return true;
+ }
+protected:
+ /**
+ * Instantiates a command that can be invoked by "name", which will be capable of issuing
+ * write batches of type "writeType", and will require privilege "action" to run.
+ */
+ ClusterWriteCmd(StringData name, BatchedCommandRequest::BatchType writeType)
+ : Command(name), _writeType(writeType) {}
- class ClusterCmdInsert : public ClusterWriteCmd {
- public:
- ClusterCmdInsert() : ClusterWriteCmd("insert", BatchedCommandRequest::BatchType_Insert) {
+private:
+ // Type of batch (e.g. insert, update).
+ const BatchedCommandRequest::BatchType _writeType;
+};
- }
- void help(stringstream& help) const {
- help << "insert documents";
- }
+class ClusterCmdInsert : public ClusterWriteCmd {
+public:
+ ClusterCmdInsert() : ClusterWriteCmd("insert", BatchedCommandRequest::BatchType_Insert) {}
- } clusterInsertCmd;
+ void help(stringstream& help) const {
+ help << "insert documents";
+ }
- class ClusterCmdUpdate : public ClusterWriteCmd {
- public:
- ClusterCmdUpdate() : ClusterWriteCmd("update", BatchedCommandRequest::BatchType_Update) {
+} clusterInsertCmd;
- }
+class ClusterCmdUpdate : public ClusterWriteCmd {
+public:
+ ClusterCmdUpdate() : ClusterWriteCmd("update", BatchedCommandRequest::BatchType_Update) {}
- void help( stringstream& help ) const {
- help << "update documents";
- }
+ void help(stringstream& help) const {
+ help << "update documents";
+ }
- } clusterUpdateCmd;
+} clusterUpdateCmd;
- class ClusterCmdDelete : public ClusterWriteCmd {
- public:
- ClusterCmdDelete() : ClusterWriteCmd("delete", BatchedCommandRequest::BatchType_Delete) {
+class ClusterCmdDelete : public ClusterWriteCmd {
+public:
+ ClusterCmdDelete() : ClusterWriteCmd("delete", BatchedCommandRequest::BatchType_Delete) {}
- }
-
- void help(stringstream& help) const {
- help << "delete documents";
- }
+ void help(stringstream& help) const {
+ help << "delete documents";
+ }
- } clusterDeleteCmd;
+} clusterDeleteCmd;
-} // namespace
-} // namespace mongo
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/s/commands/commands_public.cpp b/src/mongo/s/commands/commands_public.cpp
index 6f0d57bfdd8..395f84c8c7e 100644
--- a/src/mongo/s/commands/commands_public.cpp
+++ b/src/mongo/s/commands/commands_public.cpp
@@ -64,1394 +64,1369 @@
namespace mongo {
- using boost::intrusive_ptr;
- using std::unique_ptr;
- using std::shared_ptr;
- using std::list;
- using std::make_pair;
- using std::map;
- using std::multimap;
- using std::set;
- using std::string;
- using std::stringstream;
- using std::vector;
-
- namespace dbgrid_pub_cmds {
-
- class PublicGridCommand : public Command {
- public:
- PublicGridCommand( const char* n, const char* oldname=NULL ) : Command( n, false, oldname ) {
- }
- virtual bool slaveOk() const {
- return true;
- }
- virtual bool adminOnly() const {
- return false;
- }
-
- // Override if passthrough should also send query options
- // Safer as off by default, can slowly enable as we add more tests
- virtual bool passOptions() const { return false; }
-
- // all grid commands are designed not to lock
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- protected:
-
- bool passthrough( DBConfigPtr conf, const BSONObj& cmdObj , BSONObjBuilder& result ) {
- return _passthrough(conf->name(), conf, cmdObj, 0, result);
- }
-
- bool adminPassthrough( DBConfigPtr conf, const BSONObj& cmdObj , BSONObjBuilder& result ) {
- return _passthrough("admin", conf, cmdObj, 0, result);
- }
-
- bool passthrough( DBConfigPtr conf, const BSONObj& cmdObj , int options, BSONObjBuilder& result ) {
- return _passthrough(conf->name(), conf, cmdObj, options, result);
- }
-
- private:
- bool _passthrough(const string& db,
- DBConfigPtr conf,
- const BSONObj& cmdObj,
- int options, BSONObjBuilder& result) {
- const auto shard = grid.shardRegistry()->getShard(conf->getPrimaryId());
- ShardConnection conn(shard->getConnString(), "");
-
- BSONObj res;
- bool ok = conn->runCommand(db, cmdObj, res, passOptions() ? options : 0);
- conn.done();
-
- result.appendElements(res);
- return ok;
+using boost::intrusive_ptr;
+using std::unique_ptr;
+using std::shared_ptr;
+using std::list;
+using std::make_pair;
+using std::map;
+using std::multimap;
+using std::set;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+namespace dbgrid_pub_cmds {
+
+class PublicGridCommand : public Command {
+public:
+ PublicGridCommand(const char* n, const char* oldname = NULL) : Command(n, false, oldname) {}
+ virtual bool slaveOk() const {
+ return true;
+ }
+ virtual bool adminOnly() const {
+ return false;
+ }
+
+ // Override if passthrough should also send query options
+ // Safer as off by default, can slowly enable as we add more tests
+ virtual bool passOptions() const {
+ return false;
+ }
+
+ // all grid commands are designed not to lock
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+
+protected:
+ bool passthrough(DBConfigPtr conf, const BSONObj& cmdObj, BSONObjBuilder& result) {
+ return _passthrough(conf->name(), conf, cmdObj, 0, result);
+ }
+
+ bool adminPassthrough(DBConfigPtr conf, const BSONObj& cmdObj, BSONObjBuilder& result) {
+ return _passthrough("admin", conf, cmdObj, 0, result);
+ }
+
+ bool passthrough(DBConfigPtr conf, const BSONObj& cmdObj, int options, BSONObjBuilder& result) {
+ return _passthrough(conf->name(), conf, cmdObj, options, result);
+ }
+
+private:
+ bool _passthrough(const string& db,
+ DBConfigPtr conf,
+ const BSONObj& cmdObj,
+ int options,
+ BSONObjBuilder& result) {
+ const auto shard = grid.shardRegistry()->getShard(conf->getPrimaryId());
+ ShardConnection conn(shard->getConnString(), "");
+
+ BSONObj res;
+ bool ok = conn->runCommand(db, cmdObj, res, passOptions() ? options : 0);
+ conn.done();
+
+ result.appendElements(res);
+ return ok;
+ }
+};
+
+class AllShardsCollectionCommand : public RunOnAllShardsCommand {
+public:
+ AllShardsCollectionCommand(const char* n,
+ const char* oldname = NULL,
+ bool useShardConn = false,
+ bool implicitCreateDb = false)
+ : RunOnAllShardsCommand(n, oldname, useShardConn, implicitCreateDb) {}
+
+ virtual void getShardIds(const string& dbName, BSONObj& cmdObj, vector<ShardId>& shardIds) {
+ const string fullns = dbName + '.' + cmdObj.firstElement().valuestrsafe();
+
+ auto status = grid.catalogCache()->getDatabase(dbName);
+ uassertStatusOK(status.getStatus());
+
+ shared_ptr<DBConfig> conf = status.getValue();
+
+ if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
+ shardIds.push_back(conf->getShardId(fullns));
+ } else {
+ grid.shardRegistry()->getAllShardIds(&shardIds);
+ }
+ }
+};
+
+
+class NotAllowedOnShardedCollectionCmd : public PublicGridCommand {
+public:
+ NotAllowedOnShardedCollectionCmd(const char* n) : PublicGridCommand(n) {}
+
+ virtual bool run(OperationContext* txn,
+ const string& dbName,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const string fullns = parseNs(dbName, cmdObj);
+
+ auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
+ if (!conf->isSharded(fullns)) {
+ return passthrough(conf, cmdObj, options, result);
+ }
+
+ return appendCommandStatus(
+ result,
+ Status(ErrorCodes::IllegalOperation,
+ str::stream() << "can't do command: " << name << " on sharded collection"));
+ }
+};
+
+// ----
+
+class DropIndexesCmd : public AllShardsCollectionCommand {
+public:
+ DropIndexesCmd() : AllShardsCollectionCommand("dropIndexes", "deleteIndexes") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::dropIndex);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
+} dropIndexesCmd;
+
+class CreateIndexesCmd : public AllShardsCollectionCommand {
+public:
+ CreateIndexesCmd()
+ : AllShardsCollectionCommand("createIndexes",
+ NULL, /* oldName */
+ true /* use ShardConnection */,
+ true /* implicit create db */) {
+ // createIndexes command should use ShardConnection so the getLastError would
+ // be able to properly enforce the write concern (via the saveGLEStats callback).
+ }
+
+ /**
+ * the createIndexes command doesn't require the 'ns' field to be populated
+ * so we make sure its here as its needed for the system.indexes insert
+ */
+ BSONObj fixSpec(const NamespaceString& ns, const BSONObj& original) const {
+ if (original["ns"].type() == String)
+ return original;
+ BSONObjBuilder bb;
+ bb.appendElements(original);
+ bb.append("ns", ns.toString());
+ return bb.obj();
+ }
+
+ /**
+ * @return equivalent of gle
+ */
+ BSONObj createIndexLegacy(const string& server,
+ const NamespaceString& nss,
+ const BSONObj& spec) const {
+ try {
+ ScopedDbConnection conn(server);
+ conn->insert(nss.getSystemIndexesCollection(), spec);
+ BSONObj gle = conn->getLastErrorDetailed(nss.db().toString());
+ conn.done();
+ return gle;
+ } catch (DBException& e) {
+ BSONObjBuilder b;
+ b.append("errmsg", e.toString());
+ b.append("code", e.getCode());
+ return b.obj();
+ }
+ }
+
+ virtual BSONObj specialErrorHandler(const string& server,
+ const string& dbName,
+ const BSONObj& cmdObj,
+ const BSONObj& originalResult) const {
+ string errmsg = originalResult["errmsg"];
+ if (errmsg.find("no such cmd") == string::npos) {
+ // cannot use codes as 2.4 didn't have a code for this
+ return originalResult;
+ }
+
+ // we need to down convert
+
+ NamespaceString nss(dbName, cmdObj["createIndexes"].String());
+
+ if (cmdObj["indexes"].type() != Array)
+ return originalResult;
+
+ BSONObjBuilder newResult;
+ newResult.append("note", "downgraded");
+ newResult.append("sentTo", server);
+
+ BSONArrayBuilder individualResults;
+
+ bool ok = true;
+
+ BSONObjIterator indexIterator(cmdObj["indexes"].Obj());
+ while (indexIterator.more()) {
+ BSONObj spec = indexIterator.next().Obj();
+ spec = fixSpec(nss, spec);
+
+ BSONObj gle = createIndexLegacy(server, nss, spec);
+
+ individualResults.append(BSON("spec" << spec << "gle" << gle));
+
+ BSONElement e = gle["errmsg"];
+ if (e.type() == String && e.String().size() > 0) {
+ ok = false;
+ newResult.appendAs(e, "errmsg");
+ break;
}
- };
-
- class AllShardsCollectionCommand : public RunOnAllShardsCommand {
- public:
- AllShardsCollectionCommand(const char* n,
- const char* oldname = NULL,
- bool useShardConn = false,
- bool implicitCreateDb = false)
- : RunOnAllShardsCommand(n, oldname, useShardConn, implicitCreateDb) {
+ e = gle["err"];
+ if (e.type() == String && e.String().size() > 0) {
+ ok = false;
+ newResult.appendAs(e, "errmsg");
+ break;
}
-
- virtual void getShardIds(const string& dbName,
- BSONObj& cmdObj,
- vector<ShardId>& shardIds) {
- const string fullns = dbName + '.' + cmdObj.firstElement().valuestrsafe();
-
- auto status = grid.catalogCache()->getDatabase(dbName);
- uassertStatusOK(status.getStatus());
-
- shared_ptr<DBConfig> conf = status.getValue();
-
- if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
- shardIds.push_back(conf->getShardId(fullns));
+ }
+
+ newResult.append("eachIndex", individualResults.arr());
+
+ newResult.append("ok", ok ? 1 : 0);
+ return newResult.obj();
+ }
+
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::createIndex);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
+
+} createIndexesCmd;
+
+class ReIndexCmd : public AllShardsCollectionCommand {
+public:
+ ReIndexCmd() : AllShardsCollectionCommand("reIndex") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::reIndex);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
+} reIndexCmd;
+
+class CollectionModCmd : public AllShardsCollectionCommand {
+public:
+ CollectionModCmd() : AllShardsCollectionCommand("collMod") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::collMod);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
+} collectionModCmd;
+
+
+class ValidateCmd : public AllShardsCollectionCommand {
+public:
+ ValidateCmd() : AllShardsCollectionCommand("validate") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::validate);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
+ virtual void aggregateResults(const vector<ShardAndReply>& results, BSONObjBuilder& output) {
+ for (vector<ShardAndReply>::const_iterator it(results.begin()), end(results.end());
+ it != end;
+ it++) {
+ const BSONObj& result = std::get<1>(*it);
+ const BSONElement valid = result["valid"];
+ if (!valid.eoo()) {
+ if (!valid.trueValue()) {
+ output.appendBool("valid", false);
+ return;
}
- else {
- grid.shardRegistry()->getAllShardIds(&shardIds);
+ } else {
+ // Support pre-1.9.0 output with everything in a big string
+ const char* s = result["result"].valuestrsafe();
+ if (strstr(s, "exception") || strstr(s, "corrupt")) {
+ output.appendBool("valid", false);
+ return;
}
}
- };
-
-
- class NotAllowedOnShardedCollectionCmd : public PublicGridCommand {
- public:
- NotAllowedOnShardedCollectionCmd( const char * n ) : PublicGridCommand( n ) {}
-
- virtual bool run(OperationContext* txn,
- const string& dbName,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
-
- const string fullns = parseNs(dbName, cmdObj);
-
- auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
- if (!conf->isSharded(fullns)) {
- return passthrough( conf , cmdObj , options, result );
- }
-
- return appendCommandStatus(result,
- Status(ErrorCodes::IllegalOperation,
- str::stream() << "can't do command: " << name
- << " on sharded collection"));
- }
-
- };
-
- // ----
-
- class DropIndexesCmd : public AllShardsCollectionCommand {
- public:
- DropIndexesCmd() : AllShardsCollectionCommand("dropIndexes", "deleteIndexes") {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::dropIndex);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
- }
- } dropIndexesCmd;
-
- class CreateIndexesCmd : public AllShardsCollectionCommand {
- public:
- CreateIndexesCmd():
- AllShardsCollectionCommand("createIndexes",
- NULL, /* oldName */
- true /* use ShardConnection */,
- true /* implicit create db */) {
- // createIndexes command should use ShardConnection so the getLastError would
- // be able to properly enforce the write concern (via the saveGLEStats callback).
+ }
+
+ output.appendBool("valid", true);
+ }
+} validateCmd;
+
+class CreateCmd : public PublicGridCommand {
+public:
+ CreateCmd() : PublicGridCommand("create") {}
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ AuthorizationSession* authzSession = AuthorizationSession::get(client);
+ if (cmdObj["capped"].trueValue()) {
+ if (!authzSession->isAuthorizedForActionsOnResource(
+ parseResourcePattern(dbname, cmdObj), ActionType::convertToCapped)) {
+ return Status(ErrorCodes::Unauthorized, "unauthorized");
}
-
- /**
- * the createIndexes command doesn't require the 'ns' field to be populated
- * so we make sure its here as its needed for the system.indexes insert
- */
- BSONObj fixSpec( const NamespaceString& ns, const BSONObj& original ) const {
- if ( original["ns"].type() == String )
- return original;
- BSONObjBuilder bb;
- bb.appendElements( original );
- bb.append( "ns", ns.toString() );
- return bb.obj();
+ }
+
+ // ActionType::createCollection or ActionType::insert are both acceptable
+ if (authzSession->isAuthorizedForActionsOnResource(parseResourcePattern(dbname, cmdObj),
+ ActionType::createCollection) ||
+ authzSession->isAuthorizedForActionsOnResource(parseResourcePattern(dbname, cmdObj),
+ ActionType::insert)) {
+ return Status::OK();
+ }
+
+ return Status(ErrorCodes::Unauthorized, "unauthorized");
+ }
+ bool run(OperationContext* txn,
+ const string& dbName,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ auto status = grid.implicitCreateDb(dbName);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status.getStatus());
+ }
+
+ shared_ptr<DBConfig> conf = status.getValue();
+
+ return passthrough(conf, cmdObj, result);
+ }
+
+} createCmd;
+
+class DropCmd : public PublicGridCommand {
+public:
+ DropCmd() : PublicGridCommand("drop") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::dropCollection);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbName,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ auto status = grid.catalogCache()->getDatabase(dbName);
+ if (!status.isOK()) {
+ if (status == ErrorCodes::DatabaseNotFound) {
+ return true;
}
- /**
- * @return equivalent of gle
- */
- BSONObj createIndexLegacy( const string& server,
- const NamespaceString& nss,
- const BSONObj& spec ) const {
- try {
- ScopedDbConnection conn( server );
- conn->insert( nss.getSystemIndexesCollection(), spec );
- BSONObj gle = conn->getLastErrorDetailed( nss.db().toString() );
- conn.done();
- return gle;
- }
- catch ( DBException& e ) {
- BSONObjBuilder b;
- b.append( "errmsg", e.toString() );
- b.append( "code", e.getCode() );
- return b.obj();
+ return appendCommandStatus(result, status.getStatus());
+ }
+
+ shared_ptr<DBConfig> conf = status.getValue();
+
+ const string fullns = dbName + "." + cmdObj.firstElement().valuestrsafe();
+ log() << "DROP: " << fullns;
+
+ if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
+ log() << "\tdrop going to do passthrough";
+ return passthrough(conf, cmdObj, result);
+ }
+
+ //
+ // TODO: There will be problems if we simultaneously shard and drop a collection
+ //
+
+ ChunkManagerPtr cm;
+ ShardPtr primary;
+ conf->getChunkManagerOrPrimary(fullns, cm, primary);
+
+ if (!cm) {
+ log() << "\tdrop going to do passthrough after re-check";
+ return passthrough(conf, cmdObj, result);
+ }
+
+ uassertStatusOK(grid.catalogManager()->dropCollection(fullns));
+
+ if (!conf->removeSharding(fullns)) {
+ warning() << "collection " << fullns
+ << " was reloaded as unsharded before drop completed"
+ << " during single drop";
+ }
+
+ return 1;
+ }
+} dropCmd;
+
+class RenameCollectionCmd : public PublicGridCommand {
+public:
+ RenameCollectionCmd() : PublicGridCommand("renameCollection") {}
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return rename_collection::checkAuthForRenameCollectionCommand(client, dbname, cmdObj);
+ }
+ virtual bool adminOnly() const {
+ return true;
+ }
+ bool run(OperationContext* txn,
+ const string& dbName,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const string fullnsFrom = cmdObj.firstElement().valuestrsafe();
+ const string dbNameFrom = nsToDatabase(fullnsFrom);
+ auto confFrom = uassertStatusOK(grid.catalogCache()->getDatabase(dbNameFrom));
+
+ const string fullnsTo = cmdObj["to"].valuestrsafe();
+ const string dbNameTo = nsToDatabase(fullnsTo);
+ auto confTo = uassertStatusOK(grid.catalogCache()->getDatabase(dbNameTo));
+
+ uassert(13138, "You can't rename a sharded collection", !confFrom->isSharded(fullnsFrom));
+ uassert(13139, "You can't rename to a sharded collection", !confTo->isSharded(fullnsTo));
+
+ const ShardId& shardTo = confTo->getShardId(fullnsTo);
+ const ShardId& shardFrom = confFrom->getShardId(fullnsFrom);
+
+ uassert(13137,
+ "Source and destination collections must be on same shard",
+ shardFrom == shardTo);
+
+ return adminPassthrough(confFrom, cmdObj, result);
+ }
+} renameCollectionCmd;
+
+class CopyDBCmd : public PublicGridCommand {
+public:
+ CopyDBCmd() : PublicGridCommand("copydb") {}
+ virtual bool adminOnly() const {
+ return true;
+ }
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return copydb::checkAuthForCopydbCommand(client, dbname, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbName,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const string todb = cmdObj.getStringField("todb");
+ uassert(ErrorCodes::EmptyFieldName, "missing todb argument", !todb.empty());
+ uassert(ErrorCodes::InvalidNamespace, "invalid todb argument", nsIsDbOnly(todb));
+
+ auto confTo = uassertStatusOK(grid.implicitCreateDb(todb));
+ uassert(ErrorCodes::IllegalOperation,
+ "cannot copy to a sharded database",
+ !confTo->isShardingEnabled());
+
+ const string fromhost = cmdObj.getStringField("fromhost");
+ if (!fromhost.empty()) {
+ return adminPassthrough(confTo, cmdObj, result);
+ } else {
+ const string fromdb = cmdObj.getStringField("fromdb");
+ uassert(13399, "need a fromdb argument", !fromdb.empty());
+
+ shared_ptr<DBConfig> confFrom =
+ uassertStatusOK(grid.catalogCache()->getDatabase(fromdb));
+
+ uassert(13400, "don't know where source DB is", confFrom);
+ uassert(13401, "cant copy from sharded DB", !confFrom->isShardingEnabled());
+
+ BSONObjBuilder b;
+ BSONForEach(e, cmdObj) {
+ if (strcmp(e.fieldName(), "fromhost") != 0) {
+ b.append(e);
}
}
- virtual BSONObj specialErrorHandler( const string& server,
- const string& dbName,
- const BSONObj& cmdObj,
- const BSONObj& originalResult ) const {
- string errmsg = originalResult["errmsg"];
- if ( errmsg.find( "no such cmd" ) == string::npos ) {
- // cannot use codes as 2.4 didn't have a code for this
- return originalResult;
- }
-
- // we need to down convert
-
- NamespaceString nss( dbName, cmdObj["createIndexes"].String() );
-
- if ( cmdObj["indexes"].type() != Array )
- return originalResult;
-
- BSONObjBuilder newResult;
- newResult.append( "note", "downgraded" );
- newResult.append( "sentTo", server );
-
- BSONArrayBuilder individualResults;
-
- bool ok = true;
-
- BSONObjIterator indexIterator( cmdObj["indexes"].Obj() );
- while ( indexIterator.more() ) {
- BSONObj spec = indexIterator.next().Obj();
- spec = fixSpec( nss, spec );
-
- BSONObj gle = createIndexLegacy( server, nss, spec );
-
- individualResults.append( BSON( "spec" << spec <<
- "gle" << gle ) );
-
- BSONElement e = gle["errmsg"];
- if ( e.type() == String && e.String().size() > 0 ) {
- ok = false;
- newResult.appendAs( e, "errmsg" );
- break;
- }
+ {
+ const auto& shard = grid.shardRegistry()->getShard(confFrom->getPrimaryId());
+ b.append("fromhost", shard->getConnString().toString());
+ }
+ BSONObj fixed = b.obj();
+
+ return adminPassthrough(confTo, fixed, result);
+ }
+ }
+
+} clusterCopyDBCmd;
+
+class CollectionStats : public PublicGridCommand {
+public:
+ CollectionStats() : PublicGridCommand("collStats", "collstats") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::collStats);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbName,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const string fullns = parseNs(dbName, cmdObj);
+
+ auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
+ if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
+ result.appendBool("sharded", false);
+ result.append("primary", conf->getPrimaryId());
+
+ return passthrough(conf, cmdObj, result);
+ }
+
+ result.appendBool("sharded", true);
+
+ ChunkManagerPtr cm = conf->getChunkManager(fullns);
+ massert(12594, "how could chunk manager be null!", cm);
+
+ BSONObjBuilder shardStats;
+ map<string, long long> counts;
+ map<string, long long> indexSizes;
+ /*
+ long long count=0;
+ long long size=0;
+ long long storageSize=0;
+ */
+ int nindexes = 0;
+ bool warnedAboutIndexes = false;
+
+ set<ShardId> shardIds;
+ cm->getAllShardIds(&shardIds);
+
+ for (const ShardId& shardId : shardIds) {
+ const auto shard = grid.shardRegistry()->getShard(shardId);
+ if (!shard) {
+ continue;
+ }
- e = gle["err"];
- if ( e.type() == String && e.String().size() > 0 ) {
- ok = false;
- newResult.appendAs( e, "errmsg" );
- break;
+ BSONObj res;
+ {
+ ScopedDbConnection conn(shard->getConnString());
+ if (!conn->runCommand(dbName, cmdObj, res)) {
+ if (!res["code"].eoo()) {
+ result.append(res["code"]);
}
-
+ errmsg = "failed on shard: " + res.toString();
+ return false;
}
-
- newResult.append( "eachIndex", individualResults.arr() );
-
- newResult.append( "ok", ok ? 1 : 0 );
- return newResult.obj();
- }
-
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::createIndex);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ conn.done();
}
- } createIndexesCmd;
-
- class ReIndexCmd : public AllShardsCollectionCommand {
- public:
- ReIndexCmd() : AllShardsCollectionCommand("reIndex") {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::reIndex);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
- }
- } reIndexCmd;
-
- class CollectionModCmd : public AllShardsCollectionCommand {
- public:
- CollectionModCmd() : AllShardsCollectionCommand("collMod") {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::collMod);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
- }
- } collectionModCmd;
-
-
- class ValidateCmd : public AllShardsCollectionCommand {
- public:
- ValidateCmd() : AllShardsCollectionCommand("validate") {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::validate);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
- }
- virtual void aggregateResults(const vector<ShardAndReply>& results,
- BSONObjBuilder& output) {
-
- for (vector<ShardAndReply>::const_iterator it(results.begin()), end(results.end());
- it!=end; it++) {
- const BSONObj& result = std::get<1>(*it);
- const BSONElement valid = result["valid"];
- if (!valid.eoo()){
- if (!valid.trueValue()) {
- output.appendBool("valid", false);
- return;
- }
+ BSONObjIterator j(res);
+ while (j.more()) {
+ BSONElement e = j.next();
+
+ if (str::equals(e.fieldName(), "ns") || str::equals(e.fieldName(), "ok") ||
+ str::equals(e.fieldName(), "avgObjSize") ||
+ str::equals(e.fieldName(), "lastExtentSize") ||
+ str::equals(e.fieldName(), "paddingFactor")) {
+ continue;
+ } else if (str::equals(e.fieldName(), "count") ||
+ str::equals(e.fieldName(), "size") ||
+ str::equals(e.fieldName(), "storageSize") ||
+ str::equals(e.fieldName(), "numExtents") ||
+ str::equals(e.fieldName(), "totalIndexSize")) {
+ counts[e.fieldName()] += e.numberLong();
+ } else if (str::equals(e.fieldName(), "indexSizes")) {
+ BSONObjIterator k(e.Obj());
+ while (k.more()) {
+ BSONElement temp = k.next();
+ indexSizes[temp.fieldName()] += temp.numberLong();
}
- else {
- // Support pre-1.9.0 output with everything in a big string
- const char* s = result["result"].valuestrsafe();
- if (strstr(s, "exception") || strstr(s, "corrupt")){
- output.appendBool("valid", false);
- return;
+ }
+ // no longer used since 2.2
+ else if (str::equals(e.fieldName(), "flags")) {
+ if (!result.hasField(e.fieldName()))
+ result.append(e);
+ }
+ // flags broken out in 2.4+
+ else if (str::equals(e.fieldName(), "systemFlags")) {
+ if (!result.hasField(e.fieldName()))
+ result.append(e);
+ } else if (str::equals(e.fieldName(), "userFlags")) {
+ if (!result.hasField(e.fieldName()))
+ result.append(e);
+ } else if (str::equals(e.fieldName(), "capped")) {
+ if (!result.hasField(e.fieldName()))
+ result.append(e);
+ } else if (str::equals(e.fieldName(), "paddingFactorNote")) {
+ if (!result.hasField(e.fieldName()))
+ result.append(e);
+ } else if (str::equals(e.fieldName(), "indexDetails")) {
+ // skip this field in the rollup
+ } else if (str::equals(e.fieldName(), "wiredTiger")) {
+ // skip this field in the rollup
+ } else if (str::equals(e.fieldName(), "nindexes")) {
+ int myIndexes = e.numberInt();
+
+ if (nindexes == 0) {
+ nindexes = myIndexes;
+ } else if (nindexes == myIndexes) {
+ // no-op
+ } else {
+ // hopefully this means we're building an index
+
+ if (myIndexes > nindexes)
+ nindexes = myIndexes;
+
+ if (!warnedAboutIndexes) {
+ result.append("warning",
+ "indexes don't all match - ok if ensureIndex is running");
+ warnedAboutIndexes = true;
}
}
+ } else {
+ warning() << "mongos collstats doesn't know about: " << e.fieldName();
}
-
- output.appendBool("valid", true);
}
- } validateCmd;
-
- class CreateCmd : public PublicGridCommand {
- public:
- CreateCmd() : PublicGridCommand( "create" ) {}
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- AuthorizationSession* authzSession = AuthorizationSession::get(client);
- if (cmdObj["capped"].trueValue()) {
- if (!authzSession->isAuthorizedForActionsOnResource(
- parseResourcePattern(dbname, cmdObj), ActionType::convertToCapped)) {
- return Status(ErrorCodes::Unauthorized, "unauthorized");
- }
- }
-
- // ActionType::createCollection or ActionType::insert are both acceptable
- if (authzSession->isAuthorizedForActionsOnResource(
- parseResourcePattern(dbname, cmdObj), ActionType::createCollection) ||
- authzSession->isAuthorizedForActionsOnResource(
- parseResourcePattern(dbname, cmdObj), ActionType::insert)) {
- return Status::OK();
- }
-
- return Status(ErrorCodes::Unauthorized, "unauthorized");
+ shardStats.append(shardId, res);
+ }
+
+ result.append("ns", fullns);
+
+ for (map<string, long long>::iterator i = counts.begin(); i != counts.end(); ++i)
+ result.appendNumber(i->first, i->second);
+
+ {
+ BSONObjBuilder ib(result.subobjStart("indexSizes"));
+ for (map<string, long long>::iterator i = indexSizes.begin(); i != indexSizes.end();
+ ++i)
+ ib.appendNumber(i->first, i->second);
+ ib.done();
+ }
+
+ if (counts["count"] > 0)
+ result.append("avgObjSize", (double)counts["size"] / (double)counts["count"]);
+ else
+ result.append("avgObjSize", 0.0);
+
+ result.append("nindexes", nindexes);
+
+ result.append("nchunks", cm->numChunks());
+ result.append("shards", shardStats.obj());
+
+ return true;
+ }
+} collectionStatsCmd;
+
+class DataSizeCmd : public PublicGridCommand {
+public:
+ DataSizeCmd() : PublicGridCommand("dataSize", "datasize") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::find);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
+ bool run(OperationContext* txn,
+ const string& dbName,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const string fullns = parseNs(dbName, cmdObj);
+
+ auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
+ if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
+ return passthrough(conf, cmdObj, result);
+ }
+
+ ChunkManagerPtr cm = conf->getChunkManager(fullns);
+ massert(13407, "how could chunk manager be null!", cm);
+
+ BSONObj min = cmdObj.getObjectField("min");
+ BSONObj max = cmdObj.getObjectField("max");
+ BSONObj keyPattern = cmdObj.getObjectField("keyPattern");
+
+ uassert(13408,
+ "keyPattern must equal shard key",
+ cm->getShardKeyPattern().toBSON() == keyPattern);
+ uassert(13405,
+ str::stream() << "min value " << min << " does not have shard key",
+ cm->getShardKeyPattern().isShardKey(min));
+ uassert(13406,
+ str::stream() << "max value " << max << " does not have shard key",
+ cm->getShardKeyPattern().isShardKey(max));
+
+ min = cm->getShardKeyPattern().normalizeShardKey(min);
+ max = cm->getShardKeyPattern().normalizeShardKey(max);
+
+ // yes these are doubles...
+ double size = 0;
+ double numObjects = 0;
+ int millis = 0;
+
+ set<ShardId> shardIds;
+ cm->getShardIdsForRange(shardIds, min, max);
+ for (const ShardId& shardId : shardIds) {
+ const auto shard = grid.shardRegistry()->getShard(shardId);
+ if (!shard) {
+ continue;
}
- bool run(OperationContext* txn,
- const string& dbName,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- auto status = grid.implicitCreateDb(dbName);
- if (!status.isOK()) {
- return appendCommandStatus(result, status.getStatus());
- }
-
- shared_ptr<DBConfig> conf = status.getValue();
+ ScopedDbConnection conn(shard->getConnString());
+ BSONObj res;
+ bool ok = conn->runCommand(conf->name(), cmdObj, res);
+ conn.done();
- return passthrough(conf, cmdObj, result);
- }
-
- } createCmd;
-
- class DropCmd : public PublicGridCommand {
- public:
- DropCmd() : PublicGridCommand( "drop" ) {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::dropCollection);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ if (!ok) {
+ result.appendElements(res);
+ return false;
}
- bool run(OperationContext* txn,
+ size += res["size"].number();
+ numObjects += res["numObjects"].number();
+ millis += res["millis"].numberInt();
+ }
+
+ result.append("size", size);
+ result.append("numObjects", numObjects);
+ result.append("millis", millis);
+ return true;
+ }
+
+} DataSizeCmd;
+
+class ConvertToCappedCmd : public NotAllowedOnShardedCollectionCmd {
+public:
+ ConvertToCappedCmd() : NotAllowedOnShardedCollectionCmd("convertToCapped") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::convertToCapped);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
+
+} convertToCappedCmd;
+
+
+class GroupCmd : public NotAllowedOnShardedCollectionCmd {
+public:
+ GroupCmd() : NotAllowedOnShardedCollectionCmd("group") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::find);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
+
+ virtual bool passOptions() const {
+ return true;
+ }
+
+ virtual std::string parseNs(const std::string& dbName, const BSONObj& cmdObj) const {
+ return dbName + "." + cmdObj.firstElement().embeddedObjectUserCheck()["ns"].valuestrsafe();
+ }
+
+ Status explain(OperationContext* txn,
+ const std::string& dbname,
+ const BSONObj& cmdObj,
+ ExplainCommon::Verbosity verbosity,
+ BSONObjBuilder* out) const {
+ const string fullns = parseNs(dbname, cmdObj);
+
+ BSONObjBuilder explainCmdBob;
+ ClusterExplain::wrapAsExplain(cmdObj, verbosity, &explainCmdBob);
+
+ // We will time how long it takes to run the commands on the shards.
+ Timer timer;
+
+ Strategy::CommandResult singleResult;
+ Status commandStat =
+ Strategy::commandOpUnsharded(dbname, explainCmdBob.obj(), 0, fullns, &singleResult);
+ if (!commandStat.isOK()) {
+ return commandStat;
+ }
+
+ long long millisElapsed = timer.millis();
+
+ vector<Strategy::CommandResult> shardResults;
+ shardResults.push_back(singleResult);
+
+ return ClusterExplain::buildExplainResult(
+ shardResults, ClusterExplain::kSingleShard, millisElapsed, out);
+ }
+
+} groupCmd;
+
+class SplitVectorCmd : public NotAllowedOnShardedCollectionCmd {
+public:
+ SplitVectorCmd() : NotAllowedOnShardedCollectionCmd("splitVector") {}
+ virtual bool passOptions() const {
+ return true;
+ }
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname, cmdObj))),
+ ActionType::splitVector)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+ return Status::OK();
+ }
+ virtual bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
-
- auto status = grid.catalogCache()->getDatabase(dbName);
- if (!status.isOK()) {
- if (status == ErrorCodes::DatabaseNotFound) {
- return true;
- }
-
- return appendCommandStatus(result, status.getStatus());
- }
-
- shared_ptr<DBConfig> conf = status.getValue();
-
- const string fullns = dbName + "." + cmdObj.firstElement().valuestrsafe();
- log() << "DROP: " << fullns;
-
- if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
- log() << "\tdrop going to do passthrough";
- return passthrough( conf , cmdObj , result );
- }
-
- //
- // TODO: There will be problems if we simultaneously shard and drop a collection
- //
-
- ChunkManagerPtr cm;
- ShardPtr primary;
- conf->getChunkManagerOrPrimary( fullns, cm, primary );
-
- if( ! cm ) {
- log() << "\tdrop going to do passthrough after re-check";
- return passthrough( conf , cmdObj , result );
- }
-
- uassertStatusOK(grid.catalogManager()->dropCollection(fullns));
-
- if( ! conf->removeSharding( fullns ) ){
- warning() << "collection " << fullns
- << " was reloaded as unsharded before drop completed"
- << " during single drop";
- }
-
- return 1;
- }
- } dropCmd;
-
- class RenameCollectionCmd : public PublicGridCommand {
- public:
- RenameCollectionCmd() : PublicGridCommand( "renameCollection" ) {}
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return rename_collection::checkAuthForRenameCollectionCommand(client,
- dbname,
- cmdObj);
+ string x = parseNs(dbName, cmdObj);
+ if (!str::startsWith(x, dbName)) {
+ errmsg = str::stream() << "doing a splitVector across dbs isn't supported via mongos";
+ return false;
+ }
+ return NotAllowedOnShardedCollectionCmd::run(txn, dbName, cmdObj, options, errmsg, result);
+ }
+ virtual std::string parseNs(const string& dbname, const BSONObj& cmdObj) const {
+ return parseNsFullyQualified(dbname, cmdObj);
+ }
+
+} splitVectorCmd;
+
+
+class DistinctCmd : public PublicGridCommand {
+public:
+ DistinctCmd() : PublicGridCommand("distinct") {}
+ virtual void help(stringstream& help) const {
+ help << "{ distinct : 'collection name' , key : 'a.b' , query : {} }";
+ }
+ virtual bool passOptions() const {
+ return true;
+ }
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::find);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbName,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const string fullns = parseNs(dbName, cmdObj);
+
+ auto status = grid.catalogCache()->getDatabase(dbName);
+ if (!status.isOK()) {
+ return appendEmptyResultSet(result, status.getStatus(), fullns);
+ }
+
+ shared_ptr<DBConfig> conf = status.getValue();
+ if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
+ return passthrough(conf, cmdObj, options, result);
+ }
+
+ ChunkManagerPtr cm = conf->getChunkManager(fullns);
+ massert(10420, "how could chunk manager be null!", cm);
+
+ BSONObj query = getQuery(cmdObj);
+ set<ShardId> shardIds;
+ cm->getShardIdsForQuery(shardIds, query);
+
+ set<BSONObj, BSONObjCmp> all;
+ int size = 32;
+
+ for (const ShardId& shardId : shardIds) {
+ const auto shard = grid.shardRegistry()->getShard(shardId);
+ if (!shard) {
+ continue;
}
- virtual bool adminOnly() const {
- return true;
- }
- bool run(OperationContext* txn,
- const string& dbName,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- const string fullnsFrom = cmdObj.firstElement().valuestrsafe();
- const string dbNameFrom = nsToDatabase(fullnsFrom);
- auto confFrom = uassertStatusOK(grid.catalogCache()->getDatabase(dbNameFrom));
-
- const string fullnsTo = cmdObj["to"].valuestrsafe();
- const string dbNameTo = nsToDatabase(fullnsTo);
- auto confTo = uassertStatusOK(grid.catalogCache()->getDatabase(dbNameTo));
-
- uassert(13138, "You can't rename a sharded collection", !confFrom->isSharded(fullnsFrom));
- uassert(13139, "You can't rename to a sharded collection", !confTo->isSharded(fullnsTo));
-
- const ShardId& shardTo = confTo->getShardId(fullnsTo);
- const ShardId& shardFrom = confFrom->getShardId(fullnsFrom);
- uassert(13137,
- "Source and destination collections must be on same shard",
- shardFrom == shardTo);
+ ShardConnection conn(shard->getConnString(), fullns);
+ BSONObj res;
+ bool ok = conn->runCommand(conf->name(), cmdObj, res, options);
+ conn.done();
- return adminPassthrough( confFrom , cmdObj , result );
+ if (!ok) {
+ result.appendElements(res);
+ return false;
}
- } renameCollectionCmd;
- class CopyDBCmd : public PublicGridCommand {
- public:
- CopyDBCmd() : PublicGridCommand( "copydb" ) {}
- virtual bool adminOnly() const {
- return true;
+ BSONObjIterator it(res["values"].embeddedObject());
+ while (it.more()) {
+ BSONElement nxt = it.next();
+ BSONObjBuilder temp(32);
+ temp.appendAs(nxt, "");
+ all.insert(temp.obj());
}
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return copydb::checkAuthForCopydbCommand(client, dbname, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbName,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
-
- const string todb = cmdObj.getStringField("todb");
- uassert(ErrorCodes::EmptyFieldName, "missing todb argument", !todb.empty());
- uassert(ErrorCodes::InvalidNamespace, "invalid todb argument", nsIsDbOnly(todb));
-
- auto confTo = uassertStatusOK(grid.implicitCreateDb(todb));
- uassert(ErrorCodes::IllegalOperation,
- "cannot copy to a sharded database",
- !confTo->isShardingEnabled());
-
- const string fromhost = cmdObj.getStringField("fromhost");
- if (!fromhost.empty()) {
- return adminPassthrough( confTo , cmdObj , result );
- }
- else {
- const string fromdb = cmdObj.getStringField("fromdb");
- uassert(13399, "need a fromdb argument", !fromdb.empty());
-
- shared_ptr<DBConfig> confFrom =
- uassertStatusOK(grid.catalogCache()->getDatabase(fromdb));
-
- uassert(13400, "don't know where source DB is", confFrom);
- uassert(13401, "cant copy from sharded DB", !confFrom->isShardingEnabled());
-
- BSONObjBuilder b;
- BSONForEach(e, cmdObj) {
- if (strcmp(e.fieldName(), "fromhost") != 0) {
- b.append(e);
- }
- }
-
- {
- const auto& shard =
- grid.shardRegistry()->getShard(confFrom->getPrimaryId());
- b.append("fromhost", shard->getConnString().toString());
- }
- BSONObj fixed = b.obj();
-
- return adminPassthrough( confTo , fixed , result );
+ }
+
+ BSONObjBuilder b(size);
+ int n = 0;
+ for (set<BSONObj, BSONObjCmp>::iterator i = all.begin(); i != all.end(); i++) {
+ b.appendAs(i->firstElement(), b.numStr(n++));
+ }
+
+ result.appendArray("values", b.obj());
+ return true;
+ }
+} disinctCmd;
+
+class FileMD5Cmd : public PublicGridCommand {
+public:
+ FileMD5Cmd() : PublicGridCommand("filemd5") {}
+ virtual void help(stringstream& help) const {
+ help << " example: { filemd5 : ObjectId(aaaaaaa) , root : \"fs\" }";
+ }
+
+ virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
+ std::string collectionName = cmdObj.getStringField("root");
+ if (collectionName.empty())
+ collectionName = "fs";
+ collectionName += ".chunks";
+ return NamespaceString(dbname, collectionName).ns();
+ }
+
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), ActionType::find));
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbName,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const string fullns = parseNs(dbName, cmdObj);
+
+ auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
+ if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
+ return passthrough(conf, cmdObj, result);
+ }
+
+ ChunkManagerPtr cm = conf->getChunkManager(fullns);
+ massert(13091, "how could chunk manager be null!", cm);
+ if (cm->getShardKeyPattern().toBSON() == BSON("files_id" << 1)) {
+ BSONObj finder = BSON("files_id" << cmdObj.firstElement());
+
+ vector<Strategy::CommandResult> results;
+ Strategy::commandOp(dbName, cmdObj, 0, fullns, finder, &results);
+ verify(results.size() == 1); // querying on shard key so should only talk to one shard
+ BSONObj res = results.begin()->result;
+
+ result.appendElements(res);
+ return res["ok"].trueValue();
+ } else if (cm->getShardKeyPattern().toBSON() == BSON("files_id" << 1 << "n" << 1)) {
+ int n = 0;
+ BSONObj lastResult;
+
+ while (true) {
+ // Theory of operation: Starting with n=0, send filemd5 command to shard
+ // with that chunk (gridfs chunk not sharding chunk). That shard will then
+ // compute a partial md5 state (passed in the "md5state" field) for all
+ // contiguous chunks that it has. When it runs out or hits a discontinuity
+ // (eg [1,2,7]) it returns what it has done so far. This is repeated as
+ // long as we keep getting more chunks. The end condition is when we go to
+ // look for chunk n and it doesn't exist. This means that the file's last
+ // chunk is n-1, so we return the computed md5 results.
+ BSONObjBuilder bb;
+ bb.appendElements(cmdObj);
+ bb.appendBool("partialOk", true);
+ bb.append("startAt", n);
+ if (!lastResult.isEmpty()) {
+ bb.append(lastResult["md5state"]);
}
+ BSONObj shardCmd = bb.obj();
- }
-
- } clusterCopyDBCmd;
-
- class CollectionStats : public PublicGridCommand {
- public:
- CollectionStats() : PublicGridCommand("collStats", "collstats") { }
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::collStats);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
- }
+ BSONObj finder = BSON("files_id" << cmdObj.firstElement() << "n" << n);
- bool run(OperationContext* txn,
- const string& dbName,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- const string fullns = parseNs(dbName, cmdObj);
-
- auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
- if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
- result.appendBool("sharded", false);
- result.append("primary", conf->getPrimaryId());
-
- return passthrough( conf , cmdObj , result);
+ vector<Strategy::CommandResult> results;
+ try {
+ Strategy::commandOp(dbName, shardCmd, 0, fullns, finder, &results);
+ } catch (DBException& e) {
+ // This is handled below and logged
+ Strategy::CommandResult errResult;
+ errResult.shardTargetId = "";
+ errResult.result = BSON("errmsg" << e.what() << "ok" << 0);
+ results.push_back(errResult);
}
- result.appendBool("sharded", true);
-
- ChunkManagerPtr cm = conf->getChunkManager( fullns );
- massert( 12594 , "how could chunk manager be null!" , cm );
-
- BSONObjBuilder shardStats;
- map<string,long long> counts;
- map<string,long long> indexSizes;
- /*
- long long count=0;
- long long size=0;
- long long storageSize=0;
- */
- int nindexes=0;
- bool warnedAboutIndexes = false;
-
- set<ShardId> shardIds;
- cm->getAllShardIds(&shardIds);
-
- for (const ShardId& shardId : shardIds) {
- const auto shard = grid.shardRegistry()->getShard(shardId);
- if (!shard) {
- continue;
- }
-
- BSONObj res;
- {
- ScopedDbConnection conn(shard->getConnString());
- if ( ! conn->runCommand( dbName , cmdObj , res ) ) {
- if ( !res["code"].eoo() ) {
- result.append( res["code"] );
- }
- errmsg = "failed on shard: " + res.toString();
- return false;
- }
- conn.done();
- }
-
- BSONObjIterator j( res );
- while ( j.more() ) {
- BSONElement e = j.next();
-
- if ( str::equals( e.fieldName() , "ns" ) ||
- str::equals( e.fieldName() , "ok" ) ||
- str::equals( e.fieldName() , "avgObjSize" ) ||
- str::equals( e.fieldName() , "lastExtentSize" ) ||
- str::equals( e.fieldName() , "paddingFactor" ) ) {
- continue;
- }
- else if ( str::equals( e.fieldName() , "count" ) ||
- str::equals( e.fieldName() , "size" ) ||
- str::equals( e.fieldName() , "storageSize" ) ||
- str::equals( e.fieldName() , "numExtents" ) ||
- str::equals( e.fieldName() , "totalIndexSize" ) ) {
- counts[e.fieldName()] += e.numberLong();
- }
- else if ( str::equals( e.fieldName() , "indexSizes" ) ) {
- BSONObjIterator k( e.Obj() );
- while ( k.more() ) {
- BSONElement temp = k.next();
- indexSizes[temp.fieldName()] += temp.numberLong();
- }
- }
- // no longer used since 2.2
- else if ( str::equals( e.fieldName() , "flags" ) ) {
- if ( ! result.hasField( e.fieldName() ) )
- result.append( e );
- }
- // flags broken out in 2.4+
- else if ( str::equals( e.fieldName() , "systemFlags" ) ) {
- if ( ! result.hasField( e.fieldName() ) )
- result.append( e );
- }
- else if ( str::equals( e.fieldName() , "userFlags" ) ) {
- if ( ! result.hasField( e.fieldName() ) )
- result.append( e );
- }
- else if ( str::equals( e.fieldName() , "capped" ) ) {
- if ( ! result.hasField( e.fieldName() ) )
- result.append( e );
- }
- else if ( str::equals( e.fieldName() , "paddingFactorNote" ) ) {
- if ( ! result.hasField( e.fieldName() ) )
- result.append( e );
- }
- else if ( str::equals( e.fieldName() , "indexDetails" ) ) {
- //skip this field in the rollup
- }
- else if ( str::equals( e.fieldName() , "wiredTiger" ) ) {
- //skip this field in the rollup
- }
- else if ( str::equals( e.fieldName() , "nindexes" ) ) {
- int myIndexes = e.numberInt();
-
- if ( nindexes == 0 ) {
- nindexes = myIndexes;
- }
- else if ( nindexes == myIndexes ) {
- // no-op
- }
- else {
- // hopefully this means we're building an index
-
- if ( myIndexes > nindexes )
- nindexes = myIndexes;
-
- if ( ! warnedAboutIndexes ) {
- result.append( "warning" , "indexes don't all match - ok if ensureIndex is running" );
- warnedAboutIndexes = true;
- }
- }
- }
- else {
- warning() << "mongos collstats doesn't know about: " << e.fieldName();
- }
-
+ verify(results.size() ==
+ 1); // querying on shard key so should only talk to one shard
+ BSONObj res = results.begin()->result;
+ bool ok = res["ok"].trueValue();
+
+ if (!ok) {
+ // Add extra info to make debugging easier
+ result.append("failedAt", n);
+ result.append("sentCommand", shardCmd);
+ BSONForEach(e, res) {
+ if (!str::equals(e.fieldName(), "errmsg"))
+ result.append(e);
}
- shardStats.append(shardId, res);
- }
- result.append("ns", fullns);
-
- for ( map<string,long long>::iterator i=counts.begin(); i!=counts.end(); ++i )
- result.appendNumber( i->first , i->second );
-
- {
- BSONObjBuilder ib( result.subobjStart( "indexSizes" ) );
- for ( map<string,long long>::iterator i=indexSizes.begin(); i!=indexSizes.end(); ++i )
- ib.appendNumber( i->first , i->second );
- ib.done();
- }
+ log() << "Sharded filemd5 failed: " << result.asTempObj();
- if ( counts["count"] > 0 )
- result.append("avgObjSize", (double)counts["size"] / (double)counts["count"] );
- else
- result.append( "avgObjSize", 0.0 );
-
- result.append("nindexes", nindexes);
-
- result.append("nchunks", cm->numChunks());
- result.append("shards", shardStats.obj());
-
- return true;
- }
- } collectionStatsCmd;
-
- class DataSizeCmd : public PublicGridCommand {
- public:
- DataSizeCmd() : PublicGridCommand("dataSize", "datasize") { }
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::find);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
- }
- bool run(OperationContext* txn,
- const string& dbName,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- const string fullns = parseNs(dbName, cmdObj);
-
- auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
- if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
- return passthrough( conf , cmdObj , result);
+ errmsg =
+ string("sharded filemd5 failed because: ") + res["errmsg"].valuestrsafe();
+ return false;
}
- ChunkManagerPtr cm = conf->getChunkManager( fullns );
- massert( 13407 , "how could chunk manager be null!" , cm );
-
- BSONObj min = cmdObj.getObjectField( "min" );
- BSONObj max = cmdObj.getObjectField( "max" );
- BSONObj keyPattern = cmdObj.getObjectField( "keyPattern" );
-
- uassert( 13408, "keyPattern must equal shard key",
- cm->getShardKeyPattern().toBSON() == keyPattern );
- uassert( 13405, str::stream() << "min value " << min << " does not have shard key",
- cm->getShardKeyPattern().isShardKey(min) );
- uassert( 13406, str::stream() << "max value " << max << " does not have shard key",
- cm->getShardKeyPattern().isShardKey(max) );
-
- min = cm->getShardKeyPattern().normalizeShardKey(min);
- max = cm->getShardKeyPattern().normalizeShardKey(max);
-
- // yes these are doubles...
- double size = 0;
- double numObjects = 0;
- int millis = 0;
-
- set<ShardId> shardIds;
- cm->getShardIdsForRange(shardIds, min, max);
- for (const ShardId& shardId : shardIds) {
- const auto shard = grid.shardRegistry()->getShard(shardId);
- if (!shard) {
- continue;
- }
+ uassert(16246,
+ "Shard " + conf->name() +
+ " is too old to support GridFS sharded by {files_id:1, n:1}",
+ res.hasField("md5state"));
- ScopedDbConnection conn(shard->getConnString());
- BSONObj res;
- bool ok = conn->runCommand( conf->name() , cmdObj , res );
- conn.done();
-
- if ( ! ok ) {
- result.appendElements( res );
- return false;
- }
+ lastResult = res;
+ int nNext = res["numChunks"].numberInt();
- size += res["size"].number();
- numObjects += res["numObjects"].number();
- millis += res["millis"].numberInt();
+ if (n == nNext) {
+ // no new data means we've reached the end of the file
+ result.appendElements(res);
+ return true;
}
- result.append( "size", size );
- result.append( "numObjects" , numObjects );
- result.append( "millis" , millis );
- return true;
- }
-
- } DataSizeCmd;
-
- class ConvertToCappedCmd : public NotAllowedOnShardedCollectionCmd {
- public:
- ConvertToCappedCmd() : NotAllowedOnShardedCollectionCmd("convertToCapped") {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::convertToCapped);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ verify(nNext > n);
+ n = nNext;
}
- } convertToCappedCmd;
-
-
- class GroupCmd : public NotAllowedOnShardedCollectionCmd {
- public:
- GroupCmd() : NotAllowedOnShardedCollectionCmd("group") {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::find);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ verify(0);
+ }
+
+ // We could support arbitrary shard keys by sending commands to all shards but I don't think we should
+ errmsg =
+ "GridFS fs.chunks collection must be sharded on either {files_id:1} or {files_id:1, "
+ "n:1}";
+ return false;
+ }
+} fileMD5Cmd;
+
+class Geo2dFindNearCmd : public PublicGridCommand {
+public:
+ Geo2dFindNearCmd() : PublicGridCommand("geoNear") {}
+ void help(stringstream& h) const {
+ h << "http://dochub.mongodb.org/core/geo#GeospatialIndexing-geoNearCommand";
+ }
+ virtual bool passOptions() const {
+ return true;
+ }
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::find);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbName,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const string fullns = parseNs(dbName, cmdObj);
+
+ auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
+ if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
+ return passthrough(conf, cmdObj, options, result);
+ }
+
+ ChunkManagerPtr cm = conf->getChunkManager(fullns);
+ massert(13500, "how could chunk manager be null!", cm);
+
+ BSONObj query = getQuery(cmdObj);
+ set<ShardId> shardIds;
+ cm->getShardIdsForQuery(shardIds, query);
+
+ // We support both "num" and "limit" options to control limit
+ int limit = 100;
+ const char* limitName = cmdObj["num"].isNumber() ? "num" : "limit";
+ if (cmdObj[limitName].isNumber())
+ limit = cmdObj[limitName].numberInt();
+
+ list<shared_ptr<Future::CommandResult>> futures;
+ BSONArrayBuilder shardArray;
+ for (const ShardId& shardId : shardIds) {
+ const auto shard = grid.shardRegistry()->getShard(shardId);
+ if (!shard) {
+ continue;
}
- virtual bool passOptions() const { return true; }
-
- virtual std::string parseNs(const std::string& dbName, const BSONObj& cmdObj) const {
- return dbName + "." + cmdObj.firstElement()
- .embeddedObjectUserCheck()["ns"]
- .valuestrsafe();
- }
-
- Status explain(OperationContext* txn,
- const std::string& dbname,
- const BSONObj& cmdObj,
- ExplainCommon::Verbosity verbosity,
- BSONObjBuilder* out) const {
- const string fullns = parseNs(dbname, cmdObj);
-
- BSONObjBuilder explainCmdBob;
- ClusterExplain::wrapAsExplain(cmdObj, verbosity, &explainCmdBob);
-
- // We will time how long it takes to run the commands on the shards.
- Timer timer;
-
- Strategy::CommandResult singleResult;
- Status commandStat = Strategy::commandOpUnsharded(dbname,
- explainCmdBob.obj(),
- 0,
- fullns,
- &singleResult);
- if (!commandStat.isOK()) {
- return commandStat;
+ futures.push_back(
+ Future::spawnCommand(shard->getConnString().toString(), dbName, cmdObj, options));
+ shardArray.append(shardId);
+ }
+
+ multimap<double, BSONObj> results; // TODO: maybe use merge-sort instead
+ string nearStr;
+ double time = 0;
+ double btreelocs = 0;
+ double nscanned = 0;
+ double objectsLoaded = 0;
+ for (list<shared_ptr<Future::CommandResult>>::iterator i = futures.begin();
+ i != futures.end();
+ i++) {
+ shared_ptr<Future::CommandResult> res = *i;
+ if (!res->join()) {
+ errmsg = res->result()["errmsg"].String();
+ if (res->result().hasField("code")) {
+ result.append(res->result()["code"]);
}
-
- long long millisElapsed = timer.millis();
-
- vector<Strategy::CommandResult> shardResults;
- shardResults.push_back(singleResult);
-
- return ClusterExplain::buildExplainResult(shardResults,
- ClusterExplain::kSingleShard,
- millisElapsed,
- out);
+ return false;
}
- } groupCmd;
-
- class SplitVectorCmd : public NotAllowedOnShardedCollectionCmd {
- public:
- SplitVectorCmd() : NotAllowedOnShardedCollectionCmd("splitVector") {}
- virtual bool passOptions() const { return true; }
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
- ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname,
- cmdObj))),
- ActionType::splitVector)) {
- return Status(ErrorCodes::Unauthorized, "Unauthorized");
- }
- return Status::OK();
+ if (res->result().hasField("near")) {
+ nearStr = res->result()["near"].String();
}
- virtual bool run(OperationContext* txn,
- const string& dbName,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- string x = parseNs(dbName, cmdObj);
- if ( ! str::startsWith( x , dbName ) ) {
- errmsg = str::stream() << "doing a splitVector across dbs isn't supported via mongos";
- return false;
- }
- return NotAllowedOnShardedCollectionCmd::run(txn,
- dbName,
- cmdObj,
- options,
- errmsg,
- result);
+ time += res->result()["stats"]["time"].Number();
+ if (!res->result()["stats"]["btreelocs"].eoo()) {
+ btreelocs += res->result()["stats"]["btreelocs"].Number();
}
- virtual std::string parseNs(const string& dbname, const BSONObj& cmdObj) const {
- return parseNsFullyQualified(dbname, cmdObj);
+ nscanned += res->result()["stats"]["nscanned"].Number();
+ if (!res->result()["stats"]["objectsLoaded"].eoo()) {
+ objectsLoaded += res->result()["stats"]["objectsLoaded"].Number();
}
- } splitVectorCmd;
-
-
- class DistinctCmd : public PublicGridCommand {
- public:
- DistinctCmd() : PublicGridCommand("distinct") {}
- virtual void help( stringstream &help ) const {
- help << "{ distinct : 'collection name' , key : 'a.b' , query : {} }";
- }
- virtual bool passOptions() const { return true; }
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::find);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ BSONForEach(obj, res->result()["results"].embeddedObject()) {
+ results.insert(make_pair(obj["dis"].Number(), obj.embeddedObject().getOwned()));
}
- bool run(OperationContext* txn,
- const string& dbName ,
- BSONObj& cmdObj,
- int options,
- string& errmsg,
- BSONObjBuilder& result) {
- const string fullns = parseNs(dbName, cmdObj);
-
- auto status = grid.catalogCache()->getDatabase(dbName);
- if (!status.isOK()) {
- return appendEmptyResultSet(result, status.getStatus(), fullns);
- }
-
- shared_ptr<DBConfig> conf = status.getValue();
- if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
- return passthrough(conf, cmdObj, options, result);
- }
-
- ChunkManagerPtr cm = conf->getChunkManager( fullns );
- massert( 10420 , "how could chunk manager be null!" , cm );
-
- BSONObj query = getQuery(cmdObj);
- set<ShardId> shardIds;
- cm->getShardIdsForQuery(shardIds, query);
-
- set<BSONObj,BSONObjCmp> all;
- int size = 32;
-
- for (const ShardId& shardId : shardIds) {
- const auto shard = grid.shardRegistry()->getShard(shardId);
- if (!shard) {
- continue;
- }
+ // TODO: maybe shrink results if size() > limit
+ }
- ShardConnection conn(shard->getConnString(), fullns);
- BSONObj res;
- bool ok = conn->runCommand( conf->name() , cmdObj , res, options );
- conn.done();
+ result.append("ns", fullns);
+ result.append("near", nearStr);
- if ( ! ok ) {
- result.appendElements( res );
- return false;
- }
-
- BSONObjIterator it( res["values"].embeddedObject() );
- while ( it.more() ) {
- BSONElement nxt = it.next();
- BSONObjBuilder temp(32);
- temp.appendAs( nxt , "" );
- all.insert( temp.obj() );
- }
-
- }
-
- BSONObjBuilder b( size );
- int n=0;
- for ( set<BSONObj,BSONObjCmp>::iterator i = all.begin() ; i != all.end(); i++ ) {
- b.appendAs( i->firstElement() , b.numStr( n++ ) );
- }
-
- result.appendArray( "values" , b.obj() );
- return true;
- }
- } disinctCmd;
+ int outCount = 0;
+ double totalDistance = 0;
+ double maxDistance = 0;
+ {
+ BSONArrayBuilder sub(result.subarrayStart("results"));
+ for (multimap<double, BSONObj>::const_iterator it(results.begin()), end(results.end());
+ it != end && outCount < limit;
+ ++it, ++outCount) {
+ totalDistance += it->first;
+ maxDistance = it->first; // guaranteed to be highest so far
- class FileMD5Cmd : public PublicGridCommand {
- public:
- FileMD5Cmd() : PublicGridCommand("filemd5") {}
- virtual void help( stringstream &help ) const {
- help << " example: { filemd5 : ObjectId(aaaaaaa) , root : \"fs\" }";
+ sub.append(it->second);
}
-
- virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
- std::string collectionName = cmdObj.getStringField("root");
- if (collectionName.empty())
- collectionName = "fs";
- collectionName += ".chunks";
- return NamespaceString(dbname, collectionName).ns();
- }
-
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), ActionType::find));
- }
-
- bool run(OperationContext* txn,
+ sub.done();
+ }
+
+ {
+ BSONObjBuilder sub(result.subobjStart("stats"));
+ sub.append("time", time);
+ sub.append("btreelocs", btreelocs);
+ sub.append("nscanned", nscanned);
+ sub.append("objectsLoaded", objectsLoaded);
+ sub.append("avgDistance", (outCount == 0) ? 0 : (totalDistance / outCount));
+ sub.append("maxDistance", maxDistance);
+ sub.append("shards", shardArray.arr());
+ sub.done();
+ }
+
+ return true;
+ }
+} geo2dFindNearCmd;
+
+class ApplyOpsCmd : public PublicGridCommand {
+public:
+ ApplyOpsCmd() : PublicGridCommand("applyOps") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ // applyOps can do pretty much anything, so require all privileges.
+ RoleGraph::generateUniversalPrivileges(out);
+ }
+ virtual bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int,
string& errmsg,
BSONObjBuilder& result) {
- const string fullns = parseNs(dbName, cmdObj);
-
- auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
- if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
- return passthrough( conf , cmdObj , result );
- }
-
- ChunkManagerPtr cm = conf->getChunkManager( fullns );
- massert( 13091 , "how could chunk manager be null!" , cm );
- if(cm->getShardKeyPattern().toBSON() == BSON("files_id" << 1)) {
- BSONObj finder = BSON("files_id" << cmdObj.firstElement());
-
- vector<Strategy::CommandResult> results;
- Strategy::commandOp(dbName, cmdObj, 0, fullns, finder, &results);
- verify(results.size() == 1); // querying on shard key so should only talk to one shard
- BSONObj res = results.begin()->result;
-
- result.appendElements(res);
- return res["ok"].trueValue();
- }
- else if (cm->getShardKeyPattern().toBSON() == BSON("files_id" << 1 << "n" << 1)) {
- int n = 0;
- BSONObj lastResult;
-
- while (true) {
- // Theory of operation: Starting with n=0, send filemd5 command to shard
- // with that chunk (gridfs chunk not sharding chunk). That shard will then
- // compute a partial md5 state (passed in the "md5state" field) for all
- // contiguous chunks that it has. When it runs out or hits a discontinuity
- // (eg [1,2,7]) it returns what it has done so far. This is repeated as
- // long as we keep getting more chunks. The end condition is when we go to
- // look for chunk n and it doesn't exist. This means that the file's last
- // chunk is n-1, so we return the computed md5 results.
- BSONObjBuilder bb;
- bb.appendElements(cmdObj);
- bb.appendBool("partialOk", true);
- bb.append("startAt", n);
- if (!lastResult.isEmpty()){
- bb.append(lastResult["md5state"]);
- }
- BSONObj shardCmd = bb.obj();
-
- BSONObj finder = BSON("files_id" << cmdObj.firstElement() << "n" << n);
-
- vector<Strategy::CommandResult> results;
- try {
- Strategy::commandOp(dbName, shardCmd, 0, fullns, finder, &results);
- }
- catch( DBException& e ){
- //This is handled below and logged
- Strategy::CommandResult errResult;
- errResult.shardTargetId = "";
- errResult.result = BSON("errmsg" << e.what() << "ok" << 0 );
- results.push_back( errResult );
- }
-
- verify(results.size() == 1); // querying on shard key so should only talk to one shard
- BSONObj res = results.begin()->result;
- bool ok = res["ok"].trueValue();
-
- if (!ok) {
- // Add extra info to make debugging easier
- result.append("failedAt", n);
- result.append("sentCommand", shardCmd);
- BSONForEach(e, res){
- if (!str::equals(e.fieldName(), "errmsg"))
- result.append(e);
- }
-
- log() << "Sharded filemd5 failed: " << result.asTempObj();
-
- errmsg = string("sharded filemd5 failed because: ") + res["errmsg"].valuestrsafe();
- return false;
- }
-
- uassert(16246, "Shard " + conf->name() + " is too old to support GridFS sharded by {files_id:1, n:1}",
- res.hasField("md5state"));
-
- lastResult = res;
- int nNext = res["numChunks"].numberInt();
-
- if (n == nNext){
- // no new data means we've reached the end of the file
- result.appendElements(res);
- return true;
- }
-
- verify(nNext > n);
- n = nNext;
- }
-
- verify(0);
- }
-
- // We could support arbitrary shard keys by sending commands to all shards but I don't think we should
- errmsg = "GridFS fs.chunks collection must be sharded on either {files_id:1} or {files_id:1, n:1}";
- return false;
- }
- } fileMD5Cmd;
-
- class Geo2dFindNearCmd : public PublicGridCommand {
- public:
- Geo2dFindNearCmd() : PublicGridCommand( "geoNear" ) {}
- void help(stringstream& h) const { h << "http://dochub.mongodb.org/core/geo#GeospatialIndexing-geoNearCommand"; }
- virtual bool passOptions() const { return true; }
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::find);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
- }
-
- bool run(OperationContext* txn,
+ errmsg = "applyOps not allowed through mongos";
+ return false;
+ }
+} applyOpsCmd;
+
+
+class CompactCmd : public PublicGridCommand {
+public:
+ CompactCmd() : PublicGridCommand("compact") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::compact);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
+ virtual bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
- int options,
+ int,
string& errmsg,
BSONObjBuilder& result) {
- const string fullns = parseNs(dbName, cmdObj);
-
- auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
- if (!conf->isShardingEnabled() || !conf->isSharded(fullns)) {
- return passthrough( conf , cmdObj , options, result );
- }
-
- ChunkManagerPtr cm = conf->getChunkManager( fullns );
- massert( 13500 , "how could chunk manager be null!" , cm );
-
- BSONObj query = getQuery(cmdObj);
- set<ShardId> shardIds;
- cm->getShardIdsForQuery(shardIds, query);
-
- // We support both "num" and "limit" options to control limit
- int limit = 100;
- const char* limitName = cmdObj["num"].isNumber() ? "num" : "limit";
- if (cmdObj[limitName].isNumber())
- limit = cmdObj[limitName].numberInt();
-
- list< shared_ptr<Future::CommandResult> > futures;
- BSONArrayBuilder shardArray;
- for (const ShardId& shardId : shardIds) {
- const auto shard = grid.shardRegistry()->getShard(shardId);
- if (!shard) {
- continue;
- }
-
- futures.push_back(Future::spawnCommand(shard->getConnString().toString(),
- dbName,
- cmdObj,
- options));
- shardArray.append(shardId);
- }
-
- multimap<double, BSONObj> results; // TODO: maybe use merge-sort instead
- string nearStr;
- double time = 0;
- double btreelocs = 0;
- double nscanned = 0;
- double objectsLoaded = 0;
- for ( list< shared_ptr<Future::CommandResult> >::iterator i=futures.begin(); i!=futures.end(); i++ ) {
- shared_ptr<Future::CommandResult> res = *i;
- if ( ! res->join() ) {
- errmsg = res->result()["errmsg"].String();
- if (res->result().hasField("code")) {
- result.append(res->result()["code"]);
- }
- return false;
- }
-
- if (res->result().hasField("near")) {
- nearStr = res->result()["near"].String();
- }
- time += res->result()["stats"]["time"].Number();
- if (!res->result()["stats"]["btreelocs"].eoo()) {
- btreelocs += res->result()["stats"]["btreelocs"].Number();
- }
- nscanned += res->result()["stats"]["nscanned"].Number();
- if (!res->result()["stats"]["objectsLoaded"].eoo()) {
- objectsLoaded += res->result()["stats"]["objectsLoaded"].Number();
- }
-
- BSONForEach(obj, res->result()["results"].embeddedObject()) {
- results.insert(make_pair(obj["dis"].Number(), obj.embeddedObject().getOwned()));
- }
-
- // TODO: maybe shrink results if size() > limit
- }
-
- result.append("ns" , fullns);
- result.append("near", nearStr);
-
- int outCount = 0;
- double totalDistance = 0;
- double maxDistance = 0;
- {
- BSONArrayBuilder sub (result.subarrayStart("results"));
- for (multimap<double, BSONObj>::const_iterator it(results.begin()), end(results.end()); it!= end && outCount < limit; ++it, ++outCount) {
- totalDistance += it->first;
- maxDistance = it->first; // guaranteed to be highest so far
-
- sub.append(it->second);
- }
- sub.done();
- }
-
- {
- BSONObjBuilder sub (result.subobjStart("stats"));
- sub.append("time", time);
- sub.append("btreelocs", btreelocs);
- sub.append("nscanned", nscanned);
- sub.append("objectsLoaded", objectsLoaded);
- sub.append("avgDistance", (outCount == 0) ? 0: (totalDistance / outCount));
- sub.append("maxDistance", maxDistance);
- sub.append("shards", shardArray.arr());
- sub.done();
- }
-
- return true;
- }
- } geo2dFindNearCmd;
-
- class ApplyOpsCmd : public PublicGridCommand {
- public:
- ApplyOpsCmd() : PublicGridCommand( "applyOps" ) {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- // applyOps can do pretty much anything, so require all privileges.
- RoleGraph::generateUniversalPrivileges(out);
- }
- virtual bool run(OperationContext* txn, const string& dbName , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result) {
- errmsg = "applyOps not allowed through mongos";
- return false;
- }
- } applyOpsCmd;
-
-
- class CompactCmd : public PublicGridCommand {
- public:
- CompactCmd() : PublicGridCommand( "compact" ) {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::compact);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
- }
- virtual bool run(OperationContext* txn,
- const string& dbName,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- errmsg = "compact not allowed through mongos";
- return false;
- }
- } compactCmd;
-
- class EvalCmd : public PublicGridCommand {
- public:
- EvalCmd() : PublicGridCommand( "eval", "$eval" ) {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- // $eval can do pretty much anything, so require all privileges.
- RoleGraph::generateUniversalPrivileges(out);
- }
- virtual bool run(OperationContext* txn,
- const string& dbName,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
-
- RARELY {
- warning() << "the eval command is deprecated" << startupWarningsLog;
- }
-
- // $eval isn't allowed to access sharded collections, but we need to leave the
- // shard to detect that.
- auto status = grid.catalogCache()->getDatabase(dbName);
- if (!status.isOK()) {
- return appendCommandStatus(result, status.getStatus());
- }
-
- shared_ptr<DBConfig> conf = status.getValue();
- return passthrough( conf , cmdObj , result );
- }
- } evalCmd;
-
- class CmdListCollections : public PublicGridCommand {
- public:
- CmdListCollections() : PublicGridCommand( "listCollections" ) {}
-
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- AuthorizationSession* authzSession = AuthorizationSession::get(client);
-
- // Check for the listCollections ActionType on the database
- // or find on system.namespaces for pre 3.0 systems.
- if (authzSession->isAuthorizedForActionsOnResource(
- ResourcePattern::forDatabaseName(dbname),
- ActionType::listCollections) ||
- authzSession->isAuthorizedForActionsOnResource(
- ResourcePattern::forExactNamespace(
- NamespaceString(dbname, "system.namespaces")),
- ActionType::find)) {
- return Status::OK();
- }
-
- return Status(ErrorCodes::Unauthorized,
- str::stream() << "Not authorized to create users on db: " <<
- dbname);
- }
-
- bool run(OperationContext* txn,
+ errmsg = "compact not allowed through mongos";
+ return false;
+ }
+} compactCmd;
+
+class EvalCmd : public PublicGridCommand {
+public:
+ EvalCmd() : PublicGridCommand("eval", "$eval") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ // $eval can do pretty much anything, so require all privileges.
+ RoleGraph::generateUniversalPrivileges(out);
+ }
+ virtual bool run(OperationContext* txn,
const string& dbName,
BSONObj& cmdObj,
int,
string& errmsg,
BSONObjBuilder& result) {
-
- auto status = grid.catalogCache()->getDatabase(dbName);
- if (!status.isOK()) {
- return appendEmptyResultSet(result,
- status.getStatus(),
- dbName + ".$cmd.listCollections");
- }
-
- shared_ptr<DBConfig> conf = status.getValue();
- bool retval = passthrough( conf, cmdObj, result );
-
- const auto shard = grid.shardRegistry()->getShard(conf->getPrimaryId());
- Status storeCursorStatus = storePossibleCursor(shard->getConnString().toString(),
- result.asTempObj());
- if (!storeCursorStatus.isOK()) {
- return appendCommandStatus(result, storeCursorStatus);
- }
-
- return retval;
- }
- } cmdListCollections;
-
- class CmdListIndexes : public PublicGridCommand {
- public:
- CmdListIndexes() : PublicGridCommand( "listIndexes" ) {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- string ns = parseNs( dbname, cmdObj );
- ActionSet actions;
- actions.addAction(ActionType::listIndexes);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
- }
-
- bool run(OperationContext* txn,
- const string& dbName,
+ RARELY {
+ warning() << "the eval command is deprecated" << startupWarningsLog;
+ }
+
+ // $eval isn't allowed to access sharded collections, but we need to leave the
+ // shard to detect that.
+ auto status = grid.catalogCache()->getDatabase(dbName);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status.getStatus());
+ }
+
+ shared_ptr<DBConfig> conf = status.getValue();
+ return passthrough(conf, cmdObj, result);
+ }
+} evalCmd;
+
+class CmdListCollections : public PublicGridCommand {
+public:
+ CmdListCollections() : PublicGridCommand("listCollections") {}
+
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ AuthorizationSession* authzSession = AuthorizationSession::get(client);
+
+ // Check for the listCollections ActionType on the database
+ // or find on system.namespaces for pre 3.0 systems.
+ if (authzSession->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname),
+ ActionType::listCollections) ||
+ authzSession->isAuthorizedForActionsOnResource(
+ ResourcePattern::forExactNamespace(NamespaceString(dbname, "system.namespaces")),
+ ActionType::find)) {
+ return Status::OK();
+ }
+
+ return Status(ErrorCodes::Unauthorized,
+ str::stream() << "Not authorized to create users on db: " << dbname);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbName,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ auto status = grid.catalogCache()->getDatabase(dbName);
+ if (!status.isOK()) {
+ return appendEmptyResultSet(
+ result, status.getStatus(), dbName + ".$cmd.listCollections");
+ }
+
+ shared_ptr<DBConfig> conf = status.getValue();
+ bool retval = passthrough(conf, cmdObj, result);
+
+ const auto shard = grid.shardRegistry()->getShard(conf->getPrimaryId());
+ Status storeCursorStatus =
+ storePossibleCursor(shard->getConnString().toString(), result.asTempObj());
+ if (!storeCursorStatus.isOK()) {
+ return appendCommandStatus(result, storeCursorStatus);
+ }
+
+ return retval;
+ }
+} cmdListCollections;
+
+class CmdListIndexes : public PublicGridCommand {
+public:
+ CmdListIndexes() : PublicGridCommand("listIndexes") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ string ns = parseNs(dbname, cmdObj);
+ ActionSet actions;
+ actions.addAction(ActionType::listIndexes);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbName,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
+ bool retval = passthrough(conf, cmdObj, result);
+
+ const auto shard = grid.shardRegistry()->getShard(conf->getPrimaryId());
+ Status storeCursorStatus =
+ storePossibleCursor(shard->getConnString().toString(), result.asTempObj());
+ if (!storeCursorStatus.isOK()) {
+ return appendCommandStatus(result, storeCursorStatus);
+ }
+
+ return retval;
+ }
+
+} cmdListIndexes;
+
+class AvailableQueryOptions : public Command {
+public:
+ AvailableQueryOptions() : Command("availableQueryOptions", false, "availablequeryoptions") {}
+
+ virtual bool slaveOk() const {
+ return true;
+ }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return Status::OK();
+ }
+
+
+ virtual bool run(OperationContext* txn,
+ const string& dbname,
BSONObj& cmdObj,
- int options,
+ int,
string& errmsg,
BSONObjBuilder& result) {
+ result << "options" << QueryOption_AllSupportedForSharding;
+ return true;
+ }
+} availableQueryOptionsCmd;
- auto conf = uassertStatusOK(grid.catalogCache()->getDatabase(dbName));
- bool retval = passthrough( conf, cmdObj, result );
-
- const auto shard = grid.shardRegistry()->getShard(conf->getPrimaryId());
- Status storeCursorStatus = storePossibleCursor(shard->getConnString().toString(),
- result.asTempObj());
- if (!storeCursorStatus.isOK()) {
- return appendCommandStatus(result, storeCursorStatus);
- }
-
- return retval;
- }
-
- } cmdListIndexes;
-
- class AvailableQueryOptions : public Command {
- public:
- AvailableQueryOptions(): Command("availableQueryOptions",
- false ,
- "availablequeryoptions") {
- }
-
- virtual bool slaveOk() const { return true; }
- virtual bool isWriteCommandForConfigServer() const { return false; }
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- return Status::OK();
- }
-
-
- virtual bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- result << "options" << QueryOption_AllSupportedForSharding;
- return true;
- }
- } availableQueryOptionsCmd;
-
- } // namespace pub_grid_cmds
-
-} // namespace mongo
+} // namespace pub_grid_cmds
+} // namespace mongo
diff --git a/src/mongo/s/commands/run_on_all_shards_cmd.cpp b/src/mongo/s/commands/run_on_all_shards_cmd.cpp
index 68e9629227a..7c206b6a978 100644
--- a/src/mongo/s/commands/run_on_all_shards_cmd.cpp
+++ b/src/mongo/s/commands/run_on_all_shards_cmd.cpp
@@ -44,141 +44,128 @@
namespace mongo {
- RunOnAllShardsCommand::RunOnAllShardsCommand(const char* name,
- const char* oldName,
- bool useShardConn,
- bool implicitCreateDb)
- : Command(name, false, oldName),
- _useShardConn(useShardConn),
- _implicitCreateDb(implicitCreateDb) {
+RunOnAllShardsCommand::RunOnAllShardsCommand(const char* name,
+ const char* oldName,
+ bool useShardConn,
+ bool implicitCreateDb)
+ : Command(name, false, oldName),
+ _useShardConn(useShardConn),
+ _implicitCreateDb(implicitCreateDb) {}
+
+void RunOnAllShardsCommand::aggregateResults(const std::vector<ShardAndReply>& results,
+ BSONObjBuilder& output) {}
+
+BSONObj RunOnAllShardsCommand::specialErrorHandler(const std::string& server,
+ const std::string& db,
+ const BSONObj& cmdObj,
+ const BSONObj& originalResult) const {
+ return originalResult;
+}
- }
+void RunOnAllShardsCommand::getShardIds(const std::string& db,
+ BSONObj& cmdObj,
+ std::vector<ShardId>& shardIds) {
+ grid.shardRegistry()->getAllShardIds(&shardIds);
+}
- void RunOnAllShardsCommand::aggregateResults(const std::vector<ShardAndReply>& results,
- BSONObjBuilder& output)
- {}
+bool RunOnAllShardsCommand::run(OperationContext* txn,
+ const std::string& dbName,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& output) {
+ LOG(1) << "RunOnAllShardsCommand db: " << dbName << " cmd:" << cmdObj;
- BSONObj RunOnAllShardsCommand::specialErrorHandler(const std::string& server,
- const std::string& db,
- const BSONObj& cmdObj,
- const BSONObj& originalResult) const {
- return originalResult;
+ if (_implicitCreateDb) {
+ uassertStatusOK(grid.implicitCreateDb(dbName));
}
- void RunOnAllShardsCommand::getShardIds(const std::string& db,
- BSONObj& cmdObj,
- std::vector<ShardId>& shardIds) {
- grid.shardRegistry()->getAllShardIds(&shardIds);
- }
+ std::vector<ShardId> shardIds;
+ getShardIds(dbName, cmdObj, shardIds);
- bool RunOnAllShardsCommand::run(OperationContext* txn,
- const std::string& dbName,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& output) {
-
- LOG(1) << "RunOnAllShardsCommand db: " << dbName << " cmd:" << cmdObj;
-
- if (_implicitCreateDb) {
- uassertStatusOK(grid.implicitCreateDb(dbName));
+ std::list<std::shared_ptr<Future::CommandResult>> futures;
+ for (const ShardId& shardId : shardIds) {
+ const auto shard = grid.shardRegistry()->getShard(shardId);
+ if (!shard) {
+ continue;
}
- std::vector<ShardId> shardIds;
- getShardIds(dbName, cmdObj, shardIds);
-
- std::list<std::shared_ptr<Future::CommandResult>> futures;
- for (const ShardId& shardId : shardIds) {
- const auto shard = grid.shardRegistry()->getShard(shardId);
- if (!shard) {
- continue;
- }
+ futures.push_back(Future::spawnCommand(
+ shard->getConnString().toString(), dbName, cmdObj, 0, NULL, _useShardConn));
+ }
- futures.push_back(Future::spawnCommand(shard->getConnString().toString(),
- dbName,
- cmdObj,
- 0,
- NULL,
- _useShardConn));
+ std::vector<ShardAndReply> results;
+ BSONObjBuilder subobj(output.subobjStart("raw"));
+ BSONObjBuilder errors;
+ int commonErrCode = -1;
+
+ std::list<std::shared_ptr<Future::CommandResult>>::iterator futuresit;
+ std::vector<ShardId>::const_iterator shardIdsIt;
+ // We iterate over the set of shard ids and their corresponding futures in parallel.
+ // TODO: replace with zip iterator if we ever decide to use one from Boost or elsewhere
+ for (futuresit = futures.begin(), shardIdsIt = shardIds.cbegin();
+ futuresit != futures.end() && shardIdsIt != shardIds.end();
+ ++futuresit, ++shardIdsIt) {
+ std::shared_ptr<Future::CommandResult> res = *futuresit;
+
+ if (res->join()) {
+ // success :)
+ BSONObj result = res->result();
+ results.emplace_back(*shardIdsIt, result);
+ subobj.append(res->getServer(), result);
+ continue;
}
- std::vector<ShardAndReply> results;
- BSONObjBuilder subobj (output.subobjStart("raw"));
- BSONObjBuilder errors;
- int commonErrCode = -1;
-
- std::list< std::shared_ptr<Future::CommandResult> >::iterator futuresit;
- std::vector<ShardId>::const_iterator shardIdsIt;
- // We iterate over the set of shard ids and their corresponding futures in parallel.
- // TODO: replace with zip iterator if we ever decide to use one from Boost or elsewhere
- for (futuresit = futures.begin(), shardIdsIt = shardIds.cbegin();
- futuresit != futures.end() && shardIdsIt != shardIds.end();
- ++futuresit, ++shardIdsIt) {
-
- std::shared_ptr<Future::CommandResult> res = *futuresit;
-
- if ( res->join() ) {
- // success :)
- BSONObj result = res->result();
- results.emplace_back(*shardIdsIt, result );
- subobj.append( res->getServer(), result );
- continue;
- }
+ BSONObj result = res->result();
- BSONObj result = res->result();
-
- if ( result["errmsg"].type() ||
- result["code"].numberInt() != 0 ) {
- result = specialErrorHandler( res->getServer(), dbName, cmdObj, result );
-
- BSONElement errmsg = result["errmsg"];
- if ( errmsg.eoo() || errmsg.String().empty() ) {
- // it was fixed!
- results.emplace_back(*shardIdsIt, result );
- subobj.append( res->getServer(), result );
- continue;
- }
- }
+ if (result["errmsg"].type() || result["code"].numberInt() != 0) {
+ result = specialErrorHandler(res->getServer(), dbName, cmdObj, result);
- // Handle "errmsg".
- if( ! result["errmsg"].eoo() ){
- errors.appendAs(result["errmsg"], res->getServer());
- }
- else {
- // Can happen if message is empty, for some reason
- errors.append( res->getServer(), str::stream() <<
- "result without error message returned : " << result );
+ BSONElement errmsg = result["errmsg"];
+ if (errmsg.eoo() || errmsg.String().empty()) {
+ // it was fixed!
+ results.emplace_back(*shardIdsIt, result);
+ subobj.append(res->getServer(), result);
+ continue;
}
+ }
- // Handle "code".
- int errCode = result["code"].numberInt();
- if ( commonErrCode == -1 ) {
- commonErrCode = errCode;
- }
- else if ( commonErrCode != errCode ) {
- commonErrCode = 0;
- }
- results.emplace_back(*shardIdsIt, result );
- subobj.append( res->getServer(), result );
+ // Handle "errmsg".
+ if (!result["errmsg"].eoo()) {
+ errors.appendAs(result["errmsg"], res->getServer());
+ } else {
+ // Can happen if message is empty, for some reason
+ errors.append(res->getServer(),
+ str::stream() << "result without error message returned : " << result);
}
- subobj.done();
+ // Handle "code".
+ int errCode = result["code"].numberInt();
+ if (commonErrCode == -1) {
+ commonErrCode = errCode;
+ } else if (commonErrCode != errCode) {
+ commonErrCode = 0;
+ }
+ results.emplace_back(*shardIdsIt, result);
+ subobj.append(res->getServer(), result);
+ }
- BSONObj errobj = errors.done();
- if (! errobj.isEmpty()) {
- errmsg = errobj.toString(false, true);
+ subobj.done();
- // If every error has a code, and the code for all errors is the same, then add
- // a top-level field "code" with this value to the output object.
- if ( commonErrCode > 0 ) {
- output.append( "code", commonErrCode );
- }
+ BSONObj errobj = errors.done();
+ if (!errobj.isEmpty()) {
+ errmsg = errobj.toString(false, true);
- return false;
+ // If every error has a code, and the code for all errors is the same, then add
+ // a top-level field "code" with this value to the output object.
+ if (commonErrCode > 0) {
+ output.append("code", commonErrCode);
}
- aggregateResults(results, output);
- return true;
+ return false;
}
+ aggregateResults(results, output);
+ return true;
+}
}
diff --git a/src/mongo/s/commands/run_on_all_shards_cmd.h b/src/mongo/s/commands/run_on_all_shards_cmd.h
index bc0b6f22084..149887864a7 100644
--- a/src/mongo/s/commands/run_on_all_shards_cmd.h
+++ b/src/mongo/s/commands/run_on_all_shards_cmd.h
@@ -36,62 +36,68 @@
namespace mongo {
- class BSONObj;
- class BSONObjBuilder;
- class OperationContext;
+class BSONObj;
+class BSONObjBuilder;
+class OperationContext;
- /**
- * Logic for commands that simply map out to all shards then fold the results into
- * a single response.
- *
- * All shards are contacted in parallel.
- *
- * When extending, don't override run() - but rather aggregateResults(). If you need
- * to implement some kind of fall back logic for multiversion clusters,
- * override specialErrorHandler().
- */
- class RunOnAllShardsCommand : public Command {
- public:
- RunOnAllShardsCommand(const char* name,
- const char* oldName=NULL,
- bool useShardConn=false,
- bool implicitCreateDb=false);
+/**
+ * Logic for commands that simply map out to all shards then fold the results into
+ * a single response.
+ *
+ * All shards are contacted in parallel.
+ *
+ * When extending, don't override run() - but rather aggregateResults(). If you need
+ * to implement some kind of fall back logic for multiversion clusters,
+ * override specialErrorHandler().
+ */
+class RunOnAllShardsCommand : public Command {
+public:
+ RunOnAllShardsCommand(const char* name,
+ const char* oldName = NULL,
+ bool useShardConn = false,
+ bool implicitCreateDb = false);
- bool slaveOk() const override { return true; }
- bool adminOnly() const override { return false; }
- bool isWriteCommandForConfigServer() const override { return false; }
+ bool slaveOk() const override {
+ return true;
+ }
+ bool adminOnly() const override {
+ return false;
+ }
+ bool isWriteCommandForConfigServer() const override {
+ return false;
+ }
- // The StringData contains the shard ident.
- // This can be used to create an instance of Shard
- using ShardAndReply = std::tuple<StringData, BSONObj>;
+ // The StringData contains the shard ident.
+ // This can be used to create an instance of Shard
+ using ShardAndReply = std::tuple<StringData, BSONObj>;
- virtual void aggregateResults(const std::vector<ShardAndReply>& results,
- BSONObjBuilder& output);
+ virtual void aggregateResults(const std::vector<ShardAndReply>& results,
+ BSONObjBuilder& output);
- // The default implementation is the identity function.
- virtual BSONObj specialErrorHandler(const std::string& server,
- const std::string& db,
- const BSONObj& cmdObj,
- const BSONObj& originalResult) const;
+ // The default implementation is the identity function.
+ virtual BSONObj specialErrorHandler(const std::string& server,
+ const std::string& db,
+ const BSONObj& cmdObj,
+ const BSONObj& originalResult) const;
- // The default implementation uses all shards.
- virtual void getShardIds(const std::string& db,
- BSONObj& cmdObj,
- std::vector<ShardId>& shardIds);
+ // The default implementation uses all shards.
+ virtual void getShardIds(const std::string& db,
+ BSONObj& cmdObj,
+ std::vector<ShardId>& shardIds);
- bool run(OperationContext* txn,
- const std::string& db,
- BSONObj& cmdObj,
- int options,
- std::string& errmsg,
- BSONObjBuilder& output) final;
+ bool run(OperationContext* txn,
+ const std::string& db,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& output) final;
- private:
- // Use ShardConnection as opposed to ScopedDbConnection
- const bool _useShardConn;
+private:
+ // Use ShardConnection as opposed to ScopedDbConnection
+ const bool _useShardConn;
- // Whether the requested database should be created implicitly
- const bool _implicitCreateDb;
- };
+ // Whether the requested database should be created implicitly
+ const bool _implicitCreateDb;
+};
} // namespace mongo
diff --git a/src/mongo/s/config.cpp b/src/mongo/s/config.cpp
index 0a1904c9552..7481f949a2a 100644
--- a/src/mongo/s/config.cpp
+++ b/src/mongo/s/config.cpp
@@ -56,751 +56,712 @@
namespace mongo {
- using std::endl;
- using std::set;
- using std::string;
- using std::unique_ptr;
- using std::vector;
-
- CollectionInfo::CollectionInfo(const CollectionType& coll) {
- _dropped = coll.getDropped();
-
- shard(new ChunkManager(coll));
- _dirty = false;
+using std::endl;
+using std::set;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+CollectionInfo::CollectionInfo(const CollectionType& coll) {
+ _dropped = coll.getDropped();
+
+ shard(new ChunkManager(coll));
+ _dirty = false;
+}
+
+CollectionInfo::~CollectionInfo() {}
+
+void CollectionInfo::resetCM(ChunkManager* cm) {
+ invariant(cm);
+ invariant(_cm);
+
+ _cm.reset(cm);
+}
+
+void CollectionInfo::shard(ChunkManager* manager) {
+ // Do this *first* so we're invisible to everyone else
+ manager->loadExistingRanges(nullptr);
+
+ //
+ // Collections with no chunks are unsharded, no matter what the collections entry says
+ // This helps prevent errors when dropping in a different process
+ //
+
+ if (manager->numChunks() != 0) {
+ useChunkManager(ChunkManagerPtr(manager));
+ } else {
+ warning() << "no chunks found for collection " << manager->getns()
+ << ", assuming unsharded";
+ unshard();
}
-
- CollectionInfo::~CollectionInfo() {
-
+}
+
+void CollectionInfo::unshard() {
+ _cm.reset();
+ _dropped = true;
+ _dirty = true;
+ _key = BSONObj();
+}
+
+void CollectionInfo::useChunkManager(ChunkManagerPtr manager) {
+ _cm = manager;
+ _key = manager->getShardKeyPattern().toBSON().getOwned();
+ _unique = manager->isUnique();
+ _dirty = true;
+ _dropped = false;
+}
+
+void CollectionInfo::save(const string& ns) {
+ CollectionType coll;
+ coll.setNs(NamespaceString{ns});
+
+ if (_cm) {
+ invariant(!_dropped);
+ coll.setEpoch(_cm->getVersion().epoch());
+ // TODO(schwerin): The following isn't really a date, but is stored as one in-memory and
+ // in config.collections, as a historical oddity.
+ coll.setUpdatedAt(Date_t::fromMillisSinceEpoch(_cm->getVersion().toLong()));
+ coll.setKeyPattern(_cm->getShardKeyPattern().toBSON());
+ coll.setUnique(_cm->isUnique());
+ } else {
+ invariant(_dropped);
+ coll.setDropped(true);
+ coll.setEpoch(ChunkVersion::DROPPED().epoch());
+ coll.setUpdatedAt(Date_t::now());
}
- void CollectionInfo::resetCM(ChunkManager* cm) {
- invariant(cm);
- invariant(_cm);
-
- _cm.reset(cm);
+ uassertStatusOK(grid.catalogManager()->updateCollection(ns, coll));
+ _dirty = false;
+}
+
+DBConfig::DBConfig(std::string name, const DatabaseType& dbt) : _name(name) {
+ invariant(_name == dbt.getName());
+ _primaryId = dbt.getPrimary();
+ _shardingEnabled = dbt.getSharded();
+}
+
+bool DBConfig::isSharded(const string& ns) {
+ if (!_shardingEnabled)
+ return false;
+ stdx::lock_guard<stdx::mutex> lk(_lock);
+ return _isSharded(ns);
+}
+
+bool DBConfig::_isSharded(const string& ns) {
+ if (!_shardingEnabled) {
+ return false;
}
-
- void CollectionInfo::shard(ChunkManager* manager) {
- // Do this *first* so we're invisible to everyone else
- manager->loadExistingRanges(nullptr);
-
- //
- // Collections with no chunks are unsharded, no matter what the collections entry says
- // This helps prevent errors when dropping in a different process
- //
- if (manager->numChunks() != 0) {
- useChunkManager(ChunkManagerPtr(manager));
- }
- else{
- warning() << "no chunks found for collection " << manager->getns()
- << ", assuming unsharded";
- unshard();
- }
+ CollectionInfoMap::iterator i = _collections.find(ns);
+ if (i == _collections.end()) {
+ return false;
}
- void CollectionInfo::unshard() {
- _cm.reset();
- _dropped = true;
- _dirty = true;
- _key = BSONObj();
- }
+ return i->second.isSharded();
+}
- void CollectionInfo::useChunkManager(ChunkManagerPtr manager) {
- _cm = manager;
- _key = manager->getShardKeyPattern().toBSON().getOwned();
- _unique = manager->isUnique();
- _dirty = true;
- _dropped = false;
- }
+const ShardId& DBConfig::getShardId(const string& ns) {
+ uassert(28679, "ns can't be sharded", !isSharded(ns));
- void CollectionInfo::save(const string& ns) {
- CollectionType coll;
- coll.setNs(NamespaceString{ns});
+ uassert(10178, "no primary!", grid.shardRegistry()->getShard(_primaryId));
+ return _primaryId;
+}
- if (_cm) {
- invariant(!_dropped);
- coll.setEpoch(_cm->getVersion().epoch());
- // TODO(schwerin): The following isn't really a date, but is stored as one in-memory and
- // in config.collections, as a historical oddity.
- coll.setUpdatedAt(Date_t::fromMillisSinceEpoch(_cm->getVersion().toLong()));
- coll.setKeyPattern(_cm->getShardKeyPattern().toBSON());
- coll.setUnique(_cm->isUnique());
- }
- else {
- invariant(_dropped);
- coll.setDropped(true);
- coll.setEpoch(ChunkVersion::DROPPED().epoch());
- coll.setUpdatedAt(Date_t::now());
- }
+void DBConfig::enableSharding(bool save) {
+ if (_shardingEnabled)
+ return;
- uassertStatusOK(grid.catalogManager()->updateCollection(ns, coll));
- _dirty = false;
- }
+ verify(_name != "config");
- DBConfig::DBConfig(std::string name, const DatabaseType& dbt)
- : _name(name) {
+ stdx::lock_guard<stdx::mutex> lk(_lock);
+ _shardingEnabled = true;
+ if (save)
+ _save();
+}
- invariant(_name == dbt.getName());
- _primaryId = dbt.getPrimary();
- _shardingEnabled = dbt.getSharded();
+bool DBConfig::removeSharding(const string& ns) {
+ if (!_shardingEnabled) {
+ warning() << "could not remove sharding for collection " << ns
+ << ", sharding not enabled for db" << endl;
+ return false;
}
- bool DBConfig::isSharded( const string& ns ) {
- if ( ! _shardingEnabled )
- return false;
- stdx::lock_guard<stdx::mutex> lk( _lock );
- return _isSharded( ns );
- }
+ stdx::lock_guard<stdx::mutex> lk(_lock);
- bool DBConfig::_isSharded( const string& ns ) {
- if (!_shardingEnabled) {
- return false;
- }
+ CollectionInfoMap::iterator i = _collections.find(ns);
- CollectionInfoMap::iterator i = _collections.find( ns );
- if (i == _collections.end()) {
- return false;
- }
+ if (i == _collections.end())
+ return false;
- return i->second.isSharded();
+ CollectionInfo& ci = _collections[ns];
+ if (!ci.isSharded()) {
+ warning() << "could not remove sharding for collection " << ns
+ << ", no sharding information found" << endl;
+ return false;
}
- const ShardId& DBConfig::getShardId(const string& ns) {
- uassert(28679, "ns can't be sharded", !isSharded(ns));
+ ci.unshard();
+ _save(false, true);
+ return true;
+}
- uassert(10178,
- "no primary!",
- grid.shardRegistry()->getShard(_primaryId));
- return _primaryId;
- }
+// Handles weird logic related to getting *either* a chunk manager *or* the collection primary shard
+void DBConfig::getChunkManagerOrPrimary(const string& ns,
+ std::shared_ptr<ChunkManager>& manager,
+ std::shared_ptr<Shard>& primary) {
+ // The logic here is basically that at any time, our collection can become sharded or unsharded
+ // via a command. If we're not sharded, we want to send data to the primary, if sharded, we want
+ // to send data to the correct chunks, and we can't check both w/o the lock.
- void DBConfig::enableSharding( bool save ) {
- if ( _shardingEnabled )
- return;
-
- verify( _name != "config" );
+ manager.reset();
+ primary.reset();
- stdx::lock_guard<stdx::mutex> lk( _lock );
- _shardingEnabled = true;
- if( save ) _save();
- }
+ {
+ stdx::lock_guard<stdx::mutex> lk(_lock);
- bool DBConfig::removeSharding( const string& ns ) {
- if ( ! _shardingEnabled ) {
+ CollectionInfoMap::iterator i = _collections.find(ns);
- warning() << "could not remove sharding for collection " << ns
- << ", sharding not enabled for db" << endl;
- return false;
+ // No namespace
+ if (i == _collections.end()) {
+ // If we don't know about this namespace, it's unsharded by default
+ primary = grid.shardRegistry()->getShard(_primaryId);
+ } else {
+ CollectionInfo& cInfo = i->second;
+
+ // TODO: we need to be careful about handling shardingEnabled, b/c in some places we seem to use and
+ // some we don't. If we use this function in combination with just getChunkManager() on a slightly
+ // borked config db, we'll get lots of staleconfig retries
+ if (_shardingEnabled && cInfo.isSharded()) {
+ manager = cInfo.getCM();
+ } else {
+ primary = grid.shardRegistry()->getShard(_primaryId);
+ }
}
+ }
- stdx::lock_guard<stdx::mutex> lk( _lock );
+ verify(manager || primary);
+ verify(!manager || !primary);
+}
- CollectionInfoMap::iterator i = _collections.find( ns );
- if ( i == _collections.end() )
- return false;
+ChunkManagerPtr DBConfig::getChunkManagerIfExists(const string& ns,
+ bool shouldReload,
+ bool forceReload) {
+ // Don't report exceptions here as errors in GetLastError
+ LastError::Disabled ignoreForGLE(&LastError::get(cc()));
- CollectionInfo& ci = _collections[ns];
- if ( ! ci.isSharded() ){
-
- warning() << "could not remove sharding for collection " << ns
- << ", no sharding information found" << endl;
- return false;
- }
-
- ci.unshard();
- _save( false, true );
- return true;
+ try {
+ return getChunkManager(ns, shouldReload, forceReload);
+ } catch (AssertionException& e) {
+ warning() << "chunk manager not found for " << ns << causedBy(e);
+ return ChunkManagerPtr();
}
+}
- // Handles weird logic related to getting *either* a chunk manager *or* the collection primary shard
- void DBConfig::getChunkManagerOrPrimary(const string& ns,
- std::shared_ptr<ChunkManager>& manager,
- std::shared_ptr<Shard>& primary) {
-
- // The logic here is basically that at any time, our collection can become sharded or unsharded
- // via a command. If we're not sharded, we want to send data to the primary, if sharded, we want
- // to send data to the correct chunks, and we can't check both w/o the lock.
+std::shared_ptr<ChunkManager> DBConfig::getChunkManager(const string& ns,
+ bool shouldReload,
+ bool forceReload) {
+ BSONObj key;
+ ChunkVersion oldVersion;
+ ChunkManagerPtr oldManager;
- manager.reset();
- primary.reset();
-
- {
- stdx::lock_guard<stdx::mutex> lk( _lock );
-
- CollectionInfoMap::iterator i = _collections.find( ns );
+ {
+ stdx::lock_guard<stdx::mutex> lk(_lock);
- // No namespace
- if( i == _collections.end() ){
- // If we don't know about this namespace, it's unsharded by default
- primary = grid.shardRegistry()->getShard(_primaryId);
- }
- else {
- CollectionInfo& cInfo = i->second;
-
- // TODO: we need to be careful about handling shardingEnabled, b/c in some places we seem to use and
- // some we don't. If we use this function in combination with just getChunkManager() on a slightly
- // borked config db, we'll get lots of staleconfig retries
- if( _shardingEnabled && cInfo.isSharded() ){
- manager = cInfo.getCM();
- }
- else{
- primary = grid.shardRegistry()->getShard(_primaryId);
- }
- }
+ bool earlyReload = !_collections[ns].isSharded() && (shouldReload || forceReload);
+ if (earlyReload) {
+ // This is to catch cases where there this is a new sharded collection
+ _reload();
}
- verify( manager || primary );
- verify( ! manager || ! primary );
+ CollectionInfo& ci = _collections[ns];
+ uassert(10181, str::stream() << "not sharded:" << ns, ci.isSharded());
- }
+ invariant(!ci.key().isEmpty());
+ if (!(shouldReload || forceReload) || earlyReload) {
+ return ci.getCM();
+ }
- ChunkManagerPtr DBConfig::getChunkManagerIfExists(const string& ns,
- bool shouldReload,
- bool forceReload) {
+ key = ci.key().copy();
- // Don't report exceptions here as errors in GetLastError
- LastError::Disabled ignoreForGLE(&LastError::get(cc()));
-
- try{
- return getChunkManager(ns, shouldReload, forceReload);
- }
- catch (AssertionException& e) {
- warning() << "chunk manager not found for " << ns << causedBy(e);
- return ChunkManagerPtr();
+ if (ci.getCM()) {
+ oldManager = ci.getCM();
+ oldVersion = ci.getCM()->getVersion();
}
}
- std::shared_ptr<ChunkManager> DBConfig::getChunkManager(const string& ns,
- bool shouldReload,
- bool forceReload) {
- BSONObj key;
- ChunkVersion oldVersion;
- ChunkManagerPtr oldManager;
-
- {
- stdx::lock_guard<stdx::mutex> lk(_lock);
-
- bool earlyReload = !_collections[ns].isSharded() && (shouldReload || forceReload);
- if (earlyReload) {
- // This is to catch cases where there this is a new sharded collection
- _reload();
- }
-
- CollectionInfo& ci = _collections[ns];
- uassert(10181, str::stream() << "not sharded:" << ns, ci.isSharded());
-
- invariant(!ci.key().isEmpty());
-
- if (!(shouldReload || forceReload) || earlyReload) {
+ invariant(!key.isEmpty());
+
+ // TODO: We need to keep this first one-chunk check in until we have a more efficient way of
+ // creating/reusing a chunk manager, as doing so requires copying the full set of chunks currently
+ vector<ChunkType> newestChunk;
+ if (oldVersion.isSet() && !forceReload) {
+ uassertStatusOK(grid.catalogManager()->getChunks(
+ Query(BSON(ChunkType::ns(ns))).sort(ChunkType::DEPRECATED_lastmod(), -1),
+ 1,
+ &newestChunk));
+
+ if (!newestChunk.empty()) {
+ invariant(newestChunk.size() == 1);
+ ChunkVersion v = newestChunk[0].getVersion();
+ if (v.equals(oldVersion)) {
+ stdx::lock_guard<stdx::mutex> lk(_lock);
+ const CollectionInfo& ci = _collections[ns];
+ uassert(15885,
+ str::stream() << "not sharded after reloading from chunks : " << ns,
+ ci.isSharded());
return ci.getCM();
}
-
- key = ci.key().copy();
-
- if (ci.getCM()){
- oldManager = ci.getCM();
- oldVersion = ci.getCM()->getVersion();
- }
}
-
- invariant(!key.isEmpty());
-
- // TODO: We need to keep this first one-chunk check in until we have a more efficient way of
- // creating/reusing a chunk manager, as doing so requires copying the full set of chunks currently
- vector<ChunkType> newestChunk;
- if ( oldVersion.isSet() && ! forceReload ) {
- uassertStatusOK(grid.catalogManager()->getChunks(
- Query(BSON(ChunkType::ns(ns)))
- .sort(ChunkType::DEPRECATED_lastmod(), -1),
- 1,
- &newestChunk));
-
- if (!newestChunk.empty()) {
- invariant(newestChunk.size() == 1);
- ChunkVersion v = newestChunk[0].getVersion();
- if (v.equals(oldVersion)) {
- stdx::lock_guard<stdx::mutex> lk( _lock );
- const CollectionInfo& ci = _collections[ns];
- uassert(15885,
- str::stream() << "not sharded after reloading from chunks : "
- << ns, ci.isSharded());
- return ci.getCM();
- }
- }
- }
- else if (!oldVersion.isSet()) {
- warning() << "version 0 found when " << (forceReload ? "reloading" : "checking")
- << " chunk manager; collection '" << ns << "' initially detected as sharded";
- }
+ } else if (!oldVersion.isSet()) {
+ warning() << "version 0 found when " << (forceReload ? "reloading" : "checking")
+ << " chunk manager; collection '" << ns << "' initially detected as sharded";
+ }
- // we are not locked now, and want to load a new ChunkManager
-
- unique_ptr<ChunkManager> tempChunkManager;
+ // we are not locked now, and want to load a new ChunkManager
- {
- stdx::lock_guard<stdx::mutex> lll ( _hitConfigServerLock );
+ unique_ptr<ChunkManager> tempChunkManager;
- if (!newestChunk.empty() && !forceReload) {
- // If we have a target we're going for see if we've hit already
- stdx::lock_guard<stdx::mutex> lk( _lock );
+ {
+ stdx::lock_guard<stdx::mutex> lll(_hitConfigServerLock);
- CollectionInfo& ci = _collections[ns];
+ if (!newestChunk.empty() && !forceReload) {
+ // If we have a target we're going for see if we've hit already
+ stdx::lock_guard<stdx::mutex> lk(_lock);
- if ( ci.isSharded() && ci.getCM() ) {
- ChunkVersion currentVersion = newestChunk[0].getVersion();
+ CollectionInfo& ci = _collections[ns];
- // Only reload if the version we found is newer than our own in the same epoch
- if (currentVersion <= ci.getCM()->getVersion() &&
- ci.getCM()->getVersion().hasEqualEpoch(currentVersion)) {
+ if (ci.isSharded() && ci.getCM()) {
+ ChunkVersion currentVersion = newestChunk[0].getVersion();
- return ci.getCM();
- }
+ // Only reload if the version we found is newer than our own in the same epoch
+ if (currentVersion <= ci.getCM()->getVersion() &&
+ ci.getCM()->getVersion().hasEqualEpoch(currentVersion)) {
+ return ci.getCM();
}
}
-
- tempChunkManager.reset(new ChunkManager(oldManager->getns(),
- oldManager->getShardKeyPattern(),
- oldManager->isUnique()));
- tempChunkManager->loadExistingRanges(oldManager.get());
-
- if (tempChunkManager->numChunks() == 0) {
- // Maybe we're not sharded any more, so do a full reload
- reload();
-
- return getChunkManager(ns, false);
- }
}
- stdx::lock_guard<stdx::mutex> lk( _lock );
+ tempChunkManager.reset(new ChunkManager(
+ oldManager->getns(), oldManager->getShardKeyPattern(), oldManager->isUnique()));
+ tempChunkManager->loadExistingRanges(oldManager.get());
- CollectionInfo& ci = _collections[ns];
- uassert(14822, (string)"state changed in the middle: " + ns, ci.isSharded());
-
- // Reset if our versions aren't the same
- bool shouldReset = !tempChunkManager->getVersion().equals(ci.getCM()->getVersion());
-
- // Also reset if we're forced to do so
- if (!shouldReset && forceReload) {
- shouldReset = true;
- warning() << "chunk manager reload forced for collection '" << ns
- << "', config version is " << tempChunkManager->getVersion();
- }
+ if (tempChunkManager->numChunks() == 0) {
+ // Maybe we're not sharded any more, so do a full reload
+ reload();
- //
- // LEGACY BEHAVIOR
- //
- // It's possible to get into a state when dropping collections when our new version is
- // less than our prev version. Behave identically to legacy mongos, for now, and warn to
- // draw attention to the problem.
- //
- // TODO: Assert in next version, to allow smooth upgrades
- //
-
- if (shouldReset && tempChunkManager->getVersion() < ci.getCM()->getVersion()) {
- shouldReset = false;
-
- warning() << "not resetting chunk manager for collection '" << ns
- << "', config version is " << tempChunkManager->getVersion() << " and "
- << "old version is " << ci.getCM()->getVersion();
+ return getChunkManager(ns, false);
}
+ }
- // end legacy behavior
+ stdx::lock_guard<stdx::mutex> lk(_lock);
- if (shouldReset) {
- ci.resetCM(tempChunkManager.release());
- }
+ CollectionInfo& ci = _collections[ns];
+ uassert(14822, (string) "state changed in the middle: " + ns, ci.isSharded());
- uassert(15883, str::stream() << "not sharded after chunk manager reset : "
- << ns, ci.isSharded());
+ // Reset if our versions aren't the same
+ bool shouldReset = !tempChunkManager->getVersion().equals(ci.getCM()->getVersion());
- return ci.getCM();
+ // Also reset if we're forced to do so
+ if (!shouldReset && forceReload) {
+ shouldReset = true;
+ warning() << "chunk manager reload forced for collection '" << ns << "', config version is "
+ << tempChunkManager->getVersion();
}
- void DBConfig::setPrimary(const std::string& s) {
- const auto shard = grid.shardRegistry()->getShard(s);
-
- stdx::lock_guard<stdx::mutex> lk( _lock );
- _primaryId = shard->getId();
- _save();
+ //
+ // LEGACY BEHAVIOR
+ //
+ // It's possible to get into a state when dropping collections when our new version is
+ // less than our prev version. Behave identically to legacy mongos, for now, and warn to
+ // draw attention to the problem.
+ //
+ // TODO: Assert in next version, to allow smooth upgrades
+ //
+
+ if (shouldReset && tempChunkManager->getVersion() < ci.getCM()->getVersion()) {
+ shouldReset = false;
+
+ warning() << "not resetting chunk manager for collection '" << ns << "', config version is "
+ << tempChunkManager->getVersion() << " and "
+ << "old version is " << ci.getCM()->getVersion();
}
- bool DBConfig::load() {
- stdx::lock_guard<stdx::mutex> lk( _lock );
- return _load();
+ // end legacy behavior
+
+ if (shouldReset) {
+ ci.resetCM(tempChunkManager.release());
}
- bool DBConfig::_load() {
- StatusWith<DatabaseType> status = grid.catalogManager()->getDatabase(_name);
- if (status == ErrorCodes::DatabaseNotFound) {
- return false;
- }
+ uassert(
+ 15883, str::stream() << "not sharded after chunk manager reset : " << ns, ci.isSharded());
- // All other errors are connectivity, etc so throw an exception.
- uassertStatusOK(status.getStatus());
+ return ci.getCM();
+}
- DatabaseType dbt = status.getValue();
- invariant(_name == dbt.getName());
- _primaryId = dbt.getPrimary();
- _shardingEnabled = dbt.getSharded();
+void DBConfig::setPrimary(const std::string& s) {
+ const auto shard = grid.shardRegistry()->getShard(s);
- // Load all collections
- vector<CollectionType> collections;
- uassertStatusOK(grid.catalogManager()->getCollections(&_name, &collections));
+ stdx::lock_guard<stdx::mutex> lk(_lock);
+ _primaryId = shard->getId();
+ _save();
+}
- int numCollsErased = 0;
- int numCollsSharded = 0;
+bool DBConfig::load() {
+ stdx::lock_guard<stdx::mutex> lk(_lock);
+ return _load();
+}
- for (const auto& coll : collections) {
- if (coll.getDropped()) {
- _collections.erase(coll.getNs());
- numCollsErased++;
- }
- else {
- _collections[coll.getNs()] = CollectionInfo(coll);
- numCollsSharded++;
- }
- }
-
- LOG(2) << "found " << numCollsSharded << " collections left and "
- << numCollsErased << " collections dropped for database " << _name;
-
- return true;
+bool DBConfig::_load() {
+ StatusWith<DatabaseType> status = grid.catalogManager()->getDatabase(_name);
+ if (status == ErrorCodes::DatabaseNotFound) {
+ return false;
}
- void DBConfig::_save(bool db, bool coll) {
- if (db) {
- DatabaseType dbt;
- dbt.setName(_name);
- dbt.setPrimary(_primaryId);
- dbt.setSharded(_shardingEnabled);
+ // All other errors are connectivity, etc so throw an exception.
+ uassertStatusOK(status.getStatus());
- uassertStatusOK(grid.catalogManager()->updateDatabase(_name, dbt));
- }
+ DatabaseType dbt = status.getValue();
+ invariant(_name == dbt.getName());
+ _primaryId = dbt.getPrimary();
+ _shardingEnabled = dbt.getSharded();
- if (coll) {
- for (CollectionInfoMap::iterator i = _collections.begin();
- i != _collections.end();
- ++i) {
+ // Load all collections
+ vector<CollectionType> collections;
+ uassertStatusOK(grid.catalogManager()->getCollections(&_name, &collections));
- if (!i->second.isDirty()) {
- continue;
- }
+ int numCollsErased = 0;
+ int numCollsSharded = 0;
- i->second.save(i->first);
- }
+ for (const auto& coll : collections) {
+ if (coll.getDropped()) {
+ _collections.erase(coll.getNs());
+ numCollsErased++;
+ } else {
+ _collections[coll.getNs()] = CollectionInfo(coll);
+ numCollsSharded++;
}
}
- bool DBConfig::reload() {
- bool successful = false;
+ LOG(2) << "found " << numCollsSharded << " collections left and " << numCollsErased
+ << " collections dropped for database " << _name;
- {
- stdx::lock_guard<stdx::mutex> lk( _lock );
- successful = _reload();
- }
+ return true;
+}
- // If we aren't successful loading the database entry, we don't want to keep the stale
- // object around which has invalid data.
- if (!successful) {
- grid.catalogCache()->invalidate(_name);
- }
+void DBConfig::_save(bool db, bool coll) {
+ if (db) {
+ DatabaseType dbt;
+ dbt.setName(_name);
+ dbt.setPrimary(_primaryId);
+ dbt.setSharded(_shardingEnabled);
- return successful;
+ uassertStatusOK(grid.catalogManager()->updateDatabase(_name, dbt));
}
- bool DBConfig::_reload() {
- // TODO: i don't think is 100% correct
- return _load();
+ if (coll) {
+ for (CollectionInfoMap::iterator i = _collections.begin(); i != _collections.end(); ++i) {
+ if (!i->second.isDirty()) {
+ continue;
+ }
+
+ i->second.save(i->first);
+ }
}
+}
- bool DBConfig::dropDatabase(string& errmsg) {
- /**
- * 1) update config server
- * 2) drop and reset sharded collections
- * 3) drop and reset primary
- * 4) drop everywhere to clean up loose ends
- */
+bool DBConfig::reload() {
+ bool successful = false;
- log() << "DBConfig::dropDatabase: " << _name << endl;
- grid.catalogManager()->logChange(NULL, "dropDatabase.start", _name, BSONObj());
+ {
+ stdx::lock_guard<stdx::mutex> lk(_lock);
+ successful = _reload();
+ }
- // 1
+ // If we aren't successful loading the database entry, we don't want to keep the stale
+ // object around which has invalid data.
+ if (!successful) {
grid.catalogCache()->invalidate(_name);
+ }
- Status result = grid.catalogManager()->remove(DatabaseType::ConfigNS,
- BSON(DatabaseType::name(_name)),
- 0,
- NULL);
- if (!result.isOK()) {
- errmsg = result.reason();
- log() << "could not drop '" << _name << "': " << errmsg << endl;
- return false;
- }
+ return successful;
+}
+
+bool DBConfig::_reload() {
+ // TODO: i don't think is 100% correct
+ return _load();
+}
+
+bool DBConfig::dropDatabase(string& errmsg) {
+ /**
+ * 1) update config server
+ * 2) drop and reset sharded collections
+ * 3) drop and reset primary
+ * 4) drop everywhere to clean up loose ends
+ */
+
+ log() << "DBConfig::dropDatabase: " << _name << endl;
+ grid.catalogManager()->logChange(NULL, "dropDatabase.start", _name, BSONObj());
+
+ // 1
+ grid.catalogCache()->invalidate(_name);
+
+ Status result = grid.catalogManager()->remove(
+ DatabaseType::ConfigNS, BSON(DatabaseType::name(_name)), 0, NULL);
+ if (!result.isOK()) {
+ errmsg = result.reason();
+ log() << "could not drop '" << _name << "': " << errmsg << endl;
+ return false;
+ }
- LOG(1) << "\t removed entry from config server for: " << _name << endl;
+ LOG(1) << "\t removed entry from config server for: " << _name << endl;
- set<ShardId> shardIds;
+ set<ShardId> shardIds;
- // 2
- while ( true ) {
- int num = 0;
- if (!_dropShardedCollections(num, shardIds, errmsg)) {
- return 0;
- }
+ // 2
+ while (true) {
+ int num = 0;
+ if (!_dropShardedCollections(num, shardIds, errmsg)) {
+ return 0;
+ }
- log() << " DBConfig::dropDatabase: " << _name
- << " dropped sharded collections: " << num;
+ log() << " DBConfig::dropDatabase: " << _name << " dropped sharded collections: " << num;
- if (num == 0) {
- break;
- }
+ if (num == 0) {
+ break;
}
+ }
- // 3
- {
- const auto shard = grid.shardRegistry()->getShard(_primaryId);
- ScopedDbConnection conn(shard->getConnString(), 30.0);
- BSONObj res;
- if ( ! conn->dropDatabase( _name , &res ) ) {
- errmsg = res.toString();
- return 0;
- }
- conn.done();
+ // 3
+ {
+ const auto shard = grid.shardRegistry()->getShard(_primaryId);
+ ScopedDbConnection conn(shard->getConnString(), 30.0);
+ BSONObj res;
+ if (!conn->dropDatabase(_name, &res)) {
+ errmsg = res.toString();
+ return 0;
}
+ conn.done();
+ }
- // 4
- for (const ShardId& shardId : shardIds) {
- const auto shard = grid.shardRegistry()->getShard(shardId);
- if (!shard) {
- continue;
- }
-
- ScopedDbConnection conn(shard->getConnString(), 30.0);
- BSONObj res;
- if ( ! conn->dropDatabase( _name , &res ) ) {
- errmsg = res.toString();
- return 0;
- }
- conn.done();
+ // 4
+ for (const ShardId& shardId : shardIds) {
+ const auto shard = grid.shardRegistry()->getShard(shardId);
+ if (!shard) {
+ continue;
}
- LOG(1) << "\t dropped primary db for: " << _name << endl;
-
- grid.catalogManager()->logChange(NULL, "dropDatabase", _name, BSONObj());
-
- return true;
+ ScopedDbConnection conn(shard->getConnString(), 30.0);
+ BSONObj res;
+ if (!conn->dropDatabase(_name, &res)) {
+ errmsg = res.toString();
+ return 0;
+ }
+ conn.done();
}
- bool DBConfig::_dropShardedCollections(int& num, set<ShardId>& shardIds, string& errmsg) {
- num = 0;
- set<string> seen;
- while ( true ) {
- CollectionInfoMap::iterator i = _collections.begin();
- for ( ; i != _collections.end(); ++i ) {
- // log() << "coll : " << i->first << " and " << i->second.isSharded() << endl;
- if ( i->second.isSharded() )
- break;
- }
-
- if ( i == _collections.end() )
- break;
-
- if ( seen.count( i->first ) ) {
- errmsg = "seen a collection twice!";
- return false;
- }
+ LOG(1) << "\t dropped primary db for: " << _name << endl;
- seen.insert( i->first );
- LOG(1) << "\t dropping sharded collection: " << i->first << endl;
+ grid.catalogManager()->logChange(NULL, "dropDatabase", _name, BSONObj());
- i->second.getCM()->getAllShardIds(&shardIds);
+ return true;
+}
- uassertStatusOK(grid.catalogManager()->dropCollection(i->first));
+bool DBConfig::_dropShardedCollections(int& num, set<ShardId>& shardIds, string& errmsg) {
+ num = 0;
+ set<string> seen;
+ while (true) {
+ CollectionInfoMap::iterator i = _collections.begin();
+ for (; i != _collections.end(); ++i) {
+ // log() << "coll : " << i->first << " and " << i->second.isSharded() << endl;
+ if (i->second.isSharded())
+ break;
+ }
- // We should warn, but it's not a fatal error if someone else reloaded the db/coll as
- // unsharded in the meantime
- if( ! removeSharding( i->first ) ){
- warning() << "collection " << i->first
- << " was reloaded as unsharded before drop completed"
- << " during drop of all collections" << endl;
- }
+ if (i == _collections.end())
+ break;
- num++;
- uassert( 10184 , "_dropShardedCollections too many collections - bailing" , num < 100000 );
- LOG(2) << "\t\t dropped " << num << " so far" << endl;
+ if (seen.count(i->first)) {
+ errmsg = "seen a collection twice!";
+ return false;
}
- return true;
- }
+ seen.insert(i->first);
+ LOG(1) << "\t dropping sharded collection: " << i->first << endl;
- void DBConfig::getAllShardIds(set<ShardId>* shardIds) {
- dassert(shardIds);
+ i->second.getCM()->getAllShardIds(&shardIds);
- stdx::lock_guard<stdx::mutex> lk(_lock);
- shardIds->insert(getPrimaryId());
- for (CollectionInfoMap::const_iterator it(_collections.begin()), end(_collections.end());
- it != end;
- ++it) {
- if (it->second.isSharded()) {
- it->second.getCM()->getAllShardIds(shardIds);
- } // TODO: handle collections on non-primary shard
- }
- }
+ uassertStatusOK(grid.catalogManager()->dropCollection(i->first));
- void DBConfig::getAllShardedCollections( set<string>& namespaces ) {
- stdx::lock_guard<stdx::mutex> lk(_lock);
-
- for( CollectionInfoMap::const_iterator i = _collections.begin(); i != _collections.end(); i++ ) {
- log() << "Coll : " << i->first << " sharded? " << i->second.isSharded() << endl;
- if( i->second.isSharded() ) namespaces.insert( i->first );
+ // We should warn, but it's not a fatal error if someone else reloaded the db/coll as
+ // unsharded in the meantime
+ if (!removeSharding(i->first)) {
+ warning() << "collection " << i->first
+ << " was reloaded as unsharded before drop completed"
+ << " during drop of all collections" << endl;
}
+ num++;
+ uassert(10184, "_dropShardedCollections too many collections - bailing", num < 100000);
+ LOG(2) << "\t\t dropped " << num << " so far" << endl;
}
- /* --- ConfigServer ---- */
+ return true;
+}
- void ConfigServer::reloadSettings() {
- auto chunkSizeResult = grid.catalogManager()->getGlobalSettings(SettingsType::ChunkSizeDocKey);
- if (chunkSizeResult.isOK()) {
- const int csize = chunkSizeResult.getValue().getChunkSizeMB();
- LOG(1) << "Found MaxChunkSize: " << csize;
+void DBConfig::getAllShardIds(set<ShardId>* shardIds) {
+ dassert(shardIds);
- if (!Chunk::setMaxChunkSizeSizeMB(csize)) {
- warning() << "invalid chunksize: " << csize;
- }
- }
- else if (chunkSizeResult.getStatus() == ErrorCodes::NoMatchingDocument) {
- const int chunkSize = Chunk::MaxChunkSize / (1024 * 1024);
- Status result =
- grid.catalogManager()->insert(SettingsType::ConfigNS,
- BSON(SettingsType::key(SettingsType::ChunkSizeDocKey)
- << SettingsType::chunkSizeMB(chunkSize)),
- NULL);
- if (!result.isOK()) {
- warning() << "couldn't set chunkSize on config db" << causedBy(result);
- }
- }
- else {
- warning() << "couldn't load settings on config db: " << chunkSizeResult.getStatus();
- }
+ stdx::lock_guard<stdx::mutex> lk(_lock);
+ shardIds->insert(getPrimaryId());
+ for (CollectionInfoMap::const_iterator it(_collections.begin()), end(_collections.end());
+ it != end;
+ ++it) {
+ if (it->second.isSharded()) {
+ it->second.getCM()->getAllShardIds(shardIds);
+ } // TODO: handle collections on non-primary shard
+ }
+}
- // indexes
- Status result = clusterCreateIndex( ChunkType::ConfigNS,
- BSON( ChunkType::ns() << 1 << ChunkType::min() << 1 ),
- true, // unique
- NULL );
+void DBConfig::getAllShardedCollections(set<string>& namespaces) {
+ stdx::lock_guard<stdx::mutex> lk(_lock);
+ for (CollectionInfoMap::const_iterator i = _collections.begin(); i != _collections.end(); i++) {
+ log() << "Coll : " << i->first << " sharded? " << i->second.isSharded() << endl;
+ if (i->second.isSharded())
+ namespaces.insert(i->first);
+ }
+}
+
+/* --- ConfigServer ---- */
+
+void ConfigServer::reloadSettings() {
+ auto chunkSizeResult = grid.catalogManager()->getGlobalSettings(SettingsType::ChunkSizeDocKey);
+ if (chunkSizeResult.isOK()) {
+ const int csize = chunkSizeResult.getValue().getChunkSizeMB();
+ LOG(1) << "Found MaxChunkSize: " << csize;
+
+ if (!Chunk::setMaxChunkSizeSizeMB(csize)) {
+ warning() << "invalid chunksize: " << csize;
+ }
+ } else if (chunkSizeResult.getStatus() == ErrorCodes::NoMatchingDocument) {
+ const int chunkSize = Chunk::MaxChunkSize / (1024 * 1024);
+ Status result =
+ grid.catalogManager()->insert(SettingsType::ConfigNS,
+ BSON(SettingsType::key(SettingsType::ChunkSizeDocKey)
+ << SettingsType::chunkSizeMB(chunkSize)),
+ NULL);
if (!result.isOK()) {
- warning() << "couldn't create ns_1_min_1 index on config db" << causedBy(result);
+ warning() << "couldn't set chunkSize on config db" << causedBy(result);
}
+ } else {
+ warning() << "couldn't load settings on config db: " << chunkSizeResult.getStatus();
+ }
- result = clusterCreateIndex( ChunkType::ConfigNS,
- BSON( ChunkType::ns() << 1 <<
- ChunkType::shard() << 1 <<
- ChunkType::min() << 1 ),
- true, // unique
- NULL );
+ // indexes
+ Status result = clusterCreateIndex(ChunkType::ConfigNS,
+ BSON(ChunkType::ns() << 1 << ChunkType::min() << 1),
+ true, // unique
+ NULL);
- if (!result.isOK()) {
- warning() << "couldn't create ns_1_shard_1_min_1 index on config db"
- << causedBy(result);
- }
+ if (!result.isOK()) {
+ warning() << "couldn't create ns_1_min_1 index on config db" << causedBy(result);
+ }
- result = clusterCreateIndex( ChunkType::ConfigNS,
- BSON( ChunkType::ns() << 1 <<
- ChunkType::DEPRECATED_lastmod() << 1 ),
- true, // unique
- NULL );
+ result = clusterCreateIndex(
+ ChunkType::ConfigNS,
+ BSON(ChunkType::ns() << 1 << ChunkType::shard() << 1 << ChunkType::min() << 1),
+ true, // unique
+ NULL);
- if (!result.isOK()) {
- warning() << "couldn't create ns_1_lastmod_1 index on config db" << causedBy(result);
- }
+ if (!result.isOK()) {
+ warning() << "couldn't create ns_1_shard_1_min_1 index on config db" << causedBy(result);
+ }
- result = clusterCreateIndex( ShardType::ConfigNS,
- BSON( ShardType::host() << 1 ),
- true, // unique
- NULL );
+ result = clusterCreateIndex(ChunkType::ConfigNS,
+ BSON(ChunkType::ns() << 1 << ChunkType::DEPRECATED_lastmod() << 1),
+ true, // unique
+ NULL);
- if (!result.isOK()) {
- warning() << "couldn't create host_1 index on config db" << causedBy(result);
- }
+ if (!result.isOK()) {
+ warning() << "couldn't create ns_1_lastmod_1 index on config db" << causedBy(result);
+ }
- result = clusterCreateIndex(LocksType::ConfigNS,
- BSON(LocksType::lockID() << 1),
- false, // unique
- NULL);
+ result = clusterCreateIndex(ShardType::ConfigNS,
+ BSON(ShardType::host() << 1),
+ true, // unique
+ NULL);
- if (!result.isOK()) {
- warning() << "couldn't create lock id index on config db" << causedBy(result);
- }
+ if (!result.isOK()) {
+ warning() << "couldn't create host_1 index on config db" << causedBy(result);
+ }
- result = clusterCreateIndex( LocksType::ConfigNS,
- BSON( LocksType::state() << 1 <<
- LocksType::process() << 1 ),
- false, // unique
- NULL );
+ result = clusterCreateIndex(LocksType::ConfigNS,
+ BSON(LocksType::lockID() << 1),
+ false, // unique
+ NULL);
- if (!result.isOK()) {
- warning() << "couldn't create state and process id index on config db"
- << causedBy(result);
- }
+ if (!result.isOK()) {
+ warning() << "couldn't create lock id index on config db" << causedBy(result);
+ }
- result = clusterCreateIndex( LockpingsType::ConfigNS,
- BSON( LockpingsType::ping() << 1 ),
- false, // unique
- NULL );
+ result = clusterCreateIndex(LocksType::ConfigNS,
+ BSON(LocksType::state() << 1 << LocksType::process() << 1),
+ false, // unique
+ NULL);
- if (!result.isOK()) {
- warning() << "couldn't create lockping ping time index on config db"
- << causedBy(result);
- }
+ if (!result.isOK()) {
+ warning() << "couldn't create state and process id index on config db" << causedBy(result);
+ }
- result = clusterCreateIndex(TagsType::ConfigNS,
- BSON(TagsType::ns() << 1 << TagsType::min() << 1),
- true, // unique
- NULL);
+ result = clusterCreateIndex(LockpingsType::ConfigNS,
+ BSON(LockpingsType::ping() << 1),
+ false, // unique
+ NULL);
- if (!result.isOK()) {
- warning() << "could not create index ns_1_min_1: " << causedBy(result);
- }
+ if (!result.isOK()) {
+ warning() << "couldn't create lockping ping time index on config db" << causedBy(result);
}
- void ConfigServer::replicaSetChange(const string& setName, const string& newConnectionString) {
- // This is run in it's own thread. Exceptions escaping would result in a call to terminate.
- Client::initThread("replSetChange");
+ result = clusterCreateIndex(TagsType::ConfigNS,
+ BSON(TagsType::ns() << 1 << TagsType::min() << 1),
+ true, // unique
+ NULL);
- try {
- ShardPtr s = Shard::lookupRSName(setName);
- if (!s) {
- LOG(1) << "shard not found for set: " << newConnectionString;
- return;
- }
+ if (!result.isOK()) {
+ warning() << "could not create index ns_1_min_1: " << causedBy(result);
+ }
+}
- Status result = grid.catalogManager()->update(
- ShardType::ConfigNS,
- BSON(ShardType::name(s->getId())),
- BSON("$set" << BSON(ShardType::host(
- newConnectionString))),
- false, // upsert
- false, // multi
- NULL);
- if (!result.isOK()) {
- error() << "RSChangeWatcher: could not update config db for set: "
- << setName
- << " to: " << newConnectionString
- << ": " << result.reason();
- }
- }
- catch (const std::exception& e) {
- log() << "caught exception while updating config servers: " << e.what();
+void ConfigServer::replicaSetChange(const string& setName, const string& newConnectionString) {
+ // This is run in it's own thread. Exceptions escaping would result in a call to terminate.
+ Client::initThread("replSetChange");
+
+ try {
+ ShardPtr s = Shard::lookupRSName(setName);
+ if (!s) {
+ LOG(1) << "shard not found for set: " << newConnectionString;
+ return;
}
- catch (...) {
- log() << "caught unknown exception while updating config servers";
+
+ Status result = grid.catalogManager()->update(
+ ShardType::ConfigNS,
+ BSON(ShardType::name(s->getId())),
+ BSON("$set" << BSON(ShardType::host(newConnectionString))),
+ false, // upsert
+ false, // multi
+ NULL);
+ if (!result.isOK()) {
+ error() << "RSChangeWatcher: could not update config db for set: " << setName
+ << " to: " << newConnectionString << ": " << result.reason();
}
+ } catch (const std::exception& e) {
+ log() << "caught exception while updating config servers: " << e.what();
+ } catch (...) {
+ log() << "caught unknown exception while updating config servers";
}
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/config.h b/src/mongo/s/config.h
index e2216b67786..9622e83f3ac 100644
--- a/src/mongo/s/config.h
+++ b/src/mongo/s/config.h
@@ -36,149 +36,167 @@
namespace mongo {
- class ChunkManager;
- class CollectionType;
- class DatabaseType;
- class DBConfig;
+class ChunkManager;
+class CollectionType;
+class DatabaseType;
+class DBConfig;
- typedef std::shared_ptr<DBConfig> DBConfigPtr;
+typedef std::shared_ptr<DBConfig> DBConfigPtr;
- struct CollectionInfo {
- CollectionInfo() {
- _dirty = false;
- _dropped = false;
- }
+struct CollectionInfo {
+ CollectionInfo() {
+ _dirty = false;
+ _dropped = false;
+ }
- CollectionInfo(const CollectionType& in);
- ~CollectionInfo();
+ CollectionInfo(const CollectionType& in);
+ ~CollectionInfo();
- bool isSharded() const {
- return _cm.get();
- }
+ bool isSharded() const {
+ return _cm.get();
+ }
- std::shared_ptr<ChunkManager> getCM() const {
- return _cm;
- }
+ std::shared_ptr<ChunkManager> getCM() const {
+ return _cm;
+ }
- void resetCM(ChunkManager * cm);
+ void resetCM(ChunkManager* cm);
- void shard(ChunkManager* cm);
- void unshard();
+ void shard(ChunkManager* cm);
+ void unshard();
- bool isDirty() const { return _dirty; }
- bool wasDropped() const { return _dropped; }
+ bool isDirty() const {
+ return _dirty;
+ }
+ bool wasDropped() const {
+ return _dropped;
+ }
- void save(const std::string& ns);
+ void save(const std::string& ns);
- void useChunkManager(std::shared_ptr<ChunkManager> manager);
+ void useChunkManager(std::shared_ptr<ChunkManager> manager);
- bool unique() const { return _unique; }
- BSONObj key() const { return _key; }
+ bool unique() const {
+ return _unique;
+ }
+ BSONObj key() const {
+ return _key;
+ }
- private:
- BSONObj _key;
- bool _unique;
- std::shared_ptr<ChunkManager> _cm;
- bool _dirty;
- bool _dropped;
- };
+private:
+ BSONObj _key;
+ bool _unique;
+ std::shared_ptr<ChunkManager> _cm;
+ bool _dirty;
+ bool _dropped;
+};
+
+/**
+ * top level configuration for a database
+ */
+class DBConfig {
+public:
+ DBConfig(std::string name, const DatabaseType& dbt);
/**
- * top level configuration for a database
+ * The name of the database which this entry caches.
*/
- class DBConfig {
- public:
- DBConfig(std::string name, const DatabaseType& dbt);
-
- /**
- * The name of the database which this entry caches.
- */
- const std::string& name() const { return _name; }
+ const std::string& name() const {
+ return _name;
+ }
- /**
- * Whether sharding is enabled for this database.
- */
- bool isShardingEnabled() const { return _shardingEnabled; }
+ /**
+ * Whether sharding is enabled for this database.
+ */
+ bool isShardingEnabled() const {
+ return _shardingEnabled;
+ }
- const ShardId& getPrimaryId() const { return _primaryId; }
+ const ShardId& getPrimaryId() const {
+ return _primaryId;
+ }
- void enableSharding( bool save = true );
+ void enableSharding(bool save = true);
- /**
- @return true if there was sharding info to remove
- */
- bool removeSharding( const std::string& ns );
+ /**
+ @return true if there was sharding info to remove
+ */
+ bool removeSharding(const std::string& ns);
- /**
- * @return whether or not the 'ns' collection is partitioned
- */
- bool isSharded( const std::string& ns );
+ /**
+ * @return whether or not the 'ns' collection is partitioned
+ */
+ bool isSharded(const std::string& ns);
- // Atomically returns *either* the chunk manager *or* the primary shard for the collection,
- // neither if the collection doesn't exist.
- void getChunkManagerOrPrimary(const std::string& ns,
- std::shared_ptr<ChunkManager>& manager,
- std::shared_ptr<Shard>& primary);
+ // Atomically returns *either* the chunk manager *or* the primary shard for the collection,
+ // neither if the collection doesn't exist.
+ void getChunkManagerOrPrimary(const std::string& ns,
+ std::shared_ptr<ChunkManager>& manager,
+ std::shared_ptr<Shard>& primary);
- std::shared_ptr<ChunkManager> getChunkManager(const std::string& ns, bool reload = false, bool forceReload = false);
- std::shared_ptr<ChunkManager> getChunkManagerIfExists(const std::string& ns, bool reload = false, bool forceReload = false);
+ std::shared_ptr<ChunkManager> getChunkManager(const std::string& ns,
+ bool reload = false,
+ bool forceReload = false);
+ std::shared_ptr<ChunkManager> getChunkManagerIfExists(const std::string& ns,
+ bool reload = false,
+ bool forceReload = false);
- /**
- * Returns shard id for primary shard for the database for which this DBConfig represents.
- */
- const ShardId& getShardId(const std::string& ns);
+ /**
+ * Returns shard id for primary shard for the database for which this DBConfig represents.
+ */
+ const ShardId& getShardId(const std::string& ns);
- void setPrimary( const std::string& s );
+ void setPrimary(const std::string& s);
- bool load();
- bool reload();
+ bool load();
+ bool reload();
- bool dropDatabase( std::string& errmsg );
+ bool dropDatabase(std::string& errmsg);
- void getAllShardIds(std::set<ShardId>* shardIds);
- void getAllShardedCollections(std::set<std::string>& namespaces);
+ void getAllShardIds(std::set<ShardId>* shardIds);
+ void getAllShardedCollections(std::set<std::string>& namespaces);
- protected:
- typedef std::map<std::string, CollectionInfo> CollectionInfoMap;
+protected:
+ typedef std::map<std::string, CollectionInfo> CollectionInfoMap;
- /**
- lockless
- */
- bool _isSharded( const std::string& ns );
+ /**
+ lockless
+ */
+ bool _isSharded(const std::string& ns);
- bool _dropShardedCollections(int& num, std::set<ShardId>& shardIds, std::string& errmsg);
+ bool _dropShardedCollections(int& num, std::set<ShardId>& shardIds, std::string& errmsg);
- bool _load();
- bool _reload();
- void _save( bool db = true, bool coll = true );
+ bool _load();
+ bool _reload();
+ void _save(bool db = true, bool coll = true);
- // Name of the database which this entry caches
- const std::string _name;
+ // Name of the database which this entry caches
+ const std::string _name;
- // Primary shard id
- ShardId _primaryId;
+ // Primary shard id
+ ShardId _primaryId;
- // Whether sharding has been enabled for this database
- bool _shardingEnabled;
+ // Whether sharding has been enabled for this database
+ bool _shardingEnabled;
- // Set of collections and lock to protect access
- stdx::mutex _lock;
- CollectionInfoMap _collections;
+ // Set of collections and lock to protect access
+ stdx::mutex _lock;
+ CollectionInfoMap _collections;
- // Ensures that only one thread at a time loads collection configuration data from
- // the config server
- stdx::mutex _hitConfigServerLock;
- };
+ // Ensures that only one thread at a time loads collection configuration data from
+ // the config server
+ stdx::mutex _hitConfigServerLock;
+};
- class ConfigServer {
- public:
- static void reloadSettings();
+class ConfigServer {
+public:
+ static void reloadSettings();
- static void replicaSetChange(const std::string& setName,
- const std::string& newConnectionString);
- };
+ static void replicaSetChange(const std::string& setName,
+ const std::string& newConnectionString);
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/cursors.cpp b/src/mongo/s/cursors.cpp
index 31f62c1e2b6..6b4b4e6f2c6 100644
--- a/src/mongo/s/cursors.cpp
+++ b/src/mongo/s/cursors.cpp
@@ -54,466 +54,461 @@
namespace mongo {
- using std::unique_ptr;
- using std::endl;
- using std::string;
- using std::stringstream;
-
- const int ShardedClientCursor::INIT_REPLY_BUFFER_SIZE = 32768;
-
- // Note: There is no counter for shardedEver from cursorInfo since it is deprecated
- static Counter64 cursorStatsMultiTarget;
- static Counter64 cursorStatsSingleTarget;
-
- // Simple class to report the sum total open cursors = sharded + refs
- class CursorStatsSum {
- public:
- operator long long() const {
- return get();
- }
- long long get() const {
- return cursorStatsMultiTarget.get() + cursorStatsSingleTarget.get();
- }
- };
-
- static CursorStatsSum cursorStatsTotalOpen;
+using std::unique_ptr;
+using std::endl;
+using std::string;
+using std::stringstream;
+
+const int ShardedClientCursor::INIT_REPLY_BUFFER_SIZE = 32768;
+
+// Note: There is no counter for shardedEver from cursorInfo since it is deprecated
+static Counter64 cursorStatsMultiTarget;
+static Counter64 cursorStatsSingleTarget;
+
+// Simple class to report the sum total open cursors = sharded + refs
+class CursorStatsSum {
+public:
+ operator long long() const {
+ return get();
+ }
+ long long get() const {
+ return cursorStatsMultiTarget.get() + cursorStatsSingleTarget.get();
+ }
+};
- static ServerStatusMetricField<Counter64> dCursorStatsMultiTarget( "cursor.open.multiTarget",
- &cursorStatsMultiTarget);
- static ServerStatusMetricField<Counter64> dCursorStatsSingleTarget( "cursor.open.singleTarget",
- &cursorStatsSingleTarget);
- static ServerStatusMetricField<CursorStatsSum> dCursorStatsTotalOpen( "cursor.open.total",
- &cursorStatsTotalOpen);
+static CursorStatsSum cursorStatsTotalOpen;
+static ServerStatusMetricField<Counter64> dCursorStatsMultiTarget("cursor.open.multiTarget",
+ &cursorStatsMultiTarget);
+static ServerStatusMetricField<Counter64> dCursorStatsSingleTarget("cursor.open.singleTarget",
+ &cursorStatsSingleTarget);
+static ServerStatusMetricField<CursorStatsSum> dCursorStatsTotalOpen("cursor.open.total",
+ &cursorStatsTotalOpen);
- // -------- ShardedCursor -----------
- ShardedClientCursor::ShardedClientCursor( QueryMessage& q,
- ParallelSortClusteredCursor * cursor ) {
- verify( cursor );
- _cursor = cursor;
+// -------- ShardedCursor -----------
- _skip = q.ntoskip;
- _ntoreturn = q.ntoreturn;
+ShardedClientCursor::ShardedClientCursor(QueryMessage& q, ParallelSortClusteredCursor* cursor) {
+ verify(cursor);
+ _cursor = cursor;
- _totalSent = 0;
- _done = false;
+ _skip = q.ntoskip;
+ _ntoreturn = q.ntoreturn;
- _id = 0;
+ _totalSent = 0;
+ _done = false;
- if ( q.queryOptions & QueryOption_NoCursorTimeout ) {
- _lastAccessMillis = 0;
- }
- else
- _lastAccessMillis = Listener::getElapsedTimeMillis();
+ _id = 0;
- cursorStatsMultiTarget.increment();
- }
+ if (q.queryOptions & QueryOption_NoCursorTimeout) {
+ _lastAccessMillis = 0;
+ } else
+ _lastAccessMillis = Listener::getElapsedTimeMillis();
- ShardedClientCursor::~ShardedClientCursor() {
- verify( _cursor );
- delete _cursor;
- _cursor = 0;
- cursorStatsMultiTarget.decrement();
- }
+ cursorStatsMultiTarget.increment();
+}
- long long ShardedClientCursor::getId() {
- if ( _id <= 0 ) {
- _id = cursorCache.genId();
- verify( _id >= 0 );
- }
- return _id;
- }
+ShardedClientCursor::~ShardedClientCursor() {
+ verify(_cursor);
+ delete _cursor;
+ _cursor = 0;
+ cursorStatsMultiTarget.decrement();
+}
- int ShardedClientCursor::getTotalSent() const {
- return _totalSent;
+long long ShardedClientCursor::getId() {
+ if (_id <= 0) {
+ _id = cursorCache.genId();
+ verify(_id >= 0);
}
+ return _id;
+}
- void ShardedClientCursor::accessed() {
- if ( _lastAccessMillis > 0 )
- _lastAccessMillis = Listener::getElapsedTimeMillis();
- }
+int ShardedClientCursor::getTotalSent() const {
+ return _totalSent;
+}
- long long ShardedClientCursor::idleTime( long long now ) {
- if ( _lastAccessMillis == 0 )
- return 0;
- return now - _lastAccessMillis;
- }
+void ShardedClientCursor::accessed() {
+ if (_lastAccessMillis > 0)
+ _lastAccessMillis = Listener::getElapsedTimeMillis();
+}
- bool ShardedClientCursor::sendNextBatch(int ntoreturn, BufBuilder& buffer, int& docCount) {
- uassert( 10191 , "cursor already done" , ! _done );
-
- int maxSize = 1024 * 1024;
- if ( _totalSent > 0 )
- maxSize *= 3;
-
- docCount = 0;
-
- // If ntoreturn is negative, it means that we should send up to -ntoreturn results
- // back to the client, and that we should only send a *single batch*. An ntoreturn of
- // 1 is also a special case which means "return up to 1 result in a single batch" (so
- // that +1 actually has the same meaning of -1). For all other values of ntoreturn, we
- // may have to return multiple batches.
- const bool sendMoreBatches = ntoreturn == 0 || ntoreturn > 1;
- ntoreturn = abs( ntoreturn );
-
- bool cursorHasMore = true;
- while ( ( cursorHasMore = _cursor->more() ) ) {
- BSONObj o = _cursor->next();
-
- buffer.appendBuf( (void*)o.objdata() , o.objsize() );
- docCount++;
- // Ensure that the next batch will never wind up requesting more docs from the shard
- // than are remaining to satisfy the initial ntoreturn.
- if (ntoreturn != 0) {
- _cursor->setBatchSize(ntoreturn - docCount);
- }
+long long ShardedClientCursor::idleTime(long long now) {
+ if (_lastAccessMillis == 0)
+ return 0;
+ return now - _lastAccessMillis;
+}
- if ( buffer.len() > maxSize ) {
- break;
- }
+bool ShardedClientCursor::sendNextBatch(int ntoreturn, BufBuilder& buffer, int& docCount) {
+ uassert(10191, "cursor already done", !_done);
+
+ int maxSize = 1024 * 1024;
+ if (_totalSent > 0)
+ maxSize *= 3;
+
+ docCount = 0;
+
+ // If ntoreturn is negative, it means that we should send up to -ntoreturn results
+ // back to the client, and that we should only send a *single batch*. An ntoreturn of
+ // 1 is also a special case which means "return up to 1 result in a single batch" (so
+ // that +1 actually has the same meaning of -1). For all other values of ntoreturn, we
+ // may have to return multiple batches.
+ const bool sendMoreBatches = ntoreturn == 0 || ntoreturn > 1;
+ ntoreturn = abs(ntoreturn);
+
+ bool cursorHasMore = true;
+ while ((cursorHasMore = _cursor->more())) {
+ BSONObj o = _cursor->next();
+
+ buffer.appendBuf((void*)o.objdata(), o.objsize());
+ docCount++;
+ // Ensure that the next batch will never wind up requesting more docs from the shard
+ // than are remaining to satisfy the initial ntoreturn.
+ if (ntoreturn != 0) {
+ _cursor->setBatchSize(ntoreturn - docCount);
+ }
- if ( docCount == ntoreturn ) {
- // soft limit aka batch size
- break;
- }
+ if (buffer.len() > maxSize) {
+ break;
+ }
- if ( ntoreturn == 0 && _totalSent == 0 && docCount >= 100 ) {
- // first batch should be max 100 unless batch size specified
- break;
- }
+ if (docCount == ntoreturn) {
+ // soft limit aka batch size
+ break;
}
- // We need to request another batch if the following two conditions hold:
- //
- // 1. ntoreturn is positive and not equal to 1 (see the comment above). This condition
- // is stored in 'sendMoreBatches'.
- //
- // 2. The last call to _cursor->more() was true (i.e. we never explicitly got a false
- // value from _cursor->more()). This condition is stored in 'cursorHasMore'. If the server
- // hits EOF while executing a query or a getmore, it will pass a cursorId of 0 in the
- // query response to indicate that there are no more results. In this case, _cursor->more()
- // will be explicitly false, and we know for sure that we do not have to send more batches.
- //
- // On the other hand, if _cursor->more() is true there may or may not be more results.
- // Suppose that the mongod generates enough results to fill this batch. In this case it
- // does not know whether not there are more, because doing so would require requesting an
- // extra result and seeing whether we get EOF. The mongod sends a valid cursorId to
- // indicate that there may be more. We do the same here: we indicate that there may be
- // more results to retrieve by setting 'hasMoreBatches' to true.
- bool hasMoreBatches = sendMoreBatches && cursorHasMore;
-
- LOG(5) << "\t hasMoreBatches: " << hasMoreBatches
- << " sendMoreBatches: " << sendMoreBatches
- << " cursorHasMore: " << cursorHasMore
- << " ntoreturn: " << ntoreturn
- << " num: " << docCount
- << " id:" << getId()
- << " totalSent: " << _totalSent << endl;
-
- _totalSent += docCount;
- _done = ! hasMoreBatches;
-
- return hasMoreBatches;
+ if (ntoreturn == 0 && _totalSent == 0 && docCount >= 100) {
+ // first batch should be max 100 unless batch size specified
+ break;
+ }
}
- // ---- CursorCache -----
+ // We need to request another batch if the following two conditions hold:
+ //
+ // 1. ntoreturn is positive and not equal to 1 (see the comment above). This condition
+ // is stored in 'sendMoreBatches'.
+ //
+ // 2. The last call to _cursor->more() was true (i.e. we never explicitly got a false
+ // value from _cursor->more()). This condition is stored in 'cursorHasMore'. If the server
+ // hits EOF while executing a query or a getmore, it will pass a cursorId of 0 in the
+ // query response to indicate that there are no more results. In this case, _cursor->more()
+ // will be explicitly false, and we know for sure that we do not have to send more batches.
+ //
+ // On the other hand, if _cursor->more() is true there may or may not be more results.
+ // Suppose that the mongod generates enough results to fill this batch. In this case it
+ // does not know whether not there are more, because doing so would require requesting an
+ // extra result and seeing whether we get EOF. The mongod sends a valid cursorId to
+ // indicate that there may be more. We do the same here: we indicate that there may be
+ // more results to retrieve by setting 'hasMoreBatches' to true.
+ bool hasMoreBatches = sendMoreBatches && cursorHasMore;
+
+ LOG(5) << "\t hasMoreBatches: " << hasMoreBatches << " sendMoreBatches: " << sendMoreBatches
+ << " cursorHasMore: " << cursorHasMore << " ntoreturn: " << ntoreturn
+ << " num: " << docCount << " id:" << getId() << " totalSent: " << _totalSent << endl;
+
+ _totalSent += docCount;
+ _done = !hasMoreBatches;
+
+ return hasMoreBatches;
+}
- long long CursorCache::TIMEOUT = 10 * 60 * 1000 /* 10 minutes */;
- ExportedServerParameter<long long> cursorCacheTimeoutConfig(ServerParameterSet::getGlobal(),
- "cursorTimeoutMillis",
- &CursorCache::TIMEOUT,
- true, true);
+// ---- CursorCache -----
- unsigned getCCRandomSeed() {
- unique_ptr<SecureRandom> sr( SecureRandom::create() );
- return sr->nextInt64();
- }
+long long CursorCache::TIMEOUT = 10 * 60 * 1000 /* 10 minutes */;
+ExportedServerParameter<long long> cursorCacheTimeoutConfig(
+ ServerParameterSet::getGlobal(), "cursorTimeoutMillis", &CursorCache::TIMEOUT, true, true);
- CursorCache::CursorCache()
- : _random( getCCRandomSeed() ),
- _shardedTotal(0) {
- }
+unsigned getCCRandomSeed() {
+ unique_ptr<SecureRandom> sr(SecureRandom::create());
+ return sr->nextInt64();
+}
- CursorCache::~CursorCache() {
- // TODO: delete old cursors?
- bool print = shouldLog(logger::LogSeverity::Debug(1));
- if ( _cursors.size() || _refs.size() )
- print = true;
- verify(_refs.size() == _refsNS.size());
-
- if ( print )
- log() << " CursorCache at shutdown - "
- << " sharded: " << _cursors.size()
- << " passthrough: " << _refs.size()
- << endl;
- }
+CursorCache::CursorCache() : _random(getCCRandomSeed()), _shardedTotal(0) {}
- ShardedClientCursorPtr CursorCache::get( long long id ) const {
- LOG(_myLogLevel) << "CursorCache::get id: " << id << endl;
- stdx::lock_guard<stdx::mutex> lk( _mutex );
- MapSharded::const_iterator i = _cursors.find( id );
- if ( i == _cursors.end() ) {
- return ShardedClientCursorPtr();
- }
- i->second->accessed();
- return i->second;
- }
+CursorCache::~CursorCache() {
+ // TODO: delete old cursors?
+ bool print = shouldLog(logger::LogSeverity::Debug(1));
+ if (_cursors.size() || _refs.size())
+ print = true;
+ verify(_refs.size() == _refsNS.size());
- int CursorCache::getMaxTimeMS( long long id ) const {
- verify( id );
- stdx::lock_guard<stdx::mutex> lk( _mutex );
- MapShardedInt::const_iterator i = _cursorsMaxTimeMS.find( id );
- return ( i != _cursorsMaxTimeMS.end() ) ? i->second : 0;
- }
+ if (print)
+ log() << " CursorCache at shutdown - "
+ << " sharded: " << _cursors.size() << " passthrough: " << _refs.size() << endl;
+}
- void CursorCache::store( ShardedClientCursorPtr cursor, int maxTimeMS ) {
- LOG(_myLogLevel) << "CursorCache::store cursor " << " id: " << cursor->getId()
- << (maxTimeMS != kMaxTimeCursorNoTimeLimit ? str::stream() << "maxTimeMS: " << maxTimeMS
- : string(""))
- << endl;
- verify( cursor->getId() );
- verify( maxTimeMS == kMaxTimeCursorTimeLimitExpired
- || maxTimeMS == kMaxTimeCursorNoTimeLimit
- || maxTimeMS > 0 );
- stdx::lock_guard<stdx::mutex> lk( _mutex );
- _cursorsMaxTimeMS[cursor->getId()] = maxTimeMS;
- _cursors[cursor->getId()] = cursor;
- _shardedTotal++;
+ShardedClientCursorPtr CursorCache::get(long long id) const {
+ LOG(_myLogLevel) << "CursorCache::get id: " << id << endl;
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ MapSharded::const_iterator i = _cursors.find(id);
+ if (i == _cursors.end()) {
+ return ShardedClientCursorPtr();
}
+ i->second->accessed();
+ return i->second;
+}
- void CursorCache::updateMaxTimeMS( long long id, int maxTimeMS ) {
- verify( id );
- verify( maxTimeMS == kMaxTimeCursorTimeLimitExpired
- || maxTimeMS == kMaxTimeCursorNoTimeLimit
- || maxTimeMS > 0 );
- stdx::lock_guard<stdx::mutex> lk( _mutex );
- _cursorsMaxTimeMS[id] = maxTimeMS;
- }
+int CursorCache::getMaxTimeMS(long long id) const {
+ verify(id);
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ MapShardedInt::const_iterator i = _cursorsMaxTimeMS.find(id);
+ return (i != _cursorsMaxTimeMS.end()) ? i->second : 0;
+}
- void CursorCache::remove( long long id ) {
- verify( id );
- stdx::lock_guard<stdx::mutex> lk( _mutex );
- _cursorsMaxTimeMS.erase( id );
- _cursors.erase( id );
- }
+void CursorCache::store(ShardedClientCursorPtr cursor, int maxTimeMS) {
+ LOG(_myLogLevel) << "CursorCache::store cursor "
+ << " id: " << cursor->getId()
+ << (maxTimeMS != kMaxTimeCursorNoTimeLimit
+ ? str::stream() << "maxTimeMS: " << maxTimeMS
+ : string("")) << endl;
+ verify(cursor->getId());
+ verify(maxTimeMS == kMaxTimeCursorTimeLimitExpired || maxTimeMS == kMaxTimeCursorNoTimeLimit ||
+ maxTimeMS > 0);
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _cursorsMaxTimeMS[cursor->getId()] = maxTimeMS;
+ _cursors[cursor->getId()] = cursor;
+ _shardedTotal++;
+}
- void CursorCache::removeRef( long long id ) {
- verify( id );
- stdx::lock_guard<stdx::mutex> lk( _mutex );
- _refs.erase( id );
- _refsNS.erase( id );
- cursorStatsSingleTarget.decrement();
- }
+void CursorCache::updateMaxTimeMS(long long id, int maxTimeMS) {
+ verify(id);
+ verify(maxTimeMS == kMaxTimeCursorTimeLimitExpired || maxTimeMS == kMaxTimeCursorNoTimeLimit ||
+ maxTimeMS > 0);
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _cursorsMaxTimeMS[id] = maxTimeMS;
+}
- void CursorCache::storeRef(const std::string& server, long long id, const std::string& ns) {
- LOG(_myLogLevel) << "CursorCache::storeRef server: " << server << " id: " << id << endl;
- verify( id );
- stdx::lock_guard<stdx::mutex> lk( _mutex );
- _refs[id] = server;
- _refsNS[id] = ns;
- cursorStatsSingleTarget.increment();
- }
+void CursorCache::remove(long long id) {
+ verify(id);
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _cursorsMaxTimeMS.erase(id);
+ _cursors.erase(id);
+}
- string CursorCache::getRef( long long id ) const {
- verify( id );
- stdx::lock_guard<stdx::mutex> lk( _mutex );
- MapNormal::const_iterator i = _refs.find( id );
+void CursorCache::removeRef(long long id) {
+ verify(id);
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _refs.erase(id);
+ _refsNS.erase(id);
+ cursorStatsSingleTarget.decrement();
+}
- LOG(_myLogLevel) << "CursorCache::getRef id: " << id << " out: " << ( i == _refs.end() ? " NONE " : i->second ) << endl;
+void CursorCache::storeRef(const std::string& server, long long id, const std::string& ns) {
+ LOG(_myLogLevel) << "CursorCache::storeRef server: " << server << " id: " << id << endl;
+ verify(id);
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _refs[id] = server;
+ _refsNS[id] = ns;
+ cursorStatsSingleTarget.increment();
+}
- if ( i == _refs.end() )
- return "";
- return i->second;
- }
+string CursorCache::getRef(long long id) const {
+ verify(id);
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ MapNormal::const_iterator i = _refs.find(id);
- std::string CursorCache::getRefNS(long long id) const {
- verify(id);
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- MapNormal::const_iterator i = _refsNS.find(id);
+ LOG(_myLogLevel) << "CursorCache::getRef id: " << id
+ << " out: " << (i == _refs.end() ? " NONE " : i->second) << endl;
- LOG(_myLogLevel) << "CursorCache::getRefNs id: " << id
- << " out: " << ( i == _refsNS.end() ? " NONE " : i->second ) << std::endl;
+ if (i == _refs.end())
+ return "";
+ return i->second;
+}
- if ( i == _refsNS.end() )
- return "";
- return i->second;
- }
+std::string CursorCache::getRefNS(long long id) const {
+ verify(id);
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ MapNormal::const_iterator i = _refsNS.find(id);
+ LOG(_myLogLevel) << "CursorCache::getRefNs id: " << id
+ << " out: " << (i == _refsNS.end() ? " NONE " : i->second) << std::endl;
- long long CursorCache::genId() {
- while ( true ) {
- stdx::lock_guard<stdx::mutex> lk( _mutex );
+ if (i == _refsNS.end())
+ return "";
+ return i->second;
+}
- long long x = Listener::getElapsedTimeMillis() << 32;
- x |= _random.nextInt32();
- if ( x == 0 )
- continue;
+long long CursorCache::genId() {
+ while (true) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
- if ( x < 0 )
- x *= -1;
+ long long x = Listener::getElapsedTimeMillis() << 32;
+ x |= _random.nextInt32();
- MapSharded::iterator i = _cursors.find( x );
- if ( i != _cursors.end() )
- continue;
+ if (x == 0)
+ continue;
- MapNormal::iterator j = _refs.find( x );
- if ( j != _refs.end() )
- continue;
+ if (x < 0)
+ x *= -1;
- return x;
- }
+ MapSharded::iterator i = _cursors.find(x);
+ if (i != _cursors.end())
+ continue;
+
+ MapNormal::iterator j = _refs.find(x);
+ if (j != _refs.end())
+ continue;
+
+ return x;
}
+}
- void CursorCache::gotKillCursors(Message& m ) {
- LastError::get(cc()).disable();
- DbMessage dbmessage(m);
- int n = dbmessage.pullInt();
+void CursorCache::gotKillCursors(Message& m) {
+ LastError::get(cc()).disable();
+ DbMessage dbmessage(m);
+ int n = dbmessage.pullInt();
- if ( n > 2000 ) {
- ( n < 30000 ? warning() : error() ) << "receivedKillCursors, n=" << n << endl;
- }
+ if (n > 2000) {
+ (n < 30000 ? warning() : error()) << "receivedKillCursors, n=" << n << endl;
+ }
- uassert( 13286 , "sent 0 cursors to kill" , n >= 1 );
- uassert( 13287 , "too many cursors to kill" , n < 30000 );
- massert( 18632 , str::stream() << "bad kill cursors size: " << m.dataSize(),
- m.dataSize() == 8 + ( 8 * n ) );
+ uassert(13286, "sent 0 cursors to kill", n >= 1);
+ uassert(13287, "too many cursors to kill", n < 30000);
+ massert(18632,
+ str::stream() << "bad kill cursors size: " << m.dataSize(),
+ m.dataSize() == 8 + (8 * n));
- ConstDataCursor cursors(dbmessage.getArray(n));
+ ConstDataCursor cursors(dbmessage.getArray(n));
- ClientBasic* client = ClientBasic::getCurrent();
- AuthorizationSession* authSession = AuthorizationSession::get(client);
- for ( int i=0; i<n; i++ ) {
- long long id = cursors.readAndAdvance<LittleEndian<int64_t>>();
- LOG(_myLogLevel) << "CursorCache::gotKillCursors id: " << id << endl;
+ ClientBasic* client = ClientBasic::getCurrent();
+ AuthorizationSession* authSession = AuthorizationSession::get(client);
+ for (int i = 0; i < n; i++) {
+ long long id = cursors.readAndAdvance<LittleEndian<int64_t>>();
+ LOG(_myLogLevel) << "CursorCache::gotKillCursors id: " << id << endl;
- if ( ! id ) {
- warning() << " got cursor id of 0 to kill" << endl;
- continue;
- }
+ if (!id) {
+ warning() << " got cursor id of 0 to kill" << endl;
+ continue;
+ }
- string server;
- {
- stdx::lock_guard<stdx::mutex> lk( _mutex );
-
- MapSharded::iterator i = _cursors.find( id );
- if ( i != _cursors.end() ) {
- Status authorizationStatus = authSession->checkAuthForKillCursors(
- NamespaceString(i->second->getNS()), id);
- audit::logKillCursorsAuthzCheck(
- client,
- NamespaceString(i->second->getNS()),
- id,
- authorizationStatus.isOK() ? ErrorCodes::OK : ErrorCodes::Unauthorized);
- if (authorizationStatus.isOK()) {
- _cursorsMaxTimeMS.erase( i->second->getId() );
- _cursors.erase( i );
- }
- continue;
- }
+ string server;
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
- MapNormal::iterator refsIt = _refs.find(id);
- MapNormal::iterator refsNSIt = _refsNS.find(id);
- if (refsIt == _refs.end()) {
- warning() << "can't find cursor: " << id << endl;
- continue;
- }
- verify(refsNSIt != _refsNS.end());
- Status authorizationStatus = authSession->checkAuthForKillCursors(
- NamespaceString(refsNSIt->second), id);
+ MapSharded::iterator i = _cursors.find(id);
+ if (i != _cursors.end()) {
+ Status authorizationStatus =
+ authSession->checkAuthForKillCursors(NamespaceString(i->second->getNS()), id);
audit::logKillCursorsAuthzCheck(
- client,
- NamespaceString(refsNSIt->second),
- id,
- authorizationStatus.isOK() ? ErrorCodes::OK : ErrorCodes::Unauthorized);
- if (!authorizationStatus.isOK()) {
- continue;
+ client,
+ NamespaceString(i->second->getNS()),
+ id,
+ authorizationStatus.isOK() ? ErrorCodes::OK : ErrorCodes::Unauthorized);
+ if (authorizationStatus.isOK()) {
+ _cursorsMaxTimeMS.erase(i->second->getId());
+ _cursors.erase(i);
}
- server = refsIt->second;
- _refs.erase(refsIt);
- _refsNS.erase(refsNSIt);
- cursorStatsSingleTarget.decrement();
+ continue;
}
- LOG(_myLogLevel) << "CursorCache::found gotKillCursors id: " << id << " server: " << server << endl;
-
- verify( server.size() );
- ScopedDbConnection conn(server);
- conn->killCursor( id );
- conn.done();
+ MapNormal::iterator refsIt = _refs.find(id);
+ MapNormal::iterator refsNSIt = _refsNS.find(id);
+ if (refsIt == _refs.end()) {
+ warning() << "can't find cursor: " << id << endl;
+ continue;
+ }
+ verify(refsNSIt != _refsNS.end());
+ Status authorizationStatus =
+ authSession->checkAuthForKillCursors(NamespaceString(refsNSIt->second), id);
+ audit::logKillCursorsAuthzCheck(client,
+ NamespaceString(refsNSIt->second),
+ id,
+ authorizationStatus.isOK() ? ErrorCodes::OK
+ : ErrorCodes::Unauthorized);
+ if (!authorizationStatus.isOK()) {
+ continue;
+ }
+ server = refsIt->second;
+ _refs.erase(refsIt);
+ _refsNS.erase(refsNSIt);
+ cursorStatsSingleTarget.decrement();
}
- }
- void CursorCache::appendInfo( BSONObjBuilder& result ) const {
- stdx::lock_guard<stdx::mutex> lk( _mutex );
- result.append( "sharded", static_cast<int>(cursorStatsMultiTarget.get()));
- result.appendNumber( "shardedEver" , _shardedTotal );
- result.append( "refs", static_cast<int>(cursorStatsSingleTarget.get()));
- result.append( "totalOpen", static_cast<int>(cursorStatsTotalOpen.get()));
+ LOG(_myLogLevel) << "CursorCache::found gotKillCursors id: " << id << " server: " << server
+ << endl;
+
+ verify(server.size());
+ ScopedDbConnection conn(server);
+ conn->killCursor(id);
+ conn.done();
}
+}
- void CursorCache::doTimeouts() {
- long long now = Listener::getElapsedTimeMillis();
- stdx::lock_guard<stdx::mutex> lk( _mutex );
- for ( MapSharded::iterator i=_cursors.begin(); i!=_cursors.end(); ++i ) {
- // Note: cursors with no timeout will always have an idleTime of 0
- long long idleFor = i->second->idleTime( now );
- if ( idleFor < TIMEOUT ) {
- continue;
- }
- log() << "killing old cursor " << i->second->getId() << " idle for: " << idleFor << "ms" << endl; // TODO: make LOG(1)
- _cursorsMaxTimeMS.erase( i->second->getId() );
- _cursors.erase( i );
- i = _cursors.begin(); // possible 2nd entry will get skipped, will get on next pass
- if ( i == _cursors.end() )
- break;
+void CursorCache::appendInfo(BSONObjBuilder& result) const {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ result.append("sharded", static_cast<int>(cursorStatsMultiTarget.get()));
+ result.appendNumber("shardedEver", _shardedTotal);
+ result.append("refs", static_cast<int>(cursorStatsSingleTarget.get()));
+ result.append("totalOpen", static_cast<int>(cursorStatsTotalOpen.get()));
+}
+
+void CursorCache::doTimeouts() {
+ long long now = Listener::getElapsedTimeMillis();
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ for (MapSharded::iterator i = _cursors.begin(); i != _cursors.end(); ++i) {
+ // Note: cursors with no timeout will always have an idleTime of 0
+ long long idleFor = i->second->idleTime(now);
+ if (idleFor < TIMEOUT) {
+ continue;
}
+ log() << "killing old cursor " << i->second->getId() << " idle for: " << idleFor << "ms"
+ << endl; // TODO: make LOG(1)
+ _cursorsMaxTimeMS.erase(i->second->getId());
+ _cursors.erase(i);
+ i = _cursors.begin(); // possible 2nd entry will get skipped, will get on next pass
+ if (i == _cursors.end())
+ break;
}
+}
- CursorCache cursorCache;
-
- const int CursorCache::_myLogLevel = 3;
+CursorCache cursorCache;
- class CursorTimeoutTask : public task::Task {
- public:
- virtual string name() const { return "cursorTimeout"; }
- virtual void doWork() {
- cursorCache.doTimeouts();
- }
- };
+const int CursorCache::_myLogLevel = 3;
- void CursorCache::startTimeoutThread() {
- task::repeat( new CursorTimeoutTask , 4000 );
+class CursorTimeoutTask : public task::Task {
+public:
+ virtual string name() const {
+ return "cursorTimeout";
+ }
+ virtual void doWork() {
+ cursorCache.doTimeouts();
}
+};
- class CmdCursorInfo : public Command {
- public:
- CmdCursorInfo() : Command( "cursorInfo" ) {}
- virtual bool slaveOk() const { return true; }
- virtual void help( stringstream& help ) const {
- help << " example: { cursorInfo : 1 }";
- }
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::cursorInfo);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
- }
- virtual bool isWriteCommandForConfigServer() const { return false; }
- bool run(OperationContext* txn,
- const string&,
- BSONObj& jsobj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- cursorCache.appendInfo( result );
- if ( jsobj["setTimeout"].isNumber() )
- CursorCache::TIMEOUT = jsobj["setTimeout"].numberLong();
- return true;
- }
- } cmdCursorInfo;
+void CursorCache::startTimeoutThread() {
+ task::repeat(new CursorTimeoutTask, 4000);
+}
+class CmdCursorInfo : public Command {
+public:
+ CmdCursorInfo() : Command("cursorInfo") {}
+ virtual bool slaveOk() const {
+ return true;
+ }
+ virtual void help(stringstream& help) const {
+ help << " example: { cursorInfo : 1 }";
+ }
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::cursorInfo);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+ bool run(OperationContext* txn,
+ const string&,
+ BSONObj& jsobj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ cursorCache.appendInfo(result);
+ if (jsobj["setTimeout"].isNumber())
+ CursorCache::TIMEOUT = jsobj["setTimeout"].numberLong();
+ return true;
+ }
+} cmdCursorInfo;
}
diff --git a/src/mongo/s/cursors.h b/src/mongo/s/cursors.h
index 2e0d805402e..4ae1de00771 100644
--- a/src/mongo/s/cursors.h
+++ b/src/mongo/s/cursors.h
@@ -38,119 +38,120 @@
namespace mongo {
- class QueryMessage;
+class QueryMessage;
- class ShardedClientCursor {
- MONGO_DISALLOW_COPYING(ShardedClientCursor);
- public:
- ShardedClientCursor( QueryMessage& q , ParallelSortClusteredCursor * cursor );
- ~ShardedClientCursor();
+class ShardedClientCursor {
+ MONGO_DISALLOW_COPYING(ShardedClientCursor);
- long long getId();
+public:
+ ShardedClientCursor(QueryMessage& q, ParallelSortClusteredCursor* cursor);
+ ~ShardedClientCursor();
- /**
- * @return the cumulative number of documents seen by this cursor.
- */
- int getTotalSent() const;
+ long long getId();
- /**
- * Sends queries to the shards and gather the result for this batch.
- *
- * @param r The request object from the client
- * @param ntoreturn Number of documents to return
- * @param buffer The buffer to use to store the results.
- * @param docCount This will contain the number of documents gathered for this batch after
- * a successful call.
- *
- * @return true if this is not the final batch.
- */
- bool sendNextBatch(int ntoreturn, BufBuilder& buffer, int& docCount);
+ /**
+ * @return the cumulative number of documents seen by this cursor.
+ */
+ int getTotalSent() const;
- void accessed();
- /** @return idle time in ms */
- long long idleTime( long long now );
+ /**
+ * Sends queries to the shards and gather the result for this batch.
+ *
+ * @param r The request object from the client
+ * @param ntoreturn Number of documents to return
+ * @param buffer The buffer to use to store the results.
+ * @param docCount This will contain the number of documents gathered for this batch after
+ * a successful call.
+ *
+ * @return true if this is not the final batch.
+ */
+ bool sendNextBatch(int ntoreturn, BufBuilder& buffer, int& docCount);
- std::string getNS() { return _cursor->getNS(); }
+ void accessed();
+ /** @return idle time in ms */
+ long long idleTime(long long now);
- // The default initial buffer size for sending responses.
- static const int INIT_REPLY_BUFFER_SIZE;
+ std::string getNS() {
+ return _cursor->getNS();
+ }
- protected:
+ // The default initial buffer size for sending responses.
+ static const int INIT_REPLY_BUFFER_SIZE;
- ParallelSortClusteredCursor * _cursor;
+protected:
+ ParallelSortClusteredCursor* _cursor;
- int _skip;
- int _ntoreturn;
+ int _skip;
+ int _ntoreturn;
- int _totalSent;
- bool _done;
+ int _totalSent;
+ bool _done;
- long long _id;
- long long _lastAccessMillis; // 0 means no timeout
+ long long _id;
+ long long _lastAccessMillis; // 0 means no timeout
+};
- };
+typedef std::shared_ptr<ShardedClientCursor> ShardedClientCursorPtr;
- typedef std::shared_ptr<ShardedClientCursor> ShardedClientCursorPtr;
+class CursorCache {
+public:
+ static long long TIMEOUT;
- class CursorCache {
- public:
+ typedef std::map<long long, ShardedClientCursorPtr> MapSharded;
+ typedef std::map<long long, int> MapShardedInt;
+ typedef std::map<long long, std::string> MapNormal;
- static long long TIMEOUT;
+ CursorCache();
+ ~CursorCache();
- typedef std::map<long long,ShardedClientCursorPtr> MapSharded;
- typedef std::map<long long,int> MapShardedInt;
- typedef std::map<long long,std::string> MapNormal;
+ ShardedClientCursorPtr get(long long id) const;
+ int getMaxTimeMS(long long id) const;
+ void store(ShardedClientCursorPtr cursor, int maxTimeMS);
+ void updateMaxTimeMS(long long id, int maxTimeMS);
+ void remove(long long id);
- CursorCache();
- ~CursorCache();
+ void storeRef(const std::string& server, long long id, const std::string& ns);
+ void removeRef(long long id);
- ShardedClientCursorPtr get( long long id ) const;
- int getMaxTimeMS( long long id ) const;
- void store( ShardedClientCursorPtr cursor, int maxTimeMS );
- void updateMaxTimeMS( long long id, int maxTimeMS );
- void remove( long long id );
+ /** @return the server for id or "" */
+ std::string getRef(long long id) const;
+ /** @return the ns for id or "" */
+ std::string getRefNS(long long id) const;
- void storeRef(const std::string& server, long long id, const std::string& ns);
- void removeRef( long long id );
+ void gotKillCursors(Message& m);
- /** @return the server for id or "" */
- std::string getRef( long long id ) const ;
- /** @return the ns for id or "" */
- std::string getRefNS(long long id) const ;
-
- void gotKillCursors(Message& m );
+ void appendInfo(BSONObjBuilder& result) const;
- void appendInfo( BSONObjBuilder& result ) const ;
+ long long genId();
- long long genId();
+ void doTimeouts();
+ void startTimeoutThread();
- void doTimeouts();
- void startTimeoutThread();
- private:
- mutable stdx::mutex _mutex;
+private:
+ mutable stdx::mutex _mutex;
- PseudoRandom _random;
+ PseudoRandom _random;
- // Maps sharded cursor ID to ShardedClientCursorPtr.
- MapSharded _cursors;
+ // Maps sharded cursor ID to ShardedClientCursorPtr.
+ MapSharded _cursors;
- // Maps sharded cursor ID to remaining max time. Value can be any of:
- // - the constant "kMaxTimeCursorNoTimeLimit", or
- // - the constant "kMaxTimeCursorTimeLimitExpired", or
- // - a positive integer representing milliseconds of remaining time
- MapShardedInt _cursorsMaxTimeMS;
+ // Maps sharded cursor ID to remaining max time. Value can be any of:
+ // - the constant "kMaxTimeCursorNoTimeLimit", or
+ // - the constant "kMaxTimeCursorTimeLimitExpired", or
+ // - a positive integer representing milliseconds of remaining time
+ MapShardedInt _cursorsMaxTimeMS;
- // Maps passthrough cursor ID to shard name.
- MapNormal _refs;
+ // Maps passthrough cursor ID to shard name.
+ MapNormal _refs;
- // Maps passthrough cursor ID to namespace.
- MapNormal _refsNS;
-
- long long _shardedTotal;
+ // Maps passthrough cursor ID to namespace.
+ MapNormal _refsNS;
- static const int _myLogLevel;
- };
+ long long _shardedTotal;
- extern CursorCache cursorCache;
+ static const int _myLogLevel;
+};
+
+extern CursorCache cursorCache;
}
diff --git a/src/mongo/s/d_merge.cpp b/src/mongo/s/d_merge.cpp
index 897b3dfd32f..d14c036b0e1 100644
--- a/src/mongo/s/d_merge.cpp
+++ b/src/mongo/s/d_merge.cpp
@@ -47,344 +47,322 @@
namespace mongo {
- using std::endl;
- using std::string;
- using mongoutils::str::stream;
-
- static Status runApplyOpsCmd(const std::vector<ChunkType>&,
- const ChunkVersion&,
- const ChunkVersion&);
-
- static BSONObj buildMergeLogEntry(const std::vector<ChunkType>&,
- const ChunkVersion&,
- const ChunkVersion&);
-
- static bool isEmptyChunk( const ChunkType& );
-
- bool mergeChunks( OperationContext* txn,
- const NamespaceString& nss,
- const BSONObj& minKey,
- const BSONObj& maxKey,
- const OID& epoch,
- string* errMsg ) {
-
- //
- // Get sharding state up-to-date
- //
-
- ConnectionString configLoc = ConnectionString::parse( shardingState.getConfigServer(),
- *errMsg );
- if ( !configLoc.isValid() ){
- warning() << *errMsg << endl;
- return false;
- }
-
- //
- // Get the distributed lock
- //
-
- string whyMessage = stream() << "merging chunks in " << nss.ns()
- << " from " << minKey << " to " << maxKey;
- auto scopedDistLock = grid.catalogManager()->getDistLockManager()->lock(
- nss.ns(), whyMessage);
-
- if (!scopedDistLock.isOK()) {
- *errMsg = stream() << "could not acquire collection lock for " << nss.ns()
- << " to merge chunks in [" << minKey << "," << maxKey << ")"
- << causedBy(scopedDistLock.getStatus());
-
- warning() << *errMsg << endl;
- return false;
- }
-
- //
- // We now have the collection lock, refresh metadata to latest version and sanity check
- //
+using std::endl;
+using std::string;
+using mongoutils::str::stream;
+
+static Status runApplyOpsCmd(const std::vector<ChunkType>&,
+ const ChunkVersion&,
+ const ChunkVersion&);
+
+static BSONObj buildMergeLogEntry(const std::vector<ChunkType>&,
+ const ChunkVersion&,
+ const ChunkVersion&);
+
+static bool isEmptyChunk(const ChunkType&);
+
+bool mergeChunks(OperationContext* txn,
+ const NamespaceString& nss,
+ const BSONObj& minKey,
+ const BSONObj& maxKey,
+ const OID& epoch,
+ string* errMsg) {
+ //
+ // Get sharding state up-to-date
+ //
- ChunkVersion shardVersion;
- Status status = shardingState.refreshMetadataNow(txn, nss.ns(), &shardVersion);
+ ConnectionString configLoc = ConnectionString::parse(shardingState.getConfigServer(), *errMsg);
+ if (!configLoc.isValid()) {
+ warning() << *errMsg << endl;
+ return false;
+ }
- if ( !status.isOK() ) {
+ //
+ // Get the distributed lock
+ //
- *errMsg = str::stream() << "could not merge chunks, failed to refresh metadata for "
- << nss.ns() << causedBy( status.reason() );
+ string whyMessage = stream() << "merging chunks in " << nss.ns() << " from " << minKey << " to "
+ << maxKey;
+ auto scopedDistLock = grid.catalogManager()->getDistLockManager()->lock(nss.ns(), whyMessage);
- warning() << *errMsg << endl;
- return false;
- }
+ if (!scopedDistLock.isOK()) {
+ *errMsg = stream() << "could not acquire collection lock for " << nss.ns()
+ << " to merge chunks in [" << minKey << "," << maxKey << ")"
+ << causedBy(scopedDistLock.getStatus());
- if ( epoch.isSet() && shardVersion.epoch() != epoch ) {
+ warning() << *errMsg << endl;
+ return false;
+ }
- *errMsg = stream() << "could not merge chunks, collection " << nss.ns()
- << " has changed" << " since merge was sent" << "(sent epoch : "
- << epoch.toString()
- << ", current epoch : " << shardVersion.epoch().toString() << ")";
+ //
+ // We now have the collection lock, refresh metadata to latest version and sanity check
+ //
- warning() << *errMsg << endl;
- return false;
- }
+ ChunkVersion shardVersion;
+ Status status = shardingState.refreshMetadataNow(txn, nss.ns(), &shardVersion);
- CollectionMetadataPtr metadata = shardingState.getCollectionMetadata( nss.ns() );
+ if (!status.isOK()) {
+ *errMsg = str::stream() << "could not merge chunks, failed to refresh metadata for "
+ << nss.ns() << causedBy(status.reason());
- if ( !metadata || metadata->getKeyPattern().isEmpty() ) {
+ warning() << *errMsg << endl;
+ return false;
+ }
- *errMsg = stream() << "could not merge chunks, collection " << nss.ns()
- << " is not sharded";
+ if (epoch.isSet() && shardVersion.epoch() != epoch) {
+ *errMsg = stream() << "could not merge chunks, collection " << nss.ns() << " has changed"
+ << " since merge was sent"
+ << "(sent epoch : " << epoch.toString()
+ << ", current epoch : " << shardVersion.epoch().toString() << ")";
- warning() << *errMsg << endl;
- return false;
- }
+ warning() << *errMsg << endl;
+ return false;
+ }
- dassert( metadata->getShardVersion().equals( shardVersion ) );
+ CollectionMetadataPtr metadata = shardingState.getCollectionMetadata(nss.ns());
- if ( !metadata->isValidKey( minKey ) || !metadata->isValidKey( maxKey ) ) {
+ if (!metadata || metadata->getKeyPattern().isEmpty()) {
+ *errMsg = stream() << "could not merge chunks, collection " << nss.ns()
+ << " is not sharded";
- *errMsg = stream() << "could not merge chunks, the range "
- << rangeToString( minKey, maxKey ) << " is not valid"
- << " for collection " << nss.ns() << " with key pattern "
- << metadata->getKeyPattern();
+ warning() << *errMsg << endl;
+ return false;
+ }
- warning() << *errMsg << endl;
- return false;
- }
+ dassert(metadata->getShardVersion().equals(shardVersion));
- //
- // Get merged chunk information
- //
+ if (!metadata->isValidKey(minKey) || !metadata->isValidKey(maxKey)) {
+ *errMsg = stream() << "could not merge chunks, the range " << rangeToString(minKey, maxKey)
+ << " is not valid"
+ << " for collection " << nss.ns() << " with key pattern "
+ << metadata->getKeyPattern();
- ChunkVersion mergeVersion = metadata->getCollVersion();
- mergeVersion.incMinor();
+ warning() << *errMsg << endl;
+ return false;
+ }
- std::vector<ChunkType> chunksToMerge;
+ //
+ // Get merged chunk information
+ //
- ChunkType itChunk;
- itChunk.setMin( minKey );
- itChunk.setMax( minKey );
- itChunk.setNS( nss.ns() );
- itChunk.setShard( shardingState.getShardName() );
+ ChunkVersion mergeVersion = metadata->getCollVersion();
+ mergeVersion.incMinor();
- while (itChunk.getMax().woCompare(maxKey) < 0 &&
- metadata->getNextChunk(itChunk.getMax(), &itChunk)) {
- chunksToMerge.push_back(itChunk);
- }
+ std::vector<ChunkType> chunksToMerge;
- if ( chunksToMerge.empty() ) {
+ ChunkType itChunk;
+ itChunk.setMin(minKey);
+ itChunk.setMax(minKey);
+ itChunk.setNS(nss.ns());
+ itChunk.setShard(shardingState.getShardName());
- *errMsg = stream() << "could not merge chunks, collection " << nss.ns()
- << " range starting at " << minKey
- << " and ending at " << maxKey
- << " does not belong to shard " << shardingState.getShardName();
+ while (itChunk.getMax().woCompare(maxKey) < 0 &&
+ metadata->getNextChunk(itChunk.getMax(), &itChunk)) {
+ chunksToMerge.push_back(itChunk);
+ }
- warning() << *errMsg << endl;
- return false;
- }
+ if (chunksToMerge.empty()) {
+ *errMsg = stream() << "could not merge chunks, collection " << nss.ns()
+ << " range starting at " << minKey << " and ending at " << maxKey
+ << " does not belong to shard " << shardingState.getShardName();
- //
- // Validate the range starts and ends at chunks and has no holes, error if not valid
- //
+ warning() << *errMsg << endl;
+ return false;
+ }
- BSONObj firstDocMin = chunksToMerge.front().getMin();
- BSONObj firstDocMax = chunksToMerge.front().getMax();
- // minKey is inclusive
- bool minKeyInRange = rangeContains( firstDocMin, firstDocMax, minKey );
+ //
+ // Validate the range starts and ends at chunks and has no holes, error if not valid
+ //
- if ( !minKeyInRange ) {
+ BSONObj firstDocMin = chunksToMerge.front().getMin();
+ BSONObj firstDocMax = chunksToMerge.front().getMax();
+ // minKey is inclusive
+ bool minKeyInRange = rangeContains(firstDocMin, firstDocMax, minKey);
- *errMsg = stream() << "could not merge chunks, collection " << nss.ns()
- << " range starting at " << minKey
- << " does not belong to shard " << shardingState.getShardName();
+ if (!minKeyInRange) {
+ *errMsg = stream() << "could not merge chunks, collection " << nss.ns()
+ << " range starting at " << minKey << " does not belong to shard "
+ << shardingState.getShardName();
- warning() << *errMsg << endl;
- return false;
- }
+ warning() << *errMsg << endl;
+ return false;
+ }
- BSONObj lastDocMin = chunksToMerge.back().getMin();
- BSONObj lastDocMax = chunksToMerge.back().getMax();
- // maxKey is exclusive
- bool maxKeyInRange = lastDocMin.woCompare( maxKey ) < 0 &&
- lastDocMax.woCompare( maxKey ) >= 0;
+ BSONObj lastDocMin = chunksToMerge.back().getMin();
+ BSONObj lastDocMax = chunksToMerge.back().getMax();
+ // maxKey is exclusive
+ bool maxKeyInRange = lastDocMin.woCompare(maxKey) < 0 && lastDocMax.woCompare(maxKey) >= 0;
- if ( !maxKeyInRange ) {
- *errMsg = stream() << "could not merge chunks, collection " << nss.ns()
- << " range ending at " << maxKey
- << " does not belong to shard " << shardingState.getShardName();
+ if (!maxKeyInRange) {
+ *errMsg = stream() << "could not merge chunks, collection " << nss.ns()
+ << " range ending at " << maxKey << " does not belong to shard "
+ << shardingState.getShardName();
- warning() << *errMsg << endl;
- return false;
- }
+ warning() << *errMsg << endl;
+ return false;
+ }
- bool validRangeStartKey = firstDocMin.woCompare( minKey ) == 0;
- bool validRangeEndKey = lastDocMax.woCompare( maxKey ) == 0;
+ bool validRangeStartKey = firstDocMin.woCompare(minKey) == 0;
+ bool validRangeEndKey = lastDocMax.woCompare(maxKey) == 0;
- if ( !validRangeStartKey || !validRangeEndKey ) {
+ if (!validRangeStartKey || !validRangeEndKey) {
+ *errMsg = stream() << "could not merge chunks, collection " << nss.ns()
+ << " does not contain a chunk "
+ << (!validRangeStartKey ? "starting at " + minKey.toString() : "")
+ << (!validRangeStartKey && !validRangeEndKey ? " or " : "")
+ << (!validRangeEndKey ? "ending at " + maxKey.toString() : "");
- *errMsg = stream() << "could not merge chunks, collection " << nss.ns()
- << " does not contain a chunk "
- << ( !validRangeStartKey ? "starting at " + minKey.toString() : "" )
- << ( !validRangeStartKey && !validRangeEndKey ? " or " : "" )
- << ( !validRangeEndKey ? "ending at " + maxKey.toString() : "" );
+ warning() << *errMsg << endl;
+ return false;
+ }
- warning() << *errMsg << endl;
- return false;
- }
+ if (chunksToMerge.size() == 1) {
+ *errMsg = stream() << "could not merge chunks, collection " << nss.ns()
+ << " already contains chunk for " << rangeToString(minKey, maxKey);
- if ( chunksToMerge.size() == 1 ) {
+ warning() << *errMsg << endl;
+ return false;
+ }
- *errMsg = stream() << "could not merge chunks, collection " << nss.ns()
- << " already contains chunk for " << rangeToString( minKey, maxKey );
+ // Look for hole in range
+ for (size_t i = 1; i < chunksToMerge.size(); ++i) {
+ if (chunksToMerge[i - 1].getMax().woCompare(chunksToMerge[i].getMin()) != 0) {
+ *errMsg =
+ stream() << "could not merge chunks, collection " << nss.ns()
+ << " has a hole in the range " << rangeToString(minKey, maxKey) << " at "
+ << rangeToString(chunksToMerge[i - 1].getMax(), chunksToMerge[i].getMin());
warning() << *errMsg << endl;
return false;
}
+ }
- // Look for hole in range
- for (size_t i = 1; i < chunksToMerge.size(); ++i) {
- if (chunksToMerge[i-1].getMax().woCompare(chunksToMerge[i].getMin()) != 0) {
- *errMsg = stream() << "could not merge chunks, collection " << nss.ns()
- << " has a hole in the range " << rangeToString(minKey, maxKey)
- << " at " << rangeToString(chunksToMerge[i-1].getMax(),
- chunksToMerge[i].getMin());
-
- warning() << *errMsg << endl;
- return false;
- }
- }
-
- //
- // Run apply ops command
- //
- Status applyOpsStatus = runApplyOpsCmd(chunksToMerge, shardVersion, mergeVersion);
- if (!applyOpsStatus.isOK()) {
- warning() << applyOpsStatus;
- return false;
- }
-
- //
- // Install merged chunk metadata
- //
-
- {
- ScopedTransaction transaction(txn, MODE_IX);
- Lock::DBLock writeLk(txn->lockState(), nss.db(), MODE_IX);
- Lock::CollectionLock collLock(txn->lockState(), nss.ns(), MODE_X);
- shardingState.mergeChunks(txn, nss.ns(), minKey, maxKey, mergeVersion);
- }
-
- //
- // Log change
- //
-
- BSONObj mergeLogEntry = buildMergeLogEntry( chunksToMerge,
- shardVersion,
- mergeVersion );
+ //
+ // Run apply ops command
+ //
+ Status applyOpsStatus = runApplyOpsCmd(chunksToMerge, shardVersion, mergeVersion);
+ if (!applyOpsStatus.isOK()) {
+ warning() << applyOpsStatus;
+ return false;
+ }
- grid.catalogManager()->logChange(txn, "merge", nss.ns(), mergeLogEntry);
+ //
+ // Install merged chunk metadata
+ //
- return true;
+ {
+ ScopedTransaction transaction(txn, MODE_IX);
+ Lock::DBLock writeLk(txn->lockState(), nss.db(), MODE_IX);
+ Lock::CollectionLock collLock(txn->lockState(), nss.ns(), MODE_X);
+ shardingState.mergeChunks(txn, nss.ns(), minKey, maxKey, mergeVersion);
}
//
- // Utilities for building BSONObjs for applyOps and change logging
+ // Log change
//
- BSONObj buildMergeLogEntry(const std::vector<ChunkType>& chunksToMerge,
- const ChunkVersion& currShardVersion,
- const ChunkVersion& newMergedVersion) {
+ BSONObj mergeLogEntry = buildMergeLogEntry(chunksToMerge, shardVersion, mergeVersion);
- BSONObjBuilder logDetailB;
+ grid.catalogManager()->logChange(txn, "merge", nss.ns(), mergeLogEntry);
- BSONArrayBuilder mergedB( logDetailB.subarrayStart( "merged" ) );
+ return true;
+}
- for (const ChunkType& chunkToMerge : chunksToMerge) {
- mergedB.append(chunkToMerge.toBSON());
- }
+//
+// Utilities for building BSONObjs for applyOps and change logging
+//
- mergedB.done();
+BSONObj buildMergeLogEntry(const std::vector<ChunkType>& chunksToMerge,
+ const ChunkVersion& currShardVersion,
+ const ChunkVersion& newMergedVersion) {
+ BSONObjBuilder logDetailB;
- currShardVersion.addToBSON( logDetailB, "prevShardVersion" );
- newMergedVersion.addToBSON( logDetailB, "mergedVersion" );
+ BSONArrayBuilder mergedB(logDetailB.subarrayStart("merged"));
- return logDetailB.obj();
+ for (const ChunkType& chunkToMerge : chunksToMerge) {
+ mergedB.append(chunkToMerge.toBSON());
}
+ mergedB.done();
- BSONObj buildOpMergeChunk( const ChunkType& mergedChunk ) {
+ currShardVersion.addToBSON(logDetailB, "prevShardVersion");
+ newMergedVersion.addToBSON(logDetailB, "mergedVersion");
- BSONObjBuilder opB;
+ return logDetailB.obj();
+}
- // Op basics
- opB.append( "op" , "u" );
- opB.appendBool( "b" , false ); // no upserting
- opB.append( "ns" , ChunkType::ConfigNS );
- // New object
- opB.append( "o", mergedChunk.toBSON() );
+BSONObj buildOpMergeChunk(const ChunkType& mergedChunk) {
+ BSONObjBuilder opB;
- // Query object
- opB.append( "o2",
- BSON( ChunkType::name( mergedChunk.getName() ) ) );
+ // Op basics
+ opB.append("op", "u");
+ opB.appendBool("b", false); // no upserting
+ opB.append("ns", ChunkType::ConfigNS);
- return opB.obj();
- }
+ // New object
+ opB.append("o", mergedChunk.toBSON());
- BSONObj buildOpRemoveChunk( const ChunkType& chunkToRemove ) {
+ // Query object
+ opB.append("o2", BSON(ChunkType::name(mergedChunk.getName())));
- BSONObjBuilder opB;
-
- // Op basics
- opB.append( "op", "d" ); // delete
- opB.append( "ns", ChunkType::ConfigNS );
+ return opB.obj();
+}
- opB.append( "o", BSON( ChunkType::name( chunkToRemove.getName() ) ) );
+BSONObj buildOpRemoveChunk(const ChunkType& chunkToRemove) {
+ BSONObjBuilder opB;
- return opB.obj();
- }
+ // Op basics
+ opB.append("op", "d"); // delete
+ opB.append("ns", ChunkType::ConfigNS);
- BSONArray buildOpPrecond(const string& ns,
- const string& shardName,
- const ChunkVersion& shardVersion) {
- BSONArrayBuilder preCond;
- BSONObjBuilder condB;
- condB.append( "ns", ChunkType::ConfigNS );
- condB.append( "q", BSON( "query" << BSON( ChunkType::ns( ns ) )
- << "orderby" << BSON( ChunkType::DEPRECATED_lastmod() << -1 ) ) );
- {
- BSONObjBuilder resB( condB.subobjStart( "res" ) );
- shardVersion.addToBSON( resB, ChunkType::DEPRECATED_lastmod() );
- resB.done();
- }
- preCond.append(condB.obj());
- return preCond.arr();
- }
+ opB.append("o", BSON(ChunkType::name(chunkToRemove.getName())));
- Status runApplyOpsCmd(const std::vector<ChunkType>& chunksToMerge,
- const ChunkVersion& currShardVersion,
- const ChunkVersion& newMergedVersion) {
+ return opB.obj();
+}
- BSONArrayBuilder updatesB;
+BSONArray buildOpPrecond(const string& ns,
+ const string& shardName,
+ const ChunkVersion& shardVersion) {
+ BSONArrayBuilder preCond;
+ BSONObjBuilder condB;
+ condB.append("ns", ChunkType::ConfigNS);
+ condB.append("q",
+ BSON("query" << BSON(ChunkType::ns(ns)) << "orderby"
+ << BSON(ChunkType::DEPRECATED_lastmod() << -1)));
+ {
+ BSONObjBuilder resB(condB.subobjStart("res"));
+ shardVersion.addToBSON(resB, ChunkType::DEPRECATED_lastmod());
+ resB.done();
+ }
+ preCond.append(condB.obj());
+ return preCond.arr();
+}
- // The chunk we'll be "expanding" is the first chunk
- const ChunkType& firstChunk = chunksToMerge.front();
+Status runApplyOpsCmd(const std::vector<ChunkType>& chunksToMerge,
+ const ChunkVersion& currShardVersion,
+ const ChunkVersion& newMergedVersion) {
+ BSONArrayBuilder updatesB;
- // Fill in details not tracked by metadata
- ChunkType mergedChunk(firstChunk);
- mergedChunk.setName(Chunk::genID(firstChunk.getNS(), firstChunk.getMin()));
- mergedChunk.setMax(chunksToMerge.back().getMax());
- mergedChunk.setVersion(newMergedVersion);
+ // The chunk we'll be "expanding" is the first chunk
+ const ChunkType& firstChunk = chunksToMerge.front();
- updatesB.append(buildOpMergeChunk(mergedChunk));
+ // Fill in details not tracked by metadata
+ ChunkType mergedChunk(firstChunk);
+ mergedChunk.setName(Chunk::genID(firstChunk.getNS(), firstChunk.getMin()));
+ mergedChunk.setMax(chunksToMerge.back().getMax());
+ mergedChunk.setVersion(newMergedVersion);
- // Don't remove chunk we're expanding
- for (size_t i = 1; i < chunksToMerge.size(); ++i) {
- ChunkType chunkToMerge(chunksToMerge[i]);
- chunkToMerge.setName(Chunk::genID(chunkToMerge.getNS(), chunkToMerge.getMin()));
- updatesB.append(buildOpRemoveChunk(chunkToMerge));
- }
+ updatesB.append(buildOpMergeChunk(mergedChunk));
- BSONArray preCond = buildOpPrecond(firstChunk.getNS(),
- firstChunk.getShard(),
- currShardVersion);
- return grid.catalogManager()->applyChunkOpsDeprecated(updatesB.arr(), preCond);
+ // Don't remove chunk we're expanding
+ for (size_t i = 1; i < chunksToMerge.size(); ++i) {
+ ChunkType chunkToMerge(chunksToMerge[i]);
+ chunkToMerge.setName(Chunk::genID(chunkToMerge.getNS(), chunkToMerge.getMin()));
+ updatesB.append(buildOpRemoveChunk(chunkToMerge));
}
+ BSONArray preCond = buildOpPrecond(firstChunk.getNS(), firstChunk.getShard(), currShardVersion);
+ return grid.catalogManager()->applyChunkOpsDeprecated(updatesB.arr(), preCond);
+}
}
diff --git a/src/mongo/s/d_merge.h b/src/mongo/s/d_merge.h
index 85d39fa7f11..79fa37a2dbb 100644
--- a/src/mongo/s/d_merge.h
+++ b/src/mongo/s/d_merge.h
@@ -33,30 +33,30 @@
namespace mongo {
- class OperationContext;
+class OperationContext;
- /**
- * Merges a chunks in the specified [minKey, maxKey) range of the specified namespace.
- * Updates the local and remote metadata by expanding the bounds of the first chunk in the
- * range, dropping the others, and incrementing the minor version of the shard to the next
- * higher version. Returns true on success.
- *
- * Fails with errMsg if the 'epoch' was set and has changed (indicating the range is no longer
- * valid), or if the range does not exactly start and stop on chunks owned by this shard, or
- * if the chunks in this range are not contiguous.
- *
- * WARNING: On network failure, it is possible that the chunks in our local metadata may not
- * match the remote metadata, however the key ranges protected will be the same. All metadata
- * operations are responsible for updating the metadata before performing any actions.
- *
- * Locking note:
- * + Takes a distributed lock over the namespace
- * + Cannot be called with any other locks held
- */
- bool mergeChunks( OperationContext* txn,
- const NamespaceString& nss,
- const BSONObj& minKey,
- const BSONObj& maxKey,
- const OID& epoch,
- std::string* errMsg );
+/**
+ * Merges a chunks in the specified [minKey, maxKey) range of the specified namespace.
+ * Updates the local and remote metadata by expanding the bounds of the first chunk in the
+ * range, dropping the others, and incrementing the minor version of the shard to the next
+ * higher version. Returns true on success.
+ *
+ * Fails with errMsg if the 'epoch' was set and has changed (indicating the range is no longer
+ * valid), or if the range does not exactly start and stop on chunks owned by this shard, or
+ * if the chunks in this range are not contiguous.
+ *
+ * WARNING: On network failure, it is possible that the chunks in our local metadata may not
+ * match the remote metadata, however the key ranges protected will be the same. All metadata
+ * operations are responsible for updating the metadata before performing any actions.
+ *
+ * Locking note:
+ * + Takes a distributed lock over the namespace
+ * + Cannot be called with any other locks held
+ */
+bool mergeChunks(OperationContext* txn,
+ const NamespaceString& nss,
+ const BSONObj& minKey,
+ const BSONObj& maxKey,
+ const OID& epoch,
+ std::string* errMsg);
}
diff --git a/src/mongo/s/d_migrate.cpp b/src/mongo/s/d_migrate.cpp
index 8ed12b4d082..a0893be2524 100644
--- a/src/mongo/s/d_migrate.cpp
+++ b/src/mongo/s/d_migrate.cpp
@@ -91,653 +91,645 @@
#include "mongo/util/startup_test.h"
// Pause while a fail point is enabled.
-#define MONGO_FP_PAUSE_WHILE(symbol) while (MONGO_FAIL_POINT(symbol)) { sleepmillis(100); }
+#define MONGO_FP_PAUSE_WHILE(symbol) \
+ while (MONGO_FAIL_POINT(symbol)) { \
+ sleepmillis(100); \
+ }
namespace mongo {
- using namespace std::chrono;
- using std::list;
- using std::set;
- using std::string;
- using std::vector;
+using namespace std::chrono;
+using std::list;
+using std::set;
+using std::string;
+using std::vector;
namespace {
- const int kDefaultWTimeoutMs = 60 * 1000;
- const WriteConcernOptions DefaultWriteConcern(2, WriteConcernOptions::NONE, kDefaultWTimeoutMs);
-
- /**
- * Returns the default write concern for migration cleanup (at donor shard) and
- * cloning documents (at recipient shard).
- */
- WriteConcernOptions getDefaultWriteConcern() {
- repl::ReplicationCoordinator* replCoordinator = repl::getGlobalReplicationCoordinator();
- if (replCoordinator->getReplicationMode() ==
- mongo::repl::ReplicationCoordinator::modeReplSet) {
+const int kDefaultWTimeoutMs = 60 * 1000;
+const WriteConcernOptions DefaultWriteConcern(2, WriteConcernOptions::NONE, kDefaultWTimeoutMs);
- Status status =
- replCoordinator->checkIfWriteConcernCanBeSatisfied(DefaultWriteConcern);
- if (status.isOK()) {
- return DefaultWriteConcern;
- }
+/**
+ * Returns the default write concern for migration cleanup (at donor shard) and
+ * cloning documents (at recipient shard).
+ */
+WriteConcernOptions getDefaultWriteConcern() {
+ repl::ReplicationCoordinator* replCoordinator = repl::getGlobalReplicationCoordinator();
+ if (replCoordinator->getReplicationMode() == mongo::repl::ReplicationCoordinator::modeReplSet) {
+ Status status = replCoordinator->checkIfWriteConcernCanBeSatisfied(DefaultWriteConcern);
+ if (status.isOK()) {
+ return DefaultWriteConcern;
}
-
- return WriteConcernOptions(1, WriteConcernOptions::NONE, 0);
}
-} // namespace
-
- MONGO_FP_DECLARE(failMigrationCommit);
- MONGO_FP_DECLARE(failMigrationConfigWritePrepare);
- MONGO_FP_DECLARE(failMigrationApplyOps);
-
- Tee* migrateLog = RamLog::get("migrate");
+ return WriteConcernOptions(1, WriteConcernOptions::NONE, 0);
+}
- class MoveTimingHelper {
- public:
- MoveTimingHelper(OperationContext* txn,
- const string& where,
- const string& ns,
- BSONObj min,
- BSONObj max ,
- int total,
- string* cmdErrmsg,
- string toShard,
- string fromShard)
- : _txn(txn),
- _where(where),
- _ns(ns),
- _to(toShard),
- _from(fromShard),
- _next(0),
- _total(total),
- _cmdErrmsg(cmdErrmsg) {
-
- _b.append( "min" , min );
- _b.append( "max" , max );
- }
-
- ~MoveTimingHelper() {
- // even if logChange doesn't throw, bson does
- // sigh
- try {
- if ( !_to.empty() ){
- _b.append( "to", _to );
- }
- if ( !_from.empty() ){
- _b.append( "from", _from );
- }
- if ( _next != _total ) {
- _b.append( "note" , "aborted" );
- }
- else {
- _b.append( "note" , "success" );
- }
- if ( !_cmdErrmsg->empty() ) {
- _b.append( "errmsg" , *_cmdErrmsg );
- }
+} // namespace
+
+MONGO_FP_DECLARE(failMigrationCommit);
+MONGO_FP_DECLARE(failMigrationConfigWritePrepare);
+MONGO_FP_DECLARE(failMigrationApplyOps);
+
+Tee* migrateLog = RamLog::get("migrate");
+
+class MoveTimingHelper {
+public:
+ MoveTimingHelper(OperationContext* txn,
+ const string& where,
+ const string& ns,
+ BSONObj min,
+ BSONObj max,
+ int total,
+ string* cmdErrmsg,
+ string toShard,
+ string fromShard)
+ : _txn(txn),
+ _where(where),
+ _ns(ns),
+ _to(toShard),
+ _from(fromShard),
+ _next(0),
+ _total(total),
+ _cmdErrmsg(cmdErrmsg) {
+ _b.append("min", min);
+ _b.append("max", max);
+ }
- grid.catalogManager()->logChange(_txn,
- (string)"moveChunk." + _where,
- _ns,
- _b.obj());
+ ~MoveTimingHelper() {
+ // even if logChange doesn't throw, bson does
+ // sigh
+ try {
+ if (!_to.empty()) {
+ _b.append("to", _to);
}
- catch ( const std::exception& e ) {
- warning() << "couldn't record timing for moveChunk '" << _where << "': " << e.what() << migrateLog;
+ if (!_from.empty()) {
+ _b.append("from", _from);
+ }
+ if (_next != _total) {
+ _b.append("note", "aborted");
+ } else {
+ _b.append("note", "success");
+ }
+ if (!_cmdErrmsg->empty()) {
+ _b.append("errmsg", *_cmdErrmsg);
}
- }
- void done(int step) {
- invariant(step == ++_next);
- invariant(step <= _total);
+ grid.catalogManager()->logChange(_txn, (string) "moveChunk." + _where, _ns, _b.obj());
+ } catch (const std::exception& e) {
+ warning() << "couldn't record timing for moveChunk '" << _where << "': " << e.what()
+ << migrateLog;
+ }
+ }
- const string s = str::stream() << "step " << step << " of " << _total;
+ void done(int step) {
+ invariant(step == ++_next);
+ invariant(step <= _total);
- CurOp * op = CurOp::get(_txn);
- {
- std::lock_guard<Client> lk(*_txn->getClient());
- op->setMessage_inlock(s.c_str());
- }
+ const string s = str::stream() << "step " << step << " of " << _total;
- _b.appendNumber(s, _t.millis());
- _t.reset();
+ CurOp* op = CurOp::get(_txn);
+ {
+ std::lock_guard<Client> lk(*_txn->getClient());
+ op->setMessage_inlock(s.c_str());
}
- private:
- OperationContext* const _txn;
- Timer _t;
+ _b.appendNumber(s, _t.millis());
+ _t.reset();
+ }
- string _where;
- string _ns;
- string _to;
- string _from;
+private:
+ OperationContext* const _txn;
+ Timer _t;
- int _next;
- int _total; // expected # of steps
+ string _where;
+ string _ns;
+ string _to;
+ string _from;
- const string* _cmdErrmsg;
+ int _next;
+ int _total; // expected # of steps
- BSONObjBuilder _b;
- };
+ const string* _cmdErrmsg;
- class ChunkCommandHelper : public Command {
- public:
- ChunkCommandHelper( const char * name )
- : Command( name ) {
- }
+ BSONObjBuilder _b;
+};
- virtual void help( std::stringstream& help ) const {
- help << "internal - should not be called directly";
- }
- virtual bool slaveOk() const { return false; }
- virtual bool adminOnly() const { return true; }
- virtual bool isWriteCommandForConfigServer() const { return false; }
+class ChunkCommandHelper : public Command {
+public:
+ ChunkCommandHelper(const char* name) : Command(name) {}
- };
-
- bool isInRange( const BSONObj& obj ,
- const BSONObj& min ,
- const BSONObj& max ,
- const BSONObj& shardKeyPattern ) {
- ShardKeyPattern shardKey( shardKeyPattern );
- BSONObj k = shardKey.extractShardKeyFromDoc( obj );
- return k.woCompare( min ) >= 0 && k.woCompare( max ) < 0;
+ virtual void help(std::stringstream& help) const {
+ help << "internal - should not be called directly";
}
+ virtual bool slaveOk() const {
+ return false;
+ }
+ virtual bool adminOnly() const {
+ return true;
+ }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+};
+
+bool isInRange(const BSONObj& obj,
+ const BSONObj& min,
+ const BSONObj& max,
+ const BSONObj& shardKeyPattern) {
+ ShardKeyPattern shardKey(shardKeyPattern);
+ BSONObj k = shardKey.extractShardKeyFromDoc(obj);
+ return k.woCompare(min) >= 0 && k.woCompare(max) < 0;
+}
- class MigrateFromStatus {
- public:
- MigrateFromStatus():
- _inCriticalSection(false),
- _memoryUsed(0),
- _active(false) {
- }
-
- /**
- * @return false if cannot start. One of the reason for not being able to
- * start is there is already an existing migration in progress.
- */
- bool start(OperationContext* txn,
- const std::string& ns,
- const BSONObj& min,
- const BSONObj& max,
- const BSONObj& shardKeyPattern) {
- verify(!min.isEmpty());
- verify(!max.isEmpty());
- verify(!ns.empty());
+class MigrateFromStatus {
+public:
+ MigrateFromStatus() : _inCriticalSection(false), _memoryUsed(0), _active(false) {}
- // Get global shared to synchronize with logOp. Also see comments in the class
- // members declaration for more details.
- Lock::GlobalRead globalShared(txn->lockState());
- std::lock_guard<std::mutex> lk(_mutex);
+ /**
+ * @return false if cannot start. One of the reason for not being able to
+ * start is there is already an existing migration in progress.
+ */
+ bool start(OperationContext* txn,
+ const std::string& ns,
+ const BSONObj& min,
+ const BSONObj& max,
+ const BSONObj& shardKeyPattern) {
+ verify(!min.isEmpty());
+ verify(!max.isEmpty());
+ verify(!ns.empty());
+
+ // Get global shared to synchronize with logOp. Also see comments in the class
+ // members declaration for more details.
+ Lock::GlobalRead globalShared(txn->lockState());
+ std::lock_guard<std::mutex> lk(_mutex);
+
+ if (_active) {
+ return false;
+ }
- if (_active) {
- return false;
- }
+ _ns = ns;
+ _min = min;
+ _max = max;
+ _shardKeyPattern = shardKeyPattern;
- _ns = ns;
- _min = min;
- _max = max;
- _shardKeyPattern = shardKeyPattern;
+ verify(_deleted.size() == 0);
+ verify(_reload.size() == 0);
+ verify(_memoryUsed == 0);
- verify(_deleted.size() == 0);
- verify(_reload.size() == 0);
- verify(_memoryUsed == 0);
+ _active = true;
- _active = true;
+ std::lock_guard<std::mutex> tLock(_cloneLocsMutex);
+ verify(_cloneLocs.size() == 0);
- std::lock_guard<std::mutex> tLock(_cloneLocsMutex);
- verify(_cloneLocs.size() == 0);
+ return true;
+ }
- return true;
- }
+ void done(OperationContext* txn) {
+ log() << "MigrateFromStatus::done About to acquire global lock to exit critical "
+ "section";
- void done(OperationContext* txn) {
- log() << "MigrateFromStatus::done About to acquire global lock to exit critical "
- "section";
+ // Get global shared to synchronize with logOp. Also see comments in the class
+ // members declaration for more details.
+ Lock::GlobalRead globalShared(txn->lockState());
+ std::lock_guard<std::mutex> lk(_mutex);
- // Get global shared to synchronize with logOp. Also see comments in the class
- // members declaration for more details.
- Lock::GlobalRead globalShared(txn->lockState());
- std::lock_guard<std::mutex> lk(_mutex);
+ _active = false;
+ _deleteNotifyExec.reset(NULL);
+ _inCriticalSection = false;
+ _inCriticalSectionCV.notify_all();
- _active = false;
- _deleteNotifyExec.reset( NULL );
- _inCriticalSection = false;
- _inCriticalSectionCV.notify_all();
+ _deleted.clear();
+ _reload.clear();
+ _memoryUsed = 0;
- _deleted.clear();
- _reload.clear();
- _memoryUsed = 0;
+ std::lock_guard<std::mutex> cloneLock(_cloneLocsMutex);
+ _cloneLocs.clear();
+ }
- std::lock_guard<std::mutex> cloneLock(_cloneLocsMutex);
- _cloneLocs.clear();
+ void logOp(OperationContext* txn,
+ const char* opstr,
+ const char* ns,
+ const BSONObj& obj,
+ BSONObj* patt,
+ bool notInActiveChunk) {
+ ensureShardVersionOKOrThrow(txn->getClient(), ns);
+
+ const char op = opstr[0];
+
+ if (notInActiveChunk) {
+ // Ignore writes that came from the migration process like cleanup so they
+ // won't be transferred to the recipient shard. Also ignore ops from
+ // _migrateClone and _transferMods since it is impossible to move a chunk
+ // to self.
+ return;
}
- void logOp(OperationContext* txn,
- const char* opstr,
- const char* ns,
- const BSONObj& obj,
- BSONObj* patt,
- bool notInActiveChunk) {
- ensureShardVersionOKOrThrow(txn->getClient(), ns);
+ dassert(txn->lockState()->isWriteLocked()); // Must have Global IX.
- const char op = opstr[0];
+ if (!_active)
+ return;
- if (notInActiveChunk) {
- // Ignore writes that came from the migration process like cleanup so they
- // won't be transferred to the recipient shard. Also ignore ops from
- // _migrateClone and _transferMods since it is impossible to move a chunk
- // to self.
- return;
- }
+ if (_ns != ns)
+ return;
- dassert(txn->lockState()->isWriteLocked()); // Must have Global IX.
+ // no need to log if this is not an insertion, an update, or an actual deletion
+ // note: opstr 'db' isn't a deletion but a mention that a database exists
+ // (for replication machinery mostly).
+ if (op == 'n' || op == 'c' || (op == 'd' && opstr[1] == 'b'))
+ return;
- if (!_active)
- return;
+ BSONElement ide;
+ if (patt)
+ ide = patt->getField("_id");
+ else
+ ide = obj["_id"];
- if (_ns != ns)
- return;
+ if (ide.eoo()) {
+ warning() << "logOpForSharding got mod with no _id, ignoring obj: " << obj
+ << migrateLog;
+ return;
+ }
- // no need to log if this is not an insertion, an update, or an actual deletion
- // note: opstr 'db' isn't a deletion but a mention that a database exists
- // (for replication machinery mostly).
- if (op == 'n' || op == 'c' || (op == 'd' && opstr[1] == 'b'))
- return;
+ if (op == 'i' && (!isInRange(obj, _min, _max, _shardKeyPattern))) {
+ return;
+ }
- BSONElement ide;
- if (patt)
- ide = patt->getField("_id");
- else
- ide = obj["_id"];
+ BSONObj idObj(ide.wrap());
- if (ide.eoo()) {
- warning() << "logOpForSharding got mod with no _id, ignoring obj: "
- << obj << migrateLog;
+ if (op == 'u') {
+ BSONObj fullDoc;
+ OldClientContext ctx(txn, _ns, false);
+ if (!Helpers::findById(txn, ctx.db(), _ns.c_str(), idObj, fullDoc)) {
+ warning() << "logOpForSharding couldn't find: " << idObj
+ << " even though should have" << migrateLog;
+ dassert(false); // TODO: Abort the migration.
return;
}
- if (op == 'i' && (!isInRange(obj, _min, _max, _shardKeyPattern))) {
+ if (!isInRange(fullDoc, _min, _max, _shardKeyPattern)) {
return;
}
+ }
- BSONObj idObj(ide.wrap());
+ // Note: can't check if delete is in active chunk since the document is gone!
- if (op == 'u') {
- BSONObj fullDoc;
- OldClientContext ctx(txn, _ns, false);
- if (!Helpers::findById(txn, ctx.db(), _ns.c_str(), idObj, fullDoc)) {
- warning() << "logOpForSharding couldn't find: " << idObj
- << " even though should have" << migrateLog;
- dassert(false); // TODO: Abort the migration.
- return;
- }
+ txn->recoveryUnit()->registerChange(new LogOpForShardingHandler(this, idObj, op));
+ }
- if (!isInRange(fullDoc, _min, _max, _shardKeyPattern)) {
- return;
+ /**
+ * Insert items from docIdList to a new array with the given fieldName in the given
+ * builder. If explode is true, the inserted object will be the full version of the
+ * document. Note that the whenever an item from the docList is inserted to the array,
+ * it will also be removed from docList.
+ *
+ * Should be holding the collection lock for ns if explode is true.
+ */
+ void xfer(OperationContext* txn,
+ const string& ns,
+ Database* db,
+ list<BSONObj>* docIdList,
+ BSONObjBuilder& builder,
+ const char* fieldName,
+ long long& size,
+ bool explode) {
+ const long long maxSize = 1024 * 1024;
+
+ if (docIdList->size() == 0 || size > maxSize)
+ return;
+
+ BSONArrayBuilder arr(builder.subarrayStart(fieldName));
+
+ list<BSONObj>::iterator docIdIter = docIdList->begin();
+ while (docIdIter != docIdList->end() && size < maxSize) {
+ BSONObj idDoc = *docIdIter;
+ if (explode) {
+ BSONObj fullDoc;
+ if (Helpers::findById(txn, db, ns.c_str(), idDoc, fullDoc)) {
+ arr.append(fullDoc);
+ size += fullDoc.objsize();
}
+ } else {
+ arr.append(idDoc);
+ size += idDoc.objsize();
}
- // Note: can't check if delete is in active chunk since the document is gone!
-
- txn->recoveryUnit()->registerChange(new LogOpForShardingHandler(this, idObj, op));
+ docIdIter = docIdList->erase(docIdIter);
}
- /**
- * Insert items from docIdList to a new array with the given fieldName in the given
- * builder. If explode is true, the inserted object will be the full version of the
- * document. Note that the whenever an item from the docList is inserted to the array,
- * it will also be removed from docList.
- *
- * Should be holding the collection lock for ns if explode is true.
- */
- void xfer(OperationContext* txn,
- const string& ns,
- Database* db,
- list<BSONObj> *docIdList,
- BSONObjBuilder& builder,
- const char* fieldName,
- long long& size,
- bool explode) {
- const long long maxSize = 1024 * 1024;
-
- if (docIdList->size() == 0 || size > maxSize)
- return;
+ arr.done();
+ }
- BSONArrayBuilder arr(builder.subarrayStart(fieldName));
+ /**
+ * called from the dest of a migrate
+ * transfers mods from src to dest
+ */
+ bool transferMods(OperationContext* txn, string& errmsg, BSONObjBuilder& b) {
+ long long size = 0;
- list<BSONObj>::iterator docIdIter = docIdList->begin();
- while (docIdIter != docIdList->end() && size < maxSize) {
- BSONObj idDoc = *docIdIter;
- if (explode) {
- BSONObj fullDoc;
- if (Helpers::findById(txn, db, ns.c_str(), idDoc, fullDoc)) {
- arr.append( fullDoc );
- size += fullDoc.objsize();
- }
- }
- else {
- arr.append(idDoc);
- size += idDoc.objsize();
- }
+ {
+ AutoGetCollectionForRead ctx(txn, getNS());
- docIdIter = docIdList->erase(docIdIter);
+ std::lock_guard<std::mutex> sl(_mutex);
+ if (!_active) {
+ errmsg = "no active migration!";
+ return false;
}
- arr.done();
+ // TODO: fix SERVER-16540 race
+ xfer(txn, _ns, ctx.getDb(), &_deleted, b, "deleted", size, false);
+ xfer(txn, _ns, ctx.getDb(), &_reload, b, "reload", size, true);
}
- /**
- * called from the dest of a migrate
- * transfers mods from src to dest
- */
- bool transferMods(OperationContext* txn, string& errmsg, BSONObjBuilder& b) {
- long long size = 0;
-
- {
- AutoGetCollectionForRead ctx(txn, getNS());
-
- std::lock_guard<std::mutex> sl(_mutex);
- if (!_active) {
- errmsg = "no active migration!";
- return false;
- }
+ b.append("size", size);
- // TODO: fix SERVER-16540 race
- xfer(txn, _ns, ctx.getDb(), &_deleted, b, "deleted", size, false);
- xfer(txn, _ns, ctx.getDb(), &_reload, b, "reload", size, true);
- }
-
- b.append( "size" , size );
+ return true;
+ }
- return true;
+ /**
+ * Get the disklocs that belong to the chunk migrated and sort them in _cloneLocs
+ * (to avoid seeking disk later).
+ *
+ * @param maxChunkSize number of bytes beyond which a chunk's base data (no indices)
+ * is considered too large to move.
+ * @param errmsg filled with textual description of error if this call return false.
+ * @return false if approximate chunk size is too big to move or true otherwise.
+ */
+ bool storeCurrentLocs(OperationContext* txn,
+ long long maxChunkSize,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ AutoGetCollectionForRead ctx(txn, getNS());
+ Collection* collection = ctx.getCollection();
+ if (!collection) {
+ errmsg = "ns not found, should be impossible";
+ return false;
}
- /**
- * Get the disklocs that belong to the chunk migrated and sort them in _cloneLocs
- * (to avoid seeking disk later).
- *
- * @param maxChunkSize number of bytes beyond which a chunk's base data (no indices)
- * is considered too large to move.
- * @param errmsg filled with textual description of error if this call return false.
- * @return false if approximate chunk size is too big to move or true otherwise.
- */
- bool storeCurrentLocs(OperationContext* txn,
- long long maxChunkSize,
- string& errmsg,
- BSONObjBuilder& result ) {
- AutoGetCollectionForRead ctx(txn, getNS());
- Collection* collection = ctx.getCollection();
- if ( !collection ) {
- errmsg = "ns not found, should be impossible";
- return false;
- }
-
- // Allow multiKey based on the invariant that shard keys must be single-valued.
- // Therefore, any multi-key index prefixed by shard key cannot be multikey over
- // the shard key fields.
- IndexDescriptor *idx =
- collection->getIndexCatalog()->findShardKeyPrefixedIndex(txn,
- _shardKeyPattern ,
- false); // requireSingleKey
+ // Allow multiKey based on the invariant that shard keys must be single-valued.
+ // Therefore, any multi-key index prefixed by shard key cannot be multikey over
+ // the shard key fields.
+ IndexDescriptor* idx =
+ collection->getIndexCatalog()->findShardKeyPrefixedIndex(txn,
+ _shardKeyPattern,
+ false); // requireSingleKey
+
+ if (idx == NULL) {
+ errmsg = str::stream() << "can't find index with prefix " << _shardKeyPattern
+ << " in storeCurrentLocs for " << _ns;
+ return false;
+ }
- if (idx == NULL) {
- errmsg = str::stream() << "can't find index with prefix " << _shardKeyPattern
- << " in storeCurrentLocs for " << _ns;
- return false;
- }
+ // Assume both min and max non-empty, append MinKey's to make them fit chosen index
+ BSONObj min;
+ BSONObj max;
+ KeyPattern kp(idx->keyPattern());
- // Assume both min and max non-empty, append MinKey's to make them fit chosen index
- BSONObj min;
- BSONObj max;
- KeyPattern kp(idx->keyPattern());
+ {
+ // It's alright not to lock _mutex all the way through based on the assumption
+ // that this is only called by the main thread that drives the migration and
+ // only it can start and stop the current migration.
+ std::lock_guard<std::mutex> sl(_mutex);
- {
- // It's alright not to lock _mutex all the way through based on the assumption
- // that this is only called by the main thread that drives the migration and
- // only it can start and stop the current migration.
- std::lock_guard<std::mutex> sl(_mutex);
+ invariant(_deleteNotifyExec.get() == NULL);
+ WorkingSet* ws = new WorkingSet();
+ DeleteNotificationStage* dns = new DeleteNotificationStage();
+ PlanExecutor* deleteNotifyExec;
+ // Takes ownership of 'ws' and 'dns'.
+ Status execStatus = PlanExecutor::make(
+ txn, ws, dns, collection, PlanExecutor::YIELD_MANUAL, &deleteNotifyExec);
+ invariant(execStatus.isOK());
+ deleteNotifyExec->registerExec();
+ _deleteNotifyExec.reset(deleteNotifyExec);
+
+ min = Helpers::toKeyFormat(kp.extendRangeBound(_min, false));
+ max = Helpers::toKeyFormat(kp.extendRangeBound(_max, false));
+ }
- invariant( _deleteNotifyExec.get() == NULL );
- WorkingSet* ws = new WorkingSet();
- DeleteNotificationStage* dns = new DeleteNotificationStage();
- PlanExecutor* deleteNotifyExec;
- // Takes ownership of 'ws' and 'dns'.
- Status execStatus = PlanExecutor::make(txn,
- ws,
- dns,
- collection,
- PlanExecutor::YIELD_MANUAL,
- &deleteNotifyExec);
- invariant(execStatus.isOK());
- deleteNotifyExec->registerExec();
- _deleteNotifyExec.reset(deleteNotifyExec);
-
- min = Helpers::toKeyFormat(kp.extendRangeBound(_min, false));
- max = Helpers::toKeyFormat(kp.extendRangeBound(_max, false));
- }
-
- std::unique_ptr<PlanExecutor> exec(
- InternalPlanner::indexScan(txn, collection, idx, min, max, false));
- // We can afford to yield here because any change to the base data that we might
- // miss is already being queued and will migrate in the 'transferMods' stage.
- exec->setYieldPolicy(PlanExecutor::YIELD_AUTO);
-
- // use the average object size to estimate how many objects a full chunk would carry
- // do that while traversing the chunk's range using the sharding index, below
- // there's a fair amount of slack before we determine a chunk is too large because object sizes will vary
- unsigned long long maxRecsWhenFull;
- long long avgRecSize;
- const long long totalRecs = collection->numRecords(txn);
- if ( totalRecs > 0 ) {
- avgRecSize = collection->dataSize(txn) / totalRecs;
- maxRecsWhenFull = maxChunkSize / avgRecSize;
- maxRecsWhenFull = std::min((unsigned long long)(Chunk::MaxObjectPerChunk + 1) , 130 * maxRecsWhenFull / 100 /* slack */ );
- }
- else {
- avgRecSize = 0;
- maxRecsWhenFull = Chunk::MaxObjectPerChunk + 1;
- }
-
- // do a full traversal of the chunk and don't stop even if we think it is a large chunk
- // we want the number of records to better report, in that case
- bool isLargeChunk = false;
- unsigned long long recCount = 0;;
- RecordId dl;
- while (PlanExecutor::ADVANCED == exec->getNext(NULL, &dl)) {
- if ( ! isLargeChunk ) {
- std::lock_guard<std::mutex> lk(_cloneLocsMutex);
- _cloneLocs.insert( dl );
- }
+ std::unique_ptr<PlanExecutor> exec(
+ InternalPlanner::indexScan(txn, collection, idx, min, max, false));
+ // We can afford to yield here because any change to the base data that we might
+ // miss is already being queued and will migrate in the 'transferMods' stage.
+ exec->setYieldPolicy(PlanExecutor::YIELD_AUTO);
+
+ // use the average object size to estimate how many objects a full chunk would carry
+ // do that while traversing the chunk's range using the sharding index, below
+ // there's a fair amount of slack before we determine a chunk is too large because object sizes will vary
+ unsigned long long maxRecsWhenFull;
+ long long avgRecSize;
+ const long long totalRecs = collection->numRecords(txn);
+ if (totalRecs > 0) {
+ avgRecSize = collection->dataSize(txn) / totalRecs;
+ maxRecsWhenFull = maxChunkSize / avgRecSize;
+ maxRecsWhenFull = std::min((unsigned long long)(Chunk::MaxObjectPerChunk + 1),
+ 130 * maxRecsWhenFull / 100 /* slack */);
+ } else {
+ avgRecSize = 0;
+ maxRecsWhenFull = Chunk::MaxObjectPerChunk + 1;
+ }
- if ( ++recCount > maxRecsWhenFull ) {
- isLargeChunk = true;
- // continue on despite knowing that it will fail,
- // just to get the correct value for recCount.
- }
+ // do a full traversal of the chunk and don't stop even if we think it is a large chunk
+ // we want the number of records to better report, in that case
+ bool isLargeChunk = false;
+ unsigned long long recCount = 0;
+ ;
+ RecordId dl;
+ while (PlanExecutor::ADVANCED == exec->getNext(NULL, &dl)) {
+ if (!isLargeChunk) {
+ std::lock_guard<std::mutex> lk(_cloneLocsMutex);
+ _cloneLocs.insert(dl);
}
- exec.reset();
- if ( isLargeChunk ) {
- std::lock_guard<std::mutex> sl(_mutex);
- warning() << "cannot move chunk: the maximum number of documents for a chunk is "
- << maxRecsWhenFull << " , the maximum chunk size is " << maxChunkSize
- << " , average document size is " << avgRecSize
- << ". Found " << recCount << " documents in chunk "
- << " ns: " << _ns << " "
- << _min << " -> " << _max << migrateLog;
-
- result.appendBool( "chunkTooBig" , true );
- result.appendNumber( "estimatedChunkSize" , (long long)(recCount * avgRecSize) );
- errmsg = "chunk too big to move";
- return false;
+ if (++recCount > maxRecsWhenFull) {
+ isLargeChunk = true;
+ // continue on despite knowing that it will fail,
+ // just to get the correct value for recCount.
}
+ }
+ exec.reset();
- log() << "moveChunk number of documents: " << cloneLocsRemaining() << migrateLog;
-
- txn->recoveryUnit()->abandonSnapshot();
- return true;
+ if (isLargeChunk) {
+ std::lock_guard<std::mutex> sl(_mutex);
+ warning() << "cannot move chunk: the maximum number of documents for a chunk is "
+ << maxRecsWhenFull << " , the maximum chunk size is " << maxChunkSize
+ << " , average document size is " << avgRecSize << ". Found " << recCount
+ << " documents in chunk "
+ << " ns: " << _ns << " " << _min << " -> " << _max << migrateLog;
+
+ result.appendBool("chunkTooBig", true);
+ result.appendNumber("estimatedChunkSize", (long long)(recCount * avgRecSize));
+ errmsg = "chunk too big to move";
+ return false;
}
- bool clone(OperationContext* txn, string& errmsg , BSONObjBuilder& result ) {
- ElapsedTracker tracker(internalQueryExecYieldIterations,
- internalQueryExecYieldPeriodMS);
+ log() << "moveChunk number of documents: " << cloneLocsRemaining() << migrateLog;
- int allocSize = 0;
- {
- AutoGetCollectionForRead ctx(txn, getNS());
+ txn->recoveryUnit()->abandonSnapshot();
+ return true;
+ }
- std::lock_guard<std::mutex> sl(_mutex);
- if (!_active) {
- errmsg = "not active";
- return false;
- }
+ bool clone(OperationContext* txn, string& errmsg, BSONObjBuilder& result) {
+ ElapsedTracker tracker(internalQueryExecYieldIterations, internalQueryExecYieldPeriodMS);
- Collection* collection = ctx.getCollection();
- if (!collection) {
- errmsg = str::stream() << "collection " << _ns << " does not exist";
- return false;
- }
+ int allocSize = 0;
+ {
+ AutoGetCollectionForRead ctx(txn, getNS());
- allocSize =
- std::min(BSONObjMaxUserSize,
- static_cast<int>((12 + collection->averageObjectSize(txn)) *
- cloneLocsRemaining()));
+ std::lock_guard<std::mutex> sl(_mutex);
+ if (!_active) {
+ errmsg = "not active";
+ return false;
}
- bool isBufferFilled = false;
- BSONArrayBuilder clonedDocsArrayBuilder(allocSize);
- while (!isBufferFilled) {
- AutoGetCollectionForRead ctx(txn, getNS());
+ Collection* collection = ctx.getCollection();
+ if (!collection) {
+ errmsg = str::stream() << "collection " << _ns << " does not exist";
+ return false;
+ }
- std::lock_guard<std::mutex> sl(_mutex);
- if (!_active) {
- errmsg = "not active";
- return false;
- }
+ allocSize = std::min(
+ BSONObjMaxUserSize,
+ static_cast<int>((12 + collection->averageObjectSize(txn)) * cloneLocsRemaining()));
+ }
- // TODO: fix SERVER-16540 race
+ bool isBufferFilled = false;
+ BSONArrayBuilder clonedDocsArrayBuilder(allocSize);
+ while (!isBufferFilled) {
+ AutoGetCollectionForRead ctx(txn, getNS());
- Collection* collection = ctx.getCollection();
+ std::lock_guard<std::mutex> sl(_mutex);
+ if (!_active) {
+ errmsg = "not active";
+ return false;
+ }
- if (!collection) {
- errmsg = str::stream() << "collection " << _ns << " does not exist";
- return false;
- }
+ // TODO: fix SERVER-16540 race
- std::lock_guard<std::mutex> lk(_cloneLocsMutex);
- set<RecordId>::iterator cloneLocsIter = _cloneLocs.begin();
- for ( ; cloneLocsIter != _cloneLocs.end(); ++cloneLocsIter) {
- if (tracker.intervalHasElapsed()) // should I yield?
- break;
+ Collection* collection = ctx.getCollection();
- RecordId dl = *cloneLocsIter;
- Snapshotted<BSONObj> doc;
- if (!collection->findDoc(txn, dl, &doc)) {
- // doc was deleted
- continue;
- }
+ if (!collection) {
+ errmsg = str::stream() << "collection " << _ns << " does not exist";
+ return false;
+ }
- // Use the builder size instead of accumulating 'doc's size so that we take
- // into consideration the overhead of BSONArray indices, and *always*
- // append one doc.
- if (clonedDocsArrayBuilder.arrSize() != 0 &&
- (clonedDocsArrayBuilder.len() + doc.value().objsize() + 1024)
- > BSONObjMaxUserSize) {
- isBufferFilled = true; // break out of outer while loop
- break;
- }
+ std::lock_guard<std::mutex> lk(_cloneLocsMutex);
+ set<RecordId>::iterator cloneLocsIter = _cloneLocs.begin();
+ for (; cloneLocsIter != _cloneLocs.end(); ++cloneLocsIter) {
+ if (tracker.intervalHasElapsed()) // should I yield?
+ break;
- clonedDocsArrayBuilder.append(doc.value());
+ RecordId dl = *cloneLocsIter;
+ Snapshotted<BSONObj> doc;
+ if (!collection->findDoc(txn, dl, &doc)) {
+ // doc was deleted
+ continue;
}
- _cloneLocs.erase(_cloneLocs.begin(), cloneLocsIter);
-
- // Note: must be holding _cloneLocsMutex, don't move this inside while condition!
- if (_cloneLocs.empty()) {
+ // Use the builder size instead of accumulating 'doc's size so that we take
+ // into consideration the overhead of BSONArray indices, and *always*
+ // append one doc.
+ if (clonedDocsArrayBuilder.arrSize() != 0 &&
+ (clonedDocsArrayBuilder.len() + doc.value().objsize() + 1024) >
+ BSONObjMaxUserSize) {
+ isBufferFilled = true; // break out of outer while loop
break;
}
+
+ clonedDocsArrayBuilder.append(doc.value());
}
- result.appendArray("objects", clonedDocsArrayBuilder.arr());
- return true;
+ _cloneLocs.erase(_cloneLocs.begin(), cloneLocsIter);
+
+ // Note: must be holding _cloneLocsMutex, don't move this inside while condition!
+ if (_cloneLocs.empty()) {
+ break;
+ }
}
- void aboutToDelete( const RecordId& dl ) {
- // Even though above we call findDoc to check for existance
- // that check only works for non-mmapv1 engines, and this is needed
- // for mmapv1.
+ result.appendArray("objects", clonedDocsArrayBuilder.arr());
+ return true;
+ }
- std::lock_guard<std::mutex> lk(_cloneLocsMutex);
- _cloneLocs.erase( dl );
- }
+ void aboutToDelete(const RecordId& dl) {
+ // Even though above we call findDoc to check for existance
+ // that check only works for non-mmapv1 engines, and this is needed
+ // for mmapv1.
- std::size_t cloneLocsRemaining() {
- std::lock_guard<std::mutex> lk(_cloneLocsMutex);
- return _cloneLocs.size();
- }
+ std::lock_guard<std::mutex> lk(_cloneLocsMutex);
+ _cloneLocs.erase(dl);
+ }
- long long mbUsed() const {
- std::lock_guard<std::mutex> lk(_mutex);
- return _memoryUsed / ( 1024 * 1024 );
- }
+ std::size_t cloneLocsRemaining() {
+ std::lock_guard<std::mutex> lk(_cloneLocsMutex);
+ return _cloneLocs.size();
+ }
- bool getInCriticalSection() const {
- std::lock_guard<std::mutex> lk(_mutex);
- return _inCriticalSection;
- }
+ long long mbUsed() const {
+ std::lock_guard<std::mutex> lk(_mutex);
+ return _memoryUsed / (1024 * 1024);
+ }
- void setInCriticalSection( bool b ) {
- std::lock_guard<std::mutex> lk(_mutex);
- _inCriticalSection = b;
- _inCriticalSectionCV.notify_all();
- }
+ bool getInCriticalSection() const {
+ std::lock_guard<std::mutex> lk(_mutex);
+ return _inCriticalSection;
+ }
- std::string getNS() const {
- std::lock_guard<std::mutex> sl(_mutex);
- return _ns;
- }
+ void setInCriticalSection(bool b) {
+ std::lock_guard<std::mutex> lk(_mutex);
+ _inCriticalSection = b;
+ _inCriticalSectionCV.notify_all();
+ }
- /**
- * @return true if we are NOT in the critical section
- */
- bool waitTillNotInCriticalSection( int maxSecondsToWait ) {
- const auto deadline = system_clock::now() + seconds(maxSecondsToWait);
- std::unique_lock<std::mutex> lk(_mutex);
- while (_inCriticalSection) {
- if (std::cv_status::timeout == _inCriticalSectionCV.wait_until(lk, deadline))
- return false;
- }
+ std::string getNS() const {
+ std::lock_guard<std::mutex> sl(_mutex);
+ return _ns;
+ }
- return true;
+ /**
+ * @return true if we are NOT in the critical section
+ */
+ bool waitTillNotInCriticalSection(int maxSecondsToWait) {
+ const auto deadline = system_clock::now() + seconds(maxSecondsToWait);
+ std::unique_lock<std::mutex> lk(_mutex);
+ while (_inCriticalSection) {
+ if (std::cv_status::timeout == _inCriticalSectionCV.wait_until(lk, deadline))
+ return false;
}
- bool isActive() const { return _getActive(); }
+ return true;
+ }
- private:
- bool _getActive() const { std::lock_guard<std::mutex> lk(_mutex); return _active; }
- void _setActive( bool b ) { std::lock_guard<std::mutex> lk(_mutex); _active = b; }
+ bool isActive() const {
+ return _getActive();
+ }
+
+private:
+ bool _getActive() const {
+ std::lock_guard<std::mutex> lk(_mutex);
+ return _active;
+ }
+ void _setActive(bool b) {
+ std::lock_guard<std::mutex> lk(_mutex);
+ _active = b;
+ }
+ /**
+ * Used to commit work for LogOpForSharding. Used to keep track of changes in documents
+ * that are part of a chunk being migrated.
+ */
+ class LogOpForShardingHandler : public RecoveryUnit::Change {
+ public:
/**
- * Used to commit work for LogOpForSharding. Used to keep track of changes in documents
- * that are part of a chunk being migrated.
+ * Invariant: idObj should belong to a document that is part of the active chunk
+ * being migrated.
*/
- class LogOpForShardingHandler : public RecoveryUnit::Change {
- public:
- /**
- * Invariant: idObj should belong to a document that is part of the active chunk
- * being migrated.
- */
- LogOpForShardingHandler(MigrateFromStatus* migrateFromStatus,
- const BSONObj& idObj,
- const char op):
- _migrateFromStatus(migrateFromStatus),
- _idObj(idObj.getOwned()),
- _op(op) {
- }
-
- virtual void commit() {
- switch (_op) {
+ LogOpForShardingHandler(MigrateFromStatus* migrateFromStatus,
+ const BSONObj& idObj,
+ const char op)
+ : _migrateFromStatus(migrateFromStatus), _idObj(idObj.getOwned()), _op(op) {}
+
+ virtual void commit() {
+ switch (_op) {
case 'd': {
std::lock_guard<std::mutex> sl(_migrateFromStatus->_mutex);
_migrateFromStatus->_deleted.push_back(_idObj);
@@ -746,8 +738,7 @@ namespace {
}
case 'i':
- case 'u':
- {
+ case 'u': {
std::lock_guard<std::mutex> sl(_migrateFromStatus->_mutex);
_migrateFromStatus->_reload.push_back(_idObj);
_migrateFromStatus->_memoryUsed += _idObj.firstElement().size() + 5;
@@ -756,2184 +747,2146 @@ namespace {
default:
invariant(false);
-
- }
}
+ }
- virtual void rollback() { }
+ virtual void rollback() {}
- private:
- MigrateFromStatus* _migrateFromStatus;
- const BSONObj _idObj;
- const char _op;
- };
+ private:
+ MigrateFromStatus* _migrateFromStatus;
+ const BSONObj _idObj;
+ const char _op;
+ };
- /**
- * Used to receive invalidation notifications.
- *
- * XXX: move to the exec/ directory.
- */
- class DeleteNotificationStage : public PlanStage {
- public:
- virtual void invalidate(OperationContext* txn,
- const RecordId& dl,
- InvalidationType type);
+ /**
+ * Used to receive invalidation notifications.
+ *
+ * XXX: move to the exec/ directory.
+ */
+ class DeleteNotificationStage : public PlanStage {
+ public:
+ virtual void invalidate(OperationContext* txn, const RecordId& dl, InvalidationType type);
- virtual StageState work(WorkingSetID* out) {
- invariant( false );
- }
- virtual bool isEOF() {
- invariant( false );
- return false;
- }
- virtual void kill() {
- }
- virtual void saveState() {
- invariant( false );
- }
- virtual void restoreState(OperationContext* opCtx) {
- invariant( false );
- }
- virtual PlanStageStats* getStats() {
- invariant( false );
- return NULL;
- }
- virtual CommonStats* getCommonStats() const {
- invariant( false );
- return NULL;
- }
- virtual SpecificStats* getSpecificStats() const {
- invariant( false );
- return NULL;
- }
- virtual std::vector<PlanStage*> getChildren() const {
- vector<PlanStage*> empty;
- return empty;
- }
- virtual StageType stageType() const {
- return STAGE_NOTIFY_DELETE;
- }
- };
+ virtual StageState work(WorkingSetID* out) {
+ invariant(false);
+ }
+ virtual bool isEOF() {
+ invariant(false);
+ return false;
+ }
+ virtual void kill() {}
+ virtual void saveState() {
+ invariant(false);
+ }
+ virtual void restoreState(OperationContext* opCtx) {
+ invariant(false);
+ }
+ virtual PlanStageStats* getStats() {
+ invariant(false);
+ return NULL;
+ }
+ virtual CommonStats* getCommonStats() const {
+ invariant(false);
+ return NULL;
+ }
+ virtual SpecificStats* getSpecificStats() const {
+ invariant(false);
+ return NULL;
+ }
+ virtual std::vector<PlanStage*> getChildren() const {
+ vector<PlanStage*> empty;
+ return empty;
+ }
+ virtual StageType stageType() const {
+ return STAGE_NOTIFY_DELETE;
+ }
+ };
- //
- // All member variables are labeled with one of the following codes indicating the
- // synchronization rules for accessing them.
- //
- // (M) Must hold _mutex for access.
- // (MG) For reads, _mutex *OR* Global IX Lock must be held.
- // For writes, the _mutex *AND* (Global Shared or Exclusive Lock) must be held.
- // (C) Must hold _cloneLocsMutex for access.
- //
- // Locking order:
- //
- // Global Lock -> _mutex -> _cloneLocsMutex
+ //
+ // All member variables are labeled with one of the following codes indicating the
+ // synchronization rules for accessing them.
+ //
+ // (M) Must hold _mutex for access.
+ // (MG) For reads, _mutex *OR* Global IX Lock must be held.
+ // For writes, the _mutex *AND* (Global Shared or Exclusive Lock) must be held.
+ // (C) Must hold _cloneLocsMutex for access.
+ //
+ // Locking order:
+ //
+ // Global Lock -> _mutex -> _cloneLocsMutex
- mutable std::mutex _mutex;
+ mutable std::mutex _mutex;
- std::condition_variable _inCriticalSectionCV; // (M)
+ std::condition_variable _inCriticalSectionCV; // (M)
- // Is migration currently in critical section. This can be used to block new writes.
- bool _inCriticalSection; // (M)
+ // Is migration currently in critical section. This can be used to block new writes.
+ bool _inCriticalSection; // (M)
- std::unique_ptr<PlanExecutor> _deleteNotifyExec; // (M)
+ std::unique_ptr<PlanExecutor> _deleteNotifyExec; // (M)
- // List of _id of documents that were modified that must be re-cloned.
- list<BSONObj> _reload; // (M)
+ // List of _id of documents that were modified that must be re-cloned.
+ list<BSONObj> _reload; // (M)
- // List of _id of documents that were deleted during clone that should be deleted later.
- list<BSONObj> _deleted; // (M)
+ // List of _id of documents that were deleted during clone that should be deleted later.
+ list<BSONObj> _deleted; // (M)
- // bytes in _reload + _deleted
- long long _memoryUsed; // (M)
+ // bytes in _reload + _deleted
+ long long _memoryUsed; // (M)
- // If a migration is currently active.
- bool _active; // (MG)
+ // If a migration is currently active.
+ bool _active; // (MG)
- string _ns; // (MG)
- BSONObj _min; // (MG)
- BSONObj _max; // (MG)
- BSONObj _shardKeyPattern; // (MG)
+ string _ns; // (MG)
+ BSONObj _min; // (MG)
+ BSONObj _max; // (MG)
+ BSONObj _shardKeyPattern; // (MG)
- mutable std::mutex _cloneLocsMutex;
+ mutable std::mutex _cloneLocsMutex;
- // List of record id that needs to be transferred from here to the other side.
- set<RecordId> _cloneLocs; // (C)
+ // List of record id that needs to be transferred from here to the other side.
+ set<RecordId> _cloneLocs; // (C)
- } migrateFromStatus;
+} migrateFromStatus;
- void MigrateFromStatus::DeleteNotificationStage::invalidate(OperationContext *txn,
- const RecordId& dl,
- InvalidationType type) {
- if ( type == INVALIDATION_DELETION ) {
- migrateFromStatus.aboutToDelete( dl );
- }
+void MigrateFromStatus::DeleteNotificationStage::invalidate(OperationContext* txn,
+ const RecordId& dl,
+ InvalidationType type) {
+ if (type == INVALIDATION_DELETION) {
+ migrateFromStatus.aboutToDelete(dl);
}
+}
- struct MigrateStatusHolder {
- MigrateStatusHolder( OperationContext* txn,
- const std::string& ns ,
- const BSONObj& min ,
- const BSONObj& max ,
- const BSONObj& shardKeyPattern )
- : _txn(txn) {
- _isAnotherMigrationActive =
- !migrateFromStatus.start(txn, ns, min, max, shardKeyPattern);
- }
- ~MigrateStatusHolder() {
- if (!_isAnotherMigrationActive) {
- migrateFromStatus.done(_txn);
- }
+struct MigrateStatusHolder {
+ MigrateStatusHolder(OperationContext* txn,
+ const std::string& ns,
+ const BSONObj& min,
+ const BSONObj& max,
+ const BSONObj& shardKeyPattern)
+ : _txn(txn) {
+ _isAnotherMigrationActive = !migrateFromStatus.start(txn, ns, min, max, shardKeyPattern);
+ }
+ ~MigrateStatusHolder() {
+ if (!_isAnotherMigrationActive) {
+ migrateFromStatus.done(_txn);
}
+ }
- bool isAnotherMigrationActive() const {
- return _isAnotherMigrationActive;
- }
+ bool isAnotherMigrationActive() const {
+ return _isAnotherMigrationActive;
+ }
- private:
- OperationContext* _txn;
- bool _isAnotherMigrationActive;
- };
+private:
+ OperationContext* _txn;
+ bool _isAnotherMigrationActive;
+};
+
+void logOpForSharding(OperationContext* txn,
+ const char* opstr,
+ const char* ns,
+ const BSONObj& obj,
+ BSONObj* patt,
+ bool notInActiveChunk) {
+ migrateFromStatus.logOp(txn, opstr, ns, obj, patt, notInActiveChunk);
+}
- void logOpForSharding(OperationContext* txn,
- const char * opstr,
- const char * ns,
- const BSONObj& obj,
- BSONObj * patt,
- bool notInActiveChunk) {
- migrateFromStatus.logOp(txn, opstr, ns, obj, patt, notInActiveChunk);
+class TransferModsCommand : public ChunkCommandHelper {
+public:
+ void help(std::stringstream& h) const {
+ h << "internal";
}
+ TransferModsCommand() : ChunkCommandHelper("_transferMods") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::internal);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
+ bool run(OperationContext* txn,
+ const string&,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ return migrateFromStatus.transferMods(txn, errmsg, result);
+ }
+} transferModsCommand;
- class TransferModsCommand : public ChunkCommandHelper {
- public:
- void help(std::stringstream& h) const { h << "internal"; }
- TransferModsCommand() : ChunkCommandHelper( "_transferMods" ) {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::internal);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
- }
- bool run(OperationContext* txn,
- const string&,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- return migrateFromStatus.transferMods(txn, errmsg, result);
- }
- } transferModsCommand;
-
-
- class InitialCloneCommand : public ChunkCommandHelper {
- public:
- void help(std::stringstream& h) const { h << "internal"; }
- InitialCloneCommand() : ChunkCommandHelper( "_migrateClone" ) {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::internal);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
- }
- bool run(OperationContext* txn,
- const string&,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- return migrateFromStatus.clone(txn, errmsg, result);
- }
- } initialCloneCommand;
-
- // Tests can pause / resume moveChunk's progress at each step by enabling / disabling each fail point.
- MONGO_FP_DECLARE(moveChunkHangAtStep1);
- MONGO_FP_DECLARE(moveChunkHangAtStep2);
- MONGO_FP_DECLARE(moveChunkHangAtStep3);
- MONGO_FP_DECLARE(moveChunkHangAtStep4);
- MONGO_FP_DECLARE(moveChunkHangAtStep5);
- MONGO_FP_DECLARE(moveChunkHangAtStep6);
- /**
- * this is the main entry for moveChunk
- * called to initial a move
- * usually by a mongos
- * this is called on the "from" side
- *
- * Format:
- * {
- * moveChunk: "namespace",
- * from: "hostAndPort",
- * fromShard: "shardName",
- * to: "hostAndPort",
- * toShard: "shardName",
- * min: {},
- * max: {},
- * maxChunkBytes: numeric,
- * configdb: "hostAndPort",
- *
- * // optional
- * secondaryThrottle: bool, //defaults to true.
- * writeConcern: {} // applies to individual writes.
- * }
- */
- class MoveChunkCommand : public Command {
- public:
- MoveChunkCommand() : Command( "moveChunk" ) {}
- virtual void help( std::stringstream& help ) const {
- help << "should not be calling this directly";
- }
-
- virtual bool slaveOk() const { return false; }
- virtual bool adminOnly() const { return true; }
- virtual bool isWriteCommandForConfigServer() const { return false; }
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
- ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname, cmdObj))),
- ActionType::moveChunk)) {
- return Status(ErrorCodes::Unauthorized, "Unauthorized");
- }
- return Status::OK();
- }
- virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
- return parseNsFullyQualified(dbname, cmdObj);
- }
-
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- // 1. Parse options
- // 2. Make sure my view is complete and lock the distributed lock to ensure shard
- // metadata stability.
- // 3. Migration
- // Retrieve all RecordIds, which need to be migrated in order to do as little seeking
- // as possible during transfer. Retrieval of the RecordIds happens under a collection
- // lock, but then the collection lock is dropped. This opens up an opportunity for
- // repair or compact to invalidate these RecordIds, because these commands do not
- // synchronized with migration. Note that data modifications are not a problem,
- // because we are registered for change notifications.
- //
- // 4. pause till migrate caught up
- // 5. LOCK
- // a) update my config, essentially locking
- // b) finish migrate
- // c) update config server
- // d) logChange to config server
- // 6. wait for all current cursors to expire
- // 7. remove data locally
-
- // -------------------------------
-
- // 1.
- string ns = parseNs(dbname, cmdObj);
-
- // The shard addresses, redundant, but allows for validation
- string toShardHost = cmdObj["to"].str();
- string fromShardHost = cmdObj["from"].str();
-
- // The shard names
- string toShardName = cmdObj["toShard"].str();
- string fromShardName = cmdObj["fromShard"].str();
-
- // Process secondary throttle settings and assign defaults if necessary.
- BSONObj secThrottleObj;
- WriteConcernOptions writeConcern;
- Status status = writeConcern.parseSecondaryThrottle(cmdObj, &secThrottleObj);
-
- if (!status.isOK()){
- if (status.code() != ErrorCodes::WriteConcernNotDefined) {
- warning() << status.toString();
- return appendCommandStatus(result, status);
- }
+class InitialCloneCommand : public ChunkCommandHelper {
+public:
+ void help(std::stringstream& h) const {
+ h << "internal";
+ }
+ InitialCloneCommand() : ChunkCommandHelper("_migrateClone") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::internal);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
+ bool run(OperationContext* txn,
+ const string&,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ return migrateFromStatus.clone(txn, errmsg, result);
+ }
+} initialCloneCommand;
- writeConcern = getDefaultWriteConcern();
- }
- else {
- repl::ReplicationCoordinator* replCoordinator =
- repl::getGlobalReplicationCoordinator();
+// Tests can pause / resume moveChunk's progress at each step by enabling / disabling each fail point.
+MONGO_FP_DECLARE(moveChunkHangAtStep1);
+MONGO_FP_DECLARE(moveChunkHangAtStep2);
+MONGO_FP_DECLARE(moveChunkHangAtStep3);
+MONGO_FP_DECLARE(moveChunkHangAtStep4);
+MONGO_FP_DECLARE(moveChunkHangAtStep5);
+MONGO_FP_DECLARE(moveChunkHangAtStep6);
- if (replCoordinator->getReplicationMode() ==
- repl::ReplicationCoordinator::modeMasterSlave &&
- writeConcern.shouldWaitForOtherNodes()) {
- warning() << "moveChunk cannot check if secondary throttle setting "
- << writeConcern.toBSON()
- << " can be enforced in a master slave configuration";
- }
+/**
+ * this is the main entry for moveChunk
+ * called to initial a move
+ * usually by a mongos
+ * this is called on the "from" side
+ *
+ * Format:
+ * {
+ * moveChunk: "namespace",
+ * from: "hostAndPort",
+ * fromShard: "shardName",
+ * to: "hostAndPort",
+ * toShard: "shardName",
+ * min: {},
+ * max: {},
+ * maxChunkBytes: numeric,
+ * configdb: "hostAndPort",
+ *
+ * // optional
+ * secondaryThrottle: bool, //defaults to true.
+ * writeConcern: {} // applies to individual writes.
+ * }
+ */
+class MoveChunkCommand : public Command {
+public:
+ MoveChunkCommand() : Command("moveChunk") {}
+ virtual void help(std::stringstream& help) const {
+ help << "should not be calling this directly";
+ }
- Status status = replCoordinator->checkIfWriteConcernCanBeSatisfied(writeConcern);
- if (!status.isOK() && status != ErrorCodes::NoReplicationEnabled) {
- warning() << status.toString();
- return appendCommandStatus(result, status);
- }
- }
+ virtual bool slaveOk() const {
+ return false;
+ }
+ virtual bool adminOnly() const {
+ return true;
+ }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname, cmdObj))),
+ ActionType::moveChunk)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+ return Status::OK();
+ }
+ virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
+ return parseNsFullyQualified(dbname, cmdObj);
+ }
- if (writeConcern.shouldWaitForOtherNodes() &&
- writeConcern.wTimeout == WriteConcernOptions::kNoTimeout) {
- // Don't allow no timeout.
- writeConcern.wTimeout = kDefaultWTimeoutMs;
- }
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ // 1. Parse options
+ // 2. Make sure my view is complete and lock the distributed lock to ensure shard
+ // metadata stability.
+ // 3. Migration
+ // Retrieve all RecordIds, which need to be migrated in order to do as little seeking
+ // as possible during transfer. Retrieval of the RecordIds happens under a collection
+ // lock, but then the collection lock is dropped. This opens up an opportunity for
+ // repair or compact to invalidate these RecordIds, because these commands do not
+ // synchronized with migration. Note that data modifications are not a problem,
+ // because we are registered for change notifications.
+ //
+ // 4. pause till migrate caught up
+ // 5. LOCK
+ // a) update my config, essentially locking
+ // b) finish migrate
+ // c) update config server
+ // d) logChange to config server
+ // 6. wait for all current cursors to expire
+ // 7. remove data locally
- // Do inline deletion
- bool waitForDelete = cmdObj["waitForDelete"].trueValue();
- if (waitForDelete) {
- log() << "moveChunk waiting for full cleanup after move";
- }
+ // -------------------------------
- BSONObj min = cmdObj["min"].Obj();
- BSONObj max = cmdObj["max"].Obj();
- BSONElement maxSizeElem = cmdObj["maxChunkSizeBytes"];
+ // 1.
+ string ns = parseNs(dbname, cmdObj);
- if ( ns.empty() ) {
- errmsg = "need to specify namespace in command";
- return false;
- }
+ // The shard addresses, redundant, but allows for validation
+ string toShardHost = cmdObj["to"].str();
+ string fromShardHost = cmdObj["from"].str();
- if ( toShardName.empty() ) {
- errmsg = "need to specify shard to move chunk to";
- return false;
- }
- if ( fromShardName.empty() ) {
- errmsg = "need to specify shard to move chunk from";
- return false;
- }
+ // The shard names
+ string toShardName = cmdObj["toShard"].str();
+ string fromShardName = cmdObj["fromShard"].str();
- if ( min.isEmpty() ) {
- errmsg = "need to specify a min";
- return false;
- }
+ // Process secondary throttle settings and assign defaults if necessary.
+ BSONObj secThrottleObj;
+ WriteConcernOptions writeConcern;
+ Status status = writeConcern.parseSecondaryThrottle(cmdObj, &secThrottleObj);
- if ( max.isEmpty() ) {
- errmsg = "need to specify a max";
- return false;
+ if (!status.isOK()) {
+ if (status.code() != ErrorCodes::WriteConcernNotDefined) {
+ warning() << status.toString();
+ return appendCommandStatus(result, status);
}
- if ( maxSizeElem.eoo() || ! maxSizeElem.isNumber() ) {
- errmsg = "need to specify maxChunkSizeBytes";
- return false;
- }
- const long long maxChunkSize = maxSizeElem.numberLong(); // in bytes
+ writeConcern = getDefaultWriteConcern();
+ } else {
+ repl::ReplicationCoordinator* replCoordinator = repl::getGlobalReplicationCoordinator();
- // This could be the first call that enables sharding - make sure we initialize the
- // sharding state for this shard.
- if ( ! shardingState.enabled() ) {
- if ( cmdObj["configdb"].type() != String ) {
- errmsg = "sharding not enabled";
- warning() << errmsg;
- return false;
- }
- string configdb = cmdObj["configdb"].String();
- ShardingState::initialize(configdb);
+ if (replCoordinator->getReplicationMode() ==
+ repl::ReplicationCoordinator::modeMasterSlave &&
+ writeConcern.shouldWaitForOtherNodes()) {
+ warning() << "moveChunk cannot check if secondary throttle setting "
+ << writeConcern.toBSON()
+ << " can be enforced in a master slave configuration";
}
- // Initialize our current shard name in the shard state if needed
- shardingState.gotShardName(fromShardName);
-
- // Make sure we're as up-to-date as possible with shard information
- // This catches the case where we had to previously changed a shard's host by
- // removing/adding a shard with the same name
- Shard::reloadShardInfo();
-
- ConnectionString configLoc = ConnectionString::parse(shardingState.getConfigServer(),
- errmsg);
- if (!configLoc.isValid()) {
- warning() << errmsg;
- return false;
+ Status status = replCoordinator->checkIfWriteConcernCanBeSatisfied(writeConcern);
+ if (!status.isOK() && status != ErrorCodes::NoReplicationEnabled) {
+ warning() << status.toString();
+ return appendCommandStatus(result, status);
}
+ }
- MoveTimingHelper timing(txn, "from" , ns , min , max , 6 /* steps */ , &errmsg,
- toShardName, fromShardName );
+ if (writeConcern.shouldWaitForOtherNodes() &&
+ writeConcern.wTimeout == WriteConcernOptions::kNoTimeout) {
+ // Don't allow no timeout.
+ writeConcern.wTimeout = kDefaultWTimeoutMs;
+ }
- log() << "received moveChunk request: " << cmdObj << migrateLog;
+ // Do inline deletion
+ bool waitForDelete = cmdObj["waitForDelete"].trueValue();
+ if (waitForDelete) {
+ log() << "moveChunk waiting for full cleanup after move";
+ }
- timing.done(1);
- MONGO_FP_PAUSE_WHILE(moveChunkHangAtStep1);
+ BSONObj min = cmdObj["min"].Obj();
+ BSONObj max = cmdObj["max"].Obj();
+ BSONElement maxSizeElem = cmdObj["maxChunkSizeBytes"];
- // 2.
+ if (ns.empty()) {
+ errmsg = "need to specify namespace in command";
+ return false;
+ }
- if ( migrateFromStatus.isActive() ) {
- errmsg = "migration already in progress";
- warning() << errmsg;
- return false;
- }
+ if (toShardName.empty()) {
+ errmsg = "need to specify shard to move chunk to";
+ return false;
+ }
+ if (fromShardName.empty()) {
+ errmsg = "need to specify shard to move chunk from";
+ return false;
+ }
- //
- // Get the distributed lock
- //
+ if (min.isEmpty()) {
+ errmsg = "need to specify a min";
+ return false;
+ }
- string whyMessage(str::stream() << "migrating chunk [" << minKey << ", " << maxKey
- << ") in " << ns);
- auto scopedDistLock = grid.catalogManager()->getDistLockManager()->lock(
- ns, whyMessage);
+ if (max.isEmpty()) {
+ errmsg = "need to specify a max";
+ return false;
+ }
- if (!scopedDistLock.isOK()) {
- errmsg = stream() << "could not acquire collection lock for " << ns
- << " to migrate chunk [" << minKey << "," << maxKey << ")"
- << causedBy(scopedDistLock.getStatus());
+ if (maxSizeElem.eoo() || !maxSizeElem.isNumber()) {
+ errmsg = "need to specify maxChunkSizeBytes";
+ return false;
+ }
+ const long long maxChunkSize = maxSizeElem.numberLong(); // in bytes
+ // This could be the first call that enables sharding - make sure we initialize the
+ // sharding state for this shard.
+ if (!shardingState.enabled()) {
+ if (cmdObj["configdb"].type() != String) {
+ errmsg = "sharding not enabled";
warning() << errmsg;
return false;
}
+ string configdb = cmdObj["configdb"].String();
+ ShardingState::initialize(configdb);
+ }
- BSONObj chunkInfo = BSON("min" << min << "max" << max <<
- "from" << fromShardName << "to" << toShardName);
+ // Initialize our current shard name in the shard state if needed
+ shardingState.gotShardName(fromShardName);
- grid.catalogManager()->logChange(txn, "moveChunk.start", ns, chunkInfo);
+ // Make sure we're as up-to-date as possible with shard information
+ // This catches the case where we had to previously changed a shard's host by
+ // removing/adding a shard with the same name
+ Shard::reloadShardInfo();
- // Always refresh our metadata remotely
- ChunkVersion origShardVersion;
- Status refreshStatus = shardingState.refreshMetadataNow(txn, ns, &origShardVersion);
+ ConnectionString configLoc =
+ ConnectionString::parse(shardingState.getConfigServer(), errmsg);
+ if (!configLoc.isValid()) {
+ warning() << errmsg;
+ return false;
+ }
- if (!refreshStatus.isOK()) {
- errmsg = str::stream() << "moveChunk cannot start migrate of chunk "
- << "[" << minKey << "," << maxKey << ")"
- << causedBy(refreshStatus.reason());
+ MoveTimingHelper timing(
+ txn, "from", ns, min, max, 6 /* steps */, &errmsg, toShardName, fromShardName);
- warning() << errmsg;
- return false;
- }
-
- if (origShardVersion.majorVersion() == 0) {
- // It makes no sense to migrate if our version is zero and we have no chunks
- errmsg = str::stream() << "moveChunk cannot start migrate of chunk "
- << "[" << minKey << "," << maxKey << ")"
- << " with zero shard version";
+ log() << "received moveChunk request: " << cmdObj << migrateLog;
- warning() << errmsg;
- return false;
- }
+ timing.done(1);
+ MONGO_FP_PAUSE_WHILE(moveChunkHangAtStep1);
- // From mongos >= v3.0.
- BSONElement epochElem(cmdObj["epoch"]);
- if (epochElem.type() == jstOID) {
- OID cmdEpoch = epochElem.OID();
+ // 2.
- if (cmdEpoch != origShardVersion.epoch()) {
- errmsg = str::stream() << "moveChunk cannot move chunk "
- << "[" << minKey << ","
- << maxKey << "), "
- << "collection may have been dropped. "
- << "current epoch: " << origShardVersion.epoch()
- << ", cmd epoch: " << cmdEpoch;
- warning() << errmsg;
- return false;
- }
- }
+ if (migrateFromStatus.isActive()) {
+ errmsg = "migration already in progress";
+ warning() << errmsg;
+ return false;
+ }
- // Get collection metadata
- const CollectionMetadataPtr origCollMetadata(shardingState.getCollectionMetadata(ns));
+ //
+ // Get the distributed lock
+ //
- // With nonzero shard version, we must have metadata
- invariant(NULL != origCollMetadata);
+ string whyMessage(str::stream() << "migrating chunk [" << minKey << ", " << maxKey
+ << ") in " << ns);
+ auto scopedDistLock = grid.catalogManager()->getDistLockManager()->lock(ns, whyMessage);
- ChunkVersion origCollVersion = origCollMetadata->getCollVersion();
- BSONObj shardKeyPattern = origCollMetadata->getKeyPattern();
+ if (!scopedDistLock.isOK()) {
+ errmsg = stream() << "could not acquire collection lock for " << ns
+ << " to migrate chunk [" << minKey << "," << maxKey << ")"
+ << causedBy(scopedDistLock.getStatus());
- // With nonzero shard version, we must have a coll version >= our shard version
- invariant(origCollVersion >= origShardVersion);
+ warning() << errmsg;
+ return false;
+ }
- // With nonzero shard version, we must have a shard key
- invariant(!shardKeyPattern.isEmpty());
+ BSONObj chunkInfo =
+ BSON("min" << min << "max" << max << "from" << fromShardName << "to" << toShardName);
- ChunkType origChunk;
- if (!origCollMetadata->getNextChunk(min, &origChunk)
- || origChunk.getMin().woCompare(min)
- || origChunk.getMax().woCompare(max)) {
+ grid.catalogManager()->logChange(txn, "moveChunk.start", ns, chunkInfo);
- // Our boundaries are different from those passed in
- errmsg = str::stream() << "moveChunk cannot find chunk "
- << "[" << minKey << "," << maxKey << ")"
- << " to migrate, the chunk boundaries may be stale";
+ // Always refresh our metadata remotely
+ ChunkVersion origShardVersion;
+ Status refreshStatus = shardingState.refreshMetadataNow(txn, ns, &origShardVersion);
- warning() << errmsg;
- return false;
- }
+ if (!refreshStatus.isOK()) {
+ errmsg = str::stream() << "moveChunk cannot start migrate of chunk "
+ << "[" << minKey << "," << maxKey << ")"
+ << causedBy(refreshStatus.reason());
- log() << "moveChunk request accepted at version " << origShardVersion;
+ warning() << errmsg;
+ return false;
+ }
- timing.done(2);
- MONGO_FP_PAUSE_WHILE(moveChunkHangAtStep2);
+ if (origShardVersion.majorVersion() == 0) {
+ // It makes no sense to migrate if our version is zero and we have no chunks
+ errmsg = str::stream() << "moveChunk cannot start migrate of chunk "
+ << "[" << minKey << "," << maxKey << ")"
+ << " with zero shard version";
- // 3.
- MigrateStatusHolder statusHolder(txn, ns, min, max, shardKeyPattern);
+ warning() << errmsg;
+ return false;
+ }
- if (statusHolder.isAnotherMigrationActive()) {
- errmsg = "moveChunk is already in progress from this shard";
+ // From mongos >= v3.0.
+ BSONElement epochElem(cmdObj["epoch"]);
+ if (epochElem.type() == jstOID) {
+ OID cmdEpoch = epochElem.OID();
+
+ if (cmdEpoch != origShardVersion.epoch()) {
+ errmsg = str::stream() << "moveChunk cannot move chunk "
+ << "[" << minKey << "," << maxKey << "), "
+ << "collection may have been dropped. "
+ << "current epoch: " << origShardVersion.epoch()
+ << ", cmd epoch: " << cmdEpoch;
warning() << errmsg;
return false;
}
+ }
- ConnectionString fromShardCS;
- ConnectionString toShardCS;
+ // Get collection metadata
+ const CollectionMetadataPtr origCollMetadata(shardingState.getCollectionMetadata(ns));
- // Resolve the shard connection strings.
- {
- std::shared_ptr<Shard> fromShard =
- grid.shardRegistry()->getShard(fromShardName);
- uassert(28674,
- str::stream() << "Source shard " << fromShardName
- << " is missing. This indicates metadata corruption.",
- fromShard);
+ // With nonzero shard version, we must have metadata
+ invariant(NULL != origCollMetadata);
- fromShardCS = fromShard->getConnString();
+ ChunkVersion origCollVersion = origCollMetadata->getCollVersion();
+ BSONObj shardKeyPattern = origCollMetadata->getKeyPattern();
- std::shared_ptr<Shard> toShard = grid.shardRegistry()->getShard(toShardName);
- uassert(28675,
- str::stream() << "Destination shard " << toShardName
- << " is missing. This indicates metadata corruption.",
- toShard);
+ // With nonzero shard version, we must have a coll version >= our shard version
+ invariant(origCollVersion >= origShardVersion);
- toShardCS = toShard->getConnString();
- }
+ // With nonzero shard version, we must have a shard key
+ invariant(!shardKeyPattern.isEmpty());
- {
- // See comment at the top of the function for more information on what
- // synchronization is used here.
- if (!migrateFromStatus.storeCurrentLocs(txn, maxChunkSize, errmsg, result)) {
- warning() << errmsg;
- return false;
- }
+ ChunkType origChunk;
+ if (!origCollMetadata->getNextChunk(min, &origChunk) || origChunk.getMin().woCompare(min) ||
+ origChunk.getMax().woCompare(max)) {
+ // Our boundaries are different from those passed in
+ errmsg = str::stream() << "moveChunk cannot find chunk "
+ << "[" << minKey << "," << maxKey << ")"
+ << " to migrate, the chunk boundaries may be stale";
- ScopedDbConnection connTo(toShardCS);
- BSONObj res;
- bool ok;
-
- const bool isSecondaryThrottle(writeConcern.shouldWaitForOtherNodes());
-
- BSONObjBuilder recvChunkStartBuilder;
- recvChunkStartBuilder.append("_recvChunkStart", ns);
- recvChunkStartBuilder.append("from", fromShardCS.toString());
- recvChunkStartBuilder.append("fromShardName", fromShardName);
- recvChunkStartBuilder.append("toShardName", toShardName);
- recvChunkStartBuilder.append("min", min);
- recvChunkStartBuilder.append("max", max);
- recvChunkStartBuilder.append("shardKeyPattern", shardKeyPattern);
- recvChunkStartBuilder.append("configServer", shardingState.getConfigServer());
- recvChunkStartBuilder.append("secondaryThrottle", isSecondaryThrottle);
-
- // Follow the same convention in moveChunk.
- if (isSecondaryThrottle && !secThrottleObj.isEmpty()) {
- recvChunkStartBuilder.append("writeConcern", secThrottleObj);
- }
+ warning() << errmsg;
+ return false;
+ }
- try{
- ok = connTo->runCommand("admin", recvChunkStartBuilder.done(), res);
- }
- catch( DBException& e ){
- errmsg = str::stream() << "moveChunk could not contact to: shard "
- << toShardName << " to start transfer" << causedBy( e );
- warning() << errmsg;
- return false;
- }
+ log() << "moveChunk request accepted at version " << origShardVersion;
- connTo.done();
+ timing.done(2);
+ MONGO_FP_PAUSE_WHILE(moveChunkHangAtStep2);
- if ( ! ok ) {
- errmsg = "moveChunk failed to engage TO-shard in the data transfer: ";
- verify( res["errmsg"].type() );
- errmsg += res["errmsg"].String();
- result.append( "cause" , res );
- warning() << errmsg;
- return false;
- }
+ // 3.
+ MigrateStatusHolder statusHolder(txn, ns, min, max, shardKeyPattern);
- }
- timing.done( 3 );
- MONGO_FP_PAUSE_WHILE(moveChunkHangAtStep3);
+ if (statusHolder.isAnotherMigrationActive()) {
+ errmsg = "moveChunk is already in progress from this shard";
+ warning() << errmsg;
+ return false;
+ }
- // 4.
+ ConnectionString fromShardCS;
+ ConnectionString toShardCS;
- // Track last result from TO shard for sanity check
- BSONObj res;
- for ( int i=0; i<86400; i++ ) { // don't want a single chunk move to take more than a day
- invariant(!txn->lockState()->isLocked());
+ // Resolve the shard connection strings.
+ {
+ std::shared_ptr<Shard> fromShard = grid.shardRegistry()->getShard(fromShardName);
+ uassert(28674,
+ str::stream() << "Source shard " << fromShardName
+ << " is missing. This indicates metadata corruption.",
+ fromShard);
- // Exponential sleep backoff, up to 1024ms. Don't sleep much on the first few
- // iterations, since we want empty chunk migrations to be fast.
- sleepmillis(1 << std::min(i, 10));
+ fromShardCS = fromShard->getConnString();
- ScopedDbConnection conn(toShardCS);
- bool ok;
- res = BSONObj();
- try {
- ok = conn->runCommand( "admin" , BSON( "_recvChunkStatus" << 1 ) , res );
- res = res.getOwned();
- }
- catch( DBException& e ){
- errmsg = str::stream() << "moveChunk could not contact to: shard " << toShardName << " to monitor transfer" << causedBy( e );
- warning() << errmsg;
- return false;
- }
-
- conn.done();
-
- if ( res["ns"].str() != ns ||
- res["from"].str() != fromShardCS.toString() ||
- !res["min"].isABSONObj() ||
- res["min"].Obj().woCompare(min) != 0 ||
- !res["max"].isABSONObj() ||
- res["max"].Obj().woCompare(max) != 0 ) {
- // This can happen when the destination aborted the migration and
- // received another recvChunk before this thread sees the transition
- // to the abort state. This is currently possible only if multiple migrations
- // are happening at once. This is an unfortunate consequence of the shards not
- // being able to keep track of multiple incoming and outgoing migrations.
- errmsg = str::stream() << "Destination shard aborted migration, "
- "now running a new one: " << res;
- warning() << errmsg;
- return false;
- }
+ std::shared_ptr<Shard> toShard = grid.shardRegistry()->getShard(toShardName);
+ uassert(28675,
+ str::stream() << "Destination shard " << toShardName
+ << " is missing. This indicates metadata corruption.",
+ toShard);
- LOG(0) << "moveChunk data transfer progress: " << res << " my mem used: " << migrateFromStatus.mbUsed() << migrateLog;
+ toShardCS = toShard->getConnString();
+ }
- if ( ! ok || res["state"].String() == "fail" ) {
- warning() << "moveChunk error transferring data caused migration abort: " << res << migrateLog;
- errmsg = "data transfer error";
- result.append( "cause" , res );
- return false;
- }
+ {
+ // See comment at the top of the function for more information on what
+ // synchronization is used here.
+ if (!migrateFromStatus.storeCurrentLocs(txn, maxChunkSize, errmsg, result)) {
+ warning() << errmsg;
+ return false;
+ }
- if ( res["state"].String() == "steady" )
- break;
+ ScopedDbConnection connTo(toShardCS);
+ BSONObj res;
+ bool ok;
- if ( migrateFromStatus.mbUsed() > (500 * 1024 * 1024) ) {
- // This is too much memory for us to use for this so we're going to abort
- // the migrate
- ScopedDbConnection conn(toShardCS);
+ const bool isSecondaryThrottle(writeConcern.shouldWaitForOtherNodes());
- BSONObj res;
- if (!conn->runCommand("admin", BSON("_recvChunkAbort" << 1), res)) {
- warning() << "Error encountered while trying to abort migration on "
- << "destination shard" << toShardCS;
- }
+ BSONObjBuilder recvChunkStartBuilder;
+ recvChunkStartBuilder.append("_recvChunkStart", ns);
+ recvChunkStartBuilder.append("from", fromShardCS.toString());
+ recvChunkStartBuilder.append("fromShardName", fromShardName);
+ recvChunkStartBuilder.append("toShardName", toShardName);
+ recvChunkStartBuilder.append("min", min);
+ recvChunkStartBuilder.append("max", max);
+ recvChunkStartBuilder.append("shardKeyPattern", shardKeyPattern);
+ recvChunkStartBuilder.append("configServer", shardingState.getConfigServer());
+ recvChunkStartBuilder.append("secondaryThrottle", isSecondaryThrottle);
- res = res.getOwned();
- conn.done();
- error() << "aborting migrate because too much memory used res: " << res << migrateLog;
- errmsg = "aborting migrate because too much memory used";
- result.appendBool( "split" , true );
- return false;
- }
+ // Follow the same convention in moveChunk.
+ if (isSecondaryThrottle && !secThrottleObj.isEmpty()) {
+ recvChunkStartBuilder.append("writeConcern", secThrottleObj);
+ }
- txn->checkForInterrupt();
+ try {
+ ok = connTo->runCommand("admin", recvChunkStartBuilder.done(), res);
+ } catch (DBException& e) {
+ errmsg = str::stream() << "moveChunk could not contact to: shard " << toShardName
+ << " to start transfer" << causedBy(e);
+ warning() << errmsg;
+ return false;
}
- timing.done(4);
- MONGO_FP_PAUSE_WHILE(moveChunkHangAtStep4);
+ connTo.done();
- // 5.
+ if (!ok) {
+ errmsg = "moveChunk failed to engage TO-shard in the data transfer: ";
+ verify(res["errmsg"].type());
+ errmsg += res["errmsg"].String();
+ result.append("cause", res);
+ warning() << errmsg;
+ return false;
+ }
+ }
+ timing.done(3);
+ MONGO_FP_PAUSE_WHILE(moveChunkHangAtStep3);
- // Before we get into the critical section of the migration, let's double check
- // that the docs have been cloned, the config servers are reachable,
- // and the lock is in place.
- log() << "About to check if it is safe to enter critical section";
+ // 4.
- // Ensure all cloned docs have actually been transferred
- std::size_t locsRemaining = migrateFromStatus.cloneLocsRemaining();
- if ( locsRemaining != 0 ) {
+ // Track last result from TO shard for sanity check
+ BSONObj res;
+ for (int i = 0; i < 86400; i++) { // don't want a single chunk move to take more than a day
+ invariant(!txn->lockState()->isLocked());
- errmsg =
- str::stream() << "moveChunk cannot enter critical section before all data is"
- << " cloned, " << locsRemaining << " locs were not transferred"
- << " but to-shard reported " << res;
+ // Exponential sleep backoff, up to 1024ms. Don't sleep much on the first few
+ // iterations, since we want empty chunk migrations to be fast.
+ sleepmillis(1 << std::min(i, 10));
- // Should never happen, but safe to abort before critical section
- error() << errmsg << migrateLog;
- dassert( false );
+ ScopedDbConnection conn(toShardCS);
+ bool ok;
+ res = BSONObj();
+ try {
+ ok = conn->runCommand("admin", BSON("_recvChunkStatus" << 1), res);
+ res = res.getOwned();
+ } catch (DBException& e) {
+ errmsg = str::stream() << "moveChunk could not contact to: shard " << toShardName
+ << " to monitor transfer" << causedBy(e);
+ warning() << errmsg;
return false;
}
- // Ensure distributed lock still held
- Status lockStatus = scopedDistLock.getValue().checkStatus();
- if (!lockStatus.isOK()) {
- errmsg = str::stream() << "not entering migrate critical section because "
- << lockStatus.toString();
+ conn.done();
+
+ if (res["ns"].str() != ns || res["from"].str() != fromShardCS.toString() ||
+ !res["min"].isABSONObj() || res["min"].Obj().woCompare(min) != 0 ||
+ !res["max"].isABSONObj() || res["max"].Obj().woCompare(max) != 0) {
+ // This can happen when the destination aborted the migration and
+ // received another recvChunk before this thread sees the transition
+ // to the abort state. This is currently possible only if multiple migrations
+ // are happening at once. This is an unfortunate consequence of the shards not
+ // being able to keep track of multiple incoming and outgoing migrations.
+ errmsg = str::stream() << "Destination shard aborted migration, "
+ "now running a new one: " << res;
warning() << errmsg;
return false;
}
- log() << "About to enter migrate critical section";
+ LOG(0) << "moveChunk data transfer progress: " << res
+ << " my mem used: " << migrateFromStatus.mbUsed() << migrateLog;
- {
- // 5.a
- // we're under the collection lock here, so no other migrate can change maxVersion
- // or CollectionMetadata state
- migrateFromStatus.setInCriticalSection( true );
- ChunkVersion myVersion = origCollVersion;
- myVersion.incMajor();
+ if (!ok || res["state"].String() == "fail") {
+ warning() << "moveChunk error transferring data caused migration abort: " << res
+ << migrateLog;
+ errmsg = "data transfer error";
+ result.append("cause", res);
+ return false;
+ }
- {
- ScopedTransaction transaction(txn, MODE_IX);
- Lock::DBLock lk(txn->lockState(), nsToDatabaseSubstring(ns), MODE_IX);
- Lock::CollectionLock collLock(txn->lockState(), ns, MODE_X);
- verify( myVersion > shardingState.getVersion( ns ) );
+ if (res["state"].String() == "steady")
+ break;
- // bump the metadata's version up and "forget" about the chunk being moved
- // this is not the commit point but in practice the state in this shard won't
- // until the commit it done
- shardingState.donateChunk(txn, ns, min, max, myVersion);
+ if (migrateFromStatus.mbUsed() > (500 * 1024 * 1024)) {
+ // This is too much memory for us to use for this so we're going to abort
+ // the migrate
+ ScopedDbConnection conn(toShardCS);
+
+ BSONObj res;
+ if (!conn->runCommand("admin", BSON("_recvChunkAbort" << 1), res)) {
+ warning() << "Error encountered while trying to abort migration on "
+ << "destination shard" << toShardCS;
}
- log() << "moveChunk setting version to: " << myVersion << migrateLog;
+ res = res.getOwned();
+ conn.done();
+ error() << "aborting migrate because too much memory used res: " << res
+ << migrateLog;
+ errmsg = "aborting migrate because too much memory used";
+ result.appendBool("split", true);
+ return false;
+ }
- // 5.b
- // we're under the collection lock here, too, so we can undo the chunk donation because no other state change
- // could be ongoing
+ txn->checkForInterrupt();
+ }
- BSONObj res;
- bool ok;
+ timing.done(4);
+ MONGO_FP_PAUSE_WHILE(moveChunkHangAtStep4);
- try {
- ScopedDbConnection connTo(toShardCS, 35.0);
- ok = connTo->runCommand( "admin", BSON( "_recvChunkCommit" << 1 ), res );
- connTo.done();
- }
- catch ( DBException& e ) {
- errmsg = str::stream() << "moveChunk could not contact to: shard "
- << toShardCS.toString()
- << " to commit transfer" << causedBy(e);
- warning() << errmsg;
- ok = false;
- }
+ // 5.
- if ( !ok || MONGO_FAIL_POINT(failMigrationCommit) ) {
- log() << "moveChunk migrate commit not accepted by TO-shard: " << res
- << " resetting shard version to: " << origShardVersion << migrateLog;
- {
- ScopedTransaction transaction(txn, MODE_IX);
- Lock::DBLock dbLock(txn->lockState(), nsToDatabaseSubstring(ns), MODE_IX);
- Lock::CollectionLock collLock(txn->lockState(), ns, MODE_X);
+ // Before we get into the critical section of the migration, let's double check
+ // that the docs have been cloned, the config servers are reachable,
+ // and the lock is in place.
+ log() << "About to check if it is safe to enter critical section";
- log() << "moveChunk collection lock acquired to reset shard version from "
- "failed migration"
- ;
+ // Ensure all cloned docs have actually been transferred
+ std::size_t locsRemaining = migrateFromStatus.cloneLocsRemaining();
+ if (locsRemaining != 0) {
+ errmsg = str::stream() << "moveChunk cannot enter critical section before all data is"
+ << " cloned, " << locsRemaining << " locs were not transferred"
+ << " but to-shard reported " << res;
- // revert the chunk manager back to the state before "forgetting" about the
- // chunk
- shardingState.undoDonateChunk(txn, ns, origCollMetadata);
- }
- log() << "Shard version successfully reset to clean up failed migration"
- ;
+ // Should never happen, but safe to abort before critical section
+ error() << errmsg << migrateLog;
+ dassert(false);
+ return false;
+ }
- errmsg = "_recvChunkCommit failed!";
- result.append( "cause", res );
- return false;
- }
+ // Ensure distributed lock still held
+ Status lockStatus = scopedDistLock.getValue().checkStatus();
+ if (!lockStatus.isOK()) {
+ errmsg = str::stream() << "not entering migrate critical section because "
+ << lockStatus.toString();
+ warning() << errmsg;
+ return false;
+ }
- log() << "moveChunk migrate commit accepted by TO-shard: " << res << migrateLog;
+ log() << "About to enter migrate critical section";
- // 5.c
+ {
+ // 5.a
+ // we're under the collection lock here, so no other migrate can change maxVersion
+ // or CollectionMetadata state
+ migrateFromStatus.setInCriticalSection(true);
+ ChunkVersion myVersion = origCollVersion;
+ myVersion.incMajor();
- // version at which the next highest lastmod will be set
- // if the chunk being moved is the last in the shard, nextVersion is that chunk's lastmod
- // otherwise the highest version is from the chunk being bumped on the FROM-shard
- ChunkVersion nextVersion;
+ {
+ ScopedTransaction transaction(txn, MODE_IX);
+ Lock::DBLock lk(txn->lockState(), nsToDatabaseSubstring(ns), MODE_IX);
+ Lock::CollectionLock collLock(txn->lockState(), ns, MODE_X);
+ verify(myVersion > shardingState.getVersion(ns));
- // we want to go only once to the configDB but perhaps change two chunks, the one being migrated and another
- // local one (so to bump version for the entire shard)
- // we use the 'applyOps' mechanism to group the two updates and make them safer
- // TODO pull config update code to a module
+ // bump the metadata's version up and "forget" about the chunk being moved
+ // this is not the commit point but in practice the state in this shard won't
+ // until the commit it done
+ shardingState.donateChunk(txn, ns, min, max, myVersion);
+ }
- BSONArrayBuilder updates;
- {
- // update for the chunk being moved
- BSONObjBuilder op;
- op.append( "op" , "u" );
- op.appendBool( "b" , false /* no upserting */ );
- op.append( "ns" , ChunkType::ConfigNS );
-
- BSONObjBuilder n( op.subobjStart( "o" ) );
- n.append(ChunkType::name(), Chunk::genID(ns, min));
- myVersion.addToBSON(n, ChunkType::DEPRECATED_lastmod());
- n.append(ChunkType::ns(), ns);
- n.append(ChunkType::min(), min);
- n.append(ChunkType::max(), max);
- n.append(ChunkType::shard(), toShardName);
- n.done();
-
- BSONObjBuilder q( op.subobjStart( "o2" ) );
- q.append(ChunkType::name(), Chunk::genID(ns, min));
- q.done();
-
- updates.append( op.obj() );
- }
+ log() << "moveChunk setting version to: " << myVersion << migrateLog;
- nextVersion = myVersion;
+ // 5.b
+ // we're under the collection lock here, too, so we can undo the chunk donation because no other state change
+ // could be ongoing
- // if we have chunks left on the FROM shard, update the version of one of them as
- // well. we can figure that out by grabbing the metadata installed on 5.a
+ BSONObj res;
+ bool ok;
- const CollectionMetadataPtr bumpedCollMetadata( shardingState.getCollectionMetadata( ns ) );
- if( bumpedCollMetadata->getNumChunks() > 0 ) {
+ try {
+ ScopedDbConnection connTo(toShardCS, 35.0);
+ ok = connTo->runCommand("admin", BSON("_recvChunkCommit" << 1), res);
+ connTo.done();
+ } catch (DBException& e) {
+ errmsg = str::stream() << "moveChunk could not contact to: shard "
+ << toShardCS.toString() << " to commit transfer"
+ << causedBy(e);
+ warning() << errmsg;
+ ok = false;
+ }
- // get another chunk on that shard
- ChunkType bumpChunk;
- bool chunkRes =
- bumpedCollMetadata->getNextChunk(bumpedCollMetadata->getMinKey(),
- &bumpChunk);
- BSONObj bumpMin = bumpChunk.getMin();
- BSONObj bumpMax = bumpChunk.getMax();
+ if (!ok || MONGO_FAIL_POINT(failMigrationCommit)) {
+ log() << "moveChunk migrate commit not accepted by TO-shard: " << res
+ << " resetting shard version to: " << origShardVersion << migrateLog;
+ {
+ ScopedTransaction transaction(txn, MODE_IX);
+ Lock::DBLock dbLock(txn->lockState(), nsToDatabaseSubstring(ns), MODE_IX);
+ Lock::CollectionLock collLock(txn->lockState(), ns, MODE_X);
- (void)chunkRes; // for compile warning on non-debug
- dassert(chunkRes);
- dassert( bumpMin.woCompare( min ) != 0 );
+ log() << "moveChunk collection lock acquired to reset shard version from "
+ "failed migration";
- BSONObjBuilder op;
- op.append( "op" , "u" );
- op.appendBool( "b" , false );
- op.append( "ns" , ChunkType::ConfigNS );
+ // revert the chunk manager back to the state before "forgetting" about the
+ // chunk
+ shardingState.undoDonateChunk(txn, ns, origCollMetadata);
+ }
+ log() << "Shard version successfully reset to clean up failed migration";
- nextVersion.incMinor(); // same as used on donateChunk
- BSONObjBuilder n( op.subobjStart( "o" ) );
- n.append(ChunkType::name(), Chunk::genID(ns, bumpMin));
- nextVersion.addToBSON(n, ChunkType::DEPRECATED_lastmod());
- n.append(ChunkType::ns(), ns);
- n.append(ChunkType::min(), bumpMin);
- n.append(ChunkType::max(), bumpMax);
- n.append(ChunkType::shard(), fromShardName);
- n.done();
+ errmsg = "_recvChunkCommit failed!";
+ result.append("cause", res);
+ return false;
+ }
- BSONObjBuilder q( op.subobjStart( "o2" ) );
- q.append(ChunkType::name(), Chunk::genID(ns, bumpMin));
- q.done();
+ log() << "moveChunk migrate commit accepted by TO-shard: " << res << migrateLog;
- updates.append( op.obj() );
+ // 5.c
- log() << "moveChunk updating self version to: " << nextVersion << " through "
- << bumpMin << " -> " << bumpMax << " for collection '" << ns << "'" << migrateLog;
+ // version at which the next highest lastmod will be set
+ // if the chunk being moved is the last in the shard, nextVersion is that chunk's lastmod
+ // otherwise the highest version is from the chunk being bumped on the FROM-shard
+ ChunkVersion nextVersion;
- }
- else {
+ // we want to go only once to the configDB but perhaps change two chunks, the one being migrated and another
+ // local one (so to bump version for the entire shard)
+ // we use the 'applyOps' mechanism to group the two updates and make them safer
+ // TODO pull config update code to a module
- log() << "moveChunk moved last chunk out for collection '" << ns << "'" << migrateLog;
- }
+ BSONArrayBuilder updates;
+ {
+ // update for the chunk being moved
+ BSONObjBuilder op;
+ op.append("op", "u");
+ op.appendBool("b", false /* no upserting */);
+ op.append("ns", ChunkType::ConfigNS);
+
+ BSONObjBuilder n(op.subobjStart("o"));
+ n.append(ChunkType::name(), Chunk::genID(ns, min));
+ myVersion.addToBSON(n, ChunkType::DEPRECATED_lastmod());
+ n.append(ChunkType::ns(), ns);
+ n.append(ChunkType::min(), min);
+ n.append(ChunkType::max(), max);
+ n.append(ChunkType::shard(), toShardName);
+ n.done();
+
+ BSONObjBuilder q(op.subobjStart("o2"));
+ q.append(ChunkType::name(), Chunk::genID(ns, min));
+ q.done();
+
+ updates.append(op.obj());
+ }
+
+ nextVersion = myVersion;
+
+ // if we have chunks left on the FROM shard, update the version of one of them as
+ // well. we can figure that out by grabbing the metadata installed on 5.a
+
+ const CollectionMetadataPtr bumpedCollMetadata(shardingState.getCollectionMetadata(ns));
+ if (bumpedCollMetadata->getNumChunks() > 0) {
+ // get another chunk on that shard
+ ChunkType bumpChunk;
+ bool chunkRes =
+ bumpedCollMetadata->getNextChunk(bumpedCollMetadata->getMinKey(), &bumpChunk);
+ BSONObj bumpMin = bumpChunk.getMin();
+ BSONObj bumpMax = bumpChunk.getMax();
+
+ (void)chunkRes; // for compile warning on non-debug
+ dassert(chunkRes);
+ dassert(bumpMin.woCompare(min) != 0);
+
+ BSONObjBuilder op;
+ op.append("op", "u");
+ op.appendBool("b", false);
+ op.append("ns", ChunkType::ConfigNS);
+
+ nextVersion.incMinor(); // same as used on donateChunk
+ BSONObjBuilder n(op.subobjStart("o"));
+ n.append(ChunkType::name(), Chunk::genID(ns, bumpMin));
+ nextVersion.addToBSON(n, ChunkType::DEPRECATED_lastmod());
+ n.append(ChunkType::ns(), ns);
+ n.append(ChunkType::min(), bumpMin);
+ n.append(ChunkType::max(), bumpMax);
+ n.append(ChunkType::shard(), fromShardName);
+ n.done();
+
+ BSONObjBuilder q(op.subobjStart("o2"));
+ q.append(ChunkType::name(), Chunk::genID(ns, bumpMin));
+ q.done();
+
+ updates.append(op.obj());
+
+ log() << "moveChunk updating self version to: " << nextVersion << " through "
+ << bumpMin << " -> " << bumpMax << " for collection '" << ns << "'"
+ << migrateLog;
+
+ } else {
+ log() << "moveChunk moved last chunk out for collection '" << ns << "'"
+ << migrateLog;
+ }
- BSONArrayBuilder preCond;
+ BSONArrayBuilder preCond;
+ {
+ BSONObjBuilder b;
+ b.append("ns", ChunkType::ConfigNS);
+ b.append("q",
+ BSON("query" << BSON(ChunkType::ns(ns)) << "orderby"
+ << BSON(ChunkType::DEPRECATED_lastmod() << -1)));
{
- BSONObjBuilder b;
- b.append("ns", ChunkType::ConfigNS);
- b.append("q", BSON("query" << BSON(ChunkType::ns(ns)) <<
- "orderby" << BSON(ChunkType::DEPRECATED_lastmod() << -1)));
- {
- BSONObjBuilder bb( b.subobjStart( "res" ) );
- // TODO: For backwards compatibility, we can't yet require an epoch here
- bb.appendTimestamp(ChunkType::DEPRECATED_lastmod(), origCollVersion.toLong());
- bb.done();
- }
- preCond.append( b.obj() );
+ BSONObjBuilder bb(b.subobjStart("res"));
+ // TODO: For backwards compatibility, we can't yet require an epoch here
+ bb.appendTimestamp(ChunkType::DEPRECATED_lastmod(), origCollVersion.toLong());
+ bb.done();
}
+ preCond.append(b.obj());
+ }
- Status applyOpsStatus{Status::OK()};
- try {
-
- // For testing migration failures
- if ( MONGO_FAIL_POINT(failMigrationConfigWritePrepare) ) {
- throw DBException( "mock migration failure before config write",
- PrepareConfigsFailedCode );
- }
+ Status applyOpsStatus{Status::OK()};
+ try {
+ // For testing migration failures
+ if (MONGO_FAIL_POINT(failMigrationConfigWritePrepare)) {
+ throw DBException("mock migration failure before config write",
+ PrepareConfigsFailedCode);
+ }
- applyOpsStatus = grid.catalogManager()->applyChunkOpsDeprecated(updates.arr(),
- preCond.arr());
+ applyOpsStatus =
+ grid.catalogManager()->applyChunkOpsDeprecated(updates.arr(), preCond.arr());
- if (MONGO_FAIL_POINT(failMigrationApplyOps)) {
- throw SocketException(SocketException::RECV_ERROR,
- shardingState.getConfigServer());
- }
- }
- catch (const DBException& ex) {
- warning() << ex << migrateLog;
- applyOpsStatus = ex.toStatus();
+ if (MONGO_FAIL_POINT(failMigrationApplyOps)) {
+ throw SocketException(SocketException::RECV_ERROR,
+ shardingState.getConfigServer());
}
+ } catch (const DBException& ex) {
+ warning() << ex << migrateLog;
+ applyOpsStatus = ex.toStatus();
+ }
- if (applyOpsStatus == ErrorCodes::PrepareConfigsFailedCode) {
-
- // In the process of issuing the migrate commit, the SyncClusterConnection
- // checks that the config servers are reachable. If they are not, we are
- // sure that the applyOps command was not sent to any of the configs, so we
- // can safely back out of the migration here, by resetting the shard
- // version that we bumped up to in the donateChunk() call above.
+ if (applyOpsStatus == ErrorCodes::PrepareConfigsFailedCode) {
+ // In the process of issuing the migrate commit, the SyncClusterConnection
+ // checks that the config servers are reachable. If they are not, we are
+ // sure that the applyOps command was not sent to any of the configs, so we
+ // can safely back out of the migration here, by resetting the shard
+ // version that we bumped up to in the donateChunk() call above.
- log() << "About to acquire moveChunk coll lock to reset shard version from "
- << "failed migration";
+ log() << "About to acquire moveChunk coll lock to reset shard version from "
+ << "failed migration";
- {
- ScopedTransaction transaction(txn, MODE_IX);
- Lock::DBLock dbLock(txn->lockState(), nsToDatabaseSubstring(ns), MODE_IX);
- Lock::CollectionLock collLock(txn->lockState(), ns, MODE_X);
+ {
+ ScopedTransaction transaction(txn, MODE_IX);
+ Lock::DBLock dbLock(txn->lockState(), nsToDatabaseSubstring(ns), MODE_IX);
+ Lock::CollectionLock collLock(txn->lockState(), ns, MODE_X);
- // Revert the metadata back to the state before "forgetting"
- // about the chunk.
- shardingState.undoDonateChunk(txn, ns, origCollMetadata);
- }
+ // Revert the metadata back to the state before "forgetting"
+ // about the chunk.
+ shardingState.undoDonateChunk(txn, ns, origCollMetadata);
+ }
- log() << "Shard version successfully reset to clean up failed migration";
+ log() << "Shard version successfully reset to clean up failed migration";
- errmsg = "Failed to send migrate commit to configs because " + errmsg;
- return false;
+ errmsg = "Failed to send migrate commit to configs because " + errmsg;
+ return false;
- }
- else if (!applyOpsStatus.isOK()) {
-
- // this could be a blip in the connectivity
- // wait out a few seconds and check if the commit request made it
- //
- // if the commit made it to the config, we'll see the chunk in the new shard and there's no action
- // if the commit did not make it, currently the only way to fix this state is to bounce the mongod so
- // that the old state (before migrating) be brought in
-
- warning() << "moveChunk commit outcome ongoing" << migrateLog;
- sleepsecs( 10 );
-
- // look for the chunk in this shard whose version got bumped
- // we assume that if that mod made it to the config, the applyOps was successful
- try {
- std::vector<ChunkType> newestChunk;
- Status status = grid.catalogManager()->getChunks(
- Query(BSON(ChunkType::ns(ns)))
- .sort(ChunkType::DEPRECATED_lastmod(), -1),
- 1,
- &newestChunk);
- uassertStatusOK(status);
-
- ChunkVersion checkVersion;
- if (!newestChunk.empty()) {
- invariant(newestChunk.size() == 1);
- checkVersion = newestChunk[0].getVersion();
- }
+ } else if (!applyOpsStatus.isOK()) {
+ // this could be a blip in the connectivity
+ // wait out a few seconds and check if the commit request made it
+ //
+ // if the commit made it to the config, we'll see the chunk in the new shard and there's no action
+ // if the commit did not make it, currently the only way to fix this state is to bounce the mongod so
+ // that the old state (before migrating) be brought in
- if (checkVersion.equals(nextVersion)) {
- log() << "moveChunk commit confirmed" << migrateLog;
- errmsg.clear();
+ warning() << "moveChunk commit outcome ongoing" << migrateLog;
+ sleepsecs(10);
- }
- else {
- error() << "moveChunk commit failed: version is at "
- << checkVersion << " instead of " << nextVersion << migrateLog;
- error() << "TERMINATING" << migrateLog;
- dbexit( EXIT_SHARDING_ERROR );
- }
+ // look for the chunk in this shard whose version got bumped
+ // we assume that if that mod made it to the config, the applyOps was successful
+ try {
+ std::vector<ChunkType> newestChunk;
+ Status status = grid.catalogManager()->getChunks(
+ Query(BSON(ChunkType::ns(ns))).sort(ChunkType::DEPRECATED_lastmod(), -1),
+ 1,
+ &newestChunk);
+ uassertStatusOK(status);
+
+ ChunkVersion checkVersion;
+ if (!newestChunk.empty()) {
+ invariant(newestChunk.size() == 1);
+ checkVersion = newestChunk[0].getVersion();
}
- catch ( ... ) {
- error() << "moveChunk failed to get confirmation of commit" << migrateLog;
+
+ if (checkVersion.equals(nextVersion)) {
+ log() << "moveChunk commit confirmed" << migrateLog;
+ errmsg.clear();
+
+ } else {
+ error() << "moveChunk commit failed: version is at " << checkVersion
+ << " instead of " << nextVersion << migrateLog;
error() << "TERMINATING" << migrateLog;
- dbexit( EXIT_SHARDING_ERROR );
+ dbexit(EXIT_SHARDING_ERROR);
}
+ } catch (...) {
+ error() << "moveChunk failed to get confirmation of commit" << migrateLog;
+ error() << "TERMINATING" << migrateLog;
+ dbexit(EXIT_SHARDING_ERROR);
}
+ }
- migrateFromStatus.setInCriticalSection( false );
-
- // 5.d
- BSONObjBuilder commitInfo;
- commitInfo.appendElements( chunkInfo );
- if (res["counts"].type() == Object) {
- commitInfo.appendElements(res["counts"].Obj());
- }
+ migrateFromStatus.setInCriticalSection(false);
- grid.catalogManager()->logChange(txn, "moveChunk.commit", ns, commitInfo.obj());
+ // 5.d
+ BSONObjBuilder commitInfo;
+ commitInfo.appendElements(chunkInfo);
+ if (res["counts"].type() == Object) {
+ commitInfo.appendElements(res["counts"].Obj());
}
- migrateFromStatus.done(txn);
- timing.done(5);
- MONGO_FP_PAUSE_WHILE(moveChunkHangAtStep5);
-
- // 6.
- // NOTE: It is important that the distributed collection lock be held for this step.
- RangeDeleter* deleter = getDeleter();
- RangeDeleterOptions deleterOptions(KeyRange(ns,
- min.getOwned(),
- max.getOwned(),
- shardKeyPattern));
- deleterOptions.writeConcern = writeConcern;
- deleterOptions.waitForOpenCursors = true;
- deleterOptions.fromMigrate = true;
- deleterOptions.onlyRemoveOrphanedDocs = true;
- deleterOptions.removeSaverReason = "post-cleanup";
-
- if (waitForDelete) {
- log() << "doing delete inline for cleanup of chunk data" << migrateLog;
-
- string errMsg;
- // This is an immediate delete, and as a consequence, there could be more
- // deletes happening simultaneously than there are deleter worker threads.
- if (!deleter->deleteNow(txn,
- deleterOptions,
- &errMsg)) {
- log() << "Error occured while performing cleanup: " << errMsg;
- }
- }
- else {
- log() << "forking for cleanup of chunk data" << migrateLog;
+ grid.catalogManager()->logChange(txn, "moveChunk.commit", ns, commitInfo.obj());
+ }
- string errMsg;
- if (!deleter->queueDelete(txn,
- deleterOptions,
- NULL, // Don't want to be notified.
- &errMsg)) {
- log() << "could not queue migration cleanup: " << errMsg;
- }
+ migrateFromStatus.done(txn);
+ timing.done(5);
+ MONGO_FP_PAUSE_WHILE(moveChunkHangAtStep5);
+
+ // 6.
+ // NOTE: It is important that the distributed collection lock be held for this step.
+ RangeDeleter* deleter = getDeleter();
+ RangeDeleterOptions deleterOptions(
+ KeyRange(ns, min.getOwned(), max.getOwned(), shardKeyPattern));
+ deleterOptions.writeConcern = writeConcern;
+ deleterOptions.waitForOpenCursors = true;
+ deleterOptions.fromMigrate = true;
+ deleterOptions.onlyRemoveOrphanedDocs = true;
+ deleterOptions.removeSaverReason = "post-cleanup";
+
+ if (waitForDelete) {
+ log() << "doing delete inline for cleanup of chunk data" << migrateLog;
+
+ string errMsg;
+ // This is an immediate delete, and as a consequence, there could be more
+ // deletes happening simultaneously than there are deleter worker threads.
+ if (!deleter->deleteNow(txn, deleterOptions, &errMsg)) {
+ log() << "Error occured while performing cleanup: " << errMsg;
+ }
+ } else {
+ log() << "forking for cleanup of chunk data" << migrateLog;
+
+ string errMsg;
+ if (!deleter->queueDelete(txn,
+ deleterOptions,
+ NULL, // Don't want to be notified.
+ &errMsg)) {
+ log() << "could not queue migration cleanup: " << errMsg;
}
- timing.done(6);
- MONGO_FP_PAUSE_WHILE(moveChunkHangAtStep6);
+ }
+ timing.done(6);
+ MONGO_FP_PAUSE_WHILE(moveChunkHangAtStep6);
- return true;
+ return true;
+ }
- }
+} moveChunkCmd;
+
+bool ShardingState::inCriticalMigrateSection() {
+ return migrateFromStatus.getInCriticalSection();
+}
- } moveChunkCmd;
+bool ShardingState::waitTillNotInCriticalSection(int maxSecondsToWait) {
+ return migrateFromStatus.waitTillNotInCriticalSection(maxSecondsToWait);
+}
- bool ShardingState::inCriticalMigrateSection() {
- return migrateFromStatus.getInCriticalSection();
+/* -----
+ below this are the "to" side commands
+
+ command to initiate
+ worker thread
+ does initial clone
+ pulls initial change set
+ keeps pulling
+ keeps state
+ command to get state
+ commend to "commit"
+*/
+
+// Enabling / disabling these fail points pauses / resumes MigrateStatus::_go(), the thread
+// that receives a chunk migration from the donor.
+MONGO_FP_DECLARE(migrateThreadHangAtStep1);
+MONGO_FP_DECLARE(migrateThreadHangAtStep2);
+MONGO_FP_DECLARE(migrateThreadHangAtStep3);
+MONGO_FP_DECLARE(migrateThreadHangAtStep4);
+MONGO_FP_DECLARE(migrateThreadHangAtStep5);
+
+class MigrateStatus {
+public:
+ enum State { READY, CLONE, CATCHUP, STEADY, COMMIT_START, DONE, FAIL, ABORT };
+
+ MigrateStatus()
+ : _active(false),
+ _numCloned(0),
+ _clonedBytes(0),
+ _numCatchup(0),
+ _numSteady(0),
+ _state(READY) {}
+
+ void setState(State newState) {
+ std::lock_guard<std::mutex> sl(_mutex);
+ _state = newState;
}
- bool ShardingState::waitTillNotInCriticalSection( int maxSecondsToWait ) {
- return migrateFromStatus.waitTillNotInCriticalSection( maxSecondsToWait );
+ State getState() const {
+ std::lock_guard<std::mutex> sl(_mutex);
+ return _state;
}
- /* -----
- below this are the "to" side commands
+ /**
+ * Returns OK if preparation was successful.
+ */
+ Status prepare(const std::string& ns,
+ const std::string& fromShard,
+ const BSONObj& min,
+ const BSONObj& max,
+ const BSONObj& shardKeyPattern) {
+ std::lock_guard<std::mutex> lk(_mutex);
+
+ if (_active) {
+ return Status(ErrorCodes::ConflictingOperationInProgress,
+ str::stream() << "Active migration already in progress "
+ << "ns: " << _ns << ", from: " << _from << ", min: " << _min
+ << ", max: " << _max);
+ }
- command to initiate
- worker thread
- does initial clone
- pulls initial change set
- keeps pulling
- keeps state
- command to get state
- commend to "commit"
- */
+ _state = READY;
+ _errmsg = "";
- // Enabling / disabling these fail points pauses / resumes MigrateStatus::_go(), the thread
- // that receives a chunk migration from the donor.
- MONGO_FP_DECLARE(migrateThreadHangAtStep1);
- MONGO_FP_DECLARE(migrateThreadHangAtStep2);
- MONGO_FP_DECLARE(migrateThreadHangAtStep3);
- MONGO_FP_DECLARE(migrateThreadHangAtStep4);
- MONGO_FP_DECLARE(migrateThreadHangAtStep5);
+ _ns = ns;
+ _from = fromShard;
+ _min = min;
+ _max = max;
+ _shardKeyPattern = shardKeyPattern;
- class MigrateStatus {
- public:
- enum State {
- READY,
- CLONE,
- CATCHUP,
- STEADY,
- COMMIT_START,
- DONE,
- FAIL,
- ABORT
- };
-
- MigrateStatus():
- _active(false),
- _numCloned(0),
- _clonedBytes(0),
- _numCatchup(0),
- _numSteady(0),
- _state(READY) {
- }
-
- void setState(State newState) {
- std::lock_guard<std::mutex> sl(_mutex);
- _state = newState;
- }
+ _numCloned = 0;
+ _clonedBytes = 0;
+ _numCatchup = 0;
+ _numSteady = 0;
- State getState() const {
- std::lock_guard<std::mutex> sl(_mutex);
- return _state;
- }
+ _active = true;
- /**
- * Returns OK if preparation was successful.
- */
- Status prepare(const std::string& ns,
- const std::string& fromShard,
- const BSONObj& min,
- const BSONObj& max,
- const BSONObj& shardKeyPattern) {
- std::lock_guard<std::mutex> lk(_mutex);
-
- if (_active) {
- return Status(ErrorCodes::ConflictingOperationInProgress,
- str::stream() << "Active migration already in progress "
- << "ns: " << _ns
- << ", from: " << _from
- << ", min: " << _min
- << ", max: " << _max);
- }
-
- _state = READY;
- _errmsg = "";
-
- _ns = ns;
- _from = fromShard;
- _min = min;
- _max = max;
- _shardKeyPattern = shardKeyPattern;
-
- _numCloned = 0;
- _clonedBytes = 0;
- _numCatchup = 0;
- _numSteady = 0;
-
- _active = true;
-
- return Status::OK();
- }
-
- void go(OperationContext* txn,
- const std::string& ns,
- BSONObj min,
- BSONObj max,
- BSONObj shardKeyPattern,
- const std::string& fromShard,
- const OID& epoch,
- const WriteConcernOptions& writeConcern) {
- try {
- _go(txn, ns, min, max, shardKeyPattern, fromShard, epoch, writeConcern);
- }
- catch ( std::exception& e ) {
- {
- std::lock_guard<std::mutex> sl(_mutex);
- _state = FAIL;
- _errmsg = e.what();
- }
+ return Status::OK();
+ }
- error() << "migrate failed: " << e.what() << migrateLog;
+ void go(OperationContext* txn,
+ const std::string& ns,
+ BSONObj min,
+ BSONObj max,
+ BSONObj shardKeyPattern,
+ const std::string& fromShard,
+ const OID& epoch,
+ const WriteConcernOptions& writeConcern) {
+ try {
+ _go(txn, ns, min, max, shardKeyPattern, fromShard, epoch, writeConcern);
+ } catch (std::exception& e) {
+ {
+ std::lock_guard<std::mutex> sl(_mutex);
+ _state = FAIL;
+ _errmsg = e.what();
}
- catch ( ... ) {
- {
- std::lock_guard<std::mutex> sl(_mutex);
- _state = FAIL;
- _errmsg = "UNKNOWN ERROR";
- }
- error() << "migrate failed with unknown exception" << migrateLog;
+ error() << "migrate failed: " << e.what() << migrateLog;
+ } catch (...) {
+ {
+ std::lock_guard<std::mutex> sl(_mutex);
+ _state = FAIL;
+ _errmsg = "UNKNOWN ERROR";
}
- if ( getState() != DONE ) {
- // Unprotect the range if needed/possible on unsuccessful TO migration
- ScopedTransaction transaction(txn, MODE_IX);
- Lock::DBLock dbLock(txn->lockState(), nsToDatabaseSubstring(ns), MODE_IX);
- Lock::CollectionLock collLock(txn->lockState(), ns, MODE_X);
+ error() << "migrate failed with unknown exception" << migrateLog;
+ }
- string errMsg;
- if (!shardingState.forgetPending(txn, ns, min, max, epoch, &errMsg)) {
- warning() << errMsg;
- }
- }
+ if (getState() != DONE) {
+ // Unprotect the range if needed/possible on unsuccessful TO migration
+ ScopedTransaction transaction(txn, MODE_IX);
+ Lock::DBLock dbLock(txn->lockState(), nsToDatabaseSubstring(ns), MODE_IX);
+ Lock::CollectionLock collLock(txn->lockState(), ns, MODE_X);
- setActive( false );
+ string errMsg;
+ if (!shardingState.forgetPending(txn, ns, min, max, epoch, &errMsg)) {
+ warning() << errMsg;
+ }
}
- void _go(OperationContext* txn,
- const std::string& ns,
- BSONObj min,
- BSONObj max,
- BSONObj shardKeyPattern,
- const std::string& fromShard,
- const OID& epoch,
- const WriteConcernOptions& writeConcern) {
- verify( getActive() );
- verify( getState() == READY );
- verify( ! min.isEmpty() );
- verify( ! max.isEmpty() );
+ setActive(false);
+ }
- DisableDocumentValidation validationDisabler(txn);
+ void _go(OperationContext* txn,
+ const std::string& ns,
+ BSONObj min,
+ BSONObj max,
+ BSONObj shardKeyPattern,
+ const std::string& fromShard,
+ const OID& epoch,
+ const WriteConcernOptions& writeConcern) {
+ verify(getActive());
+ verify(getState() == READY);
+ verify(!min.isEmpty());
+ verify(!max.isEmpty());
+
+ DisableDocumentValidation validationDisabler(txn);
+
+ log() << "starting receiving-end of migration of chunk " << min << " -> " << max
+ << " for collection " << ns << " from " << fromShard << " at epoch "
+ << epoch.toString();
+
+ string errmsg;
+ MoveTimingHelper timing(txn, "to", ns, min, max, 5 /* steps */, &errmsg, "", "");
+
+ ScopedDbConnection conn(fromShard);
+ conn->getLastError(); // just test connection
+
+ NamespaceString nss(ns);
+ {
+ // 0. copy system.namespaces entry if collection doesn't already exist
+ OldClientWriteContext ctx(txn, ns);
+ if (!repl::getGlobalReplicationCoordinator()->canAcceptWritesFor(nss)) {
+ errmsg = str::stream() << "Not primary during migration: " << ns
+ << ": checking if collection exists";
+ warning() << errmsg;
+ setState(FAIL);
+ return;
+ }
- log() << "starting receiving-end of migration of chunk " << min << " -> " << max <<
- " for collection " << ns << " from " << fromShard
- << " at epoch " << epoch.toString();
+ // Only copy if ns doesn't already exist
+ Database* db = ctx.db();
+ Collection* collection = db->getCollection(ns);
- string errmsg;
- MoveTimingHelper timing(txn, "to", ns, min, max, 5 /* steps */, &errmsg, "", "");
+ if (!collection) {
+ list<BSONObj> infos = conn->getCollectionInfos(
+ nsToDatabase(ns), BSON("name" << nsToCollectionSubstring(ns)));
- ScopedDbConnection conn(fromShard);
- conn->getLastError(); // just test connection
+ BSONObj options;
+ if (infos.size() > 0) {
+ BSONObj entry = infos.front();
+ if (entry["options"].isABSONObj()) {
+ options = entry["options"].Obj();
+ }
+ }
- NamespaceString nss(ns);
- {
- // 0. copy system.namespaces entry if collection doesn't already exist
- OldClientWriteContext ctx(txn, ns );
- if (!repl::getGlobalReplicationCoordinator()->canAcceptWritesFor(nss)) {
- errmsg = str::stream() << "Not primary during migration: " << ns
- << ": checking if collection exists";
- warning() << errmsg;
- setState(FAIL);
- return;
+ WriteUnitOfWork wuow(txn);
+ Status status = userCreateNS(txn, db, ns, options, false);
+ if (!status.isOK()) {
+ warning() << "failed to create collection [" << ns << "] "
+ << " with options " << options << ": " << status;
}
+ wuow.commit();
+ }
+ }
- // Only copy if ns doesn't already exist
- Database* db = ctx.db();
- Collection* collection = db->getCollection( ns );
+ {
+ // 1. copy indexes
- if ( !collection ) {
- list<BSONObj> infos =
- conn->getCollectionInfos(nsToDatabase(ns),
- BSON("name" << nsToCollectionSubstring(ns)));
+ vector<BSONObj> indexSpecs;
+ {
+ const std::list<BSONObj> indexes = conn->getIndexSpecs(ns);
+ indexSpecs.insert(indexSpecs.begin(), indexes.begin(), indexes.end());
+ }
- BSONObj options;
- if (infos.size() > 0) {
- BSONObj entry = infos.front();
- if (entry["options"].isABSONObj()) {
- options = entry["options"].Obj();
- }
- }
+ ScopedTransaction transaction(txn, MODE_IX);
+ Lock::DBLock lk(txn->lockState(), nsToDatabaseSubstring(ns), MODE_X);
+ OldClientContext ctx(txn, ns);
+ if (!repl::getGlobalReplicationCoordinator()->canAcceptWritesFor(nss)) {
+ errmsg = str::stream() << "Not primary during migration: " << ns;
+ warning() << errmsg;
+ setState(FAIL);
+ return;
+ }
- WriteUnitOfWork wuow(txn);
- Status status = userCreateNS(txn, db, ns, options, false);
- if ( !status.isOK() ) {
- warning() << "failed to create collection [" << ns << "] "
- << " with options " << options << ": " << status;
- }
- wuow.commit();
- }
+ Database* db = ctx.db();
+ Collection* collection = db->getCollection(ns);
+ if (!collection) {
+ errmsg = str::stream() << "collection dropped during migration: " << ns;
+ warning() << errmsg;
+ setState(FAIL);
+ return;
}
- {
- // 1. copy indexes
+ MultiIndexBlock indexer(txn, collection);
- vector<BSONObj> indexSpecs;
- {
- const std::list<BSONObj> indexes = conn->getIndexSpecs(ns);
- indexSpecs.insert(indexSpecs.begin(), indexes.begin(), indexes.end());
- }
+ indexer.removeExistingIndexes(&indexSpecs);
- ScopedTransaction transaction(txn, MODE_IX);
- Lock::DBLock lk(txn->lockState(), nsToDatabaseSubstring(ns), MODE_X);
- OldClientContext ctx(txn, ns);
- if (!repl::getGlobalReplicationCoordinator()->canAcceptWritesFor(nss)) {
- errmsg = str::stream() << "Not primary during migration: " << ns;
+ if (!indexSpecs.empty()) {
+ // Only copy indexes if the collection does not have any documents.
+ if (collection->numRecords(txn) > 0) {
+ errmsg = str::stream() << "aborting migration, shard is missing "
+ << indexSpecs.size() << " indexes and "
+ << "collection is not empty. Non-trivial "
+ << "index creation should be scheduled manually";
warning() << errmsg;
setState(FAIL);
return;
}
- Database* db = ctx.db();
- Collection* collection = db->getCollection( ns );
- if ( !collection ) {
- errmsg = str::stream() << "collection dropped during migration: " << ns;
+ Status status = indexer.init(indexSpecs);
+ if (!status.isOK()) {
+ errmsg = str::stream() << "failed to create index before migrating data. "
+ << " error: " << status.toString();
warning() << errmsg;
setState(FAIL);
return;
}
- MultiIndexBlock indexer(txn, collection);
+ status = indexer.insertAllDocumentsInCollection();
+ if (!status.isOK()) {
+ errmsg = str::stream() << "failed to create index before migrating data. "
+ << " error: " << status.toString();
+ warning() << errmsg;
+ setState(FAIL);
+ return;
+ }
- indexer.removeExistingIndexes(&indexSpecs);
+ WriteUnitOfWork wunit(txn);
+ indexer.commit();
- if (!indexSpecs.empty()) {
- // Only copy indexes if the collection does not have any documents.
- if (collection->numRecords(txn) > 0) {
- errmsg = str::stream() << "aborting migration, shard is missing "
- << indexSpecs.size() << " indexes and "
- << "collection is not empty. Non-trivial "
- << "index creation should be scheduled manually";
- warning() << errmsg;
- setState(FAIL);
- return;
- }
+ for (size_t i = 0; i < indexSpecs.size(); i++) {
+ // make sure to create index on secondaries as well
+ getGlobalServiceContext()->getOpObserver()->onCreateIndex(
+ txn, db->getSystemIndexesName(), indexSpecs[i], true /* fromMigrate */);
+ }
- Status status = indexer.init(indexSpecs);
- if ( !status.isOK() ) {
- errmsg = str::stream() << "failed to create index before migrating data. "
- << " error: " << status.toString();
- warning() << errmsg;
- setState(FAIL);
- return;
- }
+ wunit.commit();
+ }
- status = indexer.insertAllDocumentsInCollection();
- if ( !status.isOK() ) {
- errmsg = str::stream() << "failed to create index before migrating data. "
- << " error: " << status.toString();
- warning() << errmsg;
- setState(FAIL);
- return;
- }
+ timing.done(1);
+ MONGO_FP_PAUSE_WHILE(migrateThreadHangAtStep1);
+ }
- WriteUnitOfWork wunit(txn);
- indexer.commit();
+ {
+ // 2. delete any data already in range
+ RangeDeleterOptions deleterOptions(
+ KeyRange(ns, min.getOwned(), max.getOwned(), shardKeyPattern));
+ deleterOptions.writeConcern = writeConcern;
+ // No need to wait since all existing cursors will filter out this range when
+ // returning the results.
+ deleterOptions.waitForOpenCursors = false;
+ deleterOptions.fromMigrate = true;
+ deleterOptions.onlyRemoveOrphanedDocs = true;
+ deleterOptions.removeSaverReason = "preCleanup";
- for (size_t i = 0; i < indexSpecs.size(); i++) {
- // make sure to create index on secondaries as well
- getGlobalServiceContext()->getOpObserver()->onCreateIndex(
- txn,
- db->getSystemIndexesName(),
- indexSpecs[i],
- true /* fromMigrate */);
- }
+ string errMsg;
- wunit.commit();
- }
-
- timing.done(1);
- MONGO_FP_PAUSE_WHILE(migrateThreadHangAtStep1);
+ if (!getDeleter()->deleteNow(txn, deleterOptions, &errMsg)) {
+ warning() << "Failed to queue delete for migrate abort: " << errMsg;
+ setState(FAIL);
+ return;
}
{
- // 2. delete any data already in range
- RangeDeleterOptions deleterOptions(KeyRange(ns,
- min.getOwned(),
- max.getOwned(),
- shardKeyPattern));
- deleterOptions.writeConcern = writeConcern;
- // No need to wait since all existing cursors will filter out this range when
- // returning the results.
- deleterOptions.waitForOpenCursors = false;
- deleterOptions.fromMigrate = true;
- deleterOptions.onlyRemoveOrphanedDocs = true;
- deleterOptions.removeSaverReason = "preCleanup";
-
- string errMsg;
-
- if (!getDeleter()->deleteNow(txn, deleterOptions, &errMsg)) {
- warning() << "Failed to queue delete for migrate abort: " << errMsg;
+ // Protect the range by noting that we're now starting a migration to it
+ ScopedTransaction transaction(txn, MODE_IX);
+ Lock::DBLock dbLock(txn->lockState(), nsToDatabaseSubstring(ns), MODE_IX);
+ Lock::CollectionLock collLock(txn->lockState(), ns, MODE_X);
+
+ if (!shardingState.notePending(txn, ns, min, max, epoch, &errmsg)) {
+ warning() << errmsg;
setState(FAIL);
return;
}
+ }
- {
- // Protect the range by noting that we're now starting a migration to it
- ScopedTransaction transaction(txn, MODE_IX);
- Lock::DBLock dbLock(txn->lockState(), nsToDatabaseSubstring(ns), MODE_IX);
- Lock::CollectionLock collLock(txn->lockState(), ns, MODE_X);
+ timing.done(2);
+ MONGO_FP_PAUSE_WHILE(migrateThreadHangAtStep2);
+ }
- if (!shardingState.notePending(txn, ns, min, max, epoch, &errmsg)) {
- warning() << errmsg;
- setState(FAIL);
- return;
- }
- }
+ State currentState = getState();
+ if (currentState == FAIL || currentState == ABORT) {
+ string errMsg;
+ RangeDeleterOptions deleterOptions(
+ KeyRange(ns, min.getOwned(), max.getOwned(), shardKeyPattern));
+ deleterOptions.writeConcern = writeConcern;
+ // No need to wait since all existing cursors will filter out this range when
+ // returning the results.
+ deleterOptions.waitForOpenCursors = false;
+ deleterOptions.fromMigrate = true;
+ deleterOptions.onlyRemoveOrphanedDocs = true;
- timing.done(2);
- MONGO_FP_PAUSE_WHILE(migrateThreadHangAtStep2);
- }
-
- State currentState = getState();
- if (currentState == FAIL || currentState == ABORT) {
- string errMsg;
- RangeDeleterOptions deleterOptions(KeyRange(ns,
- min.getOwned(),
- max.getOwned(),
- shardKeyPattern));
- deleterOptions.writeConcern = writeConcern;
- // No need to wait since all existing cursors will filter out this range when
- // returning the results.
- deleterOptions.waitForOpenCursors = false;
- deleterOptions.fromMigrate = true;
- deleterOptions.onlyRemoveOrphanedDocs = true;
-
- if (!getDeleter()->queueDelete(txn, deleterOptions, NULL /* notifier */, &errMsg)) {
- warning() << "Failed to queue delete for migrate abort: " << errMsg;
- }
+ if (!getDeleter()->queueDelete(txn, deleterOptions, NULL /* notifier */, &errMsg)) {
+ warning() << "Failed to queue delete for migrate abort: " << errMsg;
}
+ }
- {
- // 3. initial bulk clone
- setState(CLONE);
-
- while ( true ) {
- BSONObj res;
- if ( ! conn->runCommand( "admin" , BSON( "_migrateClone" << 1 ) , res ) ) { // gets array of objects to copy, in disk order
- setState(FAIL);
- errmsg = "_migrateClone failed: ";
- errmsg += res.toString();
+ {
+ // 3. initial bulk clone
+ setState(CLONE);
+
+ while (true) {
+ BSONObj res;
+ if (!conn->runCommand("admin",
+ BSON("_migrateClone" << 1),
+ res)) { // gets array of objects to copy, in disk order
+ setState(FAIL);
+ errmsg = "_migrateClone failed: ";
+ errmsg += res.toString();
+ error() << errmsg << migrateLog;
+ conn.done();
+ return;
+ }
+
+ BSONObj arr = res["objects"].Obj();
+ int thisTime = 0;
+
+ BSONObjIterator i(arr);
+ while (i.more()) {
+ txn->checkForInterrupt();
+
+ if (getState() == ABORT) {
+ errmsg = str::stream() << "Migration abort requested while "
+ << "copying documents";
error() << errmsg << migrateLog;
- conn.done();
return;
}
- BSONObj arr = res["objects"].Obj();
- int thisTime = 0;
-
- BSONObjIterator i( arr );
- while( i.more() ) {
- txn->checkForInterrupt();
-
- if ( getState() == ABORT ) {
- errmsg = str::stream() << "Migration abort requested while "
- << "copying documents";
- error() << errmsg << migrateLog;
- return;
+ BSONObj docToClone = i.next().Obj();
+ {
+ OldClientWriteContext cx(txn, ns);
+
+ BSONObj localDoc;
+ if (willOverrideLocalId(txn,
+ ns,
+ min,
+ max,
+ shardKeyPattern,
+ cx.db(),
+ docToClone,
+ &localDoc)) {
+ string errMsg = str::stream() << "cannot migrate chunk, local document "
+ << localDoc << " has same _id as cloned "
+ << "remote document " << docToClone;
+
+ warning() << errMsg;
+
+ // Exception will abort migration cleanly
+ uasserted(16976, errMsg);
}
- BSONObj docToClone = i.next().Obj();
- {
- OldClientWriteContext cx(txn, ns );
-
- BSONObj localDoc;
- if (willOverrideLocalId(txn,
- ns,
- min,
- max,
- shardKeyPattern,
- cx.db(),
- docToClone,
- &localDoc)) {
- string errMsg =
- str::stream() << "cannot migrate chunk, local document "
- << localDoc
- << " has same _id as cloned "
- << "remote document " << docToClone;
-
- warning() << errMsg;
-
- // Exception will abort migration cleanly
- uasserted( 16976, errMsg );
- }
-
- Helpers::upsert( txn, ns, docToClone, true );
- }
- thisTime++;
+ Helpers::upsert(txn, ns, docToClone, true);
+ }
+ thisTime++;
- {
- std::lock_guard<std::mutex> statsLock(_mutex);
- _numCloned++;
- _clonedBytes += docToClone.objsize();
- }
+ {
+ std::lock_guard<std::mutex> statsLock(_mutex);
+ _numCloned++;
+ _clonedBytes += docToClone.objsize();
+ }
- if (writeConcern.shouldWaitForOtherNodes()) {
- repl::ReplicationCoordinator::StatusAndDuration replStatus =
- repl::getGlobalReplicationCoordinator()->awaitReplication(
- txn,
- repl::ReplClientInfo::forClient(
- txn->getClient()).getLastOp(),
- writeConcern);
- if (replStatus.status.code() == ErrorCodes::WriteConcernFailed) {
- warning() << "secondaryThrottle on, but doc insert timed out; "
- "continuing";
- }
- else {
- massertStatusOK(replStatus.status);
- }
+ if (writeConcern.shouldWaitForOtherNodes()) {
+ repl::ReplicationCoordinator::StatusAndDuration replStatus =
+ repl::getGlobalReplicationCoordinator()->awaitReplication(
+ txn,
+ repl::ReplClientInfo::forClient(txn->getClient()).getLastOp(),
+ writeConcern);
+ if (replStatus.status.code() == ErrorCodes::WriteConcernFailed) {
+ warning() << "secondaryThrottle on, but doc insert timed out; "
+ "continuing";
+ } else {
+ massertStatusOK(replStatus.status);
}
}
-
- if ( thisTime == 0 )
- break;
}
- timing.done(3);
- MONGO_FP_PAUSE_WHILE(migrateThreadHangAtStep3);
+ if (thisTime == 0)
+ break;
}
- // If running on a replicated system, we'll need to flush the docs we cloned to the
- // secondaries
- repl::OpTime lastOpApplied =
- repl::ReplClientInfo::forClient(txn->getClient()).getLastOp();
+ timing.done(3);
+ MONGO_FP_PAUSE_WHILE(migrateThreadHangAtStep3);
+ }
- {
- // 4. do bulk of mods
- setState(CATCHUP);
- while ( true ) {
- BSONObj res;
- if ( ! conn->runCommand( "admin" , BSON( "_transferMods" << 1 ) , res ) ) {
- setState(FAIL);
- errmsg = "_transferMods failed: ";
- errmsg += res.toString();
- error() << "_transferMods failed: " << res << migrateLog;
- conn.done();
- return;
- }
- if ( res["size"].number() == 0 )
- break;
+ // If running on a replicated system, we'll need to flush the docs we cloned to the
+ // secondaries
+ repl::OpTime lastOpApplied = repl::ReplClientInfo::forClient(txn->getClient()).getLastOp();
- apply(txn, ns, min, max, shardKeyPattern, res, &lastOpApplied);
+ {
+ // 4. do bulk of mods
+ setState(CATCHUP);
+ while (true) {
+ BSONObj res;
+ if (!conn->runCommand("admin", BSON("_transferMods" << 1), res)) {
+ setState(FAIL);
+ errmsg = "_transferMods failed: ";
+ errmsg += res.toString();
+ error() << "_transferMods failed: " << res << migrateLog;
+ conn.done();
+ return;
+ }
+ if (res["size"].number() == 0)
+ break;
- const int maxIterations = 3600*50;
- int i;
- for ( i=0;i<maxIterations; i++) {
- txn->checkForInterrupt();
+ apply(txn, ns, min, max, shardKeyPattern, res, &lastOpApplied);
- if ( getState() == ABORT ) {
- errmsg = str::stream() << "Migration abort requested while waiting "
- << "for replication at catch up stage";
- error() << errmsg << migrateLog;
+ const int maxIterations = 3600 * 50;
+ int i;
+ for (i = 0; i < maxIterations; i++) {
+ txn->checkForInterrupt();
- return;
- }
+ if (getState() == ABORT) {
+ errmsg = str::stream() << "Migration abort requested while waiting "
+ << "for replication at catch up stage";
+ error() << errmsg << migrateLog;
- if (opReplicatedEnough(txn, lastOpApplied, writeConcern))
- break;
+ return;
+ }
- if ( i > 100 ) {
- warning() << "secondaries having hard time keeping up with migrate" << migrateLog;
- }
+ if (opReplicatedEnough(txn, lastOpApplied, writeConcern))
+ break;
- sleepmillis( 20 );
+ if (i > 100) {
+ warning() << "secondaries having hard time keeping up with migrate"
+ << migrateLog;
}
- if ( i == maxIterations ) {
- errmsg = "secondary can't keep up with migrate";
- error() << errmsg << migrateLog;
- conn.done();
- setState(FAIL);
- return;
- }
+ sleepmillis(20);
}
- timing.done(4);
- MONGO_FP_PAUSE_WHILE(migrateThreadHangAtStep4);
+ if (i == maxIterations) {
+ errmsg = "secondary can't keep up with migrate";
+ error() << errmsg << migrateLog;
+ conn.done();
+ setState(FAIL);
+ return;
+ }
}
- {
- // pause to wait for replication
- // this will prevent us from going into critical section until we're ready
- Timer t;
- while ( t.minutes() < 600 ) {
- txn->checkForInterrupt();
+ timing.done(4);
+ MONGO_FP_PAUSE_WHILE(migrateThreadHangAtStep4);
+ }
- if (getState() == ABORT) {
- errmsg = "Migration abort requested while waiting for replication";
- error() << errmsg << migrateLog;
- return;
- }
+ {
+ // pause to wait for replication
+ // this will prevent us from going into critical section until we're ready
+ Timer t;
+ while (t.minutes() < 600) {
+ txn->checkForInterrupt();
- log() << "Waiting for replication to catch up before entering critical section"
- ;
- if (flushPendingWrites(txn, ns, min, max, lastOpApplied, writeConcern))
- break;
- sleepsecs(1);
+ if (getState() == ABORT) {
+ errmsg = "Migration abort requested while waiting for replication";
+ error() << errmsg << migrateLog;
+ return;
}
- if (t.minutes() >= 600) {
- setState(FAIL);
- errmsg = "Cannot go to critical section because secondaries cannot keep up";
- error() << errmsg << migrateLog;
- return;
- }
+ log() << "Waiting for replication to catch up before entering critical section";
+ if (flushPendingWrites(txn, ns, min, max, lastOpApplied, writeConcern))
+ break;
+ sleepsecs(1);
}
- {
- // 5. wait for commit
-
- setState(STEADY);
- bool transferAfterCommit = false;
- while ( getState() == STEADY || getState() == COMMIT_START ) {
- txn->checkForInterrupt();
+ if (t.minutes() >= 600) {
+ setState(FAIL);
+ errmsg = "Cannot go to critical section because secondaries cannot keep up";
+ error() << errmsg << migrateLog;
+ return;
+ }
+ }
- // Make sure we do at least one transfer after recv'ing the commit message
- // If we aren't sure that at least one transfer happens *after* our state
- // changes to COMMIT_START, there could be mods still on the FROM shard that
- // got logged *after* our _transferMods but *before* the critical section.
- if ( getState() == COMMIT_START ) transferAfterCommit = true;
-
- BSONObj res;
- if ( ! conn->runCommand( "admin" , BSON( "_transferMods" << 1 ) , res ) ) {
- log() << "_transferMods failed in STEADY state: " << res << migrateLog;
- errmsg = res.toString();
- setState(FAIL);
- conn.done();
- return;
- }
+ {
+ // 5. wait for commit
- if (res["size"].number() > 0 &&
- apply(txn, ns, min, max, shardKeyPattern, res, &lastOpApplied)) {
- continue;
- }
+ setState(STEADY);
+ bool transferAfterCommit = false;
+ while (getState() == STEADY || getState() == COMMIT_START) {
+ txn->checkForInterrupt();
- if ( getState() == ABORT ) {
- return;
- }
+ // Make sure we do at least one transfer after recv'ing the commit message
+ // If we aren't sure that at least one transfer happens *after* our state
+ // changes to COMMIT_START, there could be mods still on the FROM shard that
+ // got logged *after* our _transferMods but *before* the critical section.
+ if (getState() == COMMIT_START)
+ transferAfterCommit = true;
- // We know we're finished when:
- // 1) The from side has told us that it has locked writes (COMMIT_START)
- // 2) We've checked at least one more time for un-transmitted mods
- if ( getState() == COMMIT_START && transferAfterCommit == true ) {
- if (flushPendingWrites(txn, ns, min, max, lastOpApplied, writeConcern))
- break;
- }
+ BSONObj res;
+ if (!conn->runCommand("admin", BSON("_transferMods" << 1), res)) {
+ log() << "_transferMods failed in STEADY state: " << res << migrateLog;
+ errmsg = res.toString();
+ setState(FAIL);
+ conn.done();
+ return;
+ }
- // Only sleep if we aren't committing
- if ( getState() == STEADY ) sleepmillis( 10 );
+ if (res["size"].number() > 0 &&
+ apply(txn, ns, min, max, shardKeyPattern, res, &lastOpApplied)) {
+ continue;
}
- if ( getState() == FAIL ) {
- errmsg = "timed out waiting for commit";
+ if (getState() == ABORT) {
return;
}
- timing.done(5);
- MONGO_FP_PAUSE_WHILE(migrateThreadHangAtStep5);
+ // We know we're finished when:
+ // 1) The from side has told us that it has locked writes (COMMIT_START)
+ // 2) We've checked at least one more time for un-transmitted mods
+ if (getState() == COMMIT_START && transferAfterCommit == true) {
+ if (flushPendingWrites(txn, ns, min, max, lastOpApplied, writeConcern))
+ break;
+ }
+
+ // Only sleep if we aren't committing
+ if (getState() == STEADY)
+ sleepmillis(10);
}
- setState(DONE);
- conn.done();
+ if (getState() == FAIL) {
+ errmsg = "timed out waiting for commit";
+ return;
+ }
+
+ timing.done(5);
+ MONGO_FP_PAUSE_WHILE(migrateThreadHangAtStep5);
}
- void status(BSONObjBuilder& b) {
- std::lock_guard<std::mutex> sl(_mutex);
+ setState(DONE);
+ conn.done();
+ }
- b.appendBool("active", _active);
+ void status(BSONObjBuilder& b) {
+ std::lock_guard<std::mutex> sl(_mutex);
- b.append("ns", _ns);
- b.append("from", _from);
- b.append("min", _min);
- b.append("max", _max);
- b.append("shardKeyPattern", _shardKeyPattern);
+ b.appendBool("active", _active);
- b.append("state", stateToString(_state));
- if (_state == FAIL) {
- b.append("errmsg", _errmsg);
- }
+ b.append("ns", _ns);
+ b.append("from", _from);
+ b.append("min", _min);
+ b.append("max", _max);
+ b.append("shardKeyPattern", _shardKeyPattern);
- BSONObjBuilder bb(b.subobjStart("counts"));
- bb.append("cloned", _numCloned);
- bb.append("clonedBytes", _clonedBytes);
- bb.append("catchup", _numCatchup);
- bb.append("steady", _numSteady);
- bb.done();
+ b.append("state", stateToString(_state));
+ if (_state == FAIL) {
+ b.append("errmsg", _errmsg);
}
- bool apply(OperationContext* txn,
- const string& ns,
- BSONObj min,
- BSONObj max,
- BSONObj shardKeyPattern,
- const BSONObj& xfer,
- repl::OpTime* lastOpApplied) {
-
- repl::OpTime dummy;
- if ( lastOpApplied == NULL ) {
- lastOpApplied = &dummy;
- }
+ BSONObjBuilder bb(b.subobjStart("counts"));
+ bb.append("cloned", _numCloned);
+ bb.append("clonedBytes", _clonedBytes);
+ bb.append("catchup", _numCatchup);
+ bb.append("steady", _numSteady);
+ bb.done();
+ }
- bool didAnything = false;
+ bool apply(OperationContext* txn,
+ const string& ns,
+ BSONObj min,
+ BSONObj max,
+ BSONObj shardKeyPattern,
+ const BSONObj& xfer,
+ repl::OpTime* lastOpApplied) {
+ repl::OpTime dummy;
+ if (lastOpApplied == NULL) {
+ lastOpApplied = &dummy;
+ }
- if ( xfer["deleted"].isABSONObj() ) {
- ScopedTransaction transaction(txn, MODE_IX);
- Lock::DBLock dlk(txn->lockState(), nsToDatabaseSubstring(ns), MODE_IX);
- Helpers::RemoveSaver rs( "moveChunk" , ns , "removedDuring" );
+ bool didAnything = false;
- BSONObjIterator i( xfer["deleted"].Obj() );
- while ( i.more() ) {
- Lock::CollectionLock clk(txn->lockState(), ns, MODE_X);
- OldClientContext ctx(txn, ns);
+ if (xfer["deleted"].isABSONObj()) {
+ ScopedTransaction transaction(txn, MODE_IX);
+ Lock::DBLock dlk(txn->lockState(), nsToDatabaseSubstring(ns), MODE_IX);
+ Helpers::RemoveSaver rs("moveChunk", ns, "removedDuring");
- BSONObj id = i.next().Obj();
+ BSONObjIterator i(xfer["deleted"].Obj());
+ while (i.more()) {
+ Lock::CollectionLock clk(txn->lockState(), ns, MODE_X);
+ OldClientContext ctx(txn, ns);
- // do not apply deletes if they do not belong to the chunk being migrated
- BSONObj fullObj;
- if (Helpers::findById(txn, ctx.db(), ns.c_str(), id, fullObj)) {
- if (!isInRange(fullObj , min , max , shardKeyPattern)) {
- log() << "not applying out of range deletion: " << fullObj << migrateLog;
+ BSONObj id = i.next().Obj();
- continue;
- }
- }
+ // do not apply deletes if they do not belong to the chunk being migrated
+ BSONObj fullObj;
+ if (Helpers::findById(txn, ctx.db(), ns.c_str(), id, fullObj)) {
+ if (!isInRange(fullObj, min, max, shardKeyPattern)) {
+ log() << "not applying out of range deletion: " << fullObj << migrateLog;
- if (serverGlobalParams.moveParanoia) {
- rs.goingToDelete(fullObj);
+ continue;
}
+ }
- deleteObjects(txn,
- ctx.db(),
- ns,
- id,
- PlanExecutor::YIELD_MANUAL,
- true /* justOne */,
- false /* god */,
- true /* fromMigrate */);
-
- *lastOpApplied = repl::ReplClientInfo::forClient(txn->getClient()).getLastOp();
- didAnything = true;
+ if (serverGlobalParams.moveParanoia) {
+ rs.goingToDelete(fullObj);
}
+
+ deleteObjects(txn,
+ ctx.db(),
+ ns,
+ id,
+ PlanExecutor::YIELD_MANUAL,
+ true /* justOne */,
+ false /* god */,
+ true /* fromMigrate */);
+
+ *lastOpApplied = repl::ReplClientInfo::forClient(txn->getClient()).getLastOp();
+ didAnything = true;
}
+ }
- if ( xfer["reload"].isABSONObj() ) {
- BSONObjIterator i( xfer["reload"].Obj() );
- while ( i.more() ) {
- OldClientWriteContext cx(txn, ns);
-
- BSONObj updatedDoc = i.next().Obj();
-
- BSONObj localDoc;
- if (willOverrideLocalId(txn,
- ns,
- min,
- max,
- shardKeyPattern,
- cx.db(),
- updatedDoc,
- &localDoc)) {
- string errMsg =
- str::stream() << "cannot migrate chunk, local document "
- << localDoc
- << " has same _id as reloaded remote document "
- << updatedDoc;
-
- warning() << errMsg;
-
- // Exception will abort migration cleanly
- uasserted( 16977, errMsg );
- }
+ if (xfer["reload"].isABSONObj()) {
+ BSONObjIterator i(xfer["reload"].Obj());
+ while (i.more()) {
+ OldClientWriteContext cx(txn, ns);
- // We are in write lock here, so sure we aren't killing
- Helpers::upsert( txn, ns , updatedDoc , true );
+ BSONObj updatedDoc = i.next().Obj();
- *lastOpApplied = repl::ReplClientInfo::forClient(txn->getClient()).getLastOp();
- didAnything = true;
- }
- }
+ BSONObj localDoc;
+ if (willOverrideLocalId(
+ txn, ns, min, max, shardKeyPattern, cx.db(), updatedDoc, &localDoc)) {
+ string errMsg = str::stream()
+ << "cannot migrate chunk, local document " << localDoc
+ << " has same _id as reloaded remote document " << updatedDoc;
- return didAnything;
- }
+ warning() << errMsg;
- /**
- * Checks if an upsert of a remote document will override a local document with the same _id
- * but in a different range on this shard.
- * Must be in WriteContext to avoid races and DBHelper errors.
- * TODO: Could optimize this check out if sharding on _id.
- */
- bool willOverrideLocalId(OperationContext* txn,
- const string& ns,
- BSONObj min,
- BSONObj max,
- BSONObj shardKeyPattern,
- Database* db,
- BSONObj remoteDoc,
- BSONObj* localDoc) {
+ // Exception will abort migration cleanly
+ uasserted(16977, errMsg);
+ }
+
+ // We are in write lock here, so sure we aren't killing
+ Helpers::upsert(txn, ns, updatedDoc, true);
- *localDoc = BSONObj();
- if ( Helpers::findById( txn, db, ns.c_str(), remoteDoc, *localDoc ) ) {
- return !isInRange( *localDoc , min , max , shardKeyPattern );
+ *lastOpApplied = repl::ReplClientInfo::forClient(txn->getClient()).getLastOp();
+ didAnything = true;
}
+ }
- return false;
+ return didAnything;
+ }
+
+ /**
+ * Checks if an upsert of a remote document will override a local document with the same _id
+ * but in a different range on this shard.
+ * Must be in WriteContext to avoid races and DBHelper errors.
+ * TODO: Could optimize this check out if sharding on _id.
+ */
+ bool willOverrideLocalId(OperationContext* txn,
+ const string& ns,
+ BSONObj min,
+ BSONObj max,
+ BSONObj shardKeyPattern,
+ Database* db,
+ BSONObj remoteDoc,
+ BSONObj* localDoc) {
+ *localDoc = BSONObj();
+ if (Helpers::findById(txn, db, ns.c_str(), remoteDoc, *localDoc)) {
+ return !isInRange(*localDoc, min, max, shardKeyPattern);
}
- /**
- * Returns true if the majority of the nodes and the nodes corresponding to the given
- * writeConcern (if not empty) have applied till the specified lastOp.
- */
- bool opReplicatedEnough(OperationContext* txn,
- const repl::OpTime& lastOpApplied,
- const WriteConcernOptions& writeConcern) {
- WriteConcernOptions majorityWriteConcern;
- majorityWriteConcern.wTimeout = -1;
- majorityWriteConcern.wMode = WriteConcernOptions::kMajority;
- Status majorityStatus = repl::getGlobalReplicationCoordinator()->awaitReplication(
- txn, lastOpApplied, majorityWriteConcern).status;
-
- if (!writeConcern.shouldWaitForOtherNodes()) {
- return majorityStatus.isOK();
- }
-
- // Also enforce the user specified write concern after "majority" so it covers
- // the union of the 2 write concerns.
-
- WriteConcernOptions userWriteConcern(writeConcern);
- userWriteConcern.wTimeout = -1;
- Status userStatus = repl::getGlobalReplicationCoordinator()->awaitReplication(
- txn, lastOpApplied, userWriteConcern).status;
-
- return majorityStatus.isOK() && userStatus.isOK();
- }
-
- bool flushPendingWrites(OperationContext* txn,
- const std::string& ns,
- BSONObj min,
- BSONObj max,
- const repl::OpTime& lastOpApplied,
- const WriteConcernOptions& writeConcern) {
- if (!opReplicatedEnough(txn, lastOpApplied, writeConcern)) {
- repl::OpTime op(lastOpApplied);
- OCCASIONALLY warning() << "migrate commit waiting for a majority of slaves for '"
- << ns << "' " << min << " -> " << max
- << " waiting for: " << op
- << migrateLog;
- return false;
- }
+ return false;
+ }
- log() << "migrate commit succeeded flushing to secondaries for '" << ns << "' " << min << " -> " << max << migrateLog;
+ /**
+ * Returns true if the majority of the nodes and the nodes corresponding to the given
+ * writeConcern (if not empty) have applied till the specified lastOp.
+ */
+ bool opReplicatedEnough(OperationContext* txn,
+ const repl::OpTime& lastOpApplied,
+ const WriteConcernOptions& writeConcern) {
+ WriteConcernOptions majorityWriteConcern;
+ majorityWriteConcern.wTimeout = -1;
+ majorityWriteConcern.wMode = WriteConcernOptions::kMajority;
+ Status majorityStatus = repl::getGlobalReplicationCoordinator()
+ ->awaitReplication(txn, lastOpApplied, majorityWriteConcern)
+ .status;
+
+ if (!writeConcern.shouldWaitForOtherNodes()) {
+ return majorityStatus.isOK();
+ }
- {
- // Get global lock to wait for write to be commited to journal.
- ScopedTransaction transaction(txn, MODE_S);
- Lock::GlobalRead lk(txn->lockState());
+ // Also enforce the user specified write concern after "majority" so it covers
+ // the union of the 2 write concerns.
- // if durability is on, force a write to journal
- if (getDur().commitNow(txn)) {
- log() << "migrate commit flushed to journal for '" << ns << "' " << min << " -> " << max << migrateLog;
- }
- }
+ WriteConcernOptions userWriteConcern(writeConcern);
+ userWriteConcern.wTimeout = -1;
+ Status userStatus = repl::getGlobalReplicationCoordinator()
+ ->awaitReplication(txn, lastOpApplied, userWriteConcern)
+ .status;
- return true;
- }
+ return majorityStatus.isOK() && userStatus.isOK();
+ }
- static string stateToString(State state) {
- switch (state) {
- case READY: return "ready";
- case CLONE: return "clone";
- case CATCHUP: return "catchup";
- case STEADY: return "steady";
- case COMMIT_START: return "commitStart";
- case DONE: return "done";
- case FAIL: return "fail";
- case ABORT: return "abort";
- }
- verify(0);
- return "";
+ bool flushPendingWrites(OperationContext* txn,
+ const std::string& ns,
+ BSONObj min,
+ BSONObj max,
+ const repl::OpTime& lastOpApplied,
+ const WriteConcernOptions& writeConcern) {
+ if (!opReplicatedEnough(txn, lastOpApplied, writeConcern)) {
+ repl::OpTime op(lastOpApplied);
+ OCCASIONALLY warning() << "migrate commit waiting for a majority of slaves for '" << ns
+ << "' " << min << " -> " << max << " waiting for: " << op
+ << migrateLog;
+ return false;
}
- bool startCommit() {
- std::unique_lock<std::mutex> lock(_mutex);
+ log() << "migrate commit succeeded flushing to secondaries for '" << ns << "' " << min
+ << " -> " << max << migrateLog;
- if (_state != STEADY) {
- return false;
- }
+ {
+ // Get global lock to wait for write to be commited to journal.
+ ScopedTransaction transaction(txn, MODE_S);
+ Lock::GlobalRead lk(txn->lockState());
- const auto deadline = system_clock::now() + seconds(30);
- _state = COMMIT_START;
- while (_active) {
- if (std::cv_status::timeout == isActiveCV.wait_until(lock, deadline)) {
- _state = FAIL;
- log() << "startCommit never finished!" << migrateLog;
- return false;
- }
+ // if durability is on, force a write to journal
+ if (getDur().commitNow(txn)) {
+ log() << "migrate commit flushed to journal for '" << ns << "' " << min << " -> "
+ << max << migrateLog;
}
+ }
- if (_state == DONE) {
- return true;
- }
+ return true;
+ }
- log() << "startCommit failed, final data failed to transfer" << migrateLog;
+ static string stateToString(State state) {
+ switch (state) {
+ case READY:
+ return "ready";
+ case CLONE:
+ return "clone";
+ case CATCHUP:
+ return "catchup";
+ case STEADY:
+ return "steady";
+ case COMMIT_START:
+ return "commitStart";
+ case DONE:
+ return "done";
+ case FAIL:
+ return "fail";
+ case ABORT:
+ return "abort";
+ }
+ verify(0);
+ return "";
+ }
+
+ bool startCommit() {
+ std::unique_lock<std::mutex> lock(_mutex);
+
+ if (_state != STEADY) {
return false;
}
- void abort() {
- std::lock_guard<std::mutex> sl(_mutex);
- _state = ABORT;
- _errmsg = "aborted";
+ const auto deadline = system_clock::now() + seconds(30);
+ _state = COMMIT_START;
+ while (_active) {
+ if (std::cv_status::timeout == isActiveCV.wait_until(lock, deadline)) {
+ _state = FAIL;
+ log() << "startCommit never finished!" << migrateLog;
+ return false;
+ }
}
- bool getActive() const { std::lock_guard<std::mutex> lk(_mutex); return _active; }
- void setActive( bool b ) {
- std::lock_guard<std::mutex> lk(_mutex);
- _active = b;
- isActiveCV.notify_all();
+ if (_state == DONE) {
+ return true;
}
- // Guards all fields.
- mutable std::mutex _mutex;
- bool _active;
- std::condition_variable isActiveCV;
+ log() << "startCommit failed, final data failed to transfer" << migrateLog;
+ return false;
+ }
+
+ void abort() {
+ std::lock_guard<std::mutex> sl(_mutex);
+ _state = ABORT;
+ _errmsg = "aborted";
+ }
+
+ bool getActive() const {
+ std::lock_guard<std::mutex> lk(_mutex);
+ return _active;
+ }
+ void setActive(bool b) {
+ std::lock_guard<std::mutex> lk(_mutex);
+ _active = b;
+ isActiveCV.notify_all();
+ }
- std::string _ns;
- std::string _from;
- BSONObj _min;
- BSONObj _max;
- BSONObj _shardKeyPattern;
+ // Guards all fields.
+ mutable std::mutex _mutex;
+ bool _active;
+ std::condition_variable isActiveCV;
- long long _numCloned;
- long long _clonedBytes;
- long long _numCatchup;
- long long _numSteady;
+ std::string _ns;
+ std::string _from;
+ BSONObj _min;
+ BSONObj _max;
+ BSONObj _shardKeyPattern;
- State _state;
- std::string _errmsg;
+ long long _numCloned;
+ long long _clonedBytes;
+ long long _numCatchup;
+ long long _numSteady;
- } migrateStatus;
+ State _state;
+ std::string _errmsg;
- void migrateThread(std::string ns,
- BSONObj min,
- BSONObj max,
- BSONObj shardKeyPattern,
- std::string fromShard,
- OID epoch,
- WriteConcernOptions writeConcern) {
- Client::initThread( "migrateThread" );
- OperationContextImpl txn;
- if (getGlobalAuthorizationManager()->isAuthEnabled()) {
- ShardedConnectionInfo::addHook();
- AuthorizationSession::get(txn.getClient())->grantInternalAuthorization();
- }
+} migrateStatus;
- migrateStatus.go(&txn, ns, min, max, shardKeyPattern, fromShard, epoch, writeConcern);
+void migrateThread(std::string ns,
+ BSONObj min,
+ BSONObj max,
+ BSONObj shardKeyPattern,
+ std::string fromShard,
+ OID epoch,
+ WriteConcernOptions writeConcern) {
+ Client::initThread("migrateThread");
+ OperationContextImpl txn;
+ if (getGlobalAuthorizationManager()->isAuthEnabled()) {
+ ShardedConnectionInfo::addHook();
+ AuthorizationSession::get(txn.getClient())->grantInternalAuthorization();
}
- /**
- * Command for initiating the recipient side of the migration to start copying data
- * from the donor shard.
- *
- * {
- * _recvChunkStart: "namespace",
- * congfigServer: "hostAndPort",
- * from: "hostAndPort",
- * fromShardName: "shardName",
- * toShardName: "shardName",
- * min: {},
- * max: {},
- * shardKeyPattern: {},
- *
- * // optional
- * secondaryThrottle: bool, // defaults to true
- * writeConcern: {} // applies to individual writes.
- * }
- */
- class RecvChunkStartCommand : public ChunkCommandHelper {
- public:
- void help(std::stringstream& h) const { h << "internal"; }
- RecvChunkStartCommand() : ChunkCommandHelper( "_recvChunkStart" ) {}
-
- virtual bool isWriteCommandForConfigServer() const { return false; }
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::internal);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
- }
- bool run(OperationContext* txn,
- const string&,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
-
- // Active state of TO-side migrations (MigrateStatus) is serialized by distributed
- // collection lock.
- if ( migrateStatus.getActive() ) {
- errmsg = "migrate already in progress";
- return false;
- }
+ migrateStatus.go(&txn, ns, min, max, shardKeyPattern, fromShard, epoch, writeConcern);
+}
- // Pending deletes (for migrations) are serialized by the distributed collection lock,
- // we are sure we registered a delete for a range *before* we can migrate-in a
- // subrange.
- const size_t numDeletes = getDeleter()->getTotalDeletes();
- if (numDeletes > 0) {
+/**
+ * Command for initiating the recipient side of the migration to start copying data
+ * from the donor shard.
+ *
+ * {
+ * _recvChunkStart: "namespace",
+ * congfigServer: "hostAndPort",
+ * from: "hostAndPort",
+ * fromShardName: "shardName",
+ * toShardName: "shardName",
+ * min: {},
+ * max: {},
+ * shardKeyPattern: {},
+ *
+ * // optional
+ * secondaryThrottle: bool, // defaults to true
+ * writeConcern: {} // applies to individual writes.
+ * }
+ */
+class RecvChunkStartCommand : public ChunkCommandHelper {
+public:
+ void help(std::stringstream& h) const {
+ h << "internal";
+ }
+ RecvChunkStartCommand() : ChunkCommandHelper("_recvChunkStart") {}
- errmsg = str::stream() << "can't accept new chunks because "
- << " there are still " << numDeletes
- << " deletes from previous migration";
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::internal);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
+ bool run(OperationContext* txn,
+ const string&,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ // Active state of TO-side migrations (MigrateStatus) is serialized by distributed
+ // collection lock.
+ if (migrateStatus.getActive()) {
+ errmsg = "migrate already in progress";
+ return false;
+ }
+
+ // Pending deletes (for migrations) are serialized by the distributed collection lock,
+ // we are sure we registered a delete for a range *before* we can migrate-in a
+ // subrange.
+ const size_t numDeletes = getDeleter()->getTotalDeletes();
+ if (numDeletes > 0) {
+ errmsg = str::stream() << "can't accept new chunks because "
+ << " there are still " << numDeletes
+ << " deletes from previous migration";
+
+ warning() << errmsg;
+ return false;
+ }
+
+ if (!shardingState.enabled()) {
+ if (!cmdObj["configServer"].eoo()) {
+ dassert(cmdObj["configServer"].type() == String);
+ ShardingState::initialize(cmdObj["configServer"].String());
+ } else {
+ errmsg = str::stream()
+ << "cannot start recv'ing chunk, "
+ << "sharding is not enabled and no config server was provided";
warning() << errmsg;
return false;
}
+ }
- if (!shardingState.enabled()) {
- if (!cmdObj["configServer"].eoo()) {
- dassert(cmdObj["configServer"].type() == String);
- ShardingState::initialize(cmdObj["configServer"].String());
- }
- else {
-
- errmsg = str::stream()
- << "cannot start recv'ing chunk, "
- << "sharding is not enabled and no config server was provided";
+ if (!cmdObj["toShardName"].eoo()) {
+ dassert(cmdObj["toShardName"].type() == String);
+ shardingState.gotShardName(cmdObj["toShardName"].String());
+ }
- warning() << errmsg;
- return false;
- }
- }
+ string ns = cmdObj.firstElement().String();
+ BSONObj min = cmdObj["min"].Obj().getOwned();
+ BSONObj max = cmdObj["max"].Obj().getOwned();
- if ( !cmdObj["toShardName"].eoo() ) {
- dassert( cmdObj["toShardName"].type() == String );
- shardingState.gotShardName( cmdObj["toShardName"].String() );
- }
+ // Refresh our collection manager from the config server, we need a collection manager
+ // to start registering pending chunks.
+ // We force the remote refresh here to make the behavior consistent and predictable,
+ // generally we'd refresh anyway, and to be paranoid.
+ ChunkVersion currentVersion;
+ Status status = shardingState.refreshMetadataNow(txn, ns, &currentVersion);
- string ns = cmdObj.firstElement().String();
- BSONObj min = cmdObj["min"].Obj().getOwned();
- BSONObj max = cmdObj["max"].Obj().getOwned();
+ if (!status.isOK()) {
+ errmsg = str::stream() << "cannot start recv'ing chunk "
+ << "[" << min << "," << max << ")" << causedBy(status.reason());
- // Refresh our collection manager from the config server, we need a collection manager
- // to start registering pending chunks.
- // We force the remote refresh here to make the behavior consistent and predictable,
- // generally we'd refresh anyway, and to be paranoid.
- ChunkVersion currentVersion;
- Status status = shardingState.refreshMetadataNow(txn, ns, &currentVersion );
+ warning() << errmsg;
+ return false;
+ }
- if ( !status.isOK() ) {
- errmsg = str::stream() << "cannot start recv'ing chunk "
- << "[" << min << "," << max << ")"
- << causedBy( status.reason() );
+ // Process secondary throttle settings and assign defaults if necessary.
+ WriteConcernOptions writeConcern;
+ status = writeConcern.parseSecondaryThrottle(cmdObj, NULL);
- warning() << errmsg;
- return false;
+ if (!status.isOK()) {
+ if (status.code() != ErrorCodes::WriteConcernNotDefined) {
+ warning() << status.toString();
+ return appendCommandStatus(result, status);
}
- // Process secondary throttle settings and assign defaults if necessary.
- WriteConcernOptions writeConcern;
- status = writeConcern.parseSecondaryThrottle(cmdObj, NULL);
+ writeConcern = getDefaultWriteConcern();
+ } else {
+ repl::ReplicationCoordinator* replCoordinator = repl::getGlobalReplicationCoordinator();
- if (!status.isOK()){
- if (status.code() != ErrorCodes::WriteConcernNotDefined) {
- warning() << status.toString();
- return appendCommandStatus(result, status);
- }
+ if (replCoordinator->getReplicationMode() ==
+ repl::ReplicationCoordinator::modeMasterSlave &&
+ writeConcern.shouldWaitForOtherNodes()) {
+ warning() << "recvChunk cannot check if secondary throttle setting "
+ << writeConcern.toBSON()
+ << " can be enforced in a master slave configuration";
+ }
- writeConcern = getDefaultWriteConcern();
+ Status status = replCoordinator->checkIfWriteConcernCanBeSatisfied(writeConcern);
+ if (!status.isOK() && status != ErrorCodes::NoReplicationEnabled) {
+ warning() << status.toString();
+ return appendCommandStatus(result, status);
}
- else {
- repl::ReplicationCoordinator* replCoordinator =
- repl::getGlobalReplicationCoordinator();
+ }
- if (replCoordinator->getReplicationMode() ==
- repl::ReplicationCoordinator::modeMasterSlave &&
- writeConcern.shouldWaitForOtherNodes()) {
- warning() << "recvChunk cannot check if secondary throttle setting "
- << writeConcern.toBSON()
- << " can be enforced in a master slave configuration";
- }
+ if (writeConcern.shouldWaitForOtherNodes() &&
+ writeConcern.wTimeout == WriteConcernOptions::kNoTimeout) {
+ // Don't allow no timeout.
+ writeConcern.wTimeout = kDefaultWTimeoutMs;
+ }
- Status status = replCoordinator->checkIfWriteConcernCanBeSatisfied(writeConcern);
- if (!status.isOK() && status != ErrorCodes::NoReplicationEnabled) {
- warning() << status.toString();
- return appendCommandStatus(result, status);
- }
- }
+ BSONObj shardKeyPattern;
+ if (cmdObj.hasField("shardKeyPattern")) {
+ shardKeyPattern = cmdObj["shardKeyPattern"].Obj().getOwned();
+ } else {
+ // shardKeyPattern may not be provided if another shard is from pre 2.2
+ // In that case, assume the shard key pattern is the same as the range
+ // specifiers provided.
+ BSONObj keya = Helpers::inferKeyPattern(min);
+ BSONObj keyb = Helpers::inferKeyPattern(max);
+ verify(keya == keyb);
+
+ warning()
+ << "No shard key pattern provided by source shard for migration."
+ " This is likely because the source shard is running a version prior to 2.2."
+ " Falling back to assuming the shard key matches the pattern of the min and max"
+ " chunk range specifiers. Inferred shard key: " << keya;
+
+ shardKeyPattern = keya.getOwned();
+ }
- if (writeConcern.shouldWaitForOtherNodes() &&
- writeConcern.wTimeout == WriteConcernOptions::kNoTimeout) {
- // Don't allow no timeout.
- writeConcern.wTimeout = kDefaultWTimeoutMs;
- }
+ const string fromShard(cmdObj["from"].String());
- BSONObj shardKeyPattern;
- if (cmdObj.hasField("shardKeyPattern")) {
- shardKeyPattern = cmdObj["shardKeyPattern"].Obj().getOwned();
- } else {
- // shardKeyPattern may not be provided if another shard is from pre 2.2
- // In that case, assume the shard key pattern is the same as the range
- // specifiers provided.
- BSONObj keya = Helpers::inferKeyPattern(min);
- BSONObj keyb = Helpers::inferKeyPattern(max);
- verify( keya == keyb );
+ // Set the TO-side migration to active
+ Status prepareStatus = migrateStatus.prepare(ns, fromShard, min, max, shardKeyPattern);
- warning() << "No shard key pattern provided by source shard for migration."
- " This is likely because the source shard is running a version prior to 2.2."
- " Falling back to assuming the shard key matches the pattern of the min and max"
- " chunk range specifiers. Inferred shard key: " << keya;
+ if (!prepareStatus.isOK()) {
+ return appendCommandStatus(result, prepareStatus);
+ }
- shardKeyPattern = keya.getOwned();
- }
+ std::thread m(migrateThread,
+ ns,
+ min,
+ max,
+ shardKeyPattern,
+ fromShard,
+ currentVersion.epoch(),
+ writeConcern);
+
+ m.detach();
+ result.appendBool("started", true);
+ return true;
+ }
- const string fromShard(cmdObj["from"].String());
+} recvChunkStartCmd;
- // Set the TO-side migration to active
- Status prepareStatus = migrateStatus.prepare(ns, fromShard, min, max, shardKeyPattern);
+class RecvChunkStatusCommand : public ChunkCommandHelper {
+public:
+ void help(std::stringstream& h) const {
+ h << "internal";
+ }
+ RecvChunkStatusCommand() : ChunkCommandHelper("_recvChunkStatus") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::internal);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
+ bool run(OperationContext* txn,
+ const string&,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ migrateStatus.status(result);
+ return 1;
+ }
- if (!prepareStatus.isOK()) {
- return appendCommandStatus(result, prepareStatus);
- }
+} recvChunkStatusCommand;
- std::thread m(migrateThread,
- ns,
- min,
- max,
- shardKeyPattern,
- fromShard,
- currentVersion.epoch(),
- writeConcern);
+class RecvChunkCommitCommand : public ChunkCommandHelper {
+public:
+ void help(std::stringstream& h) const {
+ h << "internal";
+ }
+ RecvChunkCommitCommand() : ChunkCommandHelper("_recvChunkCommit") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::internal);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
+ bool run(OperationContext* txn,
+ const string&,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ bool ok = migrateStatus.startCommit();
+ migrateStatus.status(result);
+ return ok;
+ }
- m.detach();
- result.appendBool( "started" , true );
- return true;
- }
+} recvChunkCommitCommand;
- } recvChunkStartCmd;
+class RecvChunkAbortCommand : public ChunkCommandHelper {
+public:
+ void help(std::stringstream& h) const {
+ h << "internal";
+ }
+ RecvChunkAbortCommand() : ChunkCommandHelper("_recvChunkAbort") {}
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::internal);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
+ bool run(OperationContext* txn,
+ const string&,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ migrateStatus.abort();
+ migrateStatus.status(result);
+ return true;
+ }
- class RecvChunkStatusCommand : public ChunkCommandHelper {
- public:
- void help(std::stringstream& h) const { h << "internal"; }
- RecvChunkStatusCommand() : ChunkCommandHelper( "_recvChunkStatus" ) {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::internal);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
- }
- bool run(OperationContext* txn,
- const string&,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- migrateStatus.status( result );
- return 1;
- }
-
- } recvChunkStatusCommand;
-
- class RecvChunkCommitCommand : public ChunkCommandHelper {
- public:
- void help(std::stringstream& h) const { h << "internal"; }
- RecvChunkCommitCommand() : ChunkCommandHelper( "_recvChunkCommit" ) {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::internal);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
- }
- bool run(OperationContext* txn,
- const string&,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- bool ok = migrateStatus.startCommit();
- migrateStatus.status( result );
- return ok;
- }
-
- } recvChunkCommitCommand;
-
- class RecvChunkAbortCommand : public ChunkCommandHelper {
- public:
- void help(std::stringstream& h) const { h << "internal"; }
- RecvChunkAbortCommand() : ChunkCommandHelper( "_recvChunkAbort" ) {}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::internal);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
- }
- bool run(OperationContext* txn,
- const string&,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- migrateStatus.abort();
- migrateStatus.status( result );
- return true;
- }
+} recvChunkAboortCommand;
- } recvChunkAboortCommand;
+class IsInRangeTest : public StartupTest {
+public:
+ void run() {
+ BSONObj min = BSON("x" << 1);
+ BSONObj max = BSON("x" << 5);
+ BSONObj skey = BSON("x" << 1);
- class IsInRangeTest : public StartupTest {
- public:
- void run() {
- BSONObj min = BSON( "x" << 1 );
- BSONObj max = BSON( "x" << 5 );
- BSONObj skey = BSON( "x" << 1 );
-
- verify( ! isInRange( BSON( "x" << 0 ) , min , max , skey ) );
- verify( isInRange( BSON( "x" << 1 ) , min , max , skey ) );
- verify( isInRange( BSON( "x" << 3 ) , min , max , skey ) );
- verify( isInRange( BSON( "x" << 4 ) , min , max , skey ) );
- verify( ! isInRange( BSON( "x" << 5 ) , min , max , skey ) );
- verify( ! isInRange( BSON( "x" << 6 ) , min , max , skey ) );
-
- BSONObj obj = BSON( "n" << 3 );
- BSONObj min2 = BSON( "x" << BSONElementHasher::hash64( obj.firstElement() , 0 ) - 2 );
- BSONObj max2 = BSON( "x" << BSONElementHasher::hash64( obj.firstElement() , 0 ) + 2 );
- BSONObj hashedKey = BSON( "x" << "hashed" );
-
- verify( isInRange( BSON( "x" << 3 ) , min2 , max2 , hashedKey ) );
- verify( ! isInRange( BSON( "x" << 3 ) , min , max , hashedKey ) );
- verify( ! isInRange( BSON( "x" << 4 ) , min2 , max2 , hashedKey ) );
-
- LOG(1) << "isInRangeTest passed" << migrateLog;
- }
- } isInRangeTest;
+ verify(!isInRange(BSON("x" << 0), min, max, skey));
+ verify(isInRange(BSON("x" << 1), min, max, skey));
+ verify(isInRange(BSON("x" << 3), min, max, skey));
+ verify(isInRange(BSON("x" << 4), min, max, skey));
+ verify(!isInRange(BSON("x" << 5), min, max, skey));
+ verify(!isInRange(BSON("x" << 6), min, max, skey));
+
+ BSONObj obj = BSON("n" << 3);
+ BSONObj min2 = BSON("x" << BSONElementHasher::hash64(obj.firstElement(), 0) - 2);
+ BSONObj max2 = BSON("x" << BSONElementHasher::hash64(obj.firstElement(), 0) + 2);
+ BSONObj hashedKey = BSON("x"
+ << "hashed");
+
+ verify(isInRange(BSON("x" << 3), min2, max2, hashedKey));
+ verify(!isInRange(BSON("x" << 3), min, max, hashedKey));
+ verify(!isInRange(BSON("x" << 4), min2, max2, hashedKey));
+
+ LOG(1) << "isInRangeTest passed" << migrateLog;
+ }
+} isInRangeTest;
}
diff --git a/src/mongo/s/d_split.cpp b/src/mongo/s/d_split.cpp
index 98d62c7029a..cbf81006723 100644
--- a/src/mongo/s/d_split.cpp
+++ b/src/mongo/s/d_split.cpp
@@ -67,866 +67,875 @@
namespace mongo {
- using std::unique_ptr;
- using std::endl;
- using std::ostringstream;
- using std::set;
- using std::string;
- using std::stringstream;
- using std::vector;
-
- class CmdMedianKey : public Command {
- public:
- CmdMedianKey() : Command( "medianKey" ) {}
- virtual bool slaveOk() const { return true; }
- virtual bool isWriteCommandForConfigServer() const { return false; }
- virtual void help( stringstream &help ) const {
- help << "Deprecated internal command. Use splitVector command instead. \n";
- }
- // No auth required as this command no longer does anything.
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {}
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& jsobj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- errmsg = "medianKey command no longer supported. Calling this indicates mismatch between mongo versions.";
+using std::unique_ptr;
+using std::endl;
+using std::ostringstream;
+using std::set;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+class CmdMedianKey : public Command {
+public:
+ CmdMedianKey() : Command("medianKey") {}
+ virtual bool slaveOk() const {
+ return true;
+ }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+ virtual void help(stringstream& help) const {
+ help << "Deprecated internal command. Use splitVector command instead. \n";
+ }
+ // No auth required as this command no longer does anything.
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {}
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& jsobj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ errmsg =
+ "medianKey command no longer supported. Calling this indicates mismatch between mongo "
+ "versions.";
+ return false;
+ }
+} cmdMedianKey;
+
+class CheckShardingIndex : public Command {
+public:
+ CheckShardingIndex() : Command("checkShardingIndex", false) {}
+ virtual bool slaveOk() const {
+ return false;
+ }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+ virtual void help(stringstream& help) const {
+ help << "Internal command.\n";
+ }
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::find);
+ out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+ }
+
+ std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
+ return parseNsFullyQualified(dbname, cmdObj);
+ }
+
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& jsobj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ std::string ns = parseNs(dbname, jsobj);
+ BSONObj keyPattern = jsobj.getObjectField("keyPattern");
+
+ if (keyPattern.isEmpty()) {
+ errmsg = "no key pattern found in checkShardingindex";
return false;
}
- } cmdMedianKey;
- class CheckShardingIndex : public Command {
- public:
- CheckShardingIndex() : Command( "checkShardingIndex" , false ) {}
- virtual bool slaveOk() const { return false; }
- virtual bool isWriteCommandForConfigServer() const { return false; }
- virtual void help( stringstream &help ) const {
- help << "Internal command.\n";
+ if (keyPattern.nFields() == 1 && str::equals("_id", keyPattern.firstElementFieldName())) {
+ result.appendBool("idskip", true);
+ return true;
+ }
+
+ BSONObj min = jsobj.getObjectField("min");
+ BSONObj max = jsobj.getObjectField("max");
+ if (min.isEmpty() != max.isEmpty()) {
+ errmsg = "either provide both min and max or leave both empty";
+ return false;
}
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::find);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
+
+ AutoGetCollectionForRead ctx(txn, ns);
+ Collection* collection = ctx.getCollection();
+ if (!collection) {
+ errmsg = "ns not found";
+ return false;
}
- std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
- return parseNsFullyQualified(dbname, cmdObj);
+ IndexDescriptor* idx =
+ collection->getIndexCatalog()->findShardKeyPrefixedIndex(txn,
+ keyPattern,
+ true); // requireSingleKey
+ if (idx == NULL) {
+ errmsg = "couldn't find valid index for shard key";
+ return false;
+ }
+ // extend min to get (min, MinKey, MinKey, ....)
+ KeyPattern kp(idx->keyPattern());
+ min = Helpers::toKeyFormat(kp.extendRangeBound(min, false));
+ if (max.isEmpty()) {
+ // if max not specified, make it (MaxKey, Maxkey, MaxKey...)
+ max = Helpers::toKeyFormat(kp.extendRangeBound(max, true));
+ } else {
+ // otherwise make it (max,MinKey,MinKey...) so that bound is non-inclusive
+ max = Helpers::toKeyFormat(kp.extendRangeBound(max, false));
}
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& jsobj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
+ unique_ptr<PlanExecutor> exec(InternalPlanner::indexScan(
+ txn, collection, idx, min, max, false, InternalPlanner::FORWARD));
+ exec->setYieldPolicy(PlanExecutor::YIELD_AUTO);
+
+ // Find the 'missingField' value used to represent a missing document field in a key of
+ // this index.
+ // NOTE A local copy of 'missingField' is made because indices may be
+ // invalidated during a db lock yield.
+ BSONObj missingFieldObj = IndexLegacy::getMissingField(txn, collection, idx->infoObj());
+ BSONElement missingField = missingFieldObj.firstElement();
+
+ // for now, the only check is that all shard keys are filled
+ // a 'missingField' valued index key is ok if the field is present in the document,
+ // TODO if $exist for nulls were picking the index, it could be used instead efficiently
+ int keyPatternLength = keyPattern.nFields();
+
+ RecordId loc;
+ BSONObj currKey;
+ while (PlanExecutor::ADVANCED == exec->getNext(&currKey, &loc)) {
+ // check that current key contains non missing elements for all fields in keyPattern
+ BSONObjIterator i(currKey);
+ for (int k = 0; k < keyPatternLength; k++) {
+ if (!i.more()) {
+ errmsg = str::stream() << "index key " << currKey << " too short for pattern "
+ << keyPattern;
+ return false;
+ }
+ BSONElement currKeyElt = i.next();
- std::string ns = parseNs(dbname, jsobj);
- BSONObj keyPattern = jsobj.getObjectField( "keyPattern" );
+ if (!currKeyElt.eoo() && !currKeyElt.valuesEqual(missingField))
+ continue;
- if ( keyPattern.isEmpty() ) {
- errmsg = "no key pattern found in checkShardingindex";
- return false;
- }
+ // This is a fetch, but it's OK. The underlying code won't throw a page fault
+ // exception.
+ BSONObj obj = collection->docFor(txn, loc).value();
+ BSONObjIterator j(keyPattern);
+ BSONElement real;
+ for (int x = 0; x <= k; x++)
+ real = j.next();
- if ( keyPattern.nFields() == 1 && str::equals( "_id" , keyPattern.firstElementFieldName() ) ) {
- result.appendBool( "idskip" , true );
- return true;
- }
+ real = obj.getFieldDotted(real.fieldName());
+
+ if (real.type())
+ continue;
- BSONObj min = jsobj.getObjectField( "min" );
- BSONObj max = jsobj.getObjectField( "max" );
- if ( min.isEmpty() != max.isEmpty() ) {
- errmsg = "either provide both min and max or leave both empty";
+ ostringstream os;
+ os << "found missing value in key " << currKey << " for doc: "
+ << (obj.hasField("_id") ? obj.toString() : obj["_id"].toString());
+ log() << "checkShardingIndex for '" << ns << "' failed: " << os.str() << endl;
+
+ errmsg = os.str();
return false;
}
+ }
+
+ return true;
+ }
+} cmdCheckShardingIndex;
+
+BSONObj prettyKey(const BSONObj& keyPattern, const BSONObj& key) {
+ return key.replaceFieldNames(keyPattern).clientReadable();
+}
+
+class SplitVector : public Command {
+public:
+ SplitVector() : Command("splitVector", false) {}
+ virtual bool slaveOk() const {
+ return false;
+ }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+ virtual void help(stringstream& help) const {
+ help << "Internal command.\n"
+ "examples:\n"
+ " { splitVector : \"blog.post\" , keyPattern:{x:1} , min:{x:10} , max:{x:20}, "
+ "maxChunkSize:200 }\n"
+ " maxChunkSize unit in MBs\n"
+ " May optionally specify 'maxSplitPoints' and 'maxChunkObjects' to avoid "
+ "traversing the whole chunk\n"
+ " \n"
+ " { splitVector : \"blog.post\" , keyPattern:{x:1} , min:{x:10} , max:{x:20}, "
+ "force: true }\n"
+ " 'force' will produce one split point even if data is small; defaults to false\n"
+ "NOTE: This command may take a while to run";
+ }
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname, cmdObj))),
+ ActionType::splitVector)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+ return Status::OK();
+ }
+ virtual std::string parseNs(const string& dbname, const BSONObj& cmdObj) const {
+ return parseNsFullyQualified(dbname, cmdObj);
+ }
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& jsobj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ //
+ // 1.a We'll parse the parameters in two steps. First, make sure the we can use the split index to get
+ // a good approximation of the size of the chunk -- without needing to access the actual data.
+ //
+
+ const std::string ns = parseNs(dbname, jsobj);
+ BSONObj keyPattern = jsobj.getObjectField("keyPattern");
+
+ if (keyPattern.isEmpty()) {
+ errmsg = "no key pattern found in splitVector";
+ return false;
+ }
+
+ // If min and max are not provided use the "minKey" and "maxKey" for the sharding key pattern.
+ BSONObj min = jsobj.getObjectField("min");
+ BSONObj max = jsobj.getObjectField("max");
+ if (min.isEmpty() != max.isEmpty()) {
+ errmsg = "either provide both min and max or leave both empty";
+ return false;
+ }
+
+ long long maxSplitPoints = 0;
+ BSONElement maxSplitPointsElem = jsobj["maxSplitPoints"];
+ if (maxSplitPointsElem.isNumber()) {
+ maxSplitPoints = maxSplitPointsElem.numberLong();
+ }
+
+ long long maxChunkObjects = Chunk::MaxObjectPerChunk;
+ BSONElement MaxChunkObjectsElem = jsobj["maxChunkObjects"];
+ if (MaxChunkObjectsElem.isNumber()) {
+ maxChunkObjects = MaxChunkObjectsElem.numberLong();
+ }
+
+ vector<BSONObj> splitKeys;
+ {
+ // Get the size estimate for this namespace
AutoGetCollectionForRead ctx(txn, ns);
Collection* collection = ctx.getCollection();
- if ( !collection ) {
+ if (!collection) {
errmsg = "ns not found";
return false;
}
- IndexDescriptor *idx =
- collection->getIndexCatalog()
- ->findShardKeyPrefixedIndex( txn,
- keyPattern,
- true ); // requireSingleKey
- if ( idx == NULL ) {
- errmsg = "couldn't find valid index for shard key";
+ // Allow multiKey based on the invariant that shard keys must be single-valued.
+ // Therefore, any multi-key index prefixed by shard key cannot be multikey over
+ // the shard key fields.
+ IndexDescriptor* idx =
+ collection->getIndexCatalog()->findShardKeyPrefixedIndex(txn, keyPattern, false);
+ if (idx == NULL) {
+ errmsg = (string) "couldn't find index over splitting key " +
+ keyPattern.clientReadable().toString();
return false;
}
// extend min to get (min, MinKey, MinKey, ....)
- KeyPattern kp( idx->keyPattern() );
- min = Helpers::toKeyFormat( kp.extendRangeBound( min, false ) );
- if ( max.isEmpty() ) {
+ KeyPattern kp(idx->keyPattern());
+ min = Helpers::toKeyFormat(kp.extendRangeBound(min, false));
+ if (max.isEmpty()) {
// if max not specified, make it (MaxKey, Maxkey, MaxKey...)
- max = Helpers::toKeyFormat( kp.extendRangeBound( max, true ) );
+ max = Helpers::toKeyFormat(kp.extendRangeBound(max, true));
} else {
// otherwise make it (max,MinKey,MinKey...) so that bound is non-inclusive
- max = Helpers::toKeyFormat( kp.extendRangeBound( max, false ) );
+ max = Helpers::toKeyFormat(kp.extendRangeBound(max, false));
}
- unique_ptr<PlanExecutor> exec(InternalPlanner::indexScan(txn, collection, idx,
- min, max, false,
- InternalPlanner::FORWARD));
- exec->setYieldPolicy(PlanExecutor::YIELD_AUTO);
+ const long long recCount = collection->numRecords(txn);
+ const long long dataSize = collection->dataSize(txn);
- // Find the 'missingField' value used to represent a missing document field in a key of
- // this index.
- // NOTE A local copy of 'missingField' is made because indices may be
- // invalidated during a db lock yield.
- BSONObj missingFieldObj = IndexLegacy::getMissingField(txn, collection, idx->infoObj());
- BSONElement missingField = missingFieldObj.firstElement();
-
- // for now, the only check is that all shard keys are filled
- // a 'missingField' valued index key is ok if the field is present in the document,
- // TODO if $exist for nulls were picking the index, it could be used instead efficiently
- int keyPatternLength = keyPattern.nFields();
-
- RecordId loc;
- BSONObj currKey;
- while (PlanExecutor::ADVANCED == exec->getNext(&currKey, &loc)) {
- //check that current key contains non missing elements for all fields in keyPattern
- BSONObjIterator i( currKey );
- for( int k = 0; k < keyPatternLength ; k++ ) {
- if( ! i.more() ) {
- errmsg = str::stream() << "index key " << currKey
- << " too short for pattern " << keyPattern;
- return false;
+ //
+ // 1.b Now that we have the size estimate, go over the remaining parameters and apply any maximum size
+ // restrictions specified there.
+ //
+
+ // 'force'-ing a split is equivalent to having maxChunkSize be the size of the current chunk, i.e., the
+ // logic below will split that chunk in half
+ long long maxChunkSize = 0;
+ bool forceMedianSplit = false;
+ {
+ BSONElement maxSizeElem = jsobj["maxChunkSize"];
+ BSONElement forceElem = jsobj["force"];
+
+ if (forceElem.trueValue()) {
+ forceMedianSplit = true;
+ // This chunk size is effectively ignored if force is true
+ maxChunkSize = dataSize;
+
+ } else if (maxSizeElem.isNumber()) {
+ maxChunkSize = maxSizeElem.numberLong() * 1 << 20;
+
+ } else {
+ maxSizeElem = jsobj["maxChunkSizeBytes"];
+ if (maxSizeElem.isNumber()) {
+ maxChunkSize = maxSizeElem.numberLong();
}
- BSONElement currKeyElt = i.next();
-
- if ( !currKeyElt.eoo() && !currKeyElt.valuesEqual( missingField ) )
- continue;
-
- // This is a fetch, but it's OK. The underlying code won't throw a page fault
- // exception.
- BSONObj obj = collection->docFor(txn, loc).value();
- BSONObjIterator j( keyPattern );
- BSONElement real;
- for ( int x=0; x <= k; x++ )
- real = j.next();
-
- real = obj.getFieldDotted( real.fieldName() );
-
- if ( real.type() )
- continue;
-
- ostringstream os;
- os << "found missing value in key " << currKey << " for doc: "
- << ( obj.hasField( "_id" ) ? obj.toString() : obj["_id"].toString() );
- log() << "checkShardingIndex for '" << ns << "' failed: " << os.str() << endl;
-
- errmsg = os.str();
+ }
+
+ // We need a maximum size for the chunk, unless we're not actually capable of finding any
+ // split points.
+ if (maxChunkSize <= 0 && recCount != 0) {
+ errmsg =
+ "need to specify the desired max chunk size (maxChunkSize or "
+ "maxChunkSizeBytes)";
return false;
}
}
- return true;
- }
- } cmdCheckShardingIndex;
- BSONObj prettyKey(const BSONObj& keyPattern, const BSONObj& key) {
- return key.replaceFieldNames(keyPattern).clientReadable();
- }
+ // If there's not enough data for more than one chunk, no point continuing.
+ if (dataSize < maxChunkSize || recCount == 0) {
+ vector<BSONObj> emptyVector;
+ result.append("splitKeys", emptyVector);
+ return true;
+ }
+
+ log() << "request split points lookup for chunk " << ns << " " << min << " -->> " << max
+ << endl;
- class SplitVector : public Command {
- public:
- SplitVector() : Command( "splitVector" , false ) {}
- virtual bool slaveOk() const { return false; }
- virtual bool isWriteCommandForConfigServer() const { return false; }
- virtual void help( stringstream &help ) const {
- help <<
- "Internal command.\n"
- "examples:\n"
- " { splitVector : \"blog.post\" , keyPattern:{x:1} , min:{x:10} , max:{x:20}, maxChunkSize:200 }\n"
- " maxChunkSize unit in MBs\n"
- " May optionally specify 'maxSplitPoints' and 'maxChunkObjects' to avoid traversing the whole chunk\n"
- " \n"
- " { splitVector : \"blog.post\" , keyPattern:{x:1} , min:{x:10} , max:{x:20}, force: true }\n"
- " 'force' will produce one split point even if data is small; defaults to false\n"
- "NOTE: This command may take a while to run";
- }
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
- ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname, cmdObj))),
- ActionType::splitVector)) {
- return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ // We'll use the average object size and number of object to find approximately how many keys
+ // each chunk should have. We'll split at half the maxChunkSize or maxChunkObjects, if
+ // provided.
+ const long long avgRecSize = dataSize / recCount;
+ long long keyCount = maxChunkSize / (2 * avgRecSize);
+ if (maxChunkObjects && (maxChunkObjects < keyCount)) {
+ log() << "limiting split vector to " << maxChunkObjects << " (from " << keyCount
+ << ") objects " << endl;
+ keyCount = maxChunkObjects;
}
- return Status::OK();
- }
- virtual std::string parseNs(const string& dbname, const BSONObj& cmdObj) const {
- return parseNsFullyQualified(dbname, cmdObj);
- }
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& jsobj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
//
- // 1.a We'll parse the parameters in two steps. First, make sure the we can use the split index to get
- // a good approximation of the size of the chunk -- without needing to access the actual data.
+ // 2. Traverse the index and add the keyCount-th key to the result vector. If that key
+ // appeared in the vector before, we omit it. The invariant here is that all the
+ // instances of a given key value live in the same chunk.
//
- const std::string ns = parseNs(dbname, jsobj);
- BSONObj keyPattern = jsobj.getObjectField( "keyPattern" );
+ Timer timer;
+ long long currCount = 0;
+ long long numChunks = 0;
- if ( keyPattern.isEmpty() ) {
- errmsg = "no key pattern found in splitVector";
- return false;
- }
+ unique_ptr<PlanExecutor> exec(InternalPlanner::indexScan(
+ txn, collection, idx, min, max, false, InternalPlanner::FORWARD));
- // If min and max are not provided use the "minKey" and "maxKey" for the sharding key pattern.
- BSONObj min = jsobj.getObjectField( "min" );
- BSONObj max = jsobj.getObjectField( "max" );
- if ( min.isEmpty() != max.isEmpty() ){
- errmsg = "either provide both min and max or leave both empty";
+ BSONObj currKey;
+ PlanExecutor::ExecState state = exec->getNext(&currKey, NULL);
+ if (PlanExecutor::ADVANCED != state) {
+ errmsg = "can't open a cursor for splitting (desired range is possibly empty)";
return false;
}
- long long maxSplitPoints = 0;
- BSONElement maxSplitPointsElem = jsobj[ "maxSplitPoints" ];
- if ( maxSplitPointsElem.isNumber() ) {
- maxSplitPoints = maxSplitPointsElem.numberLong();
- }
-
- long long maxChunkObjects = Chunk::MaxObjectPerChunk;
- BSONElement MaxChunkObjectsElem = jsobj[ "maxChunkObjects" ];
- if ( MaxChunkObjectsElem.isNumber() ) {
- maxChunkObjects = MaxChunkObjectsElem.numberLong();
- }
+ // Use every 'keyCount'-th key as a split point. We add the initial key as a sentinel, to be removed
+ // at the end. If a key appears more times than entries allowed on a chunk, we issue a warning and
+ // split on the following key.
+ set<BSONObj> tooFrequentKeys;
+ splitKeys.push_back(
+ prettyKey(idx->keyPattern(), currKey.getOwned()).extractFields(keyPattern));
- vector<BSONObj> splitKeys;
+ exec->setYieldPolicy(PlanExecutor::YIELD_AUTO);
+ while (1) {
+ while (PlanExecutor::ADVANCED == state) {
+ currCount++;
+
+ if (currCount > keyCount && !forceMedianSplit) {
+ currKey = prettyKey(idx->keyPattern(), currKey.getOwned())
+ .extractFields(keyPattern);
+ // Do not use this split key if it is the same used in the previous split point.
+ if (currKey.woCompare(splitKeys.back()) == 0) {
+ tooFrequentKeys.insert(currKey.getOwned());
+ } else {
+ splitKeys.push_back(currKey.getOwned());
+ currCount = 0;
+ numChunks++;
+ LOG(4) << "picked a split key: " << currKey << endl;
+ }
+ }
- {
- // Get the size estimate for this namespace
- AutoGetCollectionForRead ctx(txn, ns);
- Collection* collection = ctx.getCollection();
- if ( !collection ) {
- errmsg = "ns not found";
- return false;
- }
+ // Stop if we have enough split points.
+ if (maxSplitPoints && (numChunks >= maxSplitPoints)) {
+ log() << "max number of requested split points reached (" << numChunks
+ << ") before the end of chunk " << ns << " " << min << " -->> " << max
+ << endl;
+ break;
+ }
- // Allow multiKey based on the invariant that shard keys must be single-valued.
- // Therefore, any multi-key index prefixed by shard key cannot be multikey over
- // the shard key fields.
- IndexDescriptor *idx =
- collection->getIndexCatalog()->findShardKeyPrefixedIndex( txn,
- keyPattern,
- false );
- if ( idx == NULL ) {
- errmsg = (string)"couldn't find index over splitting key " +
- keyPattern.clientReadable().toString();
- return false;
- }
- // extend min to get (min, MinKey, MinKey, ....)
- KeyPattern kp( idx->keyPattern() );
- min = Helpers::toKeyFormat( kp.extendRangeBound ( min, false ) );
- if ( max.isEmpty() ) {
- // if max not specified, make it (MaxKey, Maxkey, MaxKey...)
- max = Helpers::toKeyFormat( kp.extendRangeBound( max, true ) );
- } else {
- // otherwise make it (max,MinKey,MinKey...) so that bound is non-inclusive
- max = Helpers::toKeyFormat( kp.extendRangeBound( max, false ) );
+ state = exec->getNext(&currKey, NULL);
}
- const long long recCount = collection->numRecords(txn);
- const long long dataSize = collection->dataSize(txn);
+ if (!forceMedianSplit)
+ break;
//
- // 1.b Now that we have the size estimate, go over the remaining parameters and apply any maximum size
- // restrictions specified there.
+ // If we're forcing a split at the halfway point, then the first pass was just
+ // to count the keys, and we still need a second pass.
//
-
- // 'force'-ing a split is equivalent to having maxChunkSize be the size of the current chunk, i.e., the
- // logic below will split that chunk in half
- long long maxChunkSize = 0;
- bool forceMedianSplit = false;
- {
- BSONElement maxSizeElem = jsobj[ "maxChunkSize" ];
- BSONElement forceElem = jsobj[ "force" ];
-
- if ( forceElem.trueValue() ) {
- forceMedianSplit = true;
- // This chunk size is effectively ignored if force is true
- maxChunkSize = dataSize;
-
- }
- else if ( maxSizeElem.isNumber() ) {
- maxChunkSize = maxSizeElem.numberLong() * 1<<20;
-
- }
- else {
- maxSizeElem = jsobj["maxChunkSizeBytes"];
- if ( maxSizeElem.isNumber() ) {
- maxChunkSize = maxSizeElem.numberLong();
- }
- }
- // We need a maximum size for the chunk, unless we're not actually capable of finding any
- // split points.
- if ( maxChunkSize <= 0 && recCount != 0 ) {
- errmsg = "need to specify the desired max chunk size (maxChunkSize or maxChunkSizeBytes)";
- return false;
- }
- }
-
-
- // If there's not enough data for more than one chunk, no point continuing.
- if ( dataSize < maxChunkSize || recCount == 0 ) {
- vector<BSONObj> emptyVector;
- result.append( "splitKeys" , emptyVector );
- return true;
- }
-
- log() << "request split points lookup for chunk " << ns << " " << min << " -->> " << max << endl;
-
- // We'll use the average object size and number of object to find approximately how many keys
- // each chunk should have. We'll split at half the maxChunkSize or maxChunkObjects, if
- // provided.
- const long long avgRecSize = dataSize / recCount;
- long long keyCount = maxChunkSize / (2 * avgRecSize);
- if ( maxChunkObjects && ( maxChunkObjects < keyCount ) ) {
- log() << "limiting split vector to " << maxChunkObjects << " (from " << keyCount << ") objects " << endl;
- keyCount = maxChunkObjects;
- }
-
- //
- // 2. Traverse the index and add the keyCount-th key to the result vector. If that key
- // appeared in the vector before, we omit it. The invariant here is that all the
- // instances of a given key value live in the same chunk.
- //
-
- Timer timer;
- long long currCount = 0;
- long long numChunks = 0;
-
- unique_ptr<PlanExecutor> exec(
- InternalPlanner::indexScan(txn, collection, idx, min, max,
- false, InternalPlanner::FORWARD));
-
- BSONObj currKey;
- PlanExecutor::ExecState state = exec->getNext(&currKey, NULL);
- if (PlanExecutor::ADVANCED != state) {
- errmsg = "can't open a cursor for splitting (desired range is possibly empty)";
- return false;
- }
-
- // Use every 'keyCount'-th key as a split point. We add the initial key as a sentinel, to be removed
- // at the end. If a key appears more times than entries allowed on a chunk, we issue a warning and
- // split on the following key.
- set<BSONObj> tooFrequentKeys;
- splitKeys.push_back(prettyKey(idx->keyPattern(), currKey.getOwned()).extractFields( keyPattern ) );
+ forceMedianSplit = false;
+ keyCount = currCount / 2;
+ currCount = 0;
+ log() << "splitVector doing another cycle because of force, keyCount now: "
+ << keyCount << endl;
+
+ exec.reset(InternalPlanner::indexScan(
+ txn, collection, idx, min, max, false, InternalPlanner::FORWARD));
exec->setYieldPolicy(PlanExecutor::YIELD_AUTO);
- while ( 1 ) {
- while (PlanExecutor::ADVANCED == state) {
- currCount++;
-
- if ( currCount > keyCount && !forceMedianSplit ) {
- currKey = prettyKey(idx->keyPattern(), currKey.getOwned()).extractFields(keyPattern);
- // Do not use this split key if it is the same used in the previous split point.
- if ( currKey.woCompare( splitKeys.back() ) == 0 ) {
- tooFrequentKeys.insert( currKey.getOwned() );
- }
- else {
- splitKeys.push_back( currKey.getOwned() );
- currCount = 0;
- numChunks++;
- LOG(4) << "picked a split key: " << currKey << endl;
- }
- }
+ state = exec->getNext(&currKey, NULL);
+ }
- // Stop if we have enough split points.
- if ( maxSplitPoints && ( numChunks >= maxSplitPoints ) ) {
- log() << "max number of requested split points reached (" << numChunks
- << ") before the end of chunk " << ns << " " << min << " -->> " << max
- << endl;
- break;
- }
+ //
+ // 3. Format the result and issue any warnings about the data we gathered while traversing the
+ // index
+ //
- state = exec->getNext(&currKey, NULL);
- }
-
- if ( ! forceMedianSplit )
- break;
-
- //
- // If we're forcing a split at the halfway point, then the first pass was just
- // to count the keys, and we still need a second pass.
- //
+ // Warn for keys that are more numerous than maxChunkSize allows.
+ for (set<BSONObj>::const_iterator it = tooFrequentKeys.begin();
+ it != tooFrequentKeys.end();
+ ++it) {
+ warning() << "possible low cardinality key detected in " << ns << " - key is "
+ << prettyKey(idx->keyPattern(), *it) << endl;
+ }
- forceMedianSplit = false;
- keyCount = currCount / 2;
- currCount = 0;
- log() << "splitVector doing another cycle because of force, keyCount now: " << keyCount << endl;
+ // Remove the sentinel at the beginning before returning
+ splitKeys.erase(splitKeys.begin());
- exec.reset(InternalPlanner::indexScan(txn, collection, idx, min, max,
- false, InternalPlanner::FORWARD));
+ if (timer.millis() > serverGlobalParams.slowMS) {
+ warning() << "Finding the split vector for " << ns << " over " << keyPattern
+ << " keyCount: " << keyCount << " numSplits: " << splitKeys.size()
+ << " lookedAt: " << currCount << " took " << timer.millis() << "ms"
+ << endl;
+ }
- exec->setYieldPolicy(PlanExecutor::YIELD_AUTO);
- state = exec->getNext(&currKey, NULL);
- }
+ // Warning: we are sending back an array of keys but are currently limited to
+ // 4MB work of 'result' size. This should be okay for now.
- //
- // 3. Format the result and issue any warnings about the data we gathered while traversing the
- // index
- //
-
- // Warn for keys that are more numerous than maxChunkSize allows.
- for ( set<BSONObj>::const_iterator it = tooFrequentKeys.begin(); it != tooFrequentKeys.end(); ++it ) {
- warning() << "possible low cardinality key detected in " << ns
- << " - key is " << prettyKey(idx->keyPattern(), *it ) << endl;
- }
-
- // Remove the sentinel at the beginning before returning
- splitKeys.erase( splitKeys.begin() );
-
- if (timer.millis() > serverGlobalParams.slowMS) {
- warning() << "Finding the split vector for " << ns << " over "<< keyPattern
- << " keyCount: " << keyCount << " numSplits: " << splitKeys.size()
- << " lookedAt: " << currCount << " took " << timer.millis() << "ms"
- << endl;
- }
-
- // Warning: we are sending back an array of keys but are currently limited to
- // 4MB work of 'result' size. This should be okay for now.
+ result.append("timeMillis", timer.millis());
+ }
- result.append( "timeMillis", timer.millis() );
- }
+ result.append("splitKeys", splitKeys);
+ return true;
+ }
+} cmdSplitVector;
+
+class SplitChunkCommand : public Command {
+public:
+ SplitChunkCommand() : Command("splitChunk") {}
+ virtual void help(stringstream& help) const {
+ help << "internal command usage only\n"
+ "example:\n"
+ " { splitChunk:\"db.foo\" , keyPattern: {a:1} , min : {a:100} , max: {a:200} { "
+ "splitKeys : [ {a:150} , ... ]}";
+ }
- result.append( "splitKeys" , splitKeys );
- return true;
+ virtual bool slaveOk() const {
+ return false;
+ }
+ virtual bool adminOnly() const {
+ return true;
+ }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname, cmdObj))),
+ ActionType::splitChunk)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+ return Status::OK();
+ }
+ virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
+ return parseNsFullyQualified(dbname, cmdObj);
+ }
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ //
+ // 1. check whether parameters passed to splitChunk are sound
+ //
+
+ const string ns = parseNs(dbname, cmdObj);
+ if (ns.empty()) {
+ errmsg = "need to specify namespace in command";
+ return false;
+ }
+ const BSONObj keyPattern = cmdObj["keyPattern"].Obj();
+ if (keyPattern.isEmpty()) {
+ errmsg = "need to specify the key pattern the collection is sharded over";
+ return false;
}
- } cmdSplitVector;
-
- class SplitChunkCommand : public Command {
- public:
- SplitChunkCommand() : Command( "splitChunk" ) {}
- virtual void help( stringstream& help ) const {
- help <<
- "internal command usage only\n"
- "example:\n"
- " { splitChunk:\"db.foo\" , keyPattern: {a:1} , min : {a:100} , max: {a:200} { splitKeys : [ {a:150} , ... ]}";
- }
-
- virtual bool slaveOk() const { return false; }
- virtual bool adminOnly() const { return true; }
- virtual bool isWriteCommandForConfigServer() const { return false; }
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
- ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname, cmdObj))),
- ActionType::splitChunk)) {
- return Status(ErrorCodes::Unauthorized, "Unauthorized");
- }
- return Status::OK();
+
+ const BSONObj min = cmdObj["min"].Obj();
+ if (min.isEmpty()) {
+ errmsg = "need to specify the min key for the chunk";
+ return false;
}
- virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
- return parseNsFullyQualified(dbname, cmdObj);
+
+ const BSONObj max = cmdObj["max"].Obj();
+ if (max.isEmpty()) {
+ errmsg = "need to specify the max key for the chunk";
+ return false;
}
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- //
- // 1. check whether parameters passed to splitChunk are sound
- //
+ const string shardName = cmdObj["from"].str();
+ if (shardName.empty()) {
+ errmsg = "need specify server to split chunk at";
+ return false;
+ }
- const string ns = parseNs(dbname, cmdObj);
- if ( ns.empty() ) {
- errmsg = "need to specify namespace in command";
- return false;
- }
+ const BSONObj splitKeysElem = cmdObj["splitKeys"].Obj();
+ if (splitKeysElem.isEmpty()) {
+ errmsg = "need to provide the split points to chunk over";
+ return false;
+ }
+ vector<BSONObj> splitKeys;
+ BSONObjIterator it(splitKeysElem);
+ while (it.more()) {
+ splitKeys.push_back(it.next().Obj().getOwned());
+ }
- const BSONObj keyPattern = cmdObj["keyPattern"].Obj();
- if ( keyPattern.isEmpty() ) {
- errmsg = "need to specify the key pattern the collection is sharded over";
- return false;
- }
+ //
+ // Get sharding state up-to-date
+ //
- const BSONObj min = cmdObj["min"].Obj();
- if ( min.isEmpty() ) {
- errmsg = "need to specify the min key for the chunk";
+ // This could be the first call that enables sharding - make sure we initialize the
+ // sharding state for this shard.
+ if (!shardingState.enabled()) {
+ if (cmdObj["configdb"].type() != String) {
+ errmsg = "sharding not enabled";
+ warning() << errmsg << endl;
return false;
}
+ string configdb = cmdObj["configdb"].String();
+ ShardingState::initialize(configdb);
+ }
- const BSONObj max = cmdObj["max"].Obj();
- if ( max.isEmpty() ) {
- errmsg = "need to specify the max key for the chunk";
- return false;
- }
+ // Initialize our current shard name in the shard state if needed
+ shardingState.gotShardName(shardName);
- const string shardName = cmdObj["from"].str();
- if ( shardName.empty() ) {
- errmsg = "need specify server to split chunk at";
- return false;
- }
+ ConnectionString configLoc =
+ ConnectionString::parse(shardingState.getConfigServer(), errmsg);
+ if (!configLoc.isValid()) {
+ warning() << errmsg;
+ return false;
+ }
- const BSONObj splitKeysElem = cmdObj["splitKeys"].Obj();
- if ( splitKeysElem.isEmpty() ) {
- errmsg = "need to provide the split points to chunk over";
- return false;
- }
- vector<BSONObj> splitKeys;
- BSONObjIterator it( splitKeysElem );
- while ( it.more() ) {
- splitKeys.push_back( it.next().Obj().getOwned() );
- }
+ log() << "received splitChunk request: " << cmdObj;
- //
- // Get sharding state up-to-date
- //
+ //
+ // 2. lock the collection's metadata and get highest version for the current shard
+ //
- // This could be the first call that enables sharding - make sure we initialize the
- // sharding state for this shard.
- if ( ! shardingState.enabled() ) {
- if ( cmdObj["configdb"].type() != String ) {
- errmsg = "sharding not enabled";
- warning() << errmsg << endl;
- return false;
- }
- string configdb = cmdObj["configdb"].String();
- ShardingState::initialize(configdb);
- }
+ string whyMessage(str::stream() << "splitting chunk [" << minKey << ", " << maxKey
+ << ") in " << ns);
+ auto scopedDistLock = grid.catalogManager()->getDistLockManager()->lock(ns, whyMessage);
+
+ if (!scopedDistLock.isOK()) {
+ errmsg = str::stream() << "could not acquire collection lock for " << ns
+ << " to split chunk [" << minKey << "," << maxKey << ")"
+ << causedBy(scopedDistLock.getStatus());
+ warning() << errmsg;
+ return false;
+ }
- // Initialize our current shard name in the shard state if needed
- shardingState.gotShardName(shardName);
+ // Always check our version remotely
+ ChunkVersion shardVersion;
+ Status refreshStatus = shardingState.refreshMetadataNow(txn, ns, &shardVersion);
- ConnectionString configLoc = ConnectionString::parse(shardingState.getConfigServer(),
- errmsg);
- if (!configLoc.isValid()) {
- warning() << errmsg;
- return false;
- }
+ if (!refreshStatus.isOK()) {
+ errmsg = str::stream() << "splitChunk cannot split chunk "
+ << "[" << minKey << "," << maxKey << ")"
+ << causedBy(refreshStatus.reason());
- log() << "received splitChunk request: " << cmdObj;
+ warning() << errmsg;
+ return false;
+ }
- //
- // 2. lock the collection's metadata and get highest version for the current shard
- //
+ if (shardVersion.majorVersion() == 0) {
+ // It makes no sense to split if our version is zero and we have no chunks
+ errmsg = str::stream() << "splitChunk cannot split chunk "
+ << "[" << minKey << "," << maxKey << ")"
+ << " with zero shard version";
- string whyMessage(str::stream() << "splitting chunk [" << minKey
- << ", " << maxKey
- << ") in " << ns);
- auto scopedDistLock = grid.catalogManager()->getDistLockManager()->lock(
- ns, whyMessage);
+ warning() << errmsg;
+ return false;
+ }
- if (!scopedDistLock.isOK()) {
- errmsg = str::stream() << "could not acquire collection lock for " << ns
- << " to split chunk [" << minKey << "," << maxKey << ")"
- << causedBy(scopedDistLock.getStatus());
+ // From mongos >= v3.0.
+ BSONElement epochElem(cmdObj["epoch"]);
+ if (epochElem.type() == jstOID) {
+ OID cmdEpoch = epochElem.OID();
+
+ if (cmdEpoch != shardVersion.epoch()) {
+ errmsg = str::stream() << "splitChunk cannot split chunk "
+ << "[" << minKey << "," << maxKey << "), "
+ << "collection may have been dropped. "
+ << "current epoch: " << shardVersion.epoch()
+ << ", cmd epoch: " << cmdEpoch;
warning() << errmsg;
return false;
}
+ }
- // Always check our version remotely
- ChunkVersion shardVersion;
- Status refreshStatus = shardingState.refreshMetadataNow(txn, ns, &shardVersion);
+ // Get collection metadata
+ const CollectionMetadataPtr collMetadata(shardingState.getCollectionMetadata(ns));
+ // With nonzero shard version, we must have metadata
+ invariant(NULL != collMetadata);
- if (!refreshStatus.isOK()) {
+ ChunkVersion collVersion = collMetadata->getCollVersion();
+ // With nonzero shard version, we must have a coll version >= our shard version
+ invariant(collVersion >= shardVersion);
- errmsg = str::stream() << "splitChunk cannot split chunk " << "[" << minKey << ","
- << maxKey << ")" << causedBy(refreshStatus.reason());
+ ChunkType origChunk;
+ if (!collMetadata->getNextChunk(min, &origChunk) || origChunk.getMin().woCompare(min) ||
+ origChunk.getMax().woCompare(max)) {
+ // Our boundaries are different from those passed in
+ errmsg = str::stream() << "splitChunk cannot find chunk "
+ << "[" << minKey << "," << maxKey << ")"
+ << " to split, the chunk boundaries may be stale";
- warning() << errmsg;
- return false;
- }
+ warning() << errmsg;
+ return false;
+ }
- if (shardVersion.majorVersion() == 0) {
+ log() << "splitChunk accepted at version " << shardVersion;
- // It makes no sense to split if our version is zero and we have no chunks
- errmsg = str::stream() << "splitChunk cannot split chunk " << "[" << minKey << ","
- << maxKey << ")" << " with zero shard version";
+ //
+ // 3. create the batch of updates to metadata ( the new chunks ) to be applied via 'applyOps' command
+ //
- warning() << errmsg;
- return false;
- }
+ BSONObjBuilder logDetail;
+ appendShortVersion(logDetail.subobjStart("before"), origChunk);
+ LOG(1) << "before split on " << origChunk << endl;
+ OwnedPointerVector<ChunkType> newChunks;
- // From mongos >= v3.0.
- BSONElement epochElem(cmdObj["epoch"]);
- if (epochElem.type() == jstOID) {
- OID cmdEpoch = epochElem.OID();
-
- if (cmdEpoch != shardVersion.epoch()) {
- errmsg = str::stream() << "splitChunk cannot split chunk "
- << "[" << minKey << ","
- << maxKey << "), "
- << "collection may have been dropped. "
- << "current epoch: " << shardVersion.epoch()
- << ", cmd epoch: " << cmdEpoch;
- warning() << errmsg;
- return false;
- }
- }
+ ChunkVersion nextChunkVersion = collVersion;
+ BSONObj startKey = min;
+ splitKeys.push_back(max); // makes it easier to have 'max' in the next loop. remove later.
- // Get collection metadata
- const CollectionMetadataPtr collMetadata(shardingState.getCollectionMetadata(ns));
- // With nonzero shard version, we must have metadata
- invariant(NULL != collMetadata);
+ BSONArrayBuilder updates;
- ChunkVersion collVersion = collMetadata->getCollVersion();
- // With nonzero shard version, we must have a coll version >= our shard version
- invariant(collVersion >= shardVersion);
+ for (vector<BSONObj>::const_iterator it = splitKeys.begin(); it != splitKeys.end(); ++it) {
+ BSONObj endKey = *it;
- ChunkType origChunk;
- if (!collMetadata->getNextChunk(min, &origChunk)
- || origChunk.getMin().woCompare(min) || origChunk.getMax().woCompare(max)) {
+ if (endKey.woCompare(startKey) == 0) {
+ errmsg = str::stream() << "split on the lower bound of chunk "
+ << "[" << min << ", " << max << ")"
+ << " is not allowed";
- // Our boundaries are different from those passed in
- errmsg = str::stream() << "splitChunk cannot find chunk "
- << "[" << minKey << "," << maxKey << ")"
- << " to split, the chunk boundaries may be stale";
+ warning() << errmsg << endl;
+ return false;
+ }
+ // Make sure splits don't create too-big shard keys
+ Status status = ShardKeyPattern::checkShardKeySize(endKey);
+ if (!status.isOK()) {
+ errmsg = status.reason();
warning() << errmsg;
return false;
}
- log() << "splitChunk accepted at version " << shardVersion;
+ // splits only update the 'minor' portion of version
+ nextChunkVersion.incMinor();
- //
- // 3. create the batch of updates to metadata ( the new chunks ) to be applied via 'applyOps' command
- //
+ // build an update operation against the chunks collection of the config database with
+ // upsert true
+ BSONObjBuilder op;
+ op.append("op", "u");
+ op.appendBool("b", true);
+ op.append("ns", ChunkType::ConfigNS);
- BSONObjBuilder logDetail;
- appendShortVersion(logDetail.subobjStart("before"), origChunk);
- LOG(1) << "before split on " << origChunk << endl;
- OwnedPointerVector<ChunkType> newChunks;
+ // add the modified (new) chunk information as the update object
+ BSONObjBuilder n(op.subobjStart("o"));
+ n.append(ChunkType::name(), Chunk::genID(ns, startKey));
+ nextChunkVersion.addToBSON(n, ChunkType::DEPRECATED_lastmod());
+ n.append(ChunkType::ns(), ns);
+ n.append(ChunkType::min(), startKey);
+ n.append(ChunkType::max(), endKey);
+ n.append(ChunkType::shard(), shardName);
+ n.done();
- ChunkVersion nextChunkVersion = collVersion;
- BSONObj startKey = min;
- splitKeys.push_back( max ); // makes it easier to have 'max' in the next loop. remove later.
+ // add the chunk's _id as the query part of the update statement
+ BSONObjBuilder q(op.subobjStart("o2"));
+ q.append(ChunkType::name(), Chunk::genID(ns, startKey));
+ q.done();
- BSONArrayBuilder updates;
+ updates.append(op.obj());
- for ( vector<BSONObj>::const_iterator it = splitKeys.begin(); it != splitKeys.end(); ++it ) {
- BSONObj endKey = *it;
+ // remember this chunk info for logging later
+ unique_ptr<ChunkType> chunk(new ChunkType());
+ chunk->setMin(startKey);
+ chunk->setMax(endKey);
+ chunk->setVersion(nextChunkVersion);
- if ( endKey.woCompare( startKey ) == 0) {
- errmsg = str::stream() << "split on the lower bound of chunk "
- << "[" << min << ", " << max << ")"
- << " is not allowed";
+ newChunks.push_back(chunk.release());
- warning() << errmsg << endl;
- return false;
- }
+ startKey = endKey;
+ }
- // Make sure splits don't create too-big shard keys
- Status status = ShardKeyPattern::checkShardKeySize(endKey);
- if (!status.isOK()) {
- errmsg = status.reason();
- warning() << errmsg;
- return false;
- }
+ splitKeys.pop_back(); // 'max' was used as sentinel
- // splits only update the 'minor' portion of version
- nextChunkVersion.incMinor();
-
- // build an update operation against the chunks collection of the config database with
- // upsert true
- BSONObjBuilder op;
- op.append( "op" , "u" );
- op.appendBool( "b" , true );
- op.append( "ns" , ChunkType::ConfigNS );
-
- // add the modified (new) chunk information as the update object
- BSONObjBuilder n( op.subobjStart( "o" ) );
- n.append(ChunkType::name(), Chunk::genID(ns, startKey));
- nextChunkVersion.addToBSON(n, ChunkType::DEPRECATED_lastmod());
- n.append(ChunkType::ns(), ns);
- n.append(ChunkType::min(), startKey);
- n.append(ChunkType::max(), endKey);
- n.append(ChunkType::shard(), shardName);
- n.done();
-
- // add the chunk's _id as the query part of the update statement
- BSONObjBuilder q( op.subobjStart( "o2" ) );
- q.append(ChunkType::name(), Chunk::genID(ns, startKey));
- q.done();
-
- updates.append( op.obj() );
-
- // remember this chunk info for logging later
- unique_ptr<ChunkType> chunk(new ChunkType());
- chunk->setMin(startKey);
- chunk->setMax(endKey);
- chunk->setVersion(nextChunkVersion);
-
- newChunks.push_back(chunk.release());
-
- startKey = endKey;
+ BSONArrayBuilder preCond;
+ {
+ BSONObjBuilder b;
+ b.append("ns", ChunkType::ConfigNS);
+ b.append("q",
+ BSON("query" << BSON(ChunkType::ns(ns)) << "orderby"
+ << BSON(ChunkType::DEPRECATED_lastmod() << -1)));
+ {
+ BSONObjBuilder bb(b.subobjStart("res"));
+ // TODO: For backwards compatibility, we can't yet require an epoch here
+ bb.appendTimestamp(ChunkType::DEPRECATED_lastmod(), collVersion.toLong());
+ bb.done();
}
+ preCond.append(b.obj());
+ }
- splitKeys.pop_back(); // 'max' was used as sentinel
+ //
+ // 4. apply the batch of updates to remote and local metadata
+ //
+ Status applyOpsStatus =
+ grid.catalogManager()->applyChunkOpsDeprecated(updates.arr(), preCond.arr());
+ if (!applyOpsStatus.isOK()) {
+ return appendCommandStatus(result, applyOpsStatus);
+ }
- BSONArrayBuilder preCond;
- {
- BSONObjBuilder b;
- b.append("ns", ChunkType::ConfigNS);
- b.append("q", BSON("query" << BSON(ChunkType::ns(ns)) <<
- "orderby" << BSON(ChunkType::DEPRECATED_lastmod() << -1)));
- {
- BSONObjBuilder bb( b.subobjStart( "res" ) );
- // TODO: For backwards compatibility, we can't yet require an epoch here
- bb.appendTimestamp(ChunkType::DEPRECATED_lastmod(), collVersion.toLong());
- bb.done();
- }
- preCond.append( b.obj() );
- }
+ //
+ // Install chunk metadata with knowledge about newly split chunks in this shard's state
+ //
- //
- // 4. apply the batch of updates to remote and local metadata
- //
- Status applyOpsStatus = grid.catalogManager()->applyChunkOpsDeprecated(updates.arr(),
- preCond.arr());
- if (!applyOpsStatus.isOK()) {
- return appendCommandStatus(result, applyOpsStatus);
- }
+ {
+ ScopedTransaction transaction(txn, MODE_IX);
+ Lock::DBLock writeLk(txn->lockState(), nsToDatabaseSubstring(ns), MODE_IX);
+ Lock::CollectionLock collLock(txn->lockState(), ns, MODE_X);
- //
- // Install chunk metadata with knowledge about newly split chunks in this shard's state
- //
-
- {
- ScopedTransaction transaction(txn, MODE_IX);
- Lock::DBLock writeLk(txn->lockState(), nsToDatabaseSubstring(ns), MODE_IX);
- Lock::CollectionLock collLock(txn->lockState(), ns, MODE_X);
+ // NOTE: The newShardVersion resulting from this split is higher than any
+ // other chunk version, so it's also implicitly the newCollVersion
+ ChunkVersion newShardVersion = collVersion;
- // NOTE: The newShardVersion resulting from this split is higher than any
- // other chunk version, so it's also implicitly the newCollVersion
- ChunkVersion newShardVersion = collVersion;
+ // Increment the minor version once, shardingState.splitChunk increments once
+ // per split point (resulting in the correct final shard/collection version)
+ // TODO: Revisit this interface, it's a bit clunky
+ newShardVersion.incMinor();
- // Increment the minor version once, shardingState.splitChunk increments once
- // per split point (resulting in the correct final shard/collection version)
- // TODO: Revisit this interface, it's a bit clunky
- newShardVersion.incMinor();
+ shardingState.splitChunk(txn, ns, min, max, splitKeys, newShardVersion);
+ }
- shardingState.splitChunk(txn, ns, min, max, splitKeys, newShardVersion);
- }
+ //
+ // 5. logChanges
+ //
- //
- // 5. logChanges
- //
+ // single splits are logged different than multisplits
+ if (newChunks.size() == 2) {
+ appendShortVersion(logDetail.subobjStart("left"), *newChunks[0]);
+ appendShortVersion(logDetail.subobjStart("right"), *newChunks[1]);
- // single splits are logged different than multisplits
- if ( newChunks.size() == 2 ) {
- appendShortVersion(logDetail.subobjStart("left"), *newChunks[0]);
- appendShortVersion(logDetail.subobjStart("right"), *newChunks[1]);
+ grid.catalogManager()->logChange(txn, "split", ns, logDetail.obj());
+ } else {
+ BSONObj beforeDetailObj = logDetail.obj();
+ BSONObj firstDetailObj = beforeDetailObj.getOwned();
+ const int newChunksSize = newChunks.size();
- grid.catalogManager()->logChange(txn, "split", ns, logDetail.obj());
- }
- else {
- BSONObj beforeDetailObj = logDetail.obj();
- BSONObj firstDetailObj = beforeDetailObj.getOwned();
- const int newChunksSize = newChunks.size();
-
- for ( int i=0; i < newChunksSize; i++ ) {
- BSONObjBuilder chunkDetail;
- chunkDetail.appendElements( beforeDetailObj );
- chunkDetail.append( "number", i+1 );
- chunkDetail.append( "of" , newChunksSize );
- appendShortVersion(chunkDetail.subobjStart("chunk"), *newChunks[i]);
-
- grid.catalogManager()->logChange(txn, "multi-split", ns, chunkDetail.obj());
- }
+ for (int i = 0; i < newChunksSize; i++) {
+ BSONObjBuilder chunkDetail;
+ chunkDetail.appendElements(beforeDetailObj);
+ chunkDetail.append("number", i + 1);
+ chunkDetail.append("of", newChunksSize);
+ appendShortVersion(chunkDetail.subobjStart("chunk"), *newChunks[i]);
+
+ grid.catalogManager()->logChange(txn, "multi-split", ns, chunkDetail.obj());
}
+ }
- dassert(newChunks.size() > 1);
+ dassert(newChunks.size() > 1);
- {
- // Select chunk to move out for "top chunk optimization".
- KeyPattern shardKeyPattern(collMetadata->getKeyPattern());
-
- AutoGetCollectionForRead ctx(txn, ns);
- Collection* collection = ctx.getCollection();
- if (!collection) {
- warning() << "will not perform top-chunk checking since " << ns
- << " does not exist after splitting";
- return true;
- }
+ {
+ // Select chunk to move out for "top chunk optimization".
+ KeyPattern shardKeyPattern(collMetadata->getKeyPattern());
- // Allow multiKey based on the invariant that shard keys must be
- // single-valued. Therefore, any multi-key index prefixed by shard
- // key cannot be multikey over the shard key fields.
- IndexDescriptor *idx =
- collection->getIndexCatalog()->findShardKeyPrefixedIndex(txn,
- keyPattern,
- false);
-
- if (idx == NULL) {
- return true;
- }
+ AutoGetCollectionForRead ctx(txn, ns);
+ Collection* collection = ctx.getCollection();
+ if (!collection) {
+ warning() << "will not perform top-chunk checking since " << ns
+ << " does not exist after splitting";
+ return true;
+ }
- const ChunkType* backChunk = newChunks.vector().back();
- const ChunkType* frontChunk = newChunks.vector().front();
+ // Allow multiKey based on the invariant that shard keys must be
+ // single-valued. Therefore, any multi-key index prefixed by shard
+ // key cannot be multikey over the shard key fields.
+ IndexDescriptor* idx =
+ collection->getIndexCatalog()->findShardKeyPrefixedIndex(txn, keyPattern, false);
- if (shardKeyPattern.globalMax().woCompare(backChunk->getMax()) == 0 &&
- checkIfSingleDoc(txn, collection, idx, backChunk)) {
- result.append("shouldMigrate",
- BSON("min" << backChunk->getMin()
- << "max" << backChunk->getMax()));
- }
- else if (shardKeyPattern.globalMin().woCompare(frontChunk->getMin()) == 0 &&
- checkIfSingleDoc(txn, collection, idx, frontChunk)) {
- result.append("shouldMigrate",
- BSON("min" << frontChunk->getMin()
- << "max" << frontChunk->getMax()));
- }
+ if (idx == NULL) {
+ return true;
}
- return true;
+ const ChunkType* backChunk = newChunks.vector().back();
+ const ChunkType* frontChunk = newChunks.vector().front();
+
+ if (shardKeyPattern.globalMax().woCompare(backChunk->getMax()) == 0 &&
+ checkIfSingleDoc(txn, collection, idx, backChunk)) {
+ result.append("shouldMigrate",
+ BSON("min" << backChunk->getMin() << "max" << backChunk->getMax()));
+ } else if (shardKeyPattern.globalMin().woCompare(frontChunk->getMin()) == 0 &&
+ checkIfSingleDoc(txn, collection, idx, frontChunk)) {
+ result.append("shouldMigrate",
+ BSON("min" << frontChunk->getMin() << "max" << frontChunk->getMax()));
+ }
}
- private:
+ return true;
+ }
- /**
- * Append min, max and version information from chunk to the buffer.
- */
- static void appendShortVersion(BufBuilder& b, const ChunkType& chunk) {
- BSONObjBuilder bb(b);
- bb.append(ChunkType::min(), chunk.getMin());
- bb.append(ChunkType::max(), chunk.getMax());
- if (chunk.isVersionSet())
- chunk.getVersion().addToBSON(bb, ChunkType::DEPRECATED_lastmod());
- bb.done();
- }
+private:
+ /**
+ * Append min, max and version information from chunk to the buffer.
+ */
+ static void appendShortVersion(BufBuilder& b, const ChunkType& chunk) {
+ BSONObjBuilder bb(b);
+ bb.append(ChunkType::min(), chunk.getMin());
+ bb.append(ChunkType::max(), chunk.getMax());
+ if (chunk.isVersionSet())
+ chunk.getVersion().addToBSON(bb, ChunkType::DEPRECATED_lastmod());
+ bb.done();
+ }
- static bool checkIfSingleDoc(OperationContext* txn,
- Collection* collection,
- const IndexDescriptor* idx,
- const ChunkType* chunk) {
- KeyPattern kp(idx->keyPattern());
- BSONObj newmin = Helpers::toKeyFormat(kp.extendRangeBound(chunk->getMin(), false));
- BSONObj newmax = Helpers::toKeyFormat(kp.extendRangeBound(chunk->getMax(), true));
+ static bool checkIfSingleDoc(OperationContext* txn,
+ Collection* collection,
+ const IndexDescriptor* idx,
+ const ChunkType* chunk) {
+ KeyPattern kp(idx->keyPattern());
+ BSONObj newmin = Helpers::toKeyFormat(kp.extendRangeBound(chunk->getMin(), false));
+ BSONObj newmax = Helpers::toKeyFormat(kp.extendRangeBound(chunk->getMax(), true));
- unique_ptr<PlanExecutor> exec(
- InternalPlanner::indexScan(txn, collection, idx, newmin, newmax, false));
+ unique_ptr<PlanExecutor> exec(
+ InternalPlanner::indexScan(txn, collection, idx, newmin, newmax, false));
- // check if exactly one document found
- if (PlanExecutor::ADVANCED == exec->getNext(NULL, NULL)) {
- if (PlanExecutor::IS_EOF == exec->getNext(NULL, NULL)) {
- return true;
- }
+ // check if exactly one document found
+ if (PlanExecutor::ADVANCED == exec->getNext(NULL, NULL)) {
+ if (PlanExecutor::IS_EOF == exec->getNext(NULL, NULL)) {
+ return true;
}
-
- return false;
}
- } cmdSplitChunk;
+ return false;
+ }
+
+} cmdSplitChunk;
} // namespace mongo
diff --git a/src/mongo/s/d_state.cpp b/src/mongo/s/d_state.cpp
index e7341e65093..3c7739bc804 100644
--- a/src/mongo/s/d_state.cpp
+++ b/src/mongo/s/d_state.cpp
@@ -72,1390 +72,1343 @@
namespace mongo {
- using boost::optional;
- using std::endl;
- using std::string;
- using std::stringstream;
- using std::vector;
+using boost::optional;
+using std::endl;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+namespace {
+const auto clientSCI = Client::declareDecoration<optional<ShardedConnectionInfo>>();
+} // namespace
+
+bool isMongos() {
+ return false;
+}
- namespace {
- const auto clientSCI = Client::declareDecoration<optional<ShardedConnectionInfo>>();
- } // namespace
- bool isMongos() {
- return false;
- }
+// -----ShardingState START ----
+ShardingState::ShardingState()
+ : _enabled(false),
+ _configServerTickets(3 /* max number of concurrent config server refresh threads */) {}
- // -----ShardingState START ----
+bool ShardingState::enabled() {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ return _enabled;
+}
- ShardingState::ShardingState()
- : _enabled(false),
- _configServerTickets( 3 /* max number of concurrent config server refresh threads */ ) {
- }
+string ShardingState::getConfigServer() {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ invariant(_enabled);
- bool ShardingState::enabled() {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- return _enabled;
- }
+ return grid.catalogManager()->connectionString().toString();
+}
- string ShardingState::getConfigServer() {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- invariant(_enabled);
+void ShardingState::initialize(const string& server) {
+ uassert(18509,
+ "Unable to obtain host name during sharding initialization.",
+ !getHostName().empty());
- return grid.catalogManager()->connectionString().toString();
- }
+ shardingState._initialize(server);
+}
- void ShardingState::initialize(const string& server) {
- uassert(18509,
- "Unable to obtain host name during sharding initialization.",
- !getHostName().empty());
+// TODO: Consolidate and eliminate these various ways of setting / validating shard names
+bool ShardingState::setShardName(const string& name) {
+ return setShardNameAndHost(name, "");
+}
- shardingState._initialize(server);
- }
+std::string ShardingState::getShardName() {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ return _shardName;
+}
- // TODO: Consolidate and eliminate these various ways of setting / validating shard names
- bool ShardingState::setShardName( const string& name ) {
- return setShardNameAndHost( name, "" );
- }
+bool ShardingState::setShardNameAndHost(const string& name, const string& host) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ if (_shardName.size() == 0) {
+ // TODO SERVER-2299 remotely verify the name is sound w.r.t IPs
+ _shardName = name;
- std::string ShardingState::getShardName() {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- return _shardName;
- }
+ string clientAddr = cc().clientAddress(true);
- bool ShardingState::setShardNameAndHost( const string& name, const string& host ) {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- if ( _shardName.size() == 0 ) {
- // TODO SERVER-2299 remotely verify the name is sound w.r.t IPs
- _shardName = name;
+ log() << "remote client " << clientAddr << " initialized this host "
+ << (host.empty() ? string("") : string("(") + host + ") ") << "as shard " << name;
- string clientAddr = cc().clientAddress(true);
+ return true;
+ }
- log() << "remote client " << clientAddr << " initialized this host "
- << ( host.empty() ? string( "" ) : string( "(" ) + host + ") " )
- << "as shard " << name;
+ if (_shardName == name)
+ return true;
- return true;
- }
+ string clientAddr = cc().clientAddress(true);
- if ( _shardName == name )
- return true;
+ warning() << "remote client " << clientAddr << " tried to initialize this host "
+ << (host.empty() ? string("") : string("(") + host + ") ") << "as shard " << name
+ << ", but shard name was previously initialized as " << _shardName;
- string clientAddr = cc().clientAddress(true);
+ return false;
+}
- warning() << "remote client " << clientAddr << " tried to initialize this host "
- << ( host.empty() ? string( "" ) : string( "(" ) + host + ") " )
- << "as shard " << name
- << ", but shard name was previously initialized as " << _shardName;
+void ShardingState::gotShardName(const string& name) {
+ gotShardNameAndHost(name, "");
+}
- return false;
- }
+void ShardingState::gotShardNameAndHost(const string& name, const string& host) {
+ if (setShardNameAndHost(name, host))
+ return;
- void ShardingState::gotShardName( const string& name ) {
- gotShardNameAndHost( name, "" );
- }
+ string clientAddr = cc().clientAddress(true);
+ stringstream ss;
- void ShardingState::gotShardNameAndHost( const string& name, const string& host ) {
- if ( setShardNameAndHost( name, host ) )
- return;
+ // Same error as above, to match for reporting
+ ss << "remote client " << clientAddr << " tried to initialize this host "
+ << (host.empty() ? string("") : string("(") + host + ") ") << "as shard " << name
+ << ", but shard name was previously initialized as " << _shardName;
- string clientAddr = cc().clientAddress(true);
- stringstream ss;
+ msgasserted(13298, ss.str());
+}
- // Same error as above, to match for reporting
- ss << "remote client " << clientAddr << " tried to initialize this host "
- << ( host.empty() ? string( "" ) : string( "(" ) + host + ") " )
- << "as shard " << name
- << ", but shard name was previously initialized as " << _shardName;
+void ShardingState::clearCollectionMetadata() {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _collMetadata.clear();
+}
- msgasserted( 13298 , ss.str() );
- }
+// TODO we shouldn't need three ways for checking the version. Fix this.
+bool ShardingState::hasVersion(const string& ns) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
- void ShardingState::clearCollectionMetadata() {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
- _collMetadata.clear();
- }
+ CollectionMetadataMap::const_iterator it = _collMetadata.find(ns);
+ return it != _collMetadata.end();
+}
- // TODO we shouldn't need three ways for checking the version. Fix this.
- bool ShardingState::hasVersion( const string& ns ) {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
+bool ShardingState::hasVersion(const string& ns, ChunkVersion& version) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
- CollectionMetadataMap::const_iterator it = _collMetadata.find(ns);
- return it != _collMetadata.end();
- }
+ CollectionMetadataMap::const_iterator it = _collMetadata.find(ns);
+ if (it == _collMetadata.end())
+ return false;
- bool ShardingState::hasVersion( const string& ns , ChunkVersion& version ) {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
+ CollectionMetadataPtr p = it->second;
+ version = p->getShardVersion();
+ return true;
+}
- CollectionMetadataMap::const_iterator it = _collMetadata.find(ns);
- if ( it == _collMetadata.end() )
- return false;
+ChunkVersion ShardingState::getVersion(const string& ns) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ CollectionMetadataMap::const_iterator it = _collMetadata.find(ns);
+ if (it != _collMetadata.end()) {
CollectionMetadataPtr p = it->second;
- version = p->getShardVersion();
- return true;
+ return p->getShardVersion();
+ } else {
+ return ChunkVersion(0, 0, OID());
}
+}
- ChunkVersion ShardingState::getVersion(const string& ns) {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
-
- CollectionMetadataMap::const_iterator it = _collMetadata.find( ns );
- if ( it != _collMetadata.end() ) {
- CollectionMetadataPtr p = it->second;
- return p->getShardVersion();
- }
- else {
- return ChunkVersion( 0, 0, OID() );
- }
- }
+void ShardingState::donateChunk(OperationContext* txn,
+ const string& ns,
+ const BSONObj& min,
+ const BSONObj& max,
+ ChunkVersion version) {
+ invariant(txn->lockState()->isCollectionLockedForMode(ns, MODE_X));
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+
+ CollectionMetadataMap::const_iterator it = _collMetadata.find(ns);
+ verify(it != _collMetadata.end());
+ CollectionMetadataPtr p = it->second;
+
+ // empty shards should have version 0
+ version = (p->getNumChunks() > 1) ? version : ChunkVersion(0, 0, p->getCollVersion().epoch());
+
+ ChunkType chunk;
+ chunk.setMin(min);
+ chunk.setMax(max);
+ string errMsg;
+
+ CollectionMetadataPtr cloned(p->cloneMigrate(chunk, version, &errMsg));
+ // uassert to match old behavior, TODO: report errors w/o throwing
+ uassert(16855, errMsg, NULL != cloned.get());
+
+ // TODO: a bit dangerous to have two different zero-version states - no-metadata and
+ // no-version
+ _collMetadata[ns] = cloned;
+}
- void ShardingState::donateChunk(OperationContext* txn,
+void ShardingState::undoDonateChunk(OperationContext* txn,
const string& ns,
- const BSONObj& min,
- const BSONObj& max,
- ChunkVersion version) {
+ CollectionMetadataPtr prevMetadata) {
+ invariant(txn->lockState()->isCollectionLockedForMode(ns, MODE_X));
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
- invariant(txn->lockState()->isCollectionLockedForMode(ns, MODE_X));
- stdx::lock_guard<stdx::mutex> lk( _mutex );
-
- CollectionMetadataMap::const_iterator it = _collMetadata.find( ns );
- verify( it != _collMetadata.end() ) ;
- CollectionMetadataPtr p = it->second;
+ log() << "ShardingState::undoDonateChunk acquired _mutex" << endl;
- // empty shards should have version 0
- version =
- ( p->getNumChunks() > 1 ) ?
- version : ChunkVersion( 0, 0, p->getCollVersion().epoch() );
-
- ChunkType chunk;
- chunk.setMin( min );
- chunk.setMax( max );
- string errMsg;
+ CollectionMetadataMap::iterator it = _collMetadata.find(ns);
+ verify(it != _collMetadata.end());
+ it->second = prevMetadata;
+}
- CollectionMetadataPtr cloned( p->cloneMigrate( chunk, version, &errMsg ) );
- // uassert to match old behavior, TODO: report errors w/o throwing
- uassert( 16855, errMsg, NULL != cloned.get() );
+bool ShardingState::notePending(OperationContext* txn,
+ const string& ns,
+ const BSONObj& min,
+ const BSONObj& max,
+ const OID& epoch,
+ string* errMsg) {
+ invariant(txn->lockState()->isCollectionLockedForMode(ns, MODE_X));
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+
+ CollectionMetadataMap::const_iterator it = _collMetadata.find(ns);
+ if (it == _collMetadata.end()) {
+ *errMsg = str::stream() << "could not note chunk "
+ << "[" << min << "," << max << ")"
+ << " as pending because the local metadata for " << ns
+ << " has changed";
- // TODO: a bit dangerous to have two different zero-version states - no-metadata and
- // no-version
- _collMetadata[ns] = cloned;
+ return false;
}
- void ShardingState::undoDonateChunk(OperationContext* txn,
- const string& ns,
- CollectionMetadataPtr prevMetadata) {
-
- invariant(txn->lockState()->isCollectionLockedForMode(ns, MODE_X));
- stdx::lock_guard<stdx::mutex> lk( _mutex );
+ CollectionMetadataPtr metadata = it->second;
- log() << "ShardingState::undoDonateChunk acquired _mutex" << endl;
+ // This can currently happen because drops aren't synchronized with in-migrations
+ // The idea for checking this here is that in the future we shouldn't have this problem
+ if (metadata->getCollVersion().epoch() != epoch) {
+ *errMsg = str::stream() << "could not note chunk "
+ << "[" << min << "," << max << ")"
+ << " as pending because the epoch for " << ns
+ << " has changed from " << epoch << " to "
+ << metadata->getCollVersion().epoch();
- CollectionMetadataMap::iterator it = _collMetadata.find( ns );
- verify( it != _collMetadata.end() );
- it->second = prevMetadata;
+ return false;
}
- bool ShardingState::notePending(OperationContext* txn,
- const string& ns,
- const BSONObj& min,
- const BSONObj& max,
- const OID& epoch,
- string* errMsg ) {
+ ChunkType chunk;
+ chunk.setMin(min);
+ chunk.setMax(max);
- invariant(txn->lockState()->isCollectionLockedForMode(ns, MODE_X));
- stdx::lock_guard<stdx::mutex> lk( _mutex );
+ CollectionMetadataPtr cloned(metadata->clonePlusPending(chunk, errMsg));
+ if (!cloned)
+ return false;
- CollectionMetadataMap::const_iterator it = _collMetadata.find( ns );
- if ( it == _collMetadata.end() ) {
+ _collMetadata[ns] = cloned;
+ return true;
+}
- *errMsg = str::stream() << "could not note chunk " << "[" << min << "," << max << ")"
- << " as pending because the local metadata for " << ns
- << " has changed";
+bool ShardingState::forgetPending(OperationContext* txn,
+ const string& ns,
+ const BSONObj& min,
+ const BSONObj& max,
+ const OID& epoch,
+ string* errMsg) {
+ invariant(txn->lockState()->isCollectionLockedForMode(ns, MODE_X));
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+
+ CollectionMetadataMap::const_iterator it = _collMetadata.find(ns);
+ if (it == _collMetadata.end()) {
+ *errMsg = str::stream() << "no need to forget pending chunk "
+ << "[" << min << "," << max << ")"
+ << " because the local metadata for " << ns << " has changed";
- return false;
- }
+ return false;
+ }
- CollectionMetadataPtr metadata = it->second;
+ CollectionMetadataPtr metadata = it->second;
- // This can currently happen because drops aren't synchronized with in-migrations
- // The idea for checking this here is that in the future we shouldn't have this problem
- if ( metadata->getCollVersion().epoch() != epoch ) {
+ // This can currently happen because drops aren't synchronized with in-migrations
+ // The idea for checking this here is that in the future we shouldn't have this problem
+ if (metadata->getCollVersion().epoch() != epoch) {
+ *errMsg = str::stream() << "no need to forget pending chunk "
+ << "[" << min << "," << max << ")"
+ << " because the epoch for " << ns << " has changed from " << epoch
+ << " to " << metadata->getCollVersion().epoch();
- *errMsg = str::stream() << "could not note chunk " << "[" << min << "," << max << ")"
- << " as pending because the epoch for " << ns
- << " has changed from "
- << epoch << " to " << metadata->getCollVersion().epoch();
+ return false;
+ }
- return false;
- }
+ ChunkType chunk;
+ chunk.setMin(min);
+ chunk.setMax(max);
- ChunkType chunk;
- chunk.setMin( min );
- chunk.setMax( max );
+ CollectionMetadataPtr cloned(metadata->cloneMinusPending(chunk, errMsg));
+ if (!cloned)
+ return false;
- CollectionMetadataPtr cloned( metadata->clonePlusPending( chunk, errMsg ) );
- if ( !cloned ) return false;
+ _collMetadata[ns] = cloned;
+ return true;
+}
- _collMetadata[ns] = cloned;
- return true;
- }
+void ShardingState::splitChunk(OperationContext* txn,
+ const string& ns,
+ const BSONObj& min,
+ const BSONObj& max,
+ const vector<BSONObj>& splitKeys,
+ ChunkVersion version) {
+ invariant(txn->lockState()->isCollectionLockedForMode(ns, MODE_X));
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+
+ CollectionMetadataMap::const_iterator it = _collMetadata.find(ns);
+ verify(it != _collMetadata.end());
+
+ ChunkType chunk;
+ chunk.setMin(min);
+ chunk.setMax(max);
+ string errMsg;
+
+ CollectionMetadataPtr cloned(it->second->cloneSplit(chunk, splitKeys, version, &errMsg));
+ // uassert to match old behavior, TODO: report errors w/o throwing
+ uassert(16857, errMsg, NULL != cloned.get());
+
+ _collMetadata[ns] = cloned;
+}
- bool ShardingState::forgetPending(OperationContext* txn,
- const string& ns,
- const BSONObj& min,
- const BSONObj& max,
- const OID& epoch,
- string* errMsg ) {
+void ShardingState::mergeChunks(OperationContext* txn,
+ const string& ns,
+ const BSONObj& minKey,
+ const BSONObj& maxKey,
+ ChunkVersion mergedVersion) {
+ invariant(txn->lockState()->isCollectionLockedForMode(ns, MODE_X));
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
- invariant(txn->lockState()->isCollectionLockedForMode(ns, MODE_X));
- stdx::lock_guard<stdx::mutex> lk( _mutex );
+ CollectionMetadataMap::const_iterator it = _collMetadata.find(ns);
+ verify(it != _collMetadata.end());
- CollectionMetadataMap::const_iterator it = _collMetadata.find( ns );
- if ( it == _collMetadata.end() ) {
+ string errMsg;
- *errMsg = str::stream() << "no need to forget pending chunk "
- << "[" << min << "," << max << ")"
- << " because the local metadata for " << ns << " has changed";
+ CollectionMetadataPtr cloned(it->second->cloneMerge(minKey, maxKey, mergedVersion, &errMsg));
+ // uassert to match old behavior, TODO: report errors w/o throwing
+ uassert(17004, errMsg, NULL != cloned.get());
- return false;
- }
+ _collMetadata[ns] = cloned;
+}
- CollectionMetadataPtr metadata = it->second;
+void ShardingState::resetMetadata(const string& ns) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
- // This can currently happen because drops aren't synchronized with in-migrations
- // The idea for checking this here is that in the future we shouldn't have this problem
- if ( metadata->getCollVersion().epoch() != epoch ) {
+ warning() << "resetting metadata for " << ns << ", this should only be used in testing" << endl;
- *errMsg = str::stream() << "no need to forget pending chunk "
- << "[" << min << "," << max << ")"
- << " because the epoch for " << ns << " has changed from "
- << epoch << " to " << metadata->getCollVersion().epoch();
+ _collMetadata.erase(ns);
+}
- return false;
- }
+Status ShardingState::refreshMetadataIfNeeded(OperationContext* txn,
+ const string& ns,
+ const ChunkVersion& reqShardVersion,
+ ChunkVersion* latestShardVersion) {
+ // The _configServerTickets serializes this process such that only a small number of threads
+ // can try to refresh at the same time.
- ChunkType chunk;
- chunk.setMin( min );
- chunk.setMax( max );
+ LOG(2) << "metadata refresh requested for " << ns << " at shard version " << reqShardVersion
+ << endl;
- CollectionMetadataPtr cloned( metadata->cloneMinusPending( chunk, errMsg ) );
- if ( !cloned ) return false;
+ //
+ // Queuing of refresh requests starts here when remote reload is needed. This may take time.
+ // TODO: Explicitly expose the queuing discipline.
+ //
- _collMetadata[ns] = cloned;
- return true;
- }
+ _configServerTickets.waitForTicket();
+ TicketHolderReleaser needTicketFrom(&_configServerTickets);
- void ShardingState::splitChunk(OperationContext* txn,
- const string& ns,
- const BSONObj& min,
- const BSONObj& max,
- const vector<BSONObj>& splitKeys,
- ChunkVersion version ) {
+ //
+ // Fast path - check if the requested version is at a higher version than the current
+ // metadata version or a different epoch before verifying against config server.
+ //
- invariant(txn->lockState()->isCollectionLockedForMode(ns, MODE_X));
- stdx::lock_guard<stdx::mutex> lk( _mutex );
+ CollectionMetadataPtr storedMetadata;
+ {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ CollectionMetadataMap::iterator it = _collMetadata.find(ns);
+ if (it != _collMetadata.end())
+ storedMetadata = it->second;
+ }
+ ChunkVersion storedShardVersion;
+ if (storedMetadata)
+ storedShardVersion = storedMetadata->getShardVersion();
+ *latestShardVersion = storedShardVersion;
+
+ if (storedShardVersion >= reqShardVersion &&
+ storedShardVersion.epoch() == reqShardVersion.epoch()) {
+ // Don't need to remotely reload if we're in the same epoch with a >= version
+ return Status::OK();
+ }
+
+ //
+ // Slow path - remotely reload
+ //
+ // Cases:
+ // A) Initial config load and/or secondary take-over.
+ // B) Migration TO this shard finished, notified by mongos.
+ // C) Dropping a collection, notified (currently) by mongos.
+ // D) Stale client wants to reload metadata with a different *epoch*, so we aren't sure.
+
+ if (storedShardVersion.epoch() != reqShardVersion.epoch()) {
+ // Need to remotely reload if our epochs aren't the same, to verify
+ LOG(1) << "metadata change requested for " << ns << ", from shard version "
+ << storedShardVersion << " to " << reqShardVersion
+ << ", need to verify with config server" << endl;
+ } else {
+ // Need to remotely reload since our epochs aren't the same but our version is greater
+ LOG(1) << "metadata version update requested for " << ns << ", from shard version "
+ << storedShardVersion << " to " << reqShardVersion
+ << ", need to verify with config server" << endl;
+ }
- CollectionMetadataMap::const_iterator it = _collMetadata.find( ns );
- verify( it != _collMetadata.end() ) ;
+ return doRefreshMetadata(txn, ns, reqShardVersion, true, latestShardVersion);
+}
- ChunkType chunk;
- chunk.setMin( min );
- chunk.setMax( max );
- string errMsg;
+Status ShardingState::refreshMetadataNow(OperationContext* txn,
+ const string& ns,
+ ChunkVersion* latestShardVersion) {
+ return doRefreshMetadata(txn, ns, ChunkVersion(0, 0, OID()), false, latestShardVersion);
+}
- CollectionMetadataPtr cloned( it->second->cloneSplit( chunk, splitKeys, version, &errMsg ) );
- // uassert to match old behavior, TODO: report errors w/o throwing
- uassert( 16857, errMsg, NULL != cloned.get() );
+void ShardingState::_initialize(const string& server) {
+ // Ensure only one caller at a time initializes
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
- _collMetadata[ns] = cloned;
+ if (_enabled) {
+ // TODO: Do we need to throw exception if the config servers have changed from what we
+ // already have in place? How do we test for that?
+ return;
}
- void ShardingState::mergeChunks(OperationContext* txn,
- const string& ns,
- const BSONObj& minKey,
- const BSONObj& maxKey,
- ChunkVersion mergedVersion ) {
+ ShardedConnectionInfo::addHook();
- invariant(txn->lockState()->isCollectionLockedForMode(ns, MODE_X));
- stdx::lock_guard<stdx::mutex> lk( _mutex );
+ std::string errmsg;
+ ConnectionString configServerCS = ConnectionString::parse(server, errmsg);
+ uassert(28633,
+ str::stream() << "Invalid config server connection string: " << errmsg,
+ configServerCS.isValid());
- CollectionMetadataMap::const_iterator it = _collMetadata.find( ns );
- verify( it != _collMetadata.end() );
+ auto catalogManager = stdx::make_unique<CatalogManagerLegacy>();
+ uassertStatusOK(catalogManager->init(configServerCS));
- string errMsg;
+ auto shardRegistry =
+ stdx::make_unique<ShardRegistry>(stdx::make_unique<RemoteCommandTargeterFactoryImpl>(),
+ stdx::make_unique<RemoteCommandRunnerImpl>(0),
+ std::unique_ptr<executor::TaskExecutor>{nullptr},
+ catalogManager.get());
- CollectionMetadataPtr cloned( it->second->cloneMerge( minKey,
- maxKey,
- mergedVersion,
- &errMsg ) );
- // uassert to match old behavior, TODO: report errors w/o throwing
- uassert( 17004, errMsg, NULL != cloned.get() );
+ grid.init(std::move(catalogManager), std::move(shardRegistry));
- _collMetadata[ns] = cloned;
- }
+ _enabled = true;
+}
- void ShardingState::resetMetadata( const string& ns ) {
- stdx::lock_guard<stdx::mutex> lk( _mutex );
+Status ShardingState::doRefreshMetadata(OperationContext* txn,
+ const string& ns,
+ const ChunkVersion& reqShardVersion,
+ bool useRequestedVersion,
+ ChunkVersion* latestShardVersion) {
+ // The idea here is that we're going to reload the metadata from the config server, but
+ // we need to do so outside any locks. When we get our result back, if the current metadata
+ // has changed, we may not be able to install the new metadata.
- warning() << "resetting metadata for " << ns << ", this should only be used in testing"
- << endl;
+ //
+ // Get the initial metadata
+ // No DBLock is needed since the metadata is expected to change during reload.
+ //
- _collMetadata.erase( ns );
- }
+ CollectionMetadataPtr beforeMetadata;
- Status ShardingState::refreshMetadataIfNeeded( OperationContext* txn,
- const string& ns,
- const ChunkVersion& reqShardVersion,
- ChunkVersion* latestShardVersion )
{
- // The _configServerTickets serializes this process such that only a small number of threads
- // can try to refresh at the same time.
-
- LOG( 2 ) << "metadata refresh requested for " << ns << " at shard version "
- << reqShardVersion << endl;
-
- //
- // Queuing of refresh requests starts here when remote reload is needed. This may take time.
- // TODO: Explicitly expose the queuing discipline.
- //
-
- _configServerTickets.waitForTicket();
- TicketHolderReleaser needTicketFrom( &_configServerTickets );
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
- //
- // Fast path - check if the requested version is at a higher version than the current
- // metadata version or a different epoch before verifying against config server.
- //
+ // We can't reload if sharding is not enabled - i.e. without a config server location
+ if (!_enabled) {
+ string errMsg = str::stream() << "cannot refresh metadata for " << ns
+ << " before sharding has been enabled";
- CollectionMetadataPtr storedMetadata;
- {
- stdx::lock_guard<stdx::mutex> lk( _mutex );
- CollectionMetadataMap::iterator it = _collMetadata.find( ns );
- if ( it != _collMetadata.end() ) storedMetadata = it->second;
+ warning() << errMsg;
+ return Status(ErrorCodes::NotYetInitialized, errMsg);
}
- ChunkVersion storedShardVersion;
- if ( storedMetadata ) storedShardVersion = storedMetadata->getShardVersion();
- *latestShardVersion = storedShardVersion;
- if ( storedShardVersion >= reqShardVersion &&
- storedShardVersion.epoch() == reqShardVersion.epoch() ) {
+ // We also can't reload if a shard name has not yet been set.
+ if (_shardName.empty()) {
+ string errMsg = str::stream() << "cannot refresh metadata for " << ns
+ << " before shard name has been set";
- // Don't need to remotely reload if we're in the same epoch with a >= version
- return Status::OK();
+ warning() << errMsg;
+ return Status(ErrorCodes::NotYetInitialized, errMsg);
}
- //
- // Slow path - remotely reload
- //
- // Cases:
- // A) Initial config load and/or secondary take-over.
- // B) Migration TO this shard finished, notified by mongos.
- // C) Dropping a collection, notified (currently) by mongos.
- // D) Stale client wants to reload metadata with a different *epoch*, so we aren't sure.
-
- if ( storedShardVersion.epoch() != reqShardVersion.epoch() ) {
- // Need to remotely reload if our epochs aren't the same, to verify
- LOG( 1 ) << "metadata change requested for " << ns << ", from shard version "
- << storedShardVersion << " to " << reqShardVersion
- << ", need to verify with config server" << endl;
- }
- else {
- // Need to remotely reload since our epochs aren't the same but our version is greater
- LOG( 1 ) << "metadata version update requested for " << ns
- << ", from shard version " << storedShardVersion << " to " << reqShardVersion
- << ", need to verify with config server" << endl;
+ CollectionMetadataMap::iterator it = _collMetadata.find(ns);
+ if (it != _collMetadata.end()) {
+ beforeMetadata = it->second;
}
-
- return doRefreshMetadata(txn, ns, reqShardVersion, true, latestShardVersion);
}
- Status ShardingState::refreshMetadataNow(OperationContext* txn,
- const string& ns,
- ChunkVersion* latestShardVersion) {
- return doRefreshMetadata(txn, ns, ChunkVersion(0, 0, OID()), false, latestShardVersion);
+ ChunkVersion beforeShardVersion;
+ ChunkVersion beforeCollVersion;
+ if (beforeMetadata) {
+ beforeShardVersion = beforeMetadata->getShardVersion();
+ beforeCollVersion = beforeMetadata->getCollVersion();
}
- void ShardingState::_initialize(const string& server) {
- // Ensure only one caller at a time initializes
- stdx::lock_guard<stdx::mutex> lk(_mutex);
+ *latestShardVersion = beforeShardVersion;
- if (_enabled) {
- // TODO: Do we need to throw exception if the config servers have changed from what we
- // already have in place? How do we test for that?
- return;
- }
+ //
+ // Determine whether we need to diff or fully reload
+ //
- ShardedConnectionInfo::addHook();
+ bool fullReload = false;
+ if (!beforeMetadata) {
+ // We don't have any metadata to reload from
+ fullReload = true;
+ } else if (useRequestedVersion && reqShardVersion.epoch() != beforeShardVersion.epoch()) {
+ // It's not useful to use the metadata as a base because we think the epoch will differ
+ fullReload = true;
+ }
+
+ //
+ // Load the metadata from the remote server, start construction
+ //
+
+ LOG(0) << "remotely refreshing metadata for " << ns
+ << (useRequestedVersion
+ ? string(" with requested shard version ") + reqShardVersion.toString()
+ : "")
+ << (fullReload ? ", current shard version is " : " based on current shard version ")
+ << beforeShardVersion << ", current metadata version is " << beforeCollVersion << endl;
+
+ string errMsg;
+
+ MetadataLoader mdLoader;
+ CollectionMetadata* remoteMetadataRaw = new CollectionMetadata();
+ CollectionMetadataPtr remoteMetadata(remoteMetadataRaw);
+
+ Timer refreshTimer;
+ Status status = mdLoader.makeCollectionMetadata(grid.catalogManager(),
+ ns,
+ getShardName(),
+ fullReload ? NULL : beforeMetadata.get(),
+ remoteMetadataRaw);
+ long long refreshMillis = refreshTimer.millis();
+
+ if (status.code() == ErrorCodes::NamespaceNotFound) {
+ remoteMetadata.reset();
+ remoteMetadataRaw = NULL;
+ } else if (!status.isOK()) {
+ warning() << "could not remotely refresh metadata for " << ns << causedBy(status.reason())
+ << endl;
- std::string errmsg;
- ConnectionString configServerCS = ConnectionString::parse(server, errmsg);
- uassert(28633,
- str::stream() << "Invalid config server connection string: " << errmsg,
- configServerCS.isValid());
+ return status;
+ }
- auto catalogManager = stdx::make_unique<CatalogManagerLegacy>();
- uassertStatusOK(catalogManager->init(configServerCS));
+ ChunkVersion remoteShardVersion;
+ ChunkVersion remoteCollVersion;
+ if (remoteMetadata) {
+ remoteShardVersion = remoteMetadata->getShardVersion();
+ remoteCollVersion = remoteMetadata->getCollVersion();
+ }
- auto shardRegistry = stdx::make_unique<ShardRegistry>(
- stdx::make_unique<RemoteCommandTargeterFactoryImpl>(),
- stdx::make_unique<RemoteCommandRunnerImpl>(0),
- std::unique_ptr<executor::TaskExecutor>{nullptr},
- catalogManager.get());
+ //
+ // Get ready to install loaded metadata if needed
+ //
- grid.init(std::move(catalogManager), std::move(shardRegistry));
+ CollectionMetadataPtr afterMetadata;
+ ChunkVersion afterShardVersion;
+ ChunkVersion afterCollVersion;
+ ChunkVersion::VersionChoice choice;
- _enabled = true;
- }
+ // If we choose to install the new metadata, this describes the kind of install
+ enum InstallType {
+ InstallType_New,
+ InstallType_Update,
+ InstallType_Replace,
+ InstallType_Drop,
+ InstallType_None
+ } installType = InstallType_None; // compiler complains otherwise
- Status ShardingState::doRefreshMetadata( OperationContext* txn,
- const string& ns,
- const ChunkVersion& reqShardVersion,
- bool useRequestedVersion,
- ChunkVersion* latestShardVersion )
{
- // The idea here is that we're going to reload the metadata from the config server, but
- // we need to do so outside any locks. When we get our result back, if the current metadata
- // has changed, we may not be able to install the new metadata.
+ // Exclusive collection lock needed since we're now potentially changing the metadata,
+ // and don't want reads/writes to be ongoing.
+ ScopedTransaction transaction(txn, MODE_IX);
+ Lock::DBLock dbLock(txn->lockState(), nsToDatabaseSubstring(ns), MODE_IX);
+ Lock::CollectionLock collLock(txn->lockState(), ns, MODE_X);
//
- // Get the initial metadata
- // No DBLock is needed since the metadata is expected to change during reload.
+ // Get the metadata now that the load has completed
//
- CollectionMetadataPtr beforeMetadata;
-
- {
- stdx::lock_guard<stdx::mutex> lk( _mutex );
-
- // We can't reload if sharding is not enabled - i.e. without a config server location
- if (!_enabled) {
- string errMsg = str::stream() << "cannot refresh metadata for " << ns
- << " before sharding has been enabled";
-
- warning() << errMsg;
- return Status(ErrorCodes::NotYetInitialized, errMsg);
- }
-
- // We also can't reload if a shard name has not yet been set.
- if (_shardName.empty()) {
-
- string errMsg = str::stream() << "cannot refresh metadata for " << ns
- << " before shard name has been set";
-
- warning() << errMsg;
- return Status(ErrorCodes::NotYetInitialized, errMsg);
- }
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
- CollectionMetadataMap::iterator it = _collMetadata.find(ns);
- if (it != _collMetadata.end()) {
- beforeMetadata = it->second;
- }
- }
+ // Don't reload if our config server has changed or sharding is no longer enabled
+ if (!_enabled) {
+ string errMsg = str::stream() << "could not refresh metadata for " << ns
+ << ", sharding is no longer enabled";
- ChunkVersion beforeShardVersion;
- ChunkVersion beforeCollVersion;
- if ( beforeMetadata ) {
- beforeShardVersion = beforeMetadata->getShardVersion();
- beforeCollVersion = beforeMetadata->getCollVersion();
+ warning() << errMsg;
+ return Status(ErrorCodes::NotYetInitialized, errMsg);
}
- *latestShardVersion = beforeShardVersion;
-
- //
- // Determine whether we need to diff or fully reload
- //
+ CollectionMetadataMap::iterator it = _collMetadata.find(ns);
+ if (it != _collMetadata.end())
+ afterMetadata = it->second;
- bool fullReload = false;
- if ( !beforeMetadata ) {
- // We don't have any metadata to reload from
- fullReload = true;
- }
- else if ( useRequestedVersion && reqShardVersion.epoch() != beforeShardVersion.epoch() ) {
- // It's not useful to use the metadata as a base because we think the epoch will differ
- fullReload = true;
+ if (afterMetadata) {
+ afterShardVersion = afterMetadata->getShardVersion();
+ afterCollVersion = afterMetadata->getCollVersion();
}
+ *latestShardVersion = afterShardVersion;
+
//
- // Load the metadata from the remote server, start construction
+ // Resolve newer pending chunks with the remote metadata, finish construction
//
- LOG( 0 ) << "remotely refreshing metadata for " << ns
- << ( useRequestedVersion ?
- string( " with requested shard version " ) + reqShardVersion.toString() : "" )
- << ( fullReload ?
- ", current shard version is " : " based on current shard version " )
- << beforeShardVersion
- << ", current metadata version is " << beforeCollVersion << endl;
-
- string errMsg;
-
- MetadataLoader mdLoader;
- CollectionMetadata* remoteMetadataRaw = new CollectionMetadata();
- CollectionMetadataPtr remoteMetadata( remoteMetadataRaw );
-
- Timer refreshTimer;
- Status status =
- mdLoader.makeCollectionMetadata(grid.catalogManager(),
- ns,
- getShardName(),
- fullReload ? NULL : beforeMetadata.get(),
- remoteMetadataRaw);
- long long refreshMillis = refreshTimer.millis();
-
- if ( status.code() == ErrorCodes::NamespaceNotFound ) {
- remoteMetadata.reset();
- remoteMetadataRaw = NULL;
- }
- else if ( !status.isOK() ) {
+ status = mdLoader.promotePendingChunks(afterMetadata.get(), remoteMetadataRaw);
- warning() << "could not remotely refresh metadata for " << ns
- << causedBy( status.reason() ) << endl;
+ if (!status.isOK()) {
+ warning() << "remote metadata for " << ns
+ << " is inconsistent with current pending chunks" << causedBy(status.reason())
+ << endl;
return status;
}
- ChunkVersion remoteShardVersion;
- ChunkVersion remoteCollVersion;
- if ( remoteMetadata ) {
- remoteShardVersion = remoteMetadata->getShardVersion();
- remoteCollVersion = remoteMetadata->getCollVersion();
- }
-
//
- // Get ready to install loaded metadata if needed
+ // Compare the 'before', 'after', and 'remote' versions/epochs and choose newest
+ // Zero-epochs (sentinel value for "dropped" collections), are tested by
+ // !epoch.isSet().
//
- CollectionMetadataPtr afterMetadata;
- ChunkVersion afterShardVersion;
- ChunkVersion afterCollVersion;
- ChunkVersion::VersionChoice choice;
-
- // If we choose to install the new metadata, this describes the kind of install
- enum InstallType {
- InstallType_New, InstallType_Update, InstallType_Replace, InstallType_Drop,
- InstallType_None
- } installType = InstallType_None; // compiler complains otherwise
-
- {
- // Exclusive collection lock needed since we're now potentially changing the metadata,
- // and don't want reads/writes to be ongoing.
- ScopedTransaction transaction(txn, MODE_IX);
- Lock::DBLock dbLock(txn->lockState(), nsToDatabaseSubstring(ns), MODE_IX);
- Lock::CollectionLock collLock(txn->lockState(), ns, MODE_X);
-
- //
- // Get the metadata now that the load has completed
- //
-
- stdx::lock_guard<stdx::mutex> lk( _mutex );
-
- // Don't reload if our config server has changed or sharding is no longer enabled
- if (!_enabled) {
- string errMsg = str::stream() << "could not refresh metadata for " << ns
- << ", sharding is no longer enabled";
-
- warning() << errMsg;
- return Status(ErrorCodes::NotYetInitialized, errMsg);
+ choice = ChunkVersion::chooseNewestVersion(
+ beforeCollVersion, afterCollVersion, remoteCollVersion);
+
+ if (choice == ChunkVersion::VersionChoice_Remote) {
+ dassert(!remoteCollVersion.epoch().isSet() || remoteShardVersion >= beforeShardVersion);
+
+ if (!afterCollVersion.epoch().isSet()) {
+ // First metadata load
+ installType = InstallType_New;
+ dassert(it == _collMetadata.end());
+ _collMetadata.insert(make_pair(ns, remoteMetadata));
+ } else if (remoteCollVersion.epoch().isSet() &&
+ remoteCollVersion.epoch() == afterCollVersion.epoch()) {
+ // Update to existing metadata
+ installType = InstallType_Update;
+
+ // Invariant: If CollMetadata was not found, version should be have been 0.
+ dassert(it != _collMetadata.end());
+ it->second = remoteMetadata;
+ } else if (remoteCollVersion.epoch().isSet()) {
+ // New epoch detected, replacing metadata
+ installType = InstallType_Replace;
+
+ // Invariant: If CollMetadata was not found, version should be have been 0.
+ dassert(it != _collMetadata.end());
+ it->second = remoteMetadata;
+ } else {
+ dassert(!remoteCollVersion.epoch().isSet());
+
+ // Drop detected
+ installType = InstallType_Drop;
+ _collMetadata.erase(it);
}
- CollectionMetadataMap::iterator it = _collMetadata.find( ns );
- if ( it != _collMetadata.end() ) afterMetadata = it->second;
+ *latestShardVersion = remoteShardVersion;
+ }
+ }
+ // End _mutex
+ // End DBWrite
+
+ //
+ // Do messaging based on what happened above
+ //
+ string localShardVersionMsg = beforeShardVersion.epoch() == afterShardVersion.epoch()
+ ? afterShardVersion.toString()
+ : beforeShardVersion.toString() + " / " + afterShardVersion.toString();
+
+ if (choice == ChunkVersion::VersionChoice_Unknown) {
+ string errMsg = str::stream()
+ << "need to retry loading metadata for " << ns
+ << ", collection may have been dropped or recreated during load"
+ << " (loaded shard version : " << remoteShardVersion.toString()
+ << ", stored shard versions : " << localShardVersionMsg << ", took " << refreshMillis
+ << "ms)";
+
+ warning() << errMsg;
+ return Status(ErrorCodes::RemoteChangeDetected, errMsg);
+ }
- if ( afterMetadata ) {
- afterShardVersion = afterMetadata->getShardVersion();
- afterCollVersion = afterMetadata->getCollVersion();
- }
+ if (choice == ChunkVersion::VersionChoice_Local) {
+ LOG(0) << "metadata of collection " << ns
+ << " already up to date (shard version : " << afterShardVersion.toString()
+ << ", took " << refreshMillis << "ms)" << endl;
+ return Status::OK();
+ }
- *latestShardVersion = afterShardVersion;
+ dassert(choice == ChunkVersion::VersionChoice_Remote);
- //
- // Resolve newer pending chunks with the remote metadata, finish construction
- //
+ switch (installType) {
+ case InstallType_New:
+ LOG(0) << "collection " << ns << " was previously unsharded"
+ << ", new metadata loaded with shard version " << remoteShardVersion << endl;
+ break;
+ case InstallType_Update:
+ LOG(0) << "updating metadata for " << ns << " from shard version "
+ << localShardVersionMsg << " to shard version " << remoteShardVersion << endl;
+ break;
+ case InstallType_Replace:
+ LOG(0) << "replacing metadata for " << ns << " at shard version "
+ << localShardVersionMsg << " with a new epoch (shard version "
+ << remoteShardVersion << ")" << endl;
+ break;
+ case InstallType_Drop:
+ LOG(0) << "dropping metadata for " << ns << " at shard version " << localShardVersionMsg
+ << ", took " << refreshMillis << "ms" << endl;
+ break;
+ default:
+ verify(false);
+ break;
+ }
- status = mdLoader.promotePendingChunks( afterMetadata.get(), remoteMetadataRaw );
+ if (installType != InstallType_Drop) {
+ LOG(0) << "collection version was loaded at version " << remoteCollVersion << ", took "
+ << refreshMillis << "ms" << endl;
+ }
- if ( !status.isOK() ) {
+ return Status::OK();
+}
- warning() << "remote metadata for " << ns
- << " is inconsistent with current pending chunks"
- << causedBy( status.reason() ) << endl;
+void ShardingState::appendInfo(BSONObjBuilder& builder) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
- return status;
- }
+ builder.appendBool("enabled", _enabled);
+ if (!_enabled) {
+ return;
+ }
- //
- // Compare the 'before', 'after', and 'remote' versions/epochs and choose newest
- // Zero-epochs (sentinel value for "dropped" collections), are tested by
- // !epoch.isSet().
- //
+ builder.append("configServer", grid.catalogManager()->connectionString().toString());
+ builder.append("shardName", _shardName);
- choice = ChunkVersion::chooseNewestVersion( beforeCollVersion,
- afterCollVersion,
- remoteCollVersion );
+ BSONObjBuilder versionB(builder.subobjStart("versions"));
+ for (CollectionMetadataMap::const_iterator it = _collMetadata.begin();
+ it != _collMetadata.end();
+ ++it) {
+ CollectionMetadataPtr metadata = it->second;
+ versionB.appendTimestamp(it->first, metadata->getShardVersion().toLong());
+ }
- if ( choice == ChunkVersion::VersionChoice_Remote ) {
- dassert(!remoteCollVersion.epoch().isSet() ||
- remoteShardVersion >= beforeShardVersion);
+ versionB.done();
+}
- if ( !afterCollVersion.epoch().isSet() ) {
+bool ShardingState::needCollectionMetadata(Client* client, const string& ns) const {
+ if (!_enabled)
+ return false;
- // First metadata load
- installType = InstallType_New;
- dassert( it == _collMetadata.end() );
- _collMetadata.insert( make_pair( ns, remoteMetadata ) );
- }
- else if ( remoteCollVersion.epoch().isSet() &&
- remoteCollVersion.epoch() == afterCollVersion.epoch() ) {
+ if (!ShardedConnectionInfo::get(client, false))
+ return false;
- // Update to existing metadata
- installType = InstallType_Update;
+ return true;
+}
- // Invariant: If CollMetadata was not found, version should be have been 0.
- dassert( it != _collMetadata.end() );
- it->second = remoteMetadata;
- }
- else if ( remoteCollVersion.epoch().isSet() ) {
+CollectionMetadataPtr ShardingState::getCollectionMetadata(const string& ns) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
- // New epoch detected, replacing metadata
- installType = InstallType_Replace;
+ CollectionMetadataMap::const_iterator it = _collMetadata.find(ns);
+ if (it == _collMetadata.end()) {
+ return CollectionMetadataPtr();
+ } else {
+ return it->second;
+ }
+}
- // Invariant: If CollMetadata was not found, version should be have been 0.
- dassert( it != _collMetadata.end() );
- it->second = remoteMetadata;
- }
- else {
- dassert( !remoteCollVersion.epoch().isSet() );
+ShardingState shardingState;
- // Drop detected
- installType = InstallType_Drop;
- _collMetadata.erase( it );
- }
+// -----ShardingState END ----
- *latestShardVersion = remoteShardVersion;
- }
- }
- // End _mutex
- // End DBWrite
+// -----ShardedConnectionInfo START ----
- //
- // Do messaging based on what happened above
- //
- string localShardVersionMsg =
- beforeShardVersion.epoch() == afterShardVersion.epoch() ?
- afterShardVersion.toString() :
- beforeShardVersion.toString() + " / " + afterShardVersion.toString();
+ShardedConnectionInfo::ShardedConnectionInfo() {
+ _forceVersionOk = false;
+}
- if ( choice == ChunkVersion::VersionChoice_Unknown ) {
+ShardedConnectionInfo* ShardedConnectionInfo::get(Client* client, bool create) {
+ auto& current = clientSCI(client);
- string errMsg = str::stream()
- << "need to retry loading metadata for " << ns
- << ", collection may have been dropped or recreated during load"
- << " (loaded shard version : " << remoteShardVersion.toString()
- << ", stored shard versions : " << localShardVersionMsg
- << ", took " << refreshMillis << "ms)";
+ if (!current && create) {
+ LOG(1) << "entering shard mode for connection" << endl;
+ current = boost::in_place();
+ }
- warning() << errMsg;
- return Status( ErrorCodes::RemoteChangeDetected, errMsg );
- }
+ return current ? &current.value() : nullptr;
+}
- if ( choice == ChunkVersion::VersionChoice_Local ) {
+void ShardedConnectionInfo::reset(Client* client) {
+ clientSCI(client) = boost::none;
+}
- LOG( 0 ) << "metadata of collection " << ns << " already up to date (shard version : "
- << afterShardVersion.toString() << ", took " << refreshMillis << "ms)"
- << endl;
- return Status::OK();
- }
+const ChunkVersion ShardedConnectionInfo::getVersion(const string& ns) const {
+ NSVersionMap::const_iterator it = _versions.find(ns);
+ if (it != _versions.end()) {
+ return it->second;
+ } else {
+ return ChunkVersion(0, 0, OID());
+ }
+}
- dassert( choice == ChunkVersion::VersionChoice_Remote );
+void ShardedConnectionInfo::setVersion(const string& ns, const ChunkVersion& version) {
+ _versions[ns] = version;
+}
- switch( installType ) {
- case InstallType_New:
- LOG( 0 ) << "collection " << ns << " was previously unsharded"
- << ", new metadata loaded with shard version " << remoteShardVersion
- << endl;
- break;
- case InstallType_Update:
- LOG( 0 ) << "updating metadata for " << ns << " from shard version "
- << localShardVersionMsg << " to shard version " << remoteShardVersion
- << endl;
- break;
- case InstallType_Replace:
- LOG( 0 ) << "replacing metadata for " << ns << " at shard version "
- << localShardVersionMsg << " with a new epoch (shard version "
- << remoteShardVersion << ")" << endl;
- break;
- case InstallType_Drop:
- LOG( 0 ) << "dropping metadata for " << ns << " at shard version "
- << localShardVersionMsg << ", took " << refreshMillis << "ms" << endl;
- break;
- default:
- verify( false );
- break;
- }
+void ShardedConnectionInfo::addHook() {
+ static stdx::mutex lock;
+ static bool done = false;
- if ( installType != InstallType_Drop ) {
- LOG( 0 ) << "collection version was loaded at version " << remoteCollVersion
- << ", took " << refreshMillis << "ms" << endl;
- }
+ stdx::lock_guard<stdx::mutex> lk(lock);
+ if (!done) {
+ log() << "first cluster operation detected, adding sharding hook to enable versioning "
+ "and authentication to remote servers";
- return Status::OK();
+ globalConnPool.addHook(new ShardingConnectionHook(false));
+ shardConnectionPool.addHook(new ShardingConnectionHook(true));
+ done = true;
}
+}
- void ShardingState::appendInfo(BSONObjBuilder& builder) {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
+class MongodShardCommand : public Command {
+public:
+ MongodShardCommand(const char* n) : Command(n) {}
+ virtual bool slaveOk() const {
+ return false;
+ }
+ virtual bool adminOnly() const {
+ return true;
+ }
+};
- builder.appendBool("enabled", _enabled);
- if (!_enabled) {
- return;
- }
- builder.append("configServer", grid.catalogManager()->connectionString().toString());
- builder.append("shardName", _shardName);
+bool haveLocalShardingInfo(Client* client, const string& ns) {
+ if (!shardingState.enabled())
+ return false;
- BSONObjBuilder versionB(builder.subobjStart("versions"));
- for (CollectionMetadataMap::const_iterator it = _collMetadata.begin();
- it != _collMetadata.end();
- ++it) {
+ if (!shardingState.hasVersion(ns))
+ return false;
- CollectionMetadataPtr metadata = it->second;
- versionB.appendTimestamp(it->first, metadata->getShardVersion().toLong());
- }
+ return ShardedConnectionInfo::get(client, false) != NULL;
+}
- versionB.done();
- }
+class UnsetShardingCommand : public MongodShardCommand {
+public:
+ UnsetShardingCommand() : MongodShardCommand("unsetSharding") {}
- bool ShardingState::needCollectionMetadata( Client* client, const string& ns ) const {
- if ( ! _enabled )
- return false;
+ virtual void help(stringstream& help) const {
+ help << "internal";
+ }
- if ( ! ShardedConnectionInfo::get( client, false ) )
- return false;
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+ virtual bool slaveOk() const {
return true;
}
- CollectionMetadataPtr ShardingState::getCollectionMetadata( const string& ns ) {
- stdx::lock_guard<stdx::mutex> lk( _mutex );
-
- CollectionMetadataMap::const_iterator it = _collMetadata.find( ns );
- if ( it == _collMetadata.end() ) {
- return CollectionMetadataPtr();
- }
- else {
- return it->second;
- }
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::internal);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
}
- ShardingState shardingState;
-
- // -----ShardingState END ----
-
- // -----ShardedConnectionInfo START ----
-
- ShardedConnectionInfo::ShardedConnectionInfo() {
- _forceVersionOk = false;
+ bool run(OperationContext* txn,
+ const string&,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ ShardedConnectionInfo::reset(txn->getClient());
+ return true;
}
- ShardedConnectionInfo* ShardedConnectionInfo::get( Client* client, bool create ) {
- auto& current = clientSCI(client);
+} unsetShardingCommand;
- if (!current && create) {
- LOG(1) << "entering shard mode for connection" << endl;
- current = boost::in_place();
- }
+class SetShardVersion : public MongodShardCommand {
+public:
+ SetShardVersion() : MongodShardCommand("setShardVersion") {}
- return current ? &current.value() : nullptr;
+ virtual void help(stringstream& help) const {
+ help << "internal";
}
- void ShardedConnectionInfo::reset(Client* client) {
- clientSCI(client) = boost::none;
+ virtual bool slaveOk() const {
+ return true;
}
-
- const ChunkVersion ShardedConnectionInfo::getVersion( const string& ns ) const {
- NSVersionMap::const_iterator it = _versions.find( ns );
- if ( it != _versions.end() ) {
- return it->second;
- }
- else {
- return ChunkVersion( 0, 0, OID() );
- }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
}
- void ShardedConnectionInfo::setVersion( const string& ns , const ChunkVersion& version ) {
- _versions[ns] = version;
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::internal);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
}
- void ShardedConnectionInfo::addHook() {
- static stdx::mutex lock;
- static bool done = false;
+ bool checkConfigOrInit(OperationContext* txn,
+ const string& configdb,
+ bool authoritative,
+ string& errmsg,
+ BSONObjBuilder& result,
+ bool locked = false) const {
+ if (configdb.size() == 0) {
+ errmsg = "no configdb";
+ return false;
+ }
- stdx::lock_guard<stdx::mutex> lk(lock);
- if (!done) {
- log() << "first cluster operation detected, adding sharding hook to enable versioning "
- "and authentication to remote servers";
+ if (shardingState.enabled()) {
+ if (configdb == shardingState.getConfigServer())
+ return true;
- globalConnPool.addHook(new ShardingConnectionHook(false));
- shardConnectionPool.addHook(new ShardingConnectionHook(true));
- done = true;
- }
- }
+ result.append("configdb",
+ BSON("stored" << shardingState.getConfigServer() << "given" << configdb));
- class MongodShardCommand : public Command {
- public:
- MongodShardCommand( const char * n ) : Command( n ) {
- }
- virtual bool slaveOk() const {
+ errmsg = str::stream() << "mongos specified a different config database string : "
+ << "stored : " << shardingState.getConfigServer()
+ << " vs given : " << configdb;
return false;
}
- virtual bool adminOnly() const {
- return true;
- }
- };
-
- bool haveLocalShardingInfo( Client* client, const string& ns ) {
- if ( ! shardingState.enabled() )
+ if (!authoritative) {
+ result.appendBool("need_authoritative", true);
+ errmsg = "first setShardVersion";
return false;
+ }
- if ( ! shardingState.hasVersion( ns ) )
- return false;
+ if (locked) {
+ ShardingState::initialize(configdb);
+ return true;
+ }
- return ShardedConnectionInfo::get(client, false) != NULL;
+ ScopedTransaction transaction(txn, MODE_X);
+ Lock::GlobalWrite lk(txn->lockState());
+ return checkConfigOrInit(txn, configdb, authoritative, errmsg, result, true);
}
- class UnsetShardingCommand : public MongodShardCommand {
- public:
- UnsetShardingCommand() : MongodShardCommand("unsetSharding") {}
-
- virtual void help( stringstream& help ) const {
- help << "internal";
+ bool run(OperationContext* txn,
+ const string&,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ // Compatibility error for < v3.0 mongoses still active in the cluster
+ // TODO: Remove post-3.0
+ if (!cmdObj["serverID"].eoo()) {
+ // This mongos is too old to talk to us
+ string errMsg = stream() << "v3.0 mongod is incompatible with v2.6 mongos, "
+ << "a v2.6 mongos may be running in the v3.0 cluster at "
+ << txn->getClient()->clientAddress(false);
+ error() << errMsg;
+ return appendCommandStatus(result, Status(ErrorCodes::ProtocolError, errMsg));
}
- virtual bool isWriteCommandForConfigServer() const { return false; }
+ // Steps
+ // 1. check basic config
+ // 2. extract params from command
+ // 3. fast check
+ // 4. slow check (LOCKS)
- virtual bool slaveOk() const { return true; }
+ // step 1
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::internal);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ Client* client = txn->getClient();
+ LastError::get(client).disable();
+ ShardedConnectionInfo* info = ShardedConnectionInfo::get(client, true);
+
+ bool authoritative = cmdObj.getBoolField("authoritative");
+
+ // check config server is ok or enable sharding
+ if (!checkConfigOrInit(
+ txn, cmdObj["configdb"].valuestrsafe(), authoritative, errmsg, result)) {
+ return false;
}
- bool run(OperationContext* txn,
- const string&,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- ShardedConnectionInfo::reset(txn->getClient());
- return true;
+ // check shard name is correct
+ if (cmdObj["shard"].type() == String) {
+ // The shard host is also sent when using setShardVersion, report this host if there
+ // is an error.
+ shardingState.gotShardNameAndHost(cmdObj["shard"].String(), cmdObj["shardHost"].str());
}
- } unsetShardingCommand;
+ // Handle initial shard connection
+ if (cmdObj["version"].eoo() && cmdObj["init"].trueValue()) {
+ result.append("initialized", true);
- class SetShardVersion : public MongodShardCommand {
- public:
- SetShardVersion() : MongodShardCommand("setShardVersion") {}
+ // Send back wire version to let mongos know what protocol we can speak
+ result.append("minWireVersion", minWireVersion);
+ result.append("maxWireVersion", maxWireVersion);
- virtual void help( stringstream& help ) const {
- help << "internal";
+ return true;
}
- virtual bool slaveOk() const { return true; }
- virtual bool isWriteCommandForConfigServer() const { return false; }
-
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::internal);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ string ns = cmdObj["setShardVersion"].valuestrsafe();
+ if (ns.size() == 0) {
+ errmsg = "need to specify namespace";
+ return false;
}
- bool checkConfigOrInit(OperationContext* txn,
- const string& configdb,
- bool authoritative,
- string& errmsg,
- BSONObjBuilder& result,
- bool locked = false ) const {
- if ( configdb.size() == 0 ) {
- errmsg = "no configdb";
- return false;
- }
-
- if ( shardingState.enabled() ) {
- if ( configdb == shardingState.getConfigServer() )
- return true;
-
- result.append( "configdb" , BSON( "stored" << shardingState.getConfigServer() <<
- "given" << configdb ) );
-
- errmsg = str::stream() << "mongos specified a different config database string : "
- << "stored : " << shardingState.getConfigServer()
- << " vs given : " << configdb;
- return false;
- }
-
- if ( ! authoritative ) {
- result.appendBool( "need_authoritative" , true );
- errmsg = "first setShardVersion";
- return false;
- }
-
- if ( locked ) {
- ShardingState::initialize(configdb);
- return true;
- }
- ScopedTransaction transaction(txn, MODE_X);
- Lock::GlobalWrite lk(txn->lockState());
- return checkConfigOrInit(txn, configdb, authoritative, errmsg, result, true);
+ // we can run on a slave up to here
+ if (!repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(
+ nsToDatabase(ns))) {
+ result.append("errmsg", "not master");
+ result.append("note", "from post init in setShardVersion");
+ return false;
}
- bool run(OperationContext* txn,
- const string&,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
-
- // Compatibility error for < v3.0 mongoses still active in the cluster
- // TODO: Remove post-3.0
- if (!cmdObj["serverID"].eoo()) {
-
- // This mongos is too old to talk to us
- string errMsg = stream() << "v3.0 mongod is incompatible with v2.6 mongos, "
- << "a v2.6 mongos may be running in the v3.0 cluster at "
- << txn->getClient()->clientAddress(false);
- error() << errMsg;
- return appendCommandStatus(result, Status(ErrorCodes::ProtocolError, errMsg));
- }
+ // step 2
+ if (!ChunkVersion::canParseBSON(cmdObj, "version")) {
+ errmsg = "need to specify version";
+ return false;
+ }
- // Steps
- // 1. check basic config
- // 2. extract params from command
- // 3. fast check
- // 4. slow check (LOCKS)
-
- // step 1
-
- Client* client = txn->getClient();
- LastError::get(client).disable();
- ShardedConnectionInfo* info = ShardedConnectionInfo::get( client, true );
-
- bool authoritative = cmdObj.getBoolField( "authoritative" );
-
- // check config server is ok or enable sharding
- if (!checkConfigOrInit(
- txn, cmdObj["configdb"].valuestrsafe(), authoritative, errmsg, result)) {
- return false;
- }
+ const ChunkVersion version = ChunkVersion::fromBSON(cmdObj, "version");
- // check shard name is correct
- if ( cmdObj["shard"].type() == String ) {
- // The shard host is also sent when using setShardVersion, report this host if there
- // is an error.
- shardingState.gotShardNameAndHost( cmdObj["shard"].String(),
- cmdObj["shardHost"].str() );
- }
-
- // Handle initial shard connection
- if( cmdObj["version"].eoo() && cmdObj["init"].trueValue() ){
+ // step 3
- result.append( "initialized", true );
+ const ChunkVersion oldVersion = info->getVersion(ns);
+ const ChunkVersion globalVersion = shardingState.getVersion(ns);
- // Send back wire version to let mongos know what protocol we can speak
- result.append( "minWireVersion", minWireVersion );
- result.append( "maxWireVersion", maxWireVersion );
+ oldVersion.addToBSON(result, "oldVersion");
- return true;
+ if (version.isWriteCompatibleWith(globalVersion)) {
+ // mongos and mongod agree!
+ if (!oldVersion.isWriteCompatibleWith(version)) {
+ if (oldVersion < globalVersion && oldVersion.hasEqualEpoch(globalVersion)) {
+ info->setVersion(ns, version);
+ } else if (authoritative) {
+ // this means there was a drop and our version is reset
+ info->setVersion(ns, version);
+ } else {
+ result.append("ns", ns);
+ result.appendBool("need_authoritative", true);
+ errmsg = "verifying drop on '" + ns + "'";
+ return false;
+ }
}
- string ns = cmdObj["setShardVersion"].valuestrsafe();
- if ( ns.size() == 0 ) {
- errmsg = "need to specify namespace";
- return false;
- }
+ return true;
+ }
+ // step 4
+ // Cases below all either return OR fall-through to remote metadata reload.
+ const bool isDropRequested = !version.isSet() && globalVersion.isSet();
- // we can run on a slave up to here
- if (!repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(
- nsToDatabase(ns))) {
- result.append( "errmsg" , "not master" );
- result.append( "note" , "from post init in setShardVersion" );
+ if (isDropRequested) {
+ if (!authoritative) {
+ result.appendBool("need_authoritative", true);
+ result.append("ns", ns);
+ globalVersion.addToBSON(result, "globalVersion");
+ errmsg = "dropping needs to be authoritative";
return false;
}
- // step 2
- if( ! ChunkVersion::canParseBSON( cmdObj, "version" ) ){
- errmsg = "need to specify version";
+ // Fall through to metadata reload below
+ } else {
+ // Not Dropping
+
+ // TODO: Refactor all of this
+ if (version < oldVersion && version.hasEqualEpoch(oldVersion)) {
+ errmsg = str::stream() << "this connection already had a newer version "
+ << "of collection '" << ns << "'";
+ result.append("ns", ns);
+ version.addToBSON(result, "newVersion");
+ globalVersion.addToBSON(result, "globalVersion");
return false;
}
- const ChunkVersion version = ChunkVersion::fromBSON( cmdObj, "version" );
-
- // step 3
-
- const ChunkVersion oldVersion = info->getVersion(ns);
- const ChunkVersion globalVersion = shardingState.getVersion(ns);
-
- oldVersion.addToBSON( result, "oldVersion" );
-
- if ( version.isWriteCompatibleWith( globalVersion )) {
- // mongos and mongod agree!
- if ( !oldVersion.isWriteCompatibleWith( version )) {
- if ( oldVersion < globalVersion &&
- oldVersion.hasEqualEpoch( globalVersion )) {
- info->setVersion( ns, version );
- }
- else if ( authoritative ) {
- // this means there was a drop and our version is reset
- info->setVersion( ns, version );
- }
- else {
- result.append( "ns", ns );
- result.appendBool( "need_authoritative", true );
- errmsg = "verifying drop on '" + ns + "'";
- return false;
- }
- }
-
- return true;
- }
-
- // step 4
- // Cases below all either return OR fall-through to remote metadata reload.
- const bool isDropRequested = !version.isSet() && globalVersion.isSet();
-
- if (isDropRequested) {
- if ( ! authoritative ) {
- result.appendBool( "need_authoritative" , true );
- result.append( "ns" , ns );
- globalVersion.addToBSON( result, "globalVersion" );
- errmsg = "dropping needs to be authoritative";
- return false;
+ // TODO: Refactor all of this
+ if (version < globalVersion && version.hasEqualEpoch(globalVersion)) {
+ while (shardingState.inCriticalMigrateSection()) {
+ log() << "waiting till out of critical section" << endl;
+ shardingState.waitTillNotInCriticalSection(10);
}
-
- // Fall through to metadata reload below
+ errmsg = str::stream() << "shard global version for collection is higher "
+ << "than trying to set to '" << ns << "'";
+ result.append("ns", ns);
+ version.addToBSON(result, "version");
+ globalVersion.addToBSON(result, "globalVersion");
+ result.appendBool("reloadConfig", true);
+ return false;
}
- else {
- // Not Dropping
-
- // TODO: Refactor all of this
- if ( version < oldVersion && version.hasEqualEpoch( oldVersion ) ) {
- errmsg = str::stream() << "this connection already had a newer version "
- << "of collection '" << ns << "'";
- result.append( "ns" , ns );
- version.addToBSON( result, "newVersion" );
- globalVersion.addToBSON( result, "globalVersion" );
- return false;
- }
-
- // TODO: Refactor all of this
- if ( version < globalVersion && version.hasEqualEpoch( globalVersion ) ) {
- while ( shardingState.inCriticalMigrateSection() ) {
- log() << "waiting till out of critical section" << endl;
- shardingState.waitTillNotInCriticalSection( 10 );
- }
- errmsg = str::stream() << "shard global version for collection is higher "
- << "than trying to set to '" << ns << "'";
- result.append( "ns" , ns );
- version.addToBSON( result, "version" );
- globalVersion.addToBSON( result, "globalVersion" );
- result.appendBool( "reloadConfig" , true );
- return false;
- }
- if ( ! globalVersion.isSet() && ! authoritative ) {
- // Needed b/c when the last chunk is moved off a shard,
- // the version gets reset to zero, which should require a reload.
- while ( shardingState.inCriticalMigrateSection() ) {
- log() << "waiting till out of critical section" << endl;
- shardingState.waitTillNotInCriticalSection( 10 );
- }
-
- // need authoritative for first look
- result.append( "ns" , ns );
- result.appendBool( "need_authoritative" , true );
- errmsg = "first time for collection '" + ns + "'";
- return false;
+ if (!globalVersion.isSet() && !authoritative) {
+ // Needed b/c when the last chunk is moved off a shard,
+ // the version gets reset to zero, which should require a reload.
+ while (shardingState.inCriticalMigrateSection()) {
+ log() << "waiting till out of critical section" << endl;
+ shardingState.waitTillNotInCriticalSection(10);
}
- // Fall through to metadata reload below
+ // need authoritative for first look
+ result.append("ns", ns);
+ result.appendBool("need_authoritative", true);
+ errmsg = "first time for collection '" + ns + "'";
+ return false;
}
- ChunkVersion currVersion;
- Status status = shardingState.refreshMetadataIfNeeded(txn, ns, version, &currVersion);
+ // Fall through to metadata reload below
+ }
- if (!status.isOK()) {
+ ChunkVersion currVersion;
+ Status status = shardingState.refreshMetadataIfNeeded(txn, ns, version, &currVersion);
- // The reload itself was interrupted or confused here
+ if (!status.isOK()) {
+ // The reload itself was interrupted or confused here
- errmsg = str::stream() << "could not refresh metadata for " << ns
- << " with requested shard version " << version.toString()
- << ", stored shard version is " << currVersion.toString()
- << causedBy( status.reason() );
+ errmsg = str::stream() << "could not refresh metadata for " << ns
+ << " with requested shard version " << version.toString()
+ << ", stored shard version is " << currVersion.toString()
+ << causedBy(status.reason());
- warning() << errmsg << endl;
+ warning() << errmsg << endl;
- result.append( "ns" , ns );
- version.addToBSON( result, "version" );
- currVersion.addToBSON( result, "globalVersion" );
- result.appendBool( "reloadConfig", true );
+ result.append("ns", ns);
+ version.addToBSON(result, "version");
+ currVersion.addToBSON(result, "globalVersion");
+ result.appendBool("reloadConfig", true);
- return false;
+ return false;
+ } else if (!version.isWriteCompatibleWith(currVersion)) {
+ // We reloaded a version that doesn't match the version mongos was trying to
+ // set.
+
+ errmsg = str::stream() << "requested shard version differs from"
+ << " config shard version for " << ns
+ << ", requested version is " << version.toString()
+ << " but found version " << currVersion.toString();
+
+ OCCASIONALLY warning() << errmsg << endl;
+
+ // WARNING: the exact fields below are important for compatibility with mongos
+ // version reload.
+
+ result.append("ns", ns);
+ currVersion.addToBSON(result, "globalVersion");
+
+ // If this was a reset of a collection or the last chunk moved out, inform mongos to
+ // do a full reload.
+ if (currVersion.epoch() != version.epoch() || !currVersion.isSet()) {
+ result.appendBool("reloadConfig", true);
+ // Zero-version also needed to trigger full mongos reload, sadly
+ // TODO: Make this saner, and less impactful (full reload on last chunk is bad)
+ ChunkVersion(0, 0, OID()).addToBSON(result, "version");
+ // For debugging
+ version.addToBSON(result, "origVersion");
+ } else {
+ version.addToBSON(result, "version");
}
- else if ( !version.isWriteCompatibleWith( currVersion ) ) {
-
- // We reloaded a version that doesn't match the version mongos was trying to
- // set.
- errmsg = str::stream() << "requested shard version differs from"
- << " config shard version for " << ns
- << ", requested version is " << version.toString()
- << " but found version " << currVersion.toString();
+ return false;
+ }
- OCCASIONALLY warning() << errmsg << endl;
+ info->setVersion(ns, version);
+ return true;
+ }
- // WARNING: the exact fields below are important for compatibility with mongos
- // version reload.
+} setShardVersionCmd;
- result.append( "ns" , ns );
- currVersion.addToBSON( result, "globalVersion" );
+class GetShardVersion : public MongodShardCommand {
+public:
+ GetShardVersion() : MongodShardCommand("getShardVersion") {}
- // If this was a reset of a collection or the last chunk moved out, inform mongos to
- // do a full reload.
- if (currVersion.epoch() != version.epoch() || !currVersion.isSet() ) {
- result.appendBool( "reloadConfig", true );
- // Zero-version also needed to trigger full mongos reload, sadly
- // TODO: Make this saner, and less impactful (full reload on last chunk is bad)
- ChunkVersion( 0, 0, OID() ).addToBSON( result, "version" );
- // For debugging
- version.addToBSON( result, "origVersion" );
- }
- else {
- version.addToBSON( result, "version" );
- }
+ virtual void help(stringstream& help) const {
+ help << " example: { getShardVersion : 'alleyinsider.foo' } ";
+ }
- return false;
- }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
- info->setVersion( ns , version );
- return true;
+ virtual Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname, cmdObj))),
+ ActionType::getShardVersion)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
}
+ return Status::OK();
+ }
+ virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
+ return parseNsFullyQualified(dbname, cmdObj);
+ }
- } setShardVersionCmd;
-
- class GetShardVersion : public MongodShardCommand {
- public:
- GetShardVersion() : MongodShardCommand("getShardVersion") {}
+ bool run(OperationContext* txn,
+ const string&,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ const string ns = cmdObj["getShardVersion"].valuestrsafe();
+ if (ns.size() == 0) {
+ errmsg = "need to specify full namespace";
+ return false;
+ }
- virtual void help( stringstream& help ) const {
- help << " example: { getShardVersion : 'alleyinsider.foo' } ";
+ if (shardingState.enabled()) {
+ result.append("configServer", shardingState.getConfigServer());
+ } else {
+ result.append("configServer", "");
}
- virtual bool isWriteCommandForConfigServer() const { return false; }
+ result.appendTimestamp("global", shardingState.getVersion(ns).toLong());
- virtual Status checkAuthForCommand(ClientBasic* client,
- const std::string& dbname,
- const BSONObj& cmdObj) {
- if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
- ResourcePattern::forExactNamespace(NamespaceString(parseNs(dbname, cmdObj))),
- ActionType::getShardVersion)) {
- return Status(ErrorCodes::Unauthorized, "Unauthorized");
- }
- return Status::OK();
- }
- virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
- return parseNsFullyQualified(dbname, cmdObj);
+ ShardedConnectionInfo* const info = ShardedConnectionInfo::get(txn->getClient(), false);
+ result.appendBool("inShardedMode", info != NULL);
+ if (info) {
+ result.appendTimestamp("mine", info->getVersion(ns).toLong());
+ } else {
+ result.appendTimestamp("mine", 0);
}
- bool run(OperationContext* txn,
- const string&,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- const string ns = cmdObj["getShardVersion"].valuestrsafe();
- if (ns.size() == 0) {
- errmsg = "need to specify full namespace";
- return false;
- }
-
- if (shardingState.enabled()) {
- result.append("configServer", shardingState.getConfigServer());
- }
- else {
- result.append("configServer", "");
+ if (cmdObj["fullMetadata"].trueValue()) {
+ CollectionMetadataPtr metadata = shardingState.getCollectionMetadata(ns);
+ if (metadata) {
+ result.append("metadata", metadata->toBSON());
+ } else {
+ result.append("metadata", BSONObj());
}
+ }
- result.appendTimestamp("global", shardingState.getVersion(ns).toLong());
+ return true;
+ }
- ShardedConnectionInfo* const info = ShardedConnectionInfo::get(txn->getClient(), false);
- result.appendBool("inShardedMode", info != NULL);
- if (info) {
- result.appendTimestamp("mine", info->getVersion(ns).toLong());
- }
- else {
- result.appendTimestamp("mine", 0);
- }
+} getShardVersion;
- if (cmdObj["fullMetadata"].trueValue()) {
- CollectionMetadataPtr metadata = shardingState.getCollectionMetadata(ns);
- if (metadata) {
- result.append("metadata", metadata->toBSON());
- }
- else {
- result.append("metadata", BSONObj());
- }
- }
+class ShardingStateCmd : public MongodShardCommand {
+public:
+ ShardingStateCmd() : MongodShardCommand("shardingState") {}
- return true;
- }
+ virtual bool isWriteCommandForConfigServer() const {
+ return true;
+ }
- } getShardVersion;
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ ActionSet actions;
+ actions.addAction(ActionType::shardingState);
+ out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
+ }
- class ShardingStateCmd : public MongodShardCommand {
- public:
- ShardingStateCmd() : MongodShardCommand( "shardingState" ) {}
+ bool run(OperationContext* txn,
+ const string& dbname,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ ScopedTransaction transaction(txn, MODE_IX);
+ Lock::DBLock dbXLock(txn->lockState(), dbname, MODE_X);
+ OldClientContext ctx(txn, dbname);
+
+ shardingState.appendInfo(result);
+ return true;
+ }
- virtual bool isWriteCommandForConfigServer() const { return true; }
+} shardingStateCmd;
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {
- ActionSet actions;
- actions.addAction(ActionType::shardingState);
- out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
- }
+/**
+ * @ return true if not in sharded mode
+ or if version for this client is ok
+ */
+static bool shardVersionOk(Client* client,
+ const string& ns,
+ string& errmsg,
+ ChunkVersion& received,
+ ChunkVersion& wanted) {
+ if (!shardingState.enabled())
+ return true;
- bool run(OperationContext* txn,
- const string& dbname,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
- ScopedTransaction transaction(txn, MODE_IX);
- Lock::DBLock dbXLock(txn->lockState(), dbname, MODE_X);
- OldClientContext ctx(txn, dbname);
-
- shardingState.appendInfo( result );
- return true;
- }
+ if (!repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(nsToDatabase(ns))) {
+ // right now connections to secondaries aren't versioned at all
+ return true;
+ }
- } shardingStateCmd;
+ ShardedConnectionInfo* info = ShardedConnectionInfo::get(client, false);
- /**
- * @ return true if not in sharded mode
- or if version for this client is ok
- */
- static bool shardVersionOk(Client* client,
- const string& ns,
- string& errmsg,
- ChunkVersion& received,
- ChunkVersion& wanted) {
+ if (!info) {
+ // this means the client has nothing sharded
+ // so this allows direct connections to do whatever they want
+ // which i think is the correct behavior
+ return true;
+ }
- if ( ! shardingState.enabled() )
- return true;
+ if (info->inForceVersionOkMode()) {
+ return true;
+ }
- if (!repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(
- nsToDatabase(ns))) {
- // right now connections to secondaries aren't versioned at all
- return true;
- }
+ // TODO : all collections at some point, be sharded or not, will have a version
+ // (and a CollectionMetadata)
+ received = info->getVersion(ns);
- ShardedConnectionInfo* info = ShardedConnectionInfo::get( client, false );
+ if (ChunkVersion::isIgnoredVersion(received)) {
+ return true;
+ }
- if ( ! info ) {
- // this means the client has nothing sharded
- // so this allows direct connections to do whatever they want
- // which i think is the correct behavior
- return true;
- }
+ wanted = shardingState.getVersion(ns);
- if ( info->inForceVersionOkMode() ) {
- return true;
- }
+ if (received.isWriteCompatibleWith(wanted))
+ return true;
- // TODO : all collections at some point, be sharded or not, will have a version
- // (and a CollectionMetadata)
- received = info->getVersion( ns );
+ //
+ // Figure out exactly why not compatible, send appropriate error message
+ // The versions themselves are returned in the error, so not needed in messages here
+ //
- if (ChunkVersion::isIgnoredVersion(received)) {
- return true;
- }
+ // Check epoch first, to send more meaningful message, since other parameters probably
+ // won't match either
+ if (!wanted.hasEqualEpoch(received)) {
+ errmsg = str::stream() << "version epoch mismatch detected for " << ns << ", "
+ << "the collection may have been dropped and recreated";
+ return false;
+ }
- wanted = shardingState.getVersion( ns );
+ if (!wanted.isSet() && received.isSet()) {
+ errmsg = str::stream() << "this shard no longer contains chunks for " << ns << ", "
+ << "the collection may have been dropped";
+ return false;
+ }
- if( received.isWriteCompatibleWith( wanted ) ) return true;
+ if (wanted.isSet() && !received.isSet()) {
+ errmsg = str::stream() << "this shard contains versioned chunks for " << ns << ", "
+ << "but no version set in request";
+ return false;
+ }
+ if (wanted.majorVersion() != received.majorVersion()) {
//
- // Figure out exactly why not compatible, send appropriate error message
- // The versions themselves are returned in the error, so not needed in messages here
+ // Could be > or < - wanted is > if this is the source of a migration,
+ // wanted < if this is the target of a migration
//
- // Check epoch first, to send more meaningful message, since other parameters probably
- // won't match either
- if( ! wanted.hasEqualEpoch( received ) ){
- errmsg = str::stream() << "version epoch mismatch detected for " << ns << ", "
- << "the collection may have been dropped and recreated";
- return false;
- }
-
- if( ! wanted.isSet() && received.isSet() ){
- errmsg = str::stream() << "this shard no longer contains chunks for " << ns << ", "
- << "the collection may have been dropped";
- return false;
- }
-
- if( wanted.isSet() && ! received.isSet() ){
- errmsg = str::stream() << "this shard contains versioned chunks for " << ns << ", "
- << "but no version set in request";
- return false;
- }
-
- if( wanted.majorVersion() != received.majorVersion() ){
-
- //
- // Could be > or < - wanted is > if this is the source of a migration,
- // wanted < if this is the target of a migration
- //
-
- errmsg = str::stream() << "version mismatch detected for " << ns << ", "
- << "stored major version " << wanted.majorVersion()
- << " does not match received " << received.majorVersion();
- return false;
- }
-
- // Those are all the reasons the versions can mismatch
- verify( false );
-
+ errmsg = str::stream() << "version mismatch detected for " << ns << ", "
+ << "stored major version " << wanted.majorVersion()
+ << " does not match received " << received.majorVersion();
return false;
-
}
- void ensureShardVersionOKOrThrow(Client* client, const std::string& ns) {
- string errmsg;
- ChunkVersion received;
- ChunkVersion wanted;
- if (!shardVersionOk(client, ns, errmsg, received, wanted)) {
- StringBuilder sb;
- sb << "[" << ns << "] shard version not ok: " << errmsg;
- throw SendStaleConfigException(ns, sb.str(), received, wanted);
- }
- }
+ // Those are all the reasons the versions can mismatch
+ verify(false);
- void usingAShardConnection( const string& addr ) {
- }
+ return false;
+}
- void saveGLEStats(const BSONObj& result, StringData hostString) {
- // Declared in cluster_last_error_info.h.
- //
- // This can be called in mongod, which is unfortunate. To fix this,
- // we can redesign how connection pooling works on mongod for sharded operations.
+void ensureShardVersionOKOrThrow(Client* client, const std::string& ns) {
+ string errmsg;
+ ChunkVersion received;
+ ChunkVersion wanted;
+ if (!shardVersionOk(client, ns, errmsg, received, wanted)) {
+ StringBuilder sb;
+ sb << "[" << ns << "] shard version not ok: " << errmsg;
+ throw SendStaleConfigException(ns, sb.str(), received, wanted);
}
+}
+
+void usingAShardConnection(const string& addr) {}
+void saveGLEStats(const BSONObj& result, StringData hostString) {
+ // Declared in cluster_last_error_info.h.
+ //
+ // This can be called in mongod, which is unfortunate. To fix this,
+ // we can redesign how connection pooling works on mongod for sharded operations.
+}
}
diff --git a/src/mongo/s/d_state.h b/src/mongo/s/d_state.h
index 6bd43ce6865..d0e1d502ae4 100644
--- a/src/mongo/s/d_state.h
+++ b/src/mongo/s/d_state.h
@@ -38,342 +38,343 @@
namespace mongo {
- class Database;
- class RecordId;
- class OperationContext;
-
- // --------------
- // --- global state ---
- // --------------
-
- class ShardingState {
- public:
- ShardingState();
-
- bool enabled();
- std::string getConfigServer();
-
- // Initialize sharding state and begin authenticating outgoing connections and handling
- // shard versions. If this is not run before sharded operations occur auth will not work
- // and versions will not be tracked.
- static void initialize(const std::string& server);
-
- void gotShardName( const std::string& name );
- bool setShardName( const std::string& name ); // Same as above, does not throw
- std::string getShardName();
-
- // Helpers for SetShardVersion which report the host name sent to this shard when the shard
- // name does not match. Do not use in other places.
- // TODO: Remove once SSV is deprecated
- void gotShardNameAndHost( const std::string& name, const std::string& host );
- bool setShardNameAndHost( const std::string& name, const std::string& host );
-
- /**
- * Clears the collection metadata cache after step down.
- */
- void clearCollectionMetadata();
-
- // versioning support
-
- bool hasVersion( const std::string& ns );
- bool hasVersion( const std::string& ns , ChunkVersion& version );
- ChunkVersion getVersion(const std::string& ns);
-
- /**
- * If the metadata for 'ns' at this shard is at or above the requested version,
- * 'reqShardVersion', returns OK and fills in 'latestShardVersion' with the latest shard
- * version. The latter is always greater or equal than 'reqShardVersion' if in the same
- * epoch.
- *
- * Otherwise, falls back to refreshMetadataNow.
- *
- * This call blocks if there are more than N threads
- * currently refreshing metadata. (N is the number of
- * tickets in ShardingState::_configServerTickets,
- * currently 3.)
- *
- * Locking Note:
- * + Must NOT be called with the write lock because this call may go into the network,
- * and deadlocks may occur with shard-as-a-config. Therefore, nothing here guarantees
- * that 'latestShardVersion' is indeed the current one on return.
- */
- Status refreshMetadataIfNeeded( OperationContext* txn,
- const std::string& ns,
- const ChunkVersion& reqShardVersion,
- ChunkVersion* latestShardVersion );
-
- /**
- * Refreshes collection metadata by asking the config server for the latest information.
- * Starts a new config server request.
- *
- * Locking Notes:
- * + Must NOT be called with the write lock because this call may go into the network,
- * and deadlocks may occur with shard-as-a-config. Therefore, nothing here guarantees
- * that 'latestShardVersion' is indeed the current one on return.
- *
- * + Because this call must not be issued with the DBLock held, by the time the config
- * server sent us back the collection metadata information, someone else may have
- * updated the previously stored collection metadata. There are cases when one can't
- * tell which of updated or loaded metadata are the freshest. There are also cases where
- * the data coming from configs do not correspond to a consistent snapshot.
- * In these cases, return RemoteChangeDetected. (This usually means this call needs to
- * be issued again, at caller discretion)
- *
- * @return OK if remote metadata successfully loaded (may or may not have been installed)
- * @return RemoteChangeDetected if something changed while reloading and we may retry
- * @return !OK if something else went wrong during reload
- * @return latestShardVersion the version that is now stored for this collection
- */
- Status refreshMetadataNow(OperationContext* txn,
- const std::string& ns,
- ChunkVersion* latestShardVersion);
-
- void appendInfo( BSONObjBuilder& b );
-
- // querying support
-
- bool needCollectionMetadata( Client* client, const std::string& ns ) const;
- CollectionMetadataPtr getCollectionMetadata( const std::string& ns );
-
- // chunk migrate and split support
-
- /**
- * Creates and installs a new chunk metadata for a given collection by "forgetting" about
- * one of its chunks. The new metadata uses the provided version, which has to be higher
- * than the current metadata's shard version.
- *
- * One exception: if the forgotten chunk is the last one in this shard for the collection,
- * version has to be 0.
- *
- * If it runs successfully, clients need to grab the new version to access the collection.
- *
- * LOCKING NOTE:
- * Only safe to do inside the
- *
- * @param ns the collection
- * @param min max the chunk to eliminate from the current metadata
- * @param version at which the new metadata should be at
- */
- void donateChunk(OperationContext* txn,
- const std::string& ns,
- const BSONObj& min,
- const BSONObj& max,
- ChunkVersion version);
-
- /**
- * Creates and installs new chunk metadata for a given collection by reclaiming a previously
- * donated chunk. The previous metadata's shard version has to be provided.
- *
- * If it runs successfully, clients that became stale by the previous donateChunk will be
- * able to access the collection again.
- *
- * Note: If a migration has aborted but not yet unregistered a pending chunk, replacing the
- * metadata may leave the chunk as pending - this is not dangerous and should be rare, but
- * will require a stepdown to fully recover.
- *
- * @param ns the collection
- * @param prevMetadata the previous metadata before we donated a chunk
- */
- void undoDonateChunk(OperationContext* txn,
- const std::string& ns,
- CollectionMetadataPtr prevMetadata);
-
- /**
- * Remembers a chunk range between 'min' and 'max' as a range which will have data migrated
- * into it. This data can then be protected against cleanup of orphaned data.
- *
- * Overlapping pending ranges will be removed, so it is only safe to use this when you know
- * your metadata view is definitive, such as at the start of a migration.
- *
- * @return false with errMsg if the range is owned by this shard
- */
- bool notePending(OperationContext* txn,
- const std::string& ns,
- const BSONObj& min,
- const BSONObj& max,
- const OID& epoch,
- std::string* errMsg );
-
- /**
- * Stops tracking a chunk range between 'min' and 'max' that previously was having data
- * migrated into it. This data is no longer protected against cleanup of orphaned data.
- *
- * To avoid removing pending ranges of other operations, ensure that this is only used when
- * a migration is still active.
- * TODO: Because migrations may currently be active when a collection drops, an epoch is
- * necessary to ensure the pending metadata change is still applicable.
- *
- * @return false with errMsg if the range is owned by the shard or the epoch of the metadata
- * has changed
- */
- bool forgetPending(OperationContext* txn,
- const std::string& ns,
- const BSONObj& min,
- const BSONObj& max,
- const OID& epoch,
- std::string* errMsg );
-
- /**
- * Creates and installs a new chunk metadata for a given collection by splitting one of its
- * chunks in two or more. The version for the first split chunk should be provided. The
- * subsequent chunks' version would be the latter with the minor portion incremented.
- *
- * The effect on clients will depend on the version used. If the major portion is the same
- * as the current shards, clients shouldn't perceive the split.
- *
- * @param ns the collection
- * @param min max the chunk that should be split
- * @param splitKeys point in which to split
- * @param version at which the new metadata should be at
- */
- void splitChunk(OperationContext* txn,
- const std::string& ns,
- const BSONObj& min,
- const BSONObj& max,
- const std::vector<BSONObj>& splitKeys,
- ChunkVersion version );
-
- /**
- * Creates and installs a new chunk metadata for a given collection by merging a range of
- * chunks ['minKey', 'maxKey') into a single chunk with version 'mergedVersion'.
- * The current metadata must overlap the range completely and minKey and maxKey must not
- * divide an existing chunk.
- *
- * The merged chunk version must have a greater version than the current shard version,
- * and if it has a greater major version clients will need to reload metadata.
- *
- * @param ns the collection
- * @param minKey maxKey the range which should be merged
- * @param newShardVersion the shard version the newly merged chunk should have
- */
- void mergeChunks(OperationContext* txn,
- const std::string& ns,
- const BSONObj& minKey,
- const BSONObj& maxKey,
- ChunkVersion mergedVersion );
-
- bool inCriticalMigrateSection();
-
- /**
- * @return true if we are NOT in the critical section
- */
- bool waitTillNotInCriticalSection( int maxSecondsToWait );
-
- /**
- * TESTING ONLY
- * Uninstalls the metadata for a given collection.
- */
- void resetMetadata( const std::string& ns );
-
- private:
-
- void _initialize(const std::string& server);
-
- /**
- * Refreshes collection metadata by asking the config server for the latest information.
- * May or may not be based on a requested version.
- */
- Status doRefreshMetadata( OperationContext* txn,
- const std::string& ns,
- const ChunkVersion& reqShardVersion,
- bool useRequestedVersion,
- ChunkVersion* latestShardVersion );
-
- // protects state below
- stdx::mutex _mutex;
-
- // Whether ::initialize has been called
- bool _enabled;
-
- // Sets the shard name for this host (comes through setShardVersion)
- std::string _shardName;
-
- // protects accessing the config server
- // Using a ticket holder so we can have multiple redundant tries at any given time
- mutable TicketHolder _configServerTickets;
-
- // Map from a namespace into the metadata we need for each collection on this shard
- typedef std::map<std::string,CollectionMetadataPtr> CollectionMetadataMap;
- CollectionMetadataMap _collMetadata;
- };
-
- extern ShardingState shardingState;
+class Database;
+class RecordId;
+class OperationContext;
+
+// --------------
+// --- global state ---
+// --------------
+
+class ShardingState {
+public:
+ ShardingState();
+
+ bool enabled();
+ std::string getConfigServer();
+
+ // Initialize sharding state and begin authenticating outgoing connections and handling
+ // shard versions. If this is not run before sharded operations occur auth will not work
+ // and versions will not be tracked.
+ static void initialize(const std::string& server);
+
+ void gotShardName(const std::string& name);
+ bool setShardName(const std::string& name); // Same as above, does not throw
+ std::string getShardName();
+
+ // Helpers for SetShardVersion which report the host name sent to this shard when the shard
+ // name does not match. Do not use in other places.
+ // TODO: Remove once SSV is deprecated
+ void gotShardNameAndHost(const std::string& name, const std::string& host);
+ bool setShardNameAndHost(const std::string& name, const std::string& host);
/**
- * one per connection from mongos
- * holds version state for each namespace
+ * Clears the collection metadata cache after step down.
*/
- class ShardedConnectionInfo {
- public:
+ void clearCollectionMetadata();
- ShardedConnectionInfo();
+ // versioning support
- const ChunkVersion getVersion( const std::string& ns ) const;
- void setVersion( const std::string& ns , const ChunkVersion& version );
+ bool hasVersion(const std::string& ns);
+ bool hasVersion(const std::string& ns, ChunkVersion& version);
+ ChunkVersion getVersion(const std::string& ns);
- static ShardedConnectionInfo* get( Client* client, bool create );
- static void reset( Client* client );
- static void addHook();
+ /**
+ * If the metadata for 'ns' at this shard is at or above the requested version,
+ * 'reqShardVersion', returns OK and fills in 'latestShardVersion' with the latest shard
+ * version. The latter is always greater or equal than 'reqShardVersion' if in the same
+ * epoch.
+ *
+ * Otherwise, falls back to refreshMetadataNow.
+ *
+ * This call blocks if there are more than N threads
+ * currently refreshing metadata. (N is the number of
+ * tickets in ShardingState::_configServerTickets,
+ * currently 3.)
+ *
+ * Locking Note:
+ * + Must NOT be called with the write lock because this call may go into the network,
+ * and deadlocks may occur with shard-as-a-config. Therefore, nothing here guarantees
+ * that 'latestShardVersion' is indeed the current one on return.
+ */
+ Status refreshMetadataIfNeeded(OperationContext* txn,
+ const std::string& ns,
+ const ChunkVersion& reqShardVersion,
+ ChunkVersion* latestShardVersion);
+
+ /**
+ * Refreshes collection metadata by asking the config server for the latest information.
+ * Starts a new config server request.
+ *
+ * Locking Notes:
+ * + Must NOT be called with the write lock because this call may go into the network,
+ * and deadlocks may occur with shard-as-a-config. Therefore, nothing here guarantees
+ * that 'latestShardVersion' is indeed the current one on return.
+ *
+ * + Because this call must not be issued with the DBLock held, by the time the config
+ * server sent us back the collection metadata information, someone else may have
+ * updated the previously stored collection metadata. There are cases when one can't
+ * tell which of updated or loaded metadata are the freshest. There are also cases where
+ * the data coming from configs do not correspond to a consistent snapshot.
+ * In these cases, return RemoteChangeDetected. (This usually means this call needs to
+ * be issued again, at caller discretion)
+ *
+ * @return OK if remote metadata successfully loaded (may or may not have been installed)
+ * @return RemoteChangeDetected if something changed while reloading and we may retry
+ * @return !OK if something else went wrong during reload
+ * @return latestShardVersion the version that is now stored for this collection
+ */
+ Status refreshMetadataNow(OperationContext* txn,
+ const std::string& ns,
+ ChunkVersion* latestShardVersion);
- bool inForceVersionOkMode() const {
- return _forceVersionOk;
- }
+ void appendInfo(BSONObjBuilder& b);
- void enterForceVersionOkMode() { _forceVersionOk = true; }
- void leaveForceVersionOkMode() { _forceVersionOk = false; }
+ // querying support
- private:
+ bool needCollectionMetadata(Client* client, const std::string& ns) const;
+ CollectionMetadataPtr getCollectionMetadata(const std::string& ns);
- bool _forceVersionOk; // if this is true, then chunk version #s aren't check, and all ops are allowed
+ // chunk migrate and split support
- typedef std::map<std::string,ChunkVersion> NSVersionMap;
- NSVersionMap _versions;
- };
+ /**
+ * Creates and installs a new chunk metadata for a given collection by "forgetting" about
+ * one of its chunks. The new metadata uses the provided version, which has to be higher
+ * than the current metadata's shard version.
+ *
+ * One exception: if the forgotten chunk is the last one in this shard for the collection,
+ * version has to be 0.
+ *
+ * If it runs successfully, clients need to grab the new version to access the collection.
+ *
+ * LOCKING NOTE:
+ * Only safe to do inside the
+ *
+ * @param ns the collection
+ * @param min max the chunk to eliminate from the current metadata
+ * @param version at which the new metadata should be at
+ */
+ void donateChunk(OperationContext* txn,
+ const std::string& ns,
+ const BSONObj& min,
+ const BSONObj& max,
+ ChunkVersion version);
- struct ShardForceVersionOkModeBlock {
- ShardForceVersionOkModeBlock(Client* client) {
- info = ShardedConnectionInfo::get( client, false );
- if ( info )
- info->enterForceVersionOkMode();
- }
- ~ShardForceVersionOkModeBlock() {
- if ( info )
- info->leaveForceVersionOkMode();
- }
+ /**
+ * Creates and installs new chunk metadata for a given collection by reclaiming a previously
+ * donated chunk. The previous metadata's shard version has to be provided.
+ *
+ * If it runs successfully, clients that became stale by the previous donateChunk will be
+ * able to access the collection again.
+ *
+ * Note: If a migration has aborted but not yet unregistered a pending chunk, replacing the
+ * metadata may leave the chunk as pending - this is not dangerous and should be rare, but
+ * will require a stepdown to fully recover.
+ *
+ * @param ns the collection
+ * @param prevMetadata the previous metadata before we donated a chunk
+ */
+ void undoDonateChunk(OperationContext* txn,
+ const std::string& ns,
+ CollectionMetadataPtr prevMetadata);
- ShardedConnectionInfo * info;
- };
+ /**
+ * Remembers a chunk range between 'min' and 'max' as a range which will have data migrated
+ * into it. This data can then be protected against cleanup of orphaned data.
+ *
+ * Overlapping pending ranges will be removed, so it is only safe to use this when you know
+ * your metadata view is definitive, such as at the start of a migration.
+ *
+ * @return false with errMsg if the range is owned by this shard
+ */
+ bool notePending(OperationContext* txn,
+ const std::string& ns,
+ const BSONObj& min,
+ const BSONObj& max,
+ const OID& epoch,
+ std::string* errMsg);
- // -----------------
- // --- core ---
- // -----------------
+ /**
+ * Stops tracking a chunk range between 'min' and 'max' that previously was having data
+ * migrated into it. This data is no longer protected against cleanup of orphaned data.
+ *
+ * To avoid removing pending ranges of other operations, ensure that this is only used when
+ * a migration is still active.
+ * TODO: Because migrations may currently be active when a collection drops, an epoch is
+ * necessary to ensure the pending metadata change is still applicable.
+ *
+ * @return false with errMsg if the range is owned by the shard or the epoch of the metadata
+ * has changed
+ */
+ bool forgetPending(OperationContext* txn,
+ const std::string& ns,
+ const BSONObj& min,
+ const BSONObj& max,
+ const OID& epoch,
+ std::string* errMsg);
/**
- * @return true if we have any shard info for the ns
+ * Creates and installs a new chunk metadata for a given collection by splitting one of its
+ * chunks in two or more. The version for the first split chunk should be provided. The
+ * subsequent chunks' version would be the latter with the minor portion incremented.
+ *
+ * The effect on clients will depend on the version used. If the major portion is the same
+ * as the current shards, clients shouldn't perceive the split.
+ *
+ * @param ns the collection
+ * @param min max the chunk that should be split
+ * @param splitKeys point in which to split
+ * @param version at which the new metadata should be at
*/
- bool haveLocalShardingInfo( Client* client, const std::string& ns );
+ void splitChunk(OperationContext* txn,
+ const std::string& ns,
+ const BSONObj& min,
+ const BSONObj& max,
+ const std::vector<BSONObj>& splitKeys,
+ ChunkVersion version);
/**
- * Validates whether the shard chunk version for the specified collection is up to date and if
- * not, throws SendStaleConfigException.
+ * Creates and installs a new chunk metadata for a given collection by merging a range of
+ * chunks ['minKey', 'maxKey') into a single chunk with version 'mergedVersion'.
+ * The current metadata must overlap the range completely and minKey and maxKey must not
+ * divide an existing chunk.
*
- * It is important (but not enforced) that method be called with the collection locked in at
- * least IS mode in order to ensure that the shard version won't change.
+ * The merged chunk version must have a greater version than the current shard version,
+ * and if it has a greater major version clients will need to reload metadata.
*
- * @param ns Complete collection namespace to be cheched.
+ * @param ns the collection
+ * @param minKey maxKey the range which should be merged
+ * @param newShardVersion the shard version the newly merged chunk should have
+ */
+ void mergeChunks(OperationContext* txn,
+ const std::string& ns,
+ const BSONObj& minKey,
+ const BSONObj& maxKey,
+ ChunkVersion mergedVersion);
+
+ bool inCriticalMigrateSection();
+
+ /**
+ * @return true if we are NOT in the critical section
+ */
+ bool waitTillNotInCriticalSection(int maxSecondsToWait);
+
+ /**
+ * TESTING ONLY
+ * Uninstalls the metadata for a given collection.
*/
- void ensureShardVersionOKOrThrow(Client* client, const std::string& ns);
+ void resetMetadata(const std::string& ns);
+
+private:
+ void _initialize(const std::string& server);
/**
- * If a migration for the chunk in 'ns' where 'obj' lives is occurring, save this log entry
- * if it's relevant. The entries saved here are later transferred to the receiving side of
- * the migration. A relevant entry is an insertion, a deletion, or an update.
+ * Refreshes collection metadata by asking the config server for the latest information.
+ * May or may not be based on a requested version.
*/
- void logOpForSharding( OperationContext* txn,
- const char * opstr,
- const char * ns,
- const BSONObj& obj,
- BSONObj * patt,
- bool forMigrateCleanup );
+ Status doRefreshMetadata(OperationContext* txn,
+ const std::string& ns,
+ const ChunkVersion& reqShardVersion,
+ bool useRequestedVersion,
+ ChunkVersion* latestShardVersion);
+
+ // protects state below
+ stdx::mutex _mutex;
+
+ // Whether ::initialize has been called
+ bool _enabled;
+
+ // Sets the shard name for this host (comes through setShardVersion)
+ std::string _shardName;
+
+ // protects accessing the config server
+ // Using a ticket holder so we can have multiple redundant tries at any given time
+ mutable TicketHolder _configServerTickets;
+
+ // Map from a namespace into the metadata we need for each collection on this shard
+ typedef std::map<std::string, CollectionMetadataPtr> CollectionMetadataMap;
+ CollectionMetadataMap _collMetadata;
+};
+
+extern ShardingState shardingState;
+/**
+ * one per connection from mongos
+ * holds version state for each namespace
+ */
+class ShardedConnectionInfo {
+public:
+ ShardedConnectionInfo();
+
+ const ChunkVersion getVersion(const std::string& ns) const;
+ void setVersion(const std::string& ns, const ChunkVersion& version);
+
+ static ShardedConnectionInfo* get(Client* client, bool create);
+ static void reset(Client* client);
+ static void addHook();
+
+ bool inForceVersionOkMode() const {
+ return _forceVersionOk;
+ }
+
+ void enterForceVersionOkMode() {
+ _forceVersionOk = true;
+ }
+ void leaveForceVersionOkMode() {
+ _forceVersionOk = false;
+ }
+
+private:
+ bool
+ _forceVersionOk; // if this is true, then chunk version #s aren't check, and all ops are allowed
+
+ typedef std::map<std::string, ChunkVersion> NSVersionMap;
+ NSVersionMap _versions;
+};
+
+struct ShardForceVersionOkModeBlock {
+ ShardForceVersionOkModeBlock(Client* client) {
+ info = ShardedConnectionInfo::get(client, false);
+ if (info)
+ info->enterForceVersionOkMode();
+ }
+ ~ShardForceVersionOkModeBlock() {
+ if (info)
+ info->leaveForceVersionOkMode();
+ }
+
+ ShardedConnectionInfo* info;
+};
+
+// -----------------
+// --- core ---
+// -----------------
+
+/**
+ * @return true if we have any shard info for the ns
+ */
+bool haveLocalShardingInfo(Client* client, const std::string& ns);
+
+/**
+ * Validates whether the shard chunk version for the specified collection is up to date and if
+ * not, throws SendStaleConfigException.
+ *
+ * It is important (but not enforced) that method be called with the collection locked in at
+ * least IS mode in order to ensure that the shard version won't change.
+ *
+ * @param ns Complete collection namespace to be cheched.
+ */
+void ensureShardVersionOKOrThrow(Client* client, const std::string& ns);
+
+/**
+ * If a migration for the chunk in 'ns' where 'obj' lives is occurring, save this log entry
+ * if it's relevant. The entries saved here are later transferred to the receiving side of
+ * the migration. A relevant entry is an insertion, a deletion, or an update.
+ */
+void logOpForSharding(OperationContext* txn,
+ const char* opstr,
+ const char* ns,
+ const BSONObj& obj,
+ BSONObj* patt,
+ bool forMigrateCleanup);
}
diff --git a/src/mongo/s/dbclient_shard_resolver.cpp b/src/mongo/s/dbclient_shard_resolver.cpp
index 5caef187a73..92694a51243 100644
--- a/src/mongo/s/dbclient_shard_resolver.cpp
+++ b/src/mongo/s/dbclient_shard_resolver.cpp
@@ -37,67 +37,63 @@
namespace mongo {
- using std::string;
+using std::string;
+
+Status DBClientShardResolver::chooseWriteHost(const string& shardName,
+ ConnectionString* shardHost) const {
+ // Internally uses our shard cache, does no reload
+ std::shared_ptr<Shard> shard = grid.shardRegistry()->getShard(shardName);
+ if (!shard) {
+ return Status(ErrorCodes::ShardNotFound,
+ str::stream() << "unknown shard name " << shardName);
+ }
+
+ return findMaster(shard->getConnString(), shardHost);
+}
+
+Status DBClientShardResolver::findMaster(const ConnectionString& connString,
+ ConnectionString* resolvedHost) {
+ if (connString.type() == ConnectionString::MASTER) {
+ *resolvedHost = connString;
+ return Status::OK();
+ }
- Status DBClientShardResolver::chooseWriteHost(const string& shardName,
- ConnectionString* shardHost) const {
+ dassert(connString.type() == ConnectionString::SET);
- // Internally uses our shard cache, does no reload
- std::shared_ptr<Shard> shard = grid.shardRegistry()->getShard(shardName);
- if (!shard) {
- return Status(ErrorCodes::ShardNotFound,
- str::stream() << "unknown shard name " << shardName);
- }
+ //
+ // If we need to, then get the particular node we're targeting in the replica set
+ //
+
+ // Don't create the monitor unless we need to - fast path
+ ReplicaSetMonitorPtr replMonitor = ReplicaSetMonitor::get(connString.getSetName());
+
+ if (!replMonitor) {
+ // Slow path
+ std::set<HostAndPort> seedServers(connString.getServers().begin(),
+ connString.getServers().end());
+ ReplicaSetMonitor::createIfNeeded(connString.getSetName(), seedServers);
+
+ replMonitor = ReplicaSetMonitor::get(connString.getSetName());
+ }
- return findMaster(shard->getConnString(), shardHost);
+ if (!replMonitor) {
+ return Status(ErrorCodes::ReplicaSetNotFound,
+ str::stream() << "unknown replica set " << connString.getSetName());
}
- Status DBClientShardResolver::findMaster(const ConnectionString& connString,
- ConnectionString* resolvedHost) {
-
- if (connString.type() == ConnectionString::MASTER) {
- *resolvedHost = connString;
- return Status::OK();
- }
-
- dassert(connString.type() == ConnectionString::SET);
-
- //
- // If we need to, then get the particular node we're targeting in the replica set
- //
-
- // Don't create the monitor unless we need to - fast path
- ReplicaSetMonitorPtr replMonitor = ReplicaSetMonitor::get(connString.getSetName());
-
- if (!replMonitor) {
- // Slow path
- std::set<HostAndPort> seedServers(connString.getServers().begin(),
- connString.getServers().end());
- ReplicaSetMonitor::createIfNeeded(connString.getSetName(), seedServers);
-
- replMonitor = ReplicaSetMonitor::get(connString.getSetName());
- }
-
- if (!replMonitor) {
- return Status(ErrorCodes::ReplicaSetNotFound,
- str::stream() << "unknown replica set " << connString.getSetName());
- }
-
- try {
- // This can throw when we don't find a master!
- HostAndPort masterHostAndPort = replMonitor->getMasterOrUassert();
- *resolvedHost = fassertStatusOK(28687,
- ConnectionString::parse(masterHostAndPort.toString()));
- return Status::OK();
- }
- catch ( const DBException& ) {
- return Status( ErrorCodes::HostNotFound,
- string("could not contact primary for replica set ")
- + replMonitor->getName() );
- }
-
- MONGO_UNREACHABLE;
+ try {
+ // This can throw when we don't find a master!
+ HostAndPort masterHostAndPort = replMonitor->getMasterOrUassert();
+ *resolvedHost =
+ fassertStatusOK(28687, ConnectionString::parse(masterHostAndPort.toString()));
+ return Status::OK();
+ } catch (const DBException&) {
+ return Status(ErrorCodes::HostNotFound,
+ string("could not contact primary for replica set ") +
+ replMonitor->getName());
}
-} // namespace mongo
+ MONGO_UNREACHABLE;
+}
+} // namespace mongo
diff --git a/src/mongo/s/dbclient_shard_resolver.h b/src/mongo/s/dbclient_shard_resolver.h
index fb7beb26ee5..076dd078562 100644
--- a/src/mongo/s/dbclient_shard_resolver.h
+++ b/src/mongo/s/dbclient_shard_resolver.h
@@ -32,42 +32,37 @@
namespace mongo {
+/**
+ * ShardResolver based on the Shard and ReplicaSetMonitor caches.
+ *
+ * TODO: Currently it's possible for the shard resolver to be stale after we target and remove
+ * a shard. We need to figure out how to refresh.
+ */
+class DBClientShardResolver : public ShardResolver {
+public:
+ DBClientShardResolver() {}
+
+ virtual ~DBClientShardResolver() {}
+
/**
- * ShardResolver based on the Shard and ReplicaSetMonitor caches.
+ * Returns the current host ConnectionString for a write to a shard.
+ *
+ * Note: Does *not* trigger a refresh of either the shard or replica set monitor caches,
+ * though refreshes may happen unexpectedly between calls.
*
- * TODO: Currently it's possible for the shard resolver to be stale after we target and remove
- * a shard. We need to figure out how to refresh.
+ * Returns ShardNotFound if the shard name is unknown
+ * Returns ReplicaSetNotFound if the replica set is not being tracked
+ * Returns !OK with message if the shard host could not be found for other reasons.
*/
- class DBClientShardResolver : public ShardResolver {
- public:
+ Status chooseWriteHost(const std::string& shardName, ConnectionString* shardHost) const;
- DBClientShardResolver() {
- }
-
- virtual ~DBClientShardResolver() {
- }
-
- /**
- * Returns the current host ConnectionString for a write to a shard.
- *
- * Note: Does *not* trigger a refresh of either the shard or replica set monitor caches,
- * though refreshes may happen unexpectedly between calls.
- *
- * Returns ShardNotFound if the shard name is unknown
- * Returns ReplicaSetNotFound if the replica set is not being tracked
- * Returns !OK with message if the shard host could not be found for other reasons.
- */
- Status chooseWriteHost( const std::string& shardName, ConnectionString* shardHost ) const;
-
- /**
- * Resolves a replica set connection string to a master or returns an error.
- *
- * Returns HostNotFound if the master is not reachable
- * Returns ReplicaSetNotFound if the replica set is not being tracked
- */
- static Status findMaster(const ConnectionString& connString,
- ConnectionString* resolvedHost);
-
- };
+ /**
+ * Resolves a replica set connection string to a master or returns an error.
+ *
+ * Returns HostNotFound if the master is not reachable
+ * Returns ReplicaSetNotFound if the replica set is not being tracked
+ */
+ static Status findMaster(const ConnectionString& connString, ConnectionString* resolvedHost);
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/distlock_test.cpp b/src/mongo/s/distlock_test.cpp
index 9b1dd3f8ab3..d0fea0b3448 100644
--- a/src/mongo/s/distlock_test.cpp
+++ b/src/mongo/s/distlock_test.cpp
@@ -73,375 +73,372 @@
// TODO: Make a method in BSONObj if useful, don't modify for now
-#define string_field(obj, name, def) ( obj.hasField(name) ? obj[name].String() : def )
-#define number_field(obj, name, def) ( obj.hasField(name) ? obj[name].Number() : def )
+#define string_field(obj, name, def) (obj.hasField(name) ? obj[name].String() : def)
+#define number_field(obj, name, def) (obj.hasField(name) ? obj[name].Number() : def)
namespace mongo {
- using std::shared_ptr;
- using std::endl;
- using std::string;
- using std::stringstream;
- using std::vector;
-
- /**
- * Stress test distributed lock by running multiple threads to contend with a single lock.
- * Also has an option to make some thread terminate while holding the lock and have some
- * other thread take over it after takeoverMS has elapsed. Note that this test does not check
- * whether the lock was eventually overtaken and this is only valid if the LockPinger frequency
- * is faster than takeoverMS.
- *
- * {
- * _testDistLockWithSkew: 1,
- *
- * lockName: <string for distributed lock>,
- * host: <connection string for config server>,
- * seed: <numeric seed for random generator>,
- * numThreads: <num of threads to spawn and grab the lock>,
- *
- * takeoverMS: <duration of missed ping in milliSeconds before a lock can be overtaken>,
- * wait: <time in milliseconds before stopping the test threads>,
- * skewHosts: <Array<Numeric>, numbers to be used when calling _skewClockCommand
- * against each config server>,
- * threadWait: <upper bound wait in milliSeconds while holding a lock>,
- *
- * hangThreads: <integer n, where 1 out of n threads will abort after acquiring lock>,
- * threadSleep: <upper bound sleep duration in mSecs between each round of lock operation>,
- * skewRange: <maximum skew variance in milliSeconds for a thread's clock, delta will never
- * be greater than skewRange/2>
- * }
- */
- class TestDistLockWithSkew: public Command {
- public:
-
- static const int logLvl = 1;
+using std::shared_ptr;
+using std::endl;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+/**
+ * Stress test distributed lock by running multiple threads to contend with a single lock.
+ * Also has an option to make some thread terminate while holding the lock and have some
+ * other thread take over it after takeoverMS has elapsed. Note that this test does not check
+ * whether the lock was eventually overtaken and this is only valid if the LockPinger frequency
+ * is faster than takeoverMS.
+ *
+ * {
+ * _testDistLockWithSkew: 1,
+ *
+ * lockName: <string for distributed lock>,
+ * host: <connection string for config server>,
+ * seed: <numeric seed for random generator>,
+ * numThreads: <num of threads to spawn and grab the lock>,
+ *
+ * takeoverMS: <duration of missed ping in milliSeconds before a lock can be overtaken>,
+ * wait: <time in milliseconds before stopping the test threads>,
+ * skewHosts: <Array<Numeric>, numbers to be used when calling _skewClockCommand
+ * against each config server>,
+ * threadWait: <upper bound wait in milliSeconds while holding a lock>,
+ *
+ * hangThreads: <integer n, where 1 out of n threads will abort after acquiring lock>,
+ * threadSleep: <upper bound sleep duration in mSecs between each round of lock operation>,
+ * skewRange: <maximum skew variance in milliSeconds for a thread's clock, delta will never
+ * be greater than skewRange/2>
+ * }
+ */
+class TestDistLockWithSkew : public Command {
+public:
+ static const int logLvl = 1;
- TestDistLockWithSkew() :
- Command("_testDistLockWithSkew") {
- }
- virtual void help(stringstream& help) const {
- help << "should not be calling this directly" << endl;
- }
+ TestDistLockWithSkew() : Command("_testDistLockWithSkew") {}
+ virtual void help(stringstream& help) const {
+ help << "should not be calling this directly" << endl;
+ }
- virtual bool slaveOk() const {
- return false;
- }
- virtual bool adminOnly() const {
- return true;
+ virtual bool slaveOk() const {
+ return false;
+ }
+ virtual bool adminOnly() const {
+ return true;
+ }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+ // No auth needed because it only works when enabled via command line.
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {}
+
+ void runThread(ConnectionString& hostConn,
+ unsigned threadId,
+ unsigned seed,
+ BSONObj& cmdObj,
+ BSONObjBuilder& result) {
+ stringstream ss;
+ ss << "thread-" << threadId;
+ setThreadName(ss.str().c_str());
+
+ // Lock name
+ string lockName = string_field(cmdObj, "lockName", this->name + "_lock");
+
+ // Range of clock skew in diff threads
+ int skewRange = (int)number_field(cmdObj, "skewRange", 1);
+
+ // How long to wait with the lock
+ int threadWait = (int)number_field(cmdObj, "threadWait", 30);
+ if (threadWait <= 0)
+ threadWait = 1;
+
+ // Max amount of time (ms) a thread waits before checking the lock again
+ int threadSleep = (int)number_field(cmdObj, "threadSleep", 30);
+ if (threadSleep <= 0)
+ threadSleep = 1;
+
+ // How long until the lock is forced in ms, only compared locally
+ unsigned long long takeoverMS = (unsigned long long)number_field(cmdObj, "takeoverMS", 0);
+
+ // Whether or not we should hang some threads
+ int hangThreads = (int)number_field(cmdObj, "hangThreads", 0);
+
+
+ boost::mt19937 gen((boost::mt19937::result_type)seed);
+
+ boost::variate_generator<boost::mt19937&, boost::uniform_int<>> randomSkew(
+ gen, boost::uniform_int<>(0, skewRange));
+ boost::variate_generator<boost::mt19937&, boost::uniform_int<>> randomWait(
+ gen, boost::uniform_int<>(1, threadWait));
+ boost::variate_generator<boost::mt19937&, boost::uniform_int<>> randomSleep(
+ gen, boost::uniform_int<>(1, threadSleep));
+ boost::variate_generator<boost::mt19937&, boost::uniform_int<>> randomNewLock(
+ gen, boost::uniform_int<>(0, 3));
+
+
+ int skew = 0;
+ if (!lock.get()) {
+ // Pick a skew, but the first two threads skew the whole range
+ if (threadId == 0)
+ skew = -skewRange / 2;
+ else if (threadId == 1)
+ skew = skewRange / 2;
+ else
+ skew = randomSkew() - (skewRange / 2);
+
+ // Skew this thread
+ jsTimeVirtualThreadSkew(skew);
+
+ log() << "Initializing lock with skew of " << skew << " for thread " << threadId
+ << endl;
+
+ lock.reset(new DistributedLock(hostConn, lockName, takeoverMS, true));
+
+ log() << "Skewed time " << jsTime() << " for thread " << threadId << endl
+ << " max wait (with lock: " << threadWait << ", after lock: " << threadSleep
+ << ")" << endl
+ << " takeover in " << takeoverMS << "(ms remote)" << endl;
}
- virtual bool isWriteCommandForConfigServer() const { return false; }
- // No auth needed because it only works when enabled via command line.
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {}
-
- void runThread(ConnectionString& hostConn, unsigned threadId, unsigned seed,
- BSONObj& cmdObj, BSONObjBuilder& result) {
-
- stringstream ss;
- ss << "thread-" << threadId;
- setThreadName(ss.str().c_str());
-
- // Lock name
- string lockName = string_field(cmdObj, "lockName", this->name + "_lock");
-
- // Range of clock skew in diff threads
- int skewRange = (int) number_field(cmdObj, "skewRange", 1);
-
- // How long to wait with the lock
- int threadWait = (int) number_field(cmdObj, "threadWait", 30);
- if(threadWait <= 0) threadWait = 1;
-
- // Max amount of time (ms) a thread waits before checking the lock again
- int threadSleep = (int) number_field(cmdObj, "threadSleep", 30);
- if(threadSleep <= 0) threadSleep = 1;
-
- // How long until the lock is forced in ms, only compared locally
- unsigned long long takeoverMS = (unsigned long long) number_field(cmdObj, "takeoverMS", 0);
- // Whether or not we should hang some threads
- int hangThreads = (int) number_field(cmdObj, "hangThreads", 0);
+ DistributedLock* myLock = lock.get();
+ bool errors = false;
+ BSONObj lockObj;
+ while (keepGoing.loadRelaxed()) {
+ Status pingStatus = _pinger.startPing(
+ *myLock, stdx::chrono::milliseconds(takeoverMS / LOCK_SKEW_FACTOR));
- boost::mt19937 gen((boost::mt19937::result_type) seed);
-
- boost::variate_generator<boost::mt19937&, boost::uniform_int<> > randomSkew(gen, boost::uniform_int<>(0, skewRange));
- boost::variate_generator<boost::mt19937&, boost::uniform_int<> > randomWait(gen, boost::uniform_int<>(1, threadWait));
- boost::variate_generator<boost::mt19937&, boost::uniform_int<> > randomSleep(gen, boost::uniform_int<>(1, threadSleep));
- boost::variate_generator<boost::mt19937&, boost::uniform_int<> > randomNewLock(gen, boost::uniform_int<>(0, 3));
-
-
- int skew = 0;
- if (!lock.get()) {
-
- // Pick a skew, but the first two threads skew the whole range
- if(threadId == 0)
- skew = -skewRange / 2;
- else if(threadId == 1)
- skew = skewRange / 2;
- else skew = randomSkew() - (skewRange / 2);
-
- // Skew this thread
- jsTimeVirtualThreadSkew( skew );
-
- log() << "Initializing lock with skew of " << skew << " for thread " << threadId << endl;
-
- lock.reset(new DistributedLock(hostConn, lockName, takeoverMS, true ));
-
- log() << "Skewed time " << jsTime() << " for thread " << threadId << endl
- << " max wait (with lock: " << threadWait << ", after lock: " << threadSleep << ")" << endl
- << " takeover in " << takeoverMS << "(ms remote)" << endl;
-
+ if (!pingStatus.isOK()) {
+ log() << "**** Not good for pinging: " << pingStatus;
+ break;
}
- DistributedLock* myLock = lock.get();
-
- bool errors = false;
- BSONObj lockObj;
- while (keepGoing.loadRelaxed()) {
- Status pingStatus = _pinger.startPing(*myLock,
- stdx::chrono::milliseconds(takeoverMS / LOCK_SKEW_FACTOR));
+ try {
+ if (myLock->lock_try("Testing distributed lock with skew.", &lockObj)) {
+ log() << "**** Locked for thread " << threadId << " with ts " << lockObj["ts"]
+ << endl;
+
+ if (count.loadRelaxed() % 3 == 1 &&
+ myLock->lock_try("Testing lock non-re-entry.")) {
+ errors = true;
+ log() << "**** !Invalid lock re-entry" << endl;
+ break;
+ }
- if (!pingStatus.isOK()) {
- log() << "**** Not good for pinging: " << pingStatus;
- break;
- }
+ int before = count.addAndFetch(1);
+ int sleep = randomWait();
+ sleepmillis(sleep);
+ int after = count.loadRelaxed();
- try {
-
- if (myLock->lock_try("Testing distributed lock with skew.", &lockObj)) {
-
- log() << "**** Locked for thread " << threadId << " with ts " << lockObj["ts"] << endl;
-
- if (count.loadRelaxed() % 3 == 1 &&
- myLock->lock_try( "Testing lock non-re-entry.")) {
- errors = true;
- log() << "**** !Invalid lock re-entry" << endl;
- break;
- }
-
- int before = count.addAndFetch(1);
- int sleep = randomWait();
- sleepmillis(sleep);
- int after = count.loadRelaxed();
-
- if(after != before) {
- errors = true;
- log() << "**** !Bad increment while sleeping with lock for: " << sleep << "ms" << endl;
- break;
- }
-
- // Unlock only half the time...
- if(hangThreads == 0 || threadId % hangThreads != 0) {
- log() << "**** Unlocking for thread " << threadId << " with ts " << lockObj["ts"] << endl;
- myLock->unlock(lockObj["ts"].OID());
- }
- else {
- log() << "**** Not unlocking for thread " << threadId << endl;
- _pinger.stopPing(myLock->getRemoteConnection(), myLock->getProcessId());
- // We're simulating a crashed process...
- break;
- }
+ if (after != before) {
+ errors = true;
+ log() << "**** !Bad increment while sleeping with lock for: " << sleep
+ << "ms" << endl;
+ break;
}
- }
- catch( const DBException& ex ) {
- log() << "*** !Could not try distributed lock." << causedBy( ex ) << endl;
- break;
- }
-
- // Create a new lock 1/3 of the time
- if( randomNewLock() > 1 ){
- lock.reset(new DistributedLock( hostConn, lockName, takeoverMS, true ));
- myLock = lock.get();
+ // Unlock only half the time...
+ if (hangThreads == 0 || threadId % hangThreads != 0) {
+ log() << "**** Unlocking for thread " << threadId << " with ts "
+ << lockObj["ts"] << endl;
+ myLock->unlock(lockObj["ts"].OID());
+ } else {
+ log() << "**** Not unlocking for thread " << threadId << endl;
+ _pinger.stopPing(myLock->getRemoteConnection(), myLock->getProcessId());
+ // We're simulating a crashed process...
+ break;
+ }
}
- sleepmillis(randomSleep());
+ } catch (const DBException& ex) {
+ log() << "*** !Could not try distributed lock." << causedBy(ex) << endl;
+ break;
}
- result << "errors" << errors
- << "skew" << skew
- << "takeover" << (long long) takeoverMS
- << "localTimeout" << (takeoverMS > 0);
-
- }
+ // Create a new lock 1/3 of the time
+ if (randomNewLock() > 1) {
+ lock.reset(new DistributedLock(hostConn, lockName, takeoverMS, true));
+ myLock = lock.get();
+ }
- void test(ConnectionString& hostConn, string& lockName, unsigned seed) {
- return;
+ sleepmillis(randomSleep());
}
- bool run(OperationContext* txn,
- const string&,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
-
- Timer t;
-
- ConnectionString hostConn(cmdObj["host"].String(),
- ConnectionString::SYNC);
-
- unsigned seed = (unsigned) number_field(cmdObj, "seed", 0);
- int numThreads = (int) number_field(cmdObj, "numThreads", 4);
- int wait = (int) number_field(cmdObj, "wait", 10000);
-
- log() << "Starting " << this->name << " with -" << endl
- << " seed: " << seed << endl
- << " numThreads: " << numThreads << endl
- << " total wait: " << wait << endl << endl;
-
- // Skew host clocks if needed
- try {
- skewClocks( hostConn, cmdObj );
- }
- catch( DBException e ) {
- errmsg = str::stream() << "Clocks could not be skewed." << causedBy( e );
- return false;
- }
-
- count.store(0);
- keepGoing.store(true);
-
- vector<shared_ptr<stdx::thread> > threads;
- vector<shared_ptr<BSONObjBuilder> > results;
- for (int i = 0; i < numThreads; i++) {
- results.push_back(shared_ptr<BSONObjBuilder> (new BSONObjBuilder()));
- threads.push_back(shared_ptr<stdx::thread> (new stdx::thread(
- stdx::bind(&TestDistLockWithSkew::runThread, this,
- hostConn, (unsigned) i, seed + i, boost::ref(cmdObj),
- boost::ref(*(results[i].get()))))));
- }
+ result << "errors" << errors << "skew" << skew << "takeover" << (long long)takeoverMS
+ << "localTimeout" << (takeoverMS > 0);
+ }
- sleepsecs(wait / 1000);
- keepGoing.store(false);
+ void test(ConnectionString& hostConn, string& lockName, unsigned seed) {
+ return;
+ }
- bool errors = false;
- for (unsigned i = 0; i < threads.size(); i++) {
- threads[i]->join();
- errors = errors || results[i].get()->obj()["errors"].Bool();
- }
+ bool run(OperationContext* txn,
+ const string&,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ Timer t;
+
+ ConnectionString hostConn(cmdObj["host"].String(), ConnectionString::SYNC);
+
+ unsigned seed = (unsigned)number_field(cmdObj, "seed", 0);
+ int numThreads = (int)number_field(cmdObj, "numThreads", 4);
+ int wait = (int)number_field(cmdObj, "wait", 10000);
+
+ log() << "Starting " << this->name << " with -" << endl
+ << " seed: " << seed << endl
+ << " numThreads: " << numThreads << endl
+ << " total wait: " << wait << endl
+ << endl;
+
+ // Skew host clocks if needed
+ try {
+ skewClocks(hostConn, cmdObj);
+ } catch (DBException e) {
+ errmsg = str::stream() << "Clocks could not be skewed." << causedBy(e);
+ return false;
+ }
- result.append("count", count.loadRelaxed());
- result.append("errors", errors);
- result.append("timeMS", t.millis());
+ count.store(0);
+ keepGoing.store(true);
+
+ vector<shared_ptr<stdx::thread>> threads;
+ vector<shared_ptr<BSONObjBuilder>> results;
+ for (int i = 0; i < numThreads; i++) {
+ results.push_back(shared_ptr<BSONObjBuilder>(new BSONObjBuilder()));
+ threads.push_back(shared_ptr<stdx::thread>(
+ new stdx::thread(stdx::bind(&TestDistLockWithSkew::runThread,
+ this,
+ hostConn,
+ (unsigned)i,
+ seed + i,
+ boost::ref(cmdObj),
+ boost::ref(*(results[i].get()))))));
+ }
- return !errors;
+ sleepsecs(wait / 1000);
+ keepGoing.store(false);
+ bool errors = false;
+ for (unsigned i = 0; i < threads.size(); i++) {
+ threads[i]->join();
+ errors = errors || results[i].get()->obj()["errors"].Bool();
}
- /**
- * Skews the clocks of a remote cluster by a particular amount, specified by
- * the "skewHosts" element in a BSONObj.
- */
- static void skewClocks( ConnectionString& cluster, BSONObj& cmdObj ) {
-
- vector<long long> skew;
- if(cmdObj.hasField("skewHosts")) {
- bsonArrToNumVector<long long>(cmdObj["skewHosts"], skew);
- }
- else {
- LOG( logLvl ) << "No host clocks to skew." << endl;
- return;
- }
+ result.append("count", count.loadRelaxed());
+ result.append("errors", errors);
+ result.append("timeMS", t.millis());
- LOG( logLvl ) << "Skewing clocks of hosts " << cluster << endl;
+ return !errors;
+ }
- unsigned s = 0;
- for(vector<long long>::iterator i = skew.begin(); i != skew.end(); ++i,s++) {
+ /**
+ * Skews the clocks of a remote cluster by a particular amount, specified by
+ * the "skewHosts" element in a BSONObj.
+ */
+ static void skewClocks(ConnectionString& cluster, BSONObj& cmdObj) {
+ vector<long long> skew;
+ if (cmdObj.hasField("skewHosts")) {
+ bsonArrToNumVector<long long>(cmdObj["skewHosts"], skew);
+ } else {
+ LOG(logLvl) << "No host clocks to skew." << endl;
+ return;
+ }
- ConnectionString server( cluster.getServers()[s] );
- ScopedDbConnection conn(server.toString());
+ LOG(logLvl) << "Skewing clocks of hosts " << cluster << endl;
- BSONObj result;
- try {
- bool success = conn->runCommand( string("admin"),
- BSON( "_skewClockCommand" << 1
- << "skew" << *i ),
- result );
+ unsigned s = 0;
+ for (vector<long long>::iterator i = skew.begin(); i != skew.end(); ++i, s++) {
+ ConnectionString server(cluster.getServers()[s]);
+ ScopedDbConnection conn(server.toString());
- uassert(13678, str::stream() << "Could not communicate with server " << server.toString() << " in cluster " << cluster.toString() << " to change skew by " << *i, success );
+ BSONObj result;
+ try {
+ bool success = conn->runCommand(
+ string("admin"), BSON("_skewClockCommand" << 1 << "skew" << *i), result);
- LOG( logLvl + 1 ) << " Skewed host " << server << " clock by " << *i << endl;
- }
- catch(...) {
- conn.done();
- throw;
- }
+ uassert(13678,
+ str::stream() << "Could not communicate with server " << server.toString()
+ << " in cluster " << cluster.toString()
+ << " to change skew by " << *i,
+ success);
+ LOG(logLvl + 1) << " Skewed host " << server << " clock by " << *i << endl;
+ } catch (...) {
conn.done();
-
+ throw;
}
+ conn.done();
}
-
- // variables for test
- boost::thread_specific_ptr<DistributedLock> lock;
- AtomicUInt32 count;
- AtomicWord<bool> keepGoing;
-
- private:
- LegacyDistLockPinger _pinger;
- };
- MONGO_INITIALIZER(RegisterDistLockWithSkewCmd)(InitializerContext* context) {
- if (Command::testCommandsEnabled) {
- // Leaked intentionally: a Command registers itself when constructed.
- new TestDistLockWithSkew();
- }
- return Status::OK();
}
- /**
- * Utility command to virtually skew the clock of a mongo server a particular amount.
- * This skews the clock globally, per-thread skew is also possible.
- */
- class SkewClockCommand: public Command {
- public:
- SkewClockCommand() :
- Command("_skewClockCommand") {
- }
- virtual void help(stringstream& help) const {
- help << "should not be calling this directly" << endl;
- }
-
- virtual bool slaveOk() const {
- return false;
- }
- virtual bool adminOnly() const {
- return true;
- }
- virtual bool isWriteCommandForConfigServer() const { return false; }
- // No auth needed because it only works when enabled via command line.
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) {}
-
- bool run(OperationContext* txn,
- const string&,
- BSONObj& cmdObj,
- int,
- string& errmsg,
- BSONObjBuilder& result) {
+ // variables for test
+ boost::thread_specific_ptr<DistributedLock> lock;
+ AtomicUInt32 count;
+ AtomicWord<bool> keepGoing;
+
+private:
+ LegacyDistLockPinger _pinger;
+};
+MONGO_INITIALIZER(RegisterDistLockWithSkewCmd)(InitializerContext* context) {
+ if (Command::testCommandsEnabled) {
+ // Leaked intentionally: a Command registers itself when constructed.
+ new TestDistLockWithSkew();
+ }
+ return Status::OK();
+}
- long long skew = (long long) number_field(cmdObj, "skew", 0);
+/**
+ * Utility command to virtually skew the clock of a mongo server a particular amount.
+ * This skews the clock globally, per-thread skew is also possible.
+ */
+class SkewClockCommand : public Command {
+public:
+ SkewClockCommand() : Command("_skewClockCommand") {}
+ virtual void help(stringstream& help) const {
+ help << "should not be calling this directly" << endl;
+ }
- log() << "Adjusting jsTime() clock skew to " << skew << endl;
+ virtual bool slaveOk() const {
+ return false;
+ }
+ virtual bool adminOnly() const {
+ return true;
+ }
+ virtual bool isWriteCommandForConfigServer() const {
+ return false;
+ }
+ // No auth needed because it only works when enabled via command line.
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {}
- jsTimeVirtualSkew( skew );
+ bool run(OperationContext* txn,
+ const string&,
+ BSONObj& cmdObj,
+ int,
+ string& errmsg,
+ BSONObjBuilder& result) {
+ long long skew = (long long)number_field(cmdObj, "skew", 0);
- log() << "JSTime adjusted, now is " << jsTime() << endl;
+ log() << "Adjusting jsTime() clock skew to " << skew << endl;
- return true;
+ jsTimeVirtualSkew(skew);
- }
+ log() << "JSTime adjusted, now is " << jsTime() << endl;
- };
- MONGO_INITIALIZER(RegisterSkewClockCmd)(InitializerContext* context) {
- if (Command::testCommandsEnabled) {
- // Leaked intentionally: a Command registers itself when constructed.
- new SkewClockCommand();
- }
- return Status::OK();
+ return true;
}
+};
+MONGO_INITIALIZER(RegisterSkewClockCmd)(InitializerContext* context) {
+ if (Command::testCommandsEnabled) {
+ // Leaked intentionally: a Command registers itself when constructed.
+ new SkewClockCommand();
+ }
+ return Status::OK();
+}
}
-
diff --git a/src/mongo/s/grid.cpp b/src/mongo/s/grid.cpp
index d687a7f8129..e36e1efac54 100644
--- a/src/mongo/s/grid.cpp
+++ b/src/mongo/s/grid.cpp
@@ -41,87 +41,83 @@
namespace mongo {
- Grid::Grid() : _allowLocalShard(true) {
+Grid::Grid() : _allowLocalShard(true) {}
- }
-
- void Grid::init(std::unique_ptr<CatalogManager> catalogManager,
- std::unique_ptr<ShardRegistry> shardRegistry) {
+void Grid::init(std::unique_ptr<CatalogManager> catalogManager,
+ std::unique_ptr<ShardRegistry> shardRegistry) {
+ invariant(!_catalogManager);
+ invariant(!_catalogCache);
+ invariant(!_shardRegistry);
- invariant(!_catalogManager);
- invariant(!_catalogCache);
- invariant(!_shardRegistry);
+ _catalogManager = std::move(catalogManager);
+ _catalogCache = stdx::make_unique<CatalogCache>(_catalogManager.get());
+ _shardRegistry = std::move(shardRegistry);
+}
- _catalogManager = std::move(catalogManager);
- _catalogCache = stdx::make_unique<CatalogCache>(_catalogManager.get());
- _shardRegistry = std::move(shardRegistry);
+StatusWith<std::shared_ptr<DBConfig>> Grid::implicitCreateDb(const std::string& dbName) {
+ auto status = catalogCache()->getDatabase(dbName);
+ if (status.isOK()) {
+ return status;
}
- StatusWith<std::shared_ptr<DBConfig>> Grid::implicitCreateDb(const std::string& dbName) {
- auto status = catalogCache()->getDatabase(dbName);
- if (status.isOK()) {
- return status;
+ if (status == ErrorCodes::DatabaseNotFound) {
+ auto statusCreateDb = catalogManager()->createDatabase(dbName);
+ if (statusCreateDb.isOK() || statusCreateDb == ErrorCodes::NamespaceExists) {
+ return catalogCache()->getDatabase(dbName);
}
- if (status == ErrorCodes::DatabaseNotFound) {
- auto statusCreateDb = catalogManager()->createDatabase(dbName);
- if (statusCreateDb.isOK() || statusCreateDb == ErrorCodes::NamespaceExists) {
- return catalogCache()->getDatabase(dbName);
- }
+ return statusCreateDb;
+ }
- return statusCreateDb;
- }
+ return status;
+}
- return status;
- }
+bool Grid::allowLocalHost() const {
+ return _allowLocalShard;
+}
- bool Grid::allowLocalHost() const {
- return _allowLocalShard;
+void Grid::setAllowLocalHost(bool allow) {
+ _allowLocalShard = allow;
+}
+
+/*
+ * Returns whether balancing is enabled, with optional namespace "ns" parameter for balancing on a particular
+ * collection.
+ */
+bool Grid::shouldBalance(const SettingsType& balancerSettings) const {
+ if (balancerSettings.isBalancerStoppedSet() && balancerSettings.getBalancerStopped()) {
+ return false;
}
- void Grid::setAllowLocalHost( bool allow ) {
- _allowLocalShard = allow;
+ if (balancerSettings.isBalancerActiveWindowSet()) {
+ boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+ return balancerSettings.inBalancingWindow(now);
}
- /*
- * Returns whether balancing is enabled, with optional namespace "ns" parameter for balancing on a particular
- * collection.
- */
- bool Grid::shouldBalance(const SettingsType& balancerSettings) const {
- if (balancerSettings.isBalancerStoppedSet() && balancerSettings.getBalancerStopped()) {
- return false;
- }
+ return true;
+}
- if (balancerSettings.isBalancerActiveWindowSet()) {
- boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
- return balancerSettings.inBalancingWindow(now);
- }
+bool Grid::getConfigShouldBalance() const {
+ auto balSettingsResult = grid.catalogManager()->getGlobalSettings(SettingsType::BalancerDocKey);
+ if (!balSettingsResult.isOK()) {
+ warning() << balSettingsResult.getStatus();
+ return false;
+ }
+ SettingsType balSettings = balSettingsResult.getValue();
+ if (!balSettings.isKeySet()) {
+ // Balancer settings doc does not exist. Default to yes.
return true;
}
- bool Grid::getConfigShouldBalance() const {
- auto balSettingsResult =
- grid.catalogManager()->getGlobalSettings(SettingsType::BalancerDocKey);
- if (!balSettingsResult.isOK()) {
- warning() << balSettingsResult.getStatus();
- return false;
- }
- SettingsType balSettings = balSettingsResult.getValue();
-
- if (!balSettings.isKeySet()) {
- // Balancer settings doc does not exist. Default to yes.
- return true;
- }
-
- return shouldBalance(balSettings);
- }
+ return shouldBalance(balSettings);
+}
- void Grid::clearForUnitTests() {
- _catalogManager.reset();
- _catalogCache.reset();
- _shardRegistry.reset();
- }
+void Grid::clearForUnitTests() {
+ _catalogManager.reset();
+ _catalogCache.reset();
+ _shardRegistry.reset();
+}
- Grid grid;
+Grid grid;
}
diff --git a/src/mongo/s/grid.h b/src/mongo/s/grid.h
index d15768ae21e..7262e71a629 100644
--- a/src/mongo/s/grid.h
+++ b/src/mongo/s/grid.h
@@ -35,82 +35,89 @@
namespace mongo {
- class BSONObj;
- class CatalogCache;
- class CatalogManager;
- class DBConfig;
- class SettingsType;
- class ShardRegistry;
- template<typename T> class StatusWith;
+class BSONObj;
+class CatalogCache;
+class CatalogManager;
+class DBConfig;
+class SettingsType;
+class ShardRegistry;
+template <typename T>
+class StatusWith;
+/**
+ * Holds the global sharding context. Single instance exists for a running server. Exists on
+ * both MongoD and MongoS.
+ */
+class Grid {
+public:
+ Grid();
+
/**
- * Holds the global sharding context. Single instance exists for a running server. Exists on
- * both MongoD and MongoS.
+ * Called at startup time so the global sharding services (catalog manager, shard registry)
+ * can be set. This method must be called once and once only for the lifetime of the
+ * service.
+ *
+ * NOTE: Unit-tests are allowed to call it more than once, provided they reset the object's
+ * state using clearForUnitTests.
*/
- class Grid {
- public:
- Grid();
-
- /**
- * Called at startup time so the global sharding services (catalog manager, shard registry)
- * can be set. This method must be called once and once only for the lifetime of the
- * service.
- *
- * NOTE: Unit-tests are allowed to call it more than once, provided they reset the object's
- * state using clearForUnitTests.
- */
- void init(std::unique_ptr<CatalogManager> catalogManager,
- std::unique_ptr<ShardRegistry> shardRegistry);
-
- /**
- * Implicitly creates the specified database as non-sharded.
- */
- StatusWith<std::shared_ptr<DBConfig>> implicitCreateDb(const std::string& dbName);
-
- /**
- * @return true if shards and config servers are allowed to use 'localhost' in address
- */
- bool allowLocalHost() const;
-
- /**
- * @param whether to allow shards and config servers to use 'localhost' in address
- */
- void setAllowLocalHost( bool allow );
-
- /**
- * Returns true if the balancer should be running. Caller is responsible
- * for making sure settings has the balancer key.
- */
- bool shouldBalance(const SettingsType& balancerSettings) const;
-
- /**
- * Returns true if the config server settings indicate that the balancer should be active.
- */
- bool getConfigShouldBalance() const;
-
- CatalogManager* catalogManager() const { return _catalogManager.get(); }
- CatalogCache* catalogCache() const { return _catalogCache.get(); }
- ShardRegistry* shardRegistry() const { return _shardRegistry.get(); }
-
- /**
- * Clears the grid object so that it can be reused between test executions. This will not
- * be necessary if grid is hanging off the global ServiceContext and each test gets its
- * own service context.
- *
- * NOTE: Do not use this outside of unit-tests.
- */
- void clearForUnitTests();
-
- private:
- std::unique_ptr<CatalogManager> _catalogManager;
- std::unique_ptr<CatalogCache> _catalogCache;
- std::unique_ptr<ShardRegistry> _shardRegistry;
-
- // can 'localhost' be used in shard addresses?
- bool _allowLocalShard;
- };
-
- extern Grid grid;
-
-} // namespace mongo
+ void init(std::unique_ptr<CatalogManager> catalogManager,
+ std::unique_ptr<ShardRegistry> shardRegistry);
+
+ /**
+ * Implicitly creates the specified database as non-sharded.
+ */
+ StatusWith<std::shared_ptr<DBConfig>> implicitCreateDb(const std::string& dbName);
+
+ /**
+ * @return true if shards and config servers are allowed to use 'localhost' in address
+ */
+ bool allowLocalHost() const;
+
+ /**
+ * @param whether to allow shards and config servers to use 'localhost' in address
+ */
+ void setAllowLocalHost(bool allow);
+
+ /**
+ * Returns true if the balancer should be running. Caller is responsible
+ * for making sure settings has the balancer key.
+ */
+ bool shouldBalance(const SettingsType& balancerSettings) const;
+
+ /**
+ * Returns true if the config server settings indicate that the balancer should be active.
+ */
+ bool getConfigShouldBalance() const;
+
+ CatalogManager* catalogManager() const {
+ return _catalogManager.get();
+ }
+ CatalogCache* catalogCache() const {
+ return _catalogCache.get();
+ }
+ ShardRegistry* shardRegistry() const {
+ return _shardRegistry.get();
+ }
+
+ /**
+ * Clears the grid object so that it can be reused between test executions. This will not
+ * be necessary if grid is hanging off the global ServiceContext and each test gets its
+ * own service context.
+ *
+ * NOTE: Do not use this outside of unit-tests.
+ */
+ void clearForUnitTests();
+
+private:
+ std::unique_ptr<CatalogManager> _catalogManager;
+ std::unique_ptr<CatalogCache> _catalogCache;
+ std::unique_ptr<ShardRegistry> _shardRegistry;
+
+ // can 'localhost' be used in shard addresses?
+ bool _allowLocalShard;
+};
+
+extern Grid grid;
+
+} // namespace mongo
diff --git a/src/mongo/s/metadata_loader.cpp b/src/mongo/s/metadata_loader.cpp
index 74ca1597a7d..1a0758f5734 100644
--- a/src/mongo/s/metadata_loader.cpp
+++ b/src/mongo/s/metadata_loader.cpp
@@ -44,265 +44,242 @@
namespace mongo {
- using std::unique_ptr;
- using std::endl;
- using std::make_pair;
- using std::map;
- using std::pair;
- using std::string;
-
- /**
- * This is an adapter so we can use config diffs - mongos and mongod do them slightly
- * differently.
- *
- * The mongod adapter here tracks only a single shard, and stores ranges by (min, max).
- */
- class SCMConfigDiffTracker : public ConfigDiffTracker<BSONObj, string> {
- public:
- SCMConfigDiffTracker(const string& currShard) : _currShard( currShard ) { }
-
- virtual bool isTracked(const ChunkType& chunk) const {
- return chunk.getShard() == _currShard;
- }
-
- virtual pair<BSONObj, BSONObj> rangeFor(const ChunkType& chunk) const {
- return make_pair(chunk.getMin(), chunk.getMax());
- }
-
- virtual string shardFor( const string& name ) const {
- return name;
- }
-
- virtual string nameFrom( const string& shard ) const {
- return shard;
- }
+using std::unique_ptr;
+using std::endl;
+using std::make_pair;
+using std::map;
+using std::pair;
+using std::string;
- string _currShard;
- };
-
- //
- // MetadataLoader implementation
- //
+/**
+ * This is an adapter so we can use config diffs - mongos and mongod do them slightly
+ * differently.
+ *
+ * The mongod adapter here tracks only a single shard, and stores ranges by (min, max).
+ */
+class SCMConfigDiffTracker : public ConfigDiffTracker<BSONObj, string> {
+public:
+ SCMConfigDiffTracker(const string& currShard) : _currShard(currShard) {}
- MetadataLoader::MetadataLoader() { }
+ virtual bool isTracked(const ChunkType& chunk) const {
+ return chunk.getShard() == _currShard;
+ }
- MetadataLoader::~MetadataLoader() { }
+ virtual pair<BSONObj, BSONObj> rangeFor(const ChunkType& chunk) const {
+ return make_pair(chunk.getMin(), chunk.getMax());
+ }
- Status MetadataLoader::makeCollectionMetadata(CatalogManager* catalogManager,
- const string& ns,
- const string& shard,
- const CollectionMetadata* oldMetadata,
- CollectionMetadata* metadata) const
- {
- Status status = _initCollection(catalogManager, ns, shard, metadata);
- if (!status.isOK() || metadata->getKeyPattern().isEmpty()) {
- return status;
- }
+ virtual string shardFor(const string& name) const {
+ return name;
+ }
- return initChunks(catalogManager, ns, shard, oldMetadata, metadata );
+ virtual string nameFrom(const string& shard) const {
+ return shard;
}
- Status MetadataLoader::_initCollection(CatalogManager* catalogManager,
- const string& ns,
- const string& shard,
- CollectionMetadata* metadata) const {
+ string _currShard;
+};
- auto coll = catalogManager->getCollection(ns);
- if (!coll.isOK()) {
- return coll.getStatus();
- }
+//
+// MetadataLoader implementation
+//
- CollectionType collInfo = coll.getValue();
- if (collInfo.getDropped()) {
- return Status(ErrorCodes::NamespaceNotFound,
- str::stream() << "could not load metadata, collection "
- << ns << " was dropped");
- }
+MetadataLoader::MetadataLoader() {}
- metadata->_keyPattern = collInfo.getKeyPattern().toBSON();
- metadata->fillKeyPatternFields();
- metadata->_shardVersion = ChunkVersion(0, 0, collInfo.getEpoch());
- metadata->_collVersion = ChunkVersion(0, 0, collInfo.getEpoch());
+MetadataLoader::~MetadataLoader() {}
- return Status::OK();
+Status MetadataLoader::makeCollectionMetadata(CatalogManager* catalogManager,
+ const string& ns,
+ const string& shard,
+ const CollectionMetadata* oldMetadata,
+ CollectionMetadata* metadata) const {
+ Status status = _initCollection(catalogManager, ns, shard, metadata);
+ if (!status.isOK() || metadata->getKeyPattern().isEmpty()) {
+ return status;
}
- Status MetadataLoader::initChunks(CatalogManager* catalogManager,
- const string& ns,
- const string& shard,
- const CollectionMetadata* oldMetadata,
- CollectionMetadata* metadata) const
- {
- map<string, ChunkVersion> versionMap;
+ return initChunks(catalogManager, ns, shard, oldMetadata, metadata);
+}
- // Preserve the epoch
- versionMap[shard] = metadata->_shardVersion;
- OID epoch = metadata->getCollVersion().epoch();
- bool fullReload = true;
-
- // Check to see if we should use the old version or not.
- if ( oldMetadata ) {
-
- // If our epochs are compatible, it's useful to use the old metadata for diffs
- if ( oldMetadata->getCollVersion().hasEqualEpoch( epoch ) ) {
-
- fullReload = false;
- invariant( oldMetadata->isValid() );
-
- versionMap[shard] = oldMetadata->_shardVersion;
- metadata->_collVersion = oldMetadata->_collVersion;
+Status MetadataLoader::_initCollection(CatalogManager* catalogManager,
+ const string& ns,
+ const string& shard,
+ CollectionMetadata* metadata) const {
+ auto coll = catalogManager->getCollection(ns);
+ if (!coll.isOK()) {
+ return coll.getStatus();
+ }
- // TODO: This could be made more efficient if copying not required, but
- // not as frequently reloaded as in mongos.
- metadata->_chunksMap = oldMetadata->_chunksMap;
+ CollectionType collInfo = coll.getValue();
+ if (collInfo.getDropped()) {
+ return Status(ErrorCodes::NamespaceNotFound,
+ str::stream() << "could not load metadata, collection " << ns
+ << " was dropped");
+ }
- LOG( 2 ) << "loading new chunks for collection " << ns
- << " using old metadata w/ version " << oldMetadata->getShardVersion()
- << " and " << metadata->_chunksMap.size() << " chunks" << endl;
- }
- else {
- warning() << "reloading collection metadata for " << ns << " with new epoch "
- << epoch.toString() << ", the current epoch is "
- << oldMetadata->getCollVersion().epoch().toString() << endl;
- }
+ metadata->_keyPattern = collInfo.getKeyPattern().toBSON();
+ metadata->fillKeyPatternFields();
+ metadata->_shardVersion = ChunkVersion(0, 0, collInfo.getEpoch());
+ metadata->_collVersion = ChunkVersion(0, 0, collInfo.getEpoch());
+
+ return Status::OK();
+}
+
+Status MetadataLoader::initChunks(CatalogManager* catalogManager,
+ const string& ns,
+ const string& shard,
+ const CollectionMetadata* oldMetadata,
+ CollectionMetadata* metadata) const {
+ map<string, ChunkVersion> versionMap;
+
+ // Preserve the epoch
+ versionMap[shard] = metadata->_shardVersion;
+ OID epoch = metadata->getCollVersion().epoch();
+ bool fullReload = true;
+
+ // Check to see if we should use the old version or not.
+ if (oldMetadata) {
+ // If our epochs are compatible, it's useful to use the old metadata for diffs
+ if (oldMetadata->getCollVersion().hasEqualEpoch(epoch)) {
+ fullReload = false;
+ invariant(oldMetadata->isValid());
+
+ versionMap[shard] = oldMetadata->_shardVersion;
+ metadata->_collVersion = oldMetadata->_collVersion;
+
+ // TODO: This could be made more efficient if copying not required, but
+ // not as frequently reloaded as in mongos.
+ metadata->_chunksMap = oldMetadata->_chunksMap;
+
+ LOG(2) << "loading new chunks for collection " << ns
+ << " using old metadata w/ version " << oldMetadata->getShardVersion() << " and "
+ << metadata->_chunksMap.size() << " chunks" << endl;
+ } else {
+ warning() << "reloading collection metadata for " << ns << " with new epoch "
+ << epoch.toString() << ", the current epoch is "
+ << oldMetadata->getCollVersion().epoch().toString() << endl;
}
+ }
- // Exposes the new metadata's range map and version to the "differ," who
- // would ultimately be responsible of filling them up.
- SCMConfigDiffTracker differ( shard );
- differ.attach( ns, metadata->_chunksMap, metadata->_collVersion, versionMap );
-
- try {
- std::vector<ChunkType> chunks;
- Status status = catalogManager->getChunks(differ.configDiffQuery(), 0, &chunks);
- if (!status.isOK()) {
- if (status == ErrorCodes::HostUnreachable) {
- // Make our metadata invalid
- metadata->_collVersion = ChunkVersion( 0, 0, OID() );
- metadata->_chunksMap.clear();
- }
- return status;
- }
-
- //
- // The diff tracker should always find at least one chunk (the highest chunk we saw
- // last time). If not, something has changed on the config server (potentially between
- // when we read the collection data and when we read the chunks data).
- //
- int diffsApplied = differ.calculateConfigDiff(chunks);
- if ( diffsApplied > 0 ) {
- // Chunks found, return ok
- LOG(2) << "loaded " << diffsApplied << " chunks into new metadata for " << ns
- << " with version " << metadata->_collVersion;
-
- metadata->_shardVersion = versionMap[shard];
- metadata->fillRanges();
-
- invariant( metadata->isValid() );
- return Status::OK();
- }
- else if ( diffsApplied == 0 ) {
-
- // No chunks found, the collection is dropping or we're confused
- // If this is a full reload, assume it is a drop for backwards compatibility
- // TODO: drop the config.collections entry *before* the chunks and eliminate this
- // ambiguity
-
- string errMsg =
- str::stream() << "no chunks found when reloading " << ns
- << ", previous version was "
- << metadata->_collVersion.toString()
- << ( fullReload ? ", this is a drop" : "" );
-
- warning() << errMsg << endl;
-
- metadata->_collVersion = ChunkVersion( 0, 0, OID() );
- metadata->_chunksMap.clear();
+ // Exposes the new metadata's range map and version to the "differ," who
+ // would ultimately be responsible of filling them up.
+ SCMConfigDiffTracker differ(shard);
+ differ.attach(ns, metadata->_chunksMap, metadata->_collVersion, versionMap);
- return fullReload ? Status( ErrorCodes::NamespaceNotFound, errMsg ) :
- Status( ErrorCodes::RemoteChangeDetected, errMsg );
- }
- else {
- // Invalid chunks found, our epoch may have changed because we dropped/recreated
- // the collection.
- string errMsg = str::stream() << "invalid chunks found when reloading " << ns
- << ", previous version was "
- << metadata->_collVersion.toString()
- << ", this should be rare";
- warning() << errMsg;
-
- metadata->_collVersion = ChunkVersion( 0, 0, OID() );
+ try {
+ std::vector<ChunkType> chunks;
+ Status status = catalogManager->getChunks(differ.configDiffQuery(), 0, &chunks);
+ if (!status.isOK()) {
+ if (status == ErrorCodes::HostUnreachable) {
+ // Make our metadata invalid
+ metadata->_collVersion = ChunkVersion(0, 0, OID());
metadata->_chunksMap.clear();
-
- return Status(ErrorCodes::RemoteChangeDetected, errMsg);
}
+ return status;
}
- catch (const DBException& e) {
- // We deliberately do not return connPtr to the pool, since it was involved with the
- // error here.
- return Status(ErrorCodes::HostUnreachable,
- str::stream() << "problem querying chunks metadata" << causedBy(e));
+
+ //
+ // The diff tracker should always find at least one chunk (the highest chunk we saw
+ // last time). If not, something has changed on the config server (potentially between
+ // when we read the collection data and when we read the chunks data).
+ //
+ int diffsApplied = differ.calculateConfigDiff(chunks);
+ if (diffsApplied > 0) {
+ // Chunks found, return ok
+ LOG(2) << "loaded " << diffsApplied << " chunks into new metadata for " << ns
+ << " with version " << metadata->_collVersion;
+
+ metadata->_shardVersion = versionMap[shard];
+ metadata->fillRanges();
+
+ invariant(metadata->isValid());
+ return Status::OK();
+ } else if (diffsApplied == 0) {
+ // No chunks found, the collection is dropping or we're confused
+ // If this is a full reload, assume it is a drop for backwards compatibility
+ // TODO: drop the config.collections entry *before* the chunks and eliminate this
+ // ambiguity
+
+ string errMsg = str::stream()
+ << "no chunks found when reloading " << ns << ", previous version was "
+ << metadata->_collVersion.toString() << (fullReload ? ", this is a drop" : "");
+
+ warning() << errMsg << endl;
+
+ metadata->_collVersion = ChunkVersion(0, 0, OID());
+ metadata->_chunksMap.clear();
+
+ return fullReload ? Status(ErrorCodes::NamespaceNotFound, errMsg)
+ : Status(ErrorCodes::RemoteChangeDetected, errMsg);
+ } else {
+ // Invalid chunks found, our epoch may have changed because we dropped/recreated
+ // the collection.
+ string errMsg = str::stream()
+ << "invalid chunks found when reloading " << ns << ", previous version was "
+ << metadata->_collVersion.toString() << ", this should be rare";
+ warning() << errMsg;
+
+ metadata->_collVersion = ChunkVersion(0, 0, OID());
+ metadata->_chunksMap.clear();
+
+ return Status(ErrorCodes::RemoteChangeDetected, errMsg);
}
+ } catch (const DBException& e) {
+ // We deliberately do not return connPtr to the pool, since it was involved with the
+ // error here.
+ return Status(ErrorCodes::HostUnreachable,
+ str::stream() << "problem querying chunks metadata" << causedBy(e));
}
+}
+
+Status MetadataLoader::promotePendingChunks(const CollectionMetadata* afterMetadata,
+ CollectionMetadata* remoteMetadata) const {
+ // Ensure pending chunks are applicable
+ bool notApplicable = (NULL == afterMetadata || NULL == remoteMetadata) ||
+ (afterMetadata->getShardVersion() > remoteMetadata->getShardVersion()) ||
+ (afterMetadata->getShardVersion().epoch() != remoteMetadata->getShardVersion().epoch());
+ if (notApplicable)
+ return Status::OK();
- Status MetadataLoader::promotePendingChunks( const CollectionMetadata* afterMetadata,
- CollectionMetadata* remoteMetadata ) const {
-
- // Ensure pending chunks are applicable
- bool notApplicable =
- ( NULL == afterMetadata || NULL == remoteMetadata ) ||
- ( afterMetadata->getShardVersion() > remoteMetadata->getShardVersion() ) ||
- ( afterMetadata->getShardVersion().epoch() !=
- remoteMetadata->getShardVersion().epoch() );
- if ( notApplicable ) return Status::OK();
-
- // The chunks from remoteMetadata are the latest version, and the pending chunks
- // from afterMetadata are the latest version. If no trickery is afoot, pending chunks
- // should match exactly zero or one loaded chunk.
+ // The chunks from remoteMetadata are the latest version, and the pending chunks
+ // from afterMetadata are the latest version. If no trickery is afoot, pending chunks
+ // should match exactly zero or one loaded chunk.
- remoteMetadata->_pendingMap = afterMetadata->_pendingMap;
+ remoteMetadata->_pendingMap = afterMetadata->_pendingMap;
- // Resolve our pending chunks against the chunks we've loaded
- for ( RangeMap::iterator it = remoteMetadata->_pendingMap.begin();
- it != remoteMetadata->_pendingMap.end(); ) {
+ // Resolve our pending chunks against the chunks we've loaded
+ for (RangeMap::iterator it = remoteMetadata->_pendingMap.begin();
+ it != remoteMetadata->_pendingMap.end();) {
+ if (!rangeMapOverlaps(remoteMetadata->_chunksMap, it->first, it->second)) {
+ ++it;
+ continue;
+ }
- if ( !rangeMapOverlaps( remoteMetadata->_chunksMap, it->first, it->second ) ) {
- ++it;
- continue;
- }
+ // Our pending range overlaps at least one chunk
- // Our pending range overlaps at least one chunk
+ if (rangeMapContains(remoteMetadata->_chunksMap, it->first, it->second)) {
+ // Chunk was promoted from pending, successful migration
+ LOG(2) << "verified chunk " << rangeToString(it->first, it->second)
+ << " was migrated earlier to this shard" << endl;
- if ( rangeMapContains( remoteMetadata->_chunksMap, it->first, it->second ) ) {
+ remoteMetadata->_pendingMap.erase(it++);
+ } else {
+ // Something strange happened, maybe manual editing of config?
+ RangeVector overlap;
+ getRangeMapOverlap(remoteMetadata->_chunksMap, it->first, it->second, &overlap);
- // Chunk was promoted from pending, successful migration
- LOG( 2 ) << "verified chunk " << rangeToString( it->first, it->second )
- << " was migrated earlier to this shard" << endl;
+ string errMsg = str::stream()
+ << "the remote metadata changed unexpectedly, pending range "
+ << rangeToString(it->first, it->second)
+ << " does not exactly overlap loaded chunks " << overlapToString(overlap);
- remoteMetadata->_pendingMap.erase( it++ );
- }
- else {
-
- // Something strange happened, maybe manual editing of config?
- RangeVector overlap;
- getRangeMapOverlap( remoteMetadata->_chunksMap,
- it->first,
- it->second,
- &overlap );
-
- string errMsg = str::stream()
- << "the remote metadata changed unexpectedly, pending range "
- << rangeToString( it->first, it->second )
- << " does not exactly overlap loaded chunks "
- << overlapToString( overlap );
-
- return Status( ErrorCodes::RemoteChangeDetected, errMsg );
- }
+ return Status(ErrorCodes::RemoteChangeDetected, errMsg);
}
-
- return Status::OK();
}
+ return Status::OK();
+}
+
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/metadata_loader.h b/src/mongo/s/metadata_loader.h
index 7d6e3d77798..15ca227926e 100644
--- a/src/mongo/s/metadata_loader.h
+++ b/src/mongo/s/metadata_loader.h
@@ -36,126 +36,124 @@
namespace mongo {
- class CatalogManager;
- class CollectionMetadata;
- class CollectionType;
- class DBClientCursor;
+class CatalogManager;
+class CollectionMetadata;
+class CollectionType;
+class DBClientCursor;
+
+/**
+ * The MetadataLoader is responsible for interfacing with the config servers and previous
+ * metadata to build new instances of CollectionMetadata. MetadataLoader is the "builder"
+ * class for metadata.
+ *
+ * CollectionMetadata has both persisted and volatile state (for now) - the persisted
+ * config server chunk state and the volatile pending state which is only tracked locally
+ * while a server is the primary. This requires a two-step loading process - the persisted
+ * chunk state *cannot* be loaded in a DBLock lock while the pending chunk state *must* be.
+ *
+ * Example usage:
+ * beforeMetadata = <get latest local metadata>;
+ * remoteMetadata = makeCollectionMetadata( beforeMetadata, remoteMetadata );
+ * DBLock lock(txn, dbname, MODE_X);
+ * afterMetadata = <get latest local metadata>;
+ * promotePendingChunks( afterMetadata, remoteMetadata );
+ *
+ * The loader will go out of its way to try to fetch the smaller amount possible of data
+ * from the config server without sacrificing the freshness and accuracy of the metadata it
+ * builds. (See ConfigDiffTracker class.)
+ *
+ * The class is not thread safe.
+ */
+class MetadataLoader {
+public:
+ explicit MetadataLoader();
+
+ ~MetadataLoader();
/**
- * The MetadataLoader is responsible for interfacing with the config servers and previous
- * metadata to build new instances of CollectionMetadata. MetadataLoader is the "builder"
- * class for metadata.
+ * Fills a new metadata instance representing the chunkset of the collection 'ns'
+ * (or its entirety, if not sharded) that lives on 'shard' with data from the config server.
+ * Optionally, uses an 'oldMetadata' for the same 'ns'/'shard'; the contents of
+ * 'oldMetadata' can help reducing the amount of data read from the config servers.
*
- * CollectionMetadata has both persisted and volatile state (for now) - the persisted
- * config server chunk state and the volatile pending state which is only tracked locally
- * while a server is the primary. This requires a two-step loading process - the persisted
- * chunk state *cannot* be loaded in a DBLock lock while the pending chunk state *must* be.
- *
- * Example usage:
- * beforeMetadata = <get latest local metadata>;
- * remoteMetadata = makeCollectionMetadata( beforeMetadata, remoteMetadata );
- * DBLock lock(txn, dbname, MODE_X);
- * afterMetadata = <get latest local metadata>;
- * promotePendingChunks( afterMetadata, remoteMetadata );
+ * Locking note:
+ * + Must not be called in a DBLock, since this loads over the network
*
- * The loader will go out of its way to try to fetch the smaller amount possible of data
- * from the config server without sacrificing the freshness and accuracy of the metadata it
- * builds. (See ConfigDiffTracker class.)
+ * OK on success.
*
- * The class is not thread safe.
+ * Failure return values:
+ * Abnormal:
+ * @return FailedToParse if there was an error parsing the remote config data
+ * Normal:
+ * @return NamespaceNotFound if the collection no longer exists
+ * @return HostUnreachable if there was an error contacting the config servers
+ * @return RemoteChangeDetected if the data loaded was modified by another operation
*/
- class MetadataLoader {
- public:
-
- explicit MetadataLoader();
-
- ~MetadataLoader();
+ Status makeCollectionMetadata(CatalogManager* catalogManager,
+ const std::string& ns,
+ const std::string& shard,
+ const CollectionMetadata* oldMetadata,
+ CollectionMetadata* metadata) const;
- /**
- * Fills a new metadata instance representing the chunkset of the collection 'ns'
- * (or its entirety, if not sharded) that lives on 'shard' with data from the config server.
- * Optionally, uses an 'oldMetadata' for the same 'ns'/'shard'; the contents of
- * 'oldMetadata' can help reducing the amount of data read from the config servers.
- *
- * Locking note:
- * + Must not be called in a DBLock, since this loads over the network
- *
- * OK on success.
- *
- * Failure return values:
- * Abnormal:
- * @return FailedToParse if there was an error parsing the remote config data
- * Normal:
- * @return NamespaceNotFound if the collection no longer exists
- * @return HostUnreachable if there was an error contacting the config servers
- * @return RemoteChangeDetected if the data loaded was modified by another operation
- */
- Status makeCollectionMetadata(CatalogManager* catalogManager,
- const std::string& ns,
- const std::string& shard,
- const CollectionMetadata* oldMetadata,
- CollectionMetadata* metadata) const;
-
- /**
- * Replaces the pending chunks of the remote metadata with the more up-to-date pending
- * chunks of the 'after' metadata (metadata from after the remote load), and removes pending
- * chunks which are now regular chunks.
- *
- * Pending chunks should always correspond to one or zero chunks in the remoteMetadata
- * if the epochs are the same and the remote version is the same or higher, otherwise they
- * are not applicable.
- *
- * Locking note:
- * + Must be called in a DBLock, to ensure validity of afterMetadata
- *
- * Returns OK if pending chunks correctly follow the rule above or are not applicable
- * Returns RemoteChangeDetected if pending chunks do not follow the rule above, indicating
- * either the config server or us has changed unexpectedly.
- * This should only occur with manual editing of the config
- * server.
- *
- * TODO: This is a bit ugly but necessary for now. If/when pending chunk info is stored on
- * the config server, this should go away.
- */
- Status promotePendingChunks( const CollectionMetadata* afterMetadata,
- CollectionMetadata* remoteMetadata ) const;
-
- private:
+ /**
+ * Replaces the pending chunks of the remote metadata with the more up-to-date pending
+ * chunks of the 'after' metadata (metadata from after the remote load), and removes pending
+ * chunks which are now regular chunks.
+ *
+ * Pending chunks should always correspond to one or zero chunks in the remoteMetadata
+ * if the epochs are the same and the remote version is the same or higher, otherwise they
+ * are not applicable.
+ *
+ * Locking note:
+ * + Must be called in a DBLock, to ensure validity of afterMetadata
+ *
+ * Returns OK if pending chunks correctly follow the rule above or are not applicable
+ * Returns RemoteChangeDetected if pending chunks do not follow the rule above, indicating
+ * either the config server or us has changed unexpectedly.
+ * This should only occur with manual editing of the config
+ * server.
+ *
+ * TODO: This is a bit ugly but necessary for now. If/when pending chunk info is stored on
+ * the config server, this should go away.
+ */
+ Status promotePendingChunks(const CollectionMetadata* afterMetadata,
+ CollectionMetadata* remoteMetadata) const;
- /**
- * Returns OK and fills in the internal state of 'metadata' with general collection
- * information, not including chunks.
- *
- * If information about the collection can be accessed or is invalid, returns:
- * @return NamespaceNotFound if the collection no longer exists
- * @return FailedToParse if there was an error parsing the remote config data
- * @return HostUnreachable if there was an error contacting the config servers
- * @return RemoteChangeDetected if the collection doc loaded is unexpectedly different
- *
- */
- Status _initCollection(CatalogManager* catalogManager,
- const std::string& ns,
- const std::string& shard,
- CollectionMetadata* metadata) const;
+private:
+ /**
+ * Returns OK and fills in the internal state of 'metadata' with general collection
+ * information, not including chunks.
+ *
+ * If information about the collection can be accessed or is invalid, returns:
+ * @return NamespaceNotFound if the collection no longer exists
+ * @return FailedToParse if there was an error parsing the remote config data
+ * @return HostUnreachable if there was an error contacting the config servers
+ * @return RemoteChangeDetected if the collection doc loaded is unexpectedly different
+ *
+ */
+ Status _initCollection(CatalogManager* catalogManager,
+ const std::string& ns,
+ const std::string& shard,
+ CollectionMetadata* metadata) const;
- /**
- * Returns OK and fills in the chunk state of 'metadata' to portray the chunks of the
- * collection 'ns' that sit in 'shard'. If provided, uses the contents of 'oldMetadata'
- * as a base (see description in initCollection above).
- *
- * If information about the chunks can be accessed or is invalid, returns:
- * @return HostUnreachable if there was an error contacting the config servers
- * @return RemoteChangeDetected if the chunks loaded are unexpectedly different
- *
- * For backwards compatibility,
- * @return NamespaceNotFound if there are no chunks loaded and an epoch change is detected
- * TODO: @return FailedToParse
- */
- Status initChunks(CatalogManager* catalogManager,
- const std::string& ns,
- const std::string& shard,
- const CollectionMetadata* oldMetadata,
- CollectionMetadata* metadata ) const;
- };
+ /**
+ * Returns OK and fills in the chunk state of 'metadata' to portray the chunks of the
+ * collection 'ns' that sit in 'shard'. If provided, uses the contents of 'oldMetadata'
+ * as a base (see description in initCollection above).
+ *
+ * If information about the chunks can be accessed or is invalid, returns:
+ * @return HostUnreachable if there was an error contacting the config servers
+ * @return RemoteChangeDetected if the chunks loaded are unexpectedly different
+ *
+ * For backwards compatibility,
+ * @return NamespaceNotFound if there are no chunks loaded and an epoch change is detected
+ * TODO: @return FailedToParse
+ */
+ Status initChunks(CatalogManager* catalogManager,
+ const std::string& ns,
+ const std::string& shard,
+ const CollectionMetadata* oldMetadata,
+ CollectionMetadata* metadata) const;
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/metadata_loader_test.cpp b/src/mongo/s/metadata_loader_test.cpp
index ad98acf1e94..bf0fc14b3c8 100644
--- a/src/mongo/s/metadata_loader_test.cpp
+++ b/src/mongo/s/metadata_loader_test.cpp
@@ -47,673 +47,666 @@
namespace {
- using namespace mongo;
+using namespace mongo;
- using std::unique_ptr;
- using std::string;
- using std::vector;
+using std::unique_ptr;
+using std::string;
+using std::vector;
- const std::string CONFIG_HOST_PORT = "$dummy_config:27017";
+const std::string CONFIG_HOST_PORT = "$dummy_config:27017";
- // TODO: Test config server down
- // TODO: Test read of chunks with new epoch
- // TODO: Test that you can properly load config using format with deprecated fields?
+// TODO: Test config server down
+// TODO: Test read of chunks with new epoch
+// TODO: Test that you can properly load config using format with deprecated fields?
- class MetadataLoaderFixture : public mongo::unittest::Test {
- public:
- void setUp() {
- ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT));
- ASSERT(configLoc.isValid());
- ASSERT_OK(_catalogManager.init(configLoc));
- }
-
- protected:
- CatalogManager* catalogManager() { return &_catalogManager; }
-
- private:
- CatalogManagerLegacy _catalogManager;
- };
-
-
- TEST_F(MetadataLoaderFixture, DroppedColl) {
- MockRemoteDBServer dummyConfig( CONFIG_HOST_PORT );
- mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() );
- MockConnRegistry::get()->addServer( &dummyConfig );
+class MetadataLoaderFixture : public mongo::unittest::Test {
+public:
+ void setUp() {
+ ConnectionString configLoc = ConnectionString(HostAndPort(CONFIG_HOST_PORT));
+ ASSERT(configLoc.isValid());
+ ASSERT_OK(_catalogManager.init(configLoc));
+ }
- CollectionType collInfo;
- collInfo.setNs(NamespaceString{"test.foo"});
- collInfo.setKeyPattern(BSON("a" << 1));
- collInfo.setUpdatedAt( Date_t() );
- collInfo.setEpoch( OID() );
- collInfo.setDropped( true );
- ASSERT_OK(collInfo.validate());
+protected:
+ CatalogManager* catalogManager() {
+ return &_catalogManager;
+ }
- dummyConfig.insert( CollectionType::ConfigNS, collInfo.toBSON() );
+private:
+ CatalogManagerLegacy _catalogManager;
+};
- MetadataLoader loader;
- string errmsg;
- CollectionMetadata metadata;
- Status status = loader.makeCollectionMetadata(catalogManager(),
- "test.foo",
- "shard0000",
- NULL, /* no old metadata */
- &metadata);
+TEST_F(MetadataLoaderFixture, DroppedColl) {
+ MockRemoteDBServer dummyConfig(CONFIG_HOST_PORT);
+ mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook());
+ MockConnRegistry::get()->addServer(&dummyConfig);
- ASSERT_EQUALS( status.code(), ErrorCodes::NamespaceNotFound );
+ CollectionType collInfo;
+ collInfo.setNs(NamespaceString{"test.foo"});
+ collInfo.setKeyPattern(BSON("a" << 1));
+ collInfo.setUpdatedAt(Date_t());
+ collInfo.setEpoch(OID());
+ collInfo.setDropped(true);
+ ASSERT_OK(collInfo.validate());
- MockConnRegistry::get()->clear();
- ScopedDbConnection::clearPool();
- }
+ dummyConfig.insert(CollectionType::ConfigNS, collInfo.toBSON());
- TEST_F(MetadataLoaderFixture, EmptyColl) {
- MockRemoteDBServer dummyConfig( CONFIG_HOST_PORT );
- mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() );
- MockConnRegistry::get()->addServer( &dummyConfig );
+ MetadataLoader loader;
- MetadataLoader loader;
+ string errmsg;
+ CollectionMetadata metadata;
+ Status status = loader.makeCollectionMetadata(catalogManager(),
+ "test.foo",
+ "shard0000",
+ NULL, /* no old metadata */
+ &metadata);
- string errmsg;
- CollectionMetadata metadata;
- Status status = loader.makeCollectionMetadata(catalogManager(),
- "test.foo",
- "shard0000",
- NULL, /* no old metadata */
- &metadata);
+ ASSERT_EQUALS(status.code(), ErrorCodes::NamespaceNotFound);
- ASSERT_EQUALS( status.code(), ErrorCodes::NamespaceNotFound );
+ MockConnRegistry::get()->clear();
+ ScopedDbConnection::clearPool();
+}
- MockConnRegistry::get()->clear();
- ScopedDbConnection::clearPool();
- }
+TEST_F(MetadataLoaderFixture, EmptyColl) {
+ MockRemoteDBServer dummyConfig(CONFIG_HOST_PORT);
+ mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook());
+ MockConnRegistry::get()->addServer(&dummyConfig);
- TEST_F(MetadataLoaderFixture, BadColl) {
- MockRemoteDBServer dummyConfig( CONFIG_HOST_PORT );
- mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() );
- MockConnRegistry::get()->addServer( &dummyConfig );
+ MetadataLoader loader;
- dummyConfig.insert(CollectionType::ConfigNS, BSON(CollectionType::fullNs("test.foo")));
+ string errmsg;
+ CollectionMetadata metadata;
+ Status status = loader.makeCollectionMetadata(catalogManager(),
+ "test.foo",
+ "shard0000",
+ NULL, /* no old metadata */
+ &metadata);
- MetadataLoader loader;
+ ASSERT_EQUALS(status.code(), ErrorCodes::NamespaceNotFound);
- string errmsg;
- CollectionMetadata metadata;
- Status status = loader.makeCollectionMetadata(catalogManager(),
- "test.foo",
- "shard0000",
- NULL, /* no old metadata */
- &metadata);
+ MockConnRegistry::get()->clear();
+ ScopedDbConnection::clearPool();
+}
- ASSERT_EQUALS(status.code(), ErrorCodes::NoSuchKey);
+TEST_F(MetadataLoaderFixture, BadColl) {
+ MockRemoteDBServer dummyConfig(CONFIG_HOST_PORT);
+ mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook());
+ MockConnRegistry::get()->addServer(&dummyConfig);
- MockConnRegistry::get()->clear();
- ScopedDbConnection::clearPool();
- }
+ dummyConfig.insert(CollectionType::ConfigNS, BSON(CollectionType::fullNs("test.foo")));
- TEST_F(MetadataLoaderFixture, BadChunk) {
- MockRemoteDBServer dummyConfig( CONFIG_HOST_PORT );
- mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() );
- MockConnRegistry::get()->addServer( &dummyConfig );
+ MetadataLoader loader;
- CollectionType collInfo;
- collInfo.setNs(NamespaceString{"test.foo"});
- collInfo.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
- collInfo.setKeyPattern( BSON("a" << 1) );
- collInfo.setEpoch( OID::gen() );
- ASSERT_OK(collInfo.validate());
+ string errmsg;
+ CollectionMetadata metadata;
+ Status status = loader.makeCollectionMetadata(catalogManager(),
+ "test.foo",
+ "shard0000",
+ NULL, /* no old metadata */
+ &metadata);
- dummyConfig.insert( CollectionType::ConfigNS, collInfo.toBSON() );
+ ASSERT_EQUALS(status.code(), ErrorCodes::NoSuchKey);
- ChunkType chunkInfo;
- chunkInfo.setNS(NamespaceString{"test.foo"});
- chunkInfo.setVersion( ChunkVersion( 1, 0, collInfo.getEpoch() ) );
- ASSERT(!chunkInfo.validate().isOK());
+ MockConnRegistry::get()->clear();
+ ScopedDbConnection::clearPool();
+}
- dummyConfig.insert( ChunkType::ConfigNS, chunkInfo.toBSON() );
+TEST_F(MetadataLoaderFixture, BadChunk) {
+ MockRemoteDBServer dummyConfig(CONFIG_HOST_PORT);
+ mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook());
+ MockConnRegistry::get()->addServer(&dummyConfig);
- MetadataLoader loader;
+ CollectionType collInfo;
+ collInfo.setNs(NamespaceString{"test.foo"});
+ collInfo.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
+ collInfo.setKeyPattern(BSON("a" << 1));
+ collInfo.setEpoch(OID::gen());
+ ASSERT_OK(collInfo.validate());
- string errmsg;
- CollectionMetadata metadata;
- Status status = loader.makeCollectionMetadata(catalogManager(),
- "test.foo",
- "shard0000",
- NULL, /* no old metadata */
- &metadata);
+ dummyConfig.insert(CollectionType::ConfigNS, collInfo.toBSON());
- ASSERT_EQUALS(status.code(), ErrorCodes::FailedToParse);
+ ChunkType chunkInfo;
+ chunkInfo.setNS(NamespaceString{"test.foo"});
+ chunkInfo.setVersion(ChunkVersion(1, 0, collInfo.getEpoch()));
+ ASSERT(!chunkInfo.validate().isOK());
- MockConnRegistry::get()->clear();
- ScopedDbConnection::clearPool();
- }
+ dummyConfig.insert(ChunkType::ConfigNS, chunkInfo.toBSON());
- class NoChunkFixture : public MetadataLoaderFixture {
- protected:
- void setUp() {
- MetadataLoaderFixture::setUp();
+ MetadataLoader loader;
- _dummyConfig.reset( new MockRemoteDBServer( CONFIG_HOST_PORT ) );
- mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() );
- MockConnRegistry::get()->addServer( _dummyConfig.get() );
+ string errmsg;
+ CollectionMetadata metadata;
+ Status status = loader.makeCollectionMetadata(catalogManager(),
+ "test.foo",
+ "shard0000",
+ NULL, /* no old metadata */
+ &metadata);
- OID epoch = OID::gen();
+ ASSERT_EQUALS(status.code(), ErrorCodes::FailedToParse);
- CollectionType collType;
- collType.setNs(NamespaceString{"test.foo"});
- collType.setKeyPattern(BSON("a" << 1));
- collType.setUnique(false);
- collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
- collType.setEpoch(epoch);
- _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON());
- }
+ MockConnRegistry::get()->clear();
+ ScopedDbConnection::clearPool();
+}
- void tearDown() {
- MockConnRegistry::get()->clear();
- ScopedDbConnection::clearPool();
- }
+class NoChunkFixture : public MetadataLoaderFixture {
+protected:
+ void setUp() {
+ MetadataLoaderFixture::setUp();
- private:
- unique_ptr<MockRemoteDBServer> _dummyConfig;
- };
+ _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT));
+ mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook());
+ MockConnRegistry::get()->addServer(_dummyConfig.get());
- TEST_F(NoChunkFixture, NoChunksIsDropped) {
- MetadataLoader loader;
+ OID epoch = OID::gen();
- CollectionMetadata metadata;
- Status status = loader.makeCollectionMetadata(catalogManager(),
- "test.foo",
- "shard0000",
- NULL, /* no old metadata */
- &metadata);
-
- // This is interpreted as a dropped ns, since we drop the chunks first
- ASSERT_EQUALS( status.code(), ErrorCodes::NamespaceNotFound );
+ CollectionType collType;
+ collType.setNs(NamespaceString{"test.foo"});
+ collType.setKeyPattern(BSON("a" << 1));
+ collType.setUnique(false);
+ collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
+ collType.setEpoch(epoch);
+ _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON());
}
- class NoChunkHereFixture : public MetadataLoaderFixture {
- protected:
- void setUp() {
- MetadataLoaderFixture::setUp();
+ void tearDown() {
+ MockConnRegistry::get()->clear();
+ ScopedDbConnection::clearPool();
+ }
- _dummyConfig.reset( new MockRemoteDBServer( CONFIG_HOST_PORT ) );
- mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() );
- MockConnRegistry::get()->addServer( _dummyConfig.get() );
+private:
+ unique_ptr<MockRemoteDBServer> _dummyConfig;
+};
- OID epoch = OID::gen();
+TEST_F(NoChunkFixture, NoChunksIsDropped) {
+ MetadataLoader loader;
- CollectionType collType;
- collType.setNs(NamespaceString{"test.foo"});
- collType.setKeyPattern( BSON("a" << 1) );
- collType.setUnique( false );
- collType.setUpdatedAt( Date_t::fromMillisSinceEpoch(1) );
- collType.setEpoch( epoch );
- ASSERT_OK(collType.validate());
-
- _dummyConfig->insert( CollectionType::ConfigNS, collType.toBSON() );
-
- // Need a chunk on another shard, otherwise the chunks are invalid in general and we
- // can't load metadata
- ChunkType chunkType;
- chunkType.setNS( "test.foo" );
- chunkType.setShard( "shard0001" );
- chunkType.setMin( BSON( "a" << MINKEY ) );
- chunkType.setMax( BSON( "a" << MAXKEY ) );
- chunkType.setVersion( ChunkVersion( 1, 0, epoch ) );
- chunkType.setName( OID::gen().toString() );
- ASSERT(chunkType.validate().isOK());
-
- _dummyConfig->insert( ChunkType::ConfigNS, chunkType.toBSON() );
- }
+ CollectionMetadata metadata;
+ Status status = loader.makeCollectionMetadata(catalogManager(),
+ "test.foo",
+ "shard0000",
+ NULL, /* no old metadata */
+ &metadata);
- MockRemoteDBServer* getDummyConfig() {
- return _dummyConfig.get();
- }
+ // This is interpreted as a dropped ns, since we drop the chunks first
+ ASSERT_EQUALS(status.code(), ErrorCodes::NamespaceNotFound);
+}
- void tearDown() {
- MockConnRegistry::get()->clear();
- ScopedDbConnection::clearPool();
- }
+class NoChunkHereFixture : public MetadataLoaderFixture {
+protected:
+ void setUp() {
+ MetadataLoaderFixture::setUp();
- private:
- unique_ptr<MockRemoteDBServer> _dummyConfig;
- };
+ _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT));
+ mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook());
+ MockConnRegistry::get()->addServer(_dummyConfig.get());
- TEST_F(NoChunkHereFixture, CheckNumChunk) {
- MetadataLoader loader;
+ OID epoch = OID::gen();
- CollectionMetadata metadata;
- Status status = loader.makeCollectionMetadata(catalogManager(),
- "test.foo",
- "shard0000",
- NULL, /* no old metadata */
- &metadata);
-
- ASSERT( status.isOK() );
- ASSERT_EQUALS( 0U, metadata.getNumChunks() );
- ASSERT_EQUALS( 1, metadata.getCollVersion().majorVersion() );
- ASSERT_EQUALS( 0, metadata.getShardVersion().majorVersion() );
- ASSERT_NOT_EQUALS( OID(), metadata.getCollVersion().epoch() );
- ASSERT_NOT_EQUALS( OID(), metadata.getShardVersion().epoch() );
+ CollectionType collType;
+ collType.setNs(NamespaceString{"test.foo"});
+ collType.setKeyPattern(BSON("a" << 1));
+ collType.setUnique(false);
+ collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
+ collType.setEpoch(epoch);
+ ASSERT_OK(collType.validate());
+
+ _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON());
+
+ // Need a chunk on another shard, otherwise the chunks are invalid in general and we
+ // can't load metadata
+ ChunkType chunkType;
+ chunkType.setNS("test.foo");
+ chunkType.setShard("shard0001");
+ chunkType.setMin(BSON("a" << MINKEY));
+ chunkType.setMax(BSON("a" << MAXKEY));
+ chunkType.setVersion(ChunkVersion(1, 0, epoch));
+ chunkType.setName(OID::gen().toString());
+ ASSERT(chunkType.validate().isOK());
+
+ _dummyConfig->insert(ChunkType::ConfigNS, chunkType.toBSON());
}
- class ConfigServerFixture : public MetadataLoaderFixture {
- protected:
- void setUp() {
- MetadataLoaderFixture::setUp();
-
- _dummyConfig.reset( new MockRemoteDBServer( CONFIG_HOST_PORT ) );
- mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() );
- MockConnRegistry::get()->addServer( _dummyConfig.get() );
-
- OID epoch = OID::gen();
- _maxCollVersion = ChunkVersion( 1, 0, epoch );
-
- CollectionType collType;
- collType.setNs(NamespaceString{"test.foo"});
- collType.setKeyPattern(BSON("a" << 1));
- collType.setUnique(false);
- collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
- collType.setEpoch(epoch);
- _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON());
+ MockRemoteDBServer* getDummyConfig() {
+ return _dummyConfig.get();
+ }
- BSONObj fooSingle = BSON(
- ChunkType::name("test.foo-a_MinKey") <<
- ChunkType::ns("test.foo") <<
- ChunkType::min(BSON("a" << MINKEY)) <<
- ChunkType::max(BSON("a" << MAXKEY)) <<
- ChunkType::DEPRECATED_lastmod(Date_t::fromMillisSinceEpoch(
- _maxCollVersion.toLong())) <<
- ChunkType::DEPRECATED_epoch(epoch) <<
- ChunkType::shard("shard0000"));
- _dummyConfig->insert( ChunkType::ConfigNS, fooSingle );
- }
+ void tearDown() {
+ MockConnRegistry::get()->clear();
+ ScopedDbConnection::clearPool();
+ }
- void tearDown() {
- MockConnRegistry::get()->clear();
- }
+private:
+ unique_ptr<MockRemoteDBServer> _dummyConfig;
+};
+
+TEST_F(NoChunkHereFixture, CheckNumChunk) {
+ MetadataLoader loader;
+
+ CollectionMetadata metadata;
+ Status status = loader.makeCollectionMetadata(catalogManager(),
+ "test.foo",
+ "shard0000",
+ NULL, /* no old metadata */
+ &metadata);
+
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(0U, metadata.getNumChunks());
+ ASSERT_EQUALS(1, metadata.getCollVersion().majorVersion());
+ ASSERT_EQUALS(0, metadata.getShardVersion().majorVersion());
+ ASSERT_NOT_EQUALS(OID(), metadata.getCollVersion().epoch());
+ ASSERT_NOT_EQUALS(OID(), metadata.getShardVersion().epoch());
+}
- ChunkVersion getMaxCollVersion() const {
- return _maxCollVersion;
- }
+class ConfigServerFixture : public MetadataLoaderFixture {
+protected:
+ void setUp() {
+ MetadataLoaderFixture::setUp();
- ChunkVersion getMaxShardVersion() const {
- return _maxCollVersion;
- }
+ _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT));
+ mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook());
+ MockConnRegistry::get()->addServer(_dummyConfig.get());
- MockRemoteDBServer* getConfigServer() const {
- return _dummyConfig.get();
- }
+ OID epoch = OID::gen();
+ _maxCollVersion = ChunkVersion(1, 0, epoch);
+
+ CollectionType collType;
+ collType.setNs(NamespaceString{"test.foo"});
+ collType.setKeyPattern(BSON("a" << 1));
+ collType.setUnique(false);
+ collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
+ collType.setEpoch(epoch);
+ _dummyConfig->insert(CollectionType::ConfigNS, collType.toBSON());
+
+ BSONObj fooSingle = BSON(
+ ChunkType::name("test.foo-a_MinKey")
+ << ChunkType::ns("test.foo") << ChunkType::min(BSON("a" << MINKEY))
+ << ChunkType::max(BSON("a" << MAXKEY))
+ << ChunkType::DEPRECATED_lastmod(Date_t::fromMillisSinceEpoch(_maxCollVersion.toLong()))
+ << ChunkType::DEPRECATED_epoch(epoch) << ChunkType::shard("shard0000"));
+ _dummyConfig->insert(ChunkType::ConfigNS, fooSingle);
+ }
- private:
- unique_ptr<MockRemoteDBServer> _dummyConfig;
- ChunkVersion _maxCollVersion;
- };
+ void tearDown() {
+ MockConnRegistry::get()->clear();
+ }
- TEST_F(ConfigServerFixture, SingleChunkCheckNumChunk) {
- MetadataLoader loader;
- CollectionMetadata metadata;
- Status status = loader.makeCollectionMetadata(catalogManager(),
- "test.foo",
- "shard0000",
- NULL, /* no old metadata */
- &metadata);
- ASSERT( status.isOK() );
- ASSERT_EQUALS( 1U, metadata.getNumChunks() );
+ ChunkVersion getMaxCollVersion() const {
+ return _maxCollVersion;
}
- TEST_F(ConfigServerFixture, SingleChunkGetNext) {
- MetadataLoader loader;
- CollectionMetadata metadata;
- loader.makeCollectionMetadata(catalogManager(),
- "test.foo",
- "shard0000",
- NULL, /* no old metadata */
- &metadata);
- ChunkType chunkInfo;
- ASSERT_TRUE(metadata.getNextChunk(metadata.getMinKey(), &chunkInfo));
+ ChunkVersion getMaxShardVersion() const {
+ return _maxCollVersion;
}
- TEST_F(ConfigServerFixture, SingleChunkGetShardKey) {
- MetadataLoader loader;
- CollectionMetadata metadata;
- loader.makeCollectionMetadata(catalogManager(),
- "test.foo",
- "shard0000",
- NULL, /* no old metadata */
- &metadata);
- ASSERT_TRUE( metadata.getKeyPattern().equal(BSON("a" << 1)) );
+ MockRemoteDBServer* getConfigServer() const {
+ return _dummyConfig.get();
}
- TEST_F(ConfigServerFixture, SingleChunkGetMaxCollVersion) {
- MetadataLoader loader;
- CollectionMetadata metadata;
- loader.makeCollectionMetadata(catalogManager(),
- "test.foo",
- "shard0000",
- NULL, /* no old metadata */
- &metadata);
+private:
+ unique_ptr<MockRemoteDBServer> _dummyConfig;
+ ChunkVersion _maxCollVersion;
+};
+
+TEST_F(ConfigServerFixture, SingleChunkCheckNumChunk) {
+ MetadataLoader loader;
+ CollectionMetadata metadata;
+ Status status = loader.makeCollectionMetadata(catalogManager(),
+ "test.foo",
+ "shard0000",
+ NULL, /* no old metadata */
+ &metadata);
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(1U, metadata.getNumChunks());
+}
- ASSERT_TRUE( getMaxCollVersion().equals( metadata.getCollVersion() ) );
- }
+TEST_F(ConfigServerFixture, SingleChunkGetNext) {
+ MetadataLoader loader;
+ CollectionMetadata metadata;
+ loader.makeCollectionMetadata(catalogManager(),
+ "test.foo",
+ "shard0000",
+ NULL, /* no old metadata */
+ &metadata);
+ ChunkType chunkInfo;
+ ASSERT_TRUE(metadata.getNextChunk(metadata.getMinKey(), &chunkInfo));
+}
- TEST_F(ConfigServerFixture, SingleChunkGetMaxShardVersion) {
- MetadataLoader loader;
- CollectionMetadata metadata;
- loader.makeCollectionMetadata(catalogManager(),
- "test.foo",
- "shard0000",
- NULL, /* no old metadata */
- &metadata);
+TEST_F(ConfigServerFixture, SingleChunkGetShardKey) {
+ MetadataLoader loader;
+ CollectionMetadata metadata;
+ loader.makeCollectionMetadata(catalogManager(),
+ "test.foo",
+ "shard0000",
+ NULL, /* no old metadata */
+ &metadata);
+ ASSERT_TRUE(metadata.getKeyPattern().equal(BSON("a" << 1)));
+}
- ASSERT_TRUE( getMaxShardVersion().equals( metadata.getShardVersion() ) );
- }
+TEST_F(ConfigServerFixture, SingleChunkGetMaxCollVersion) {
+ MetadataLoader loader;
+ CollectionMetadata metadata;
+ loader.makeCollectionMetadata(catalogManager(),
+ "test.foo",
+ "shard0000",
+ NULL, /* no old metadata */
+ &metadata);
- TEST_F(ConfigServerFixture, NoChunks) {
- getConfigServer()->remove( ChunkType::ConfigNS, BSONObj() );
+ ASSERT_TRUE(getMaxCollVersion().equals(metadata.getCollVersion()));
+}
- MetadataLoader loader;
- CollectionMetadata metadata;
- Status status = loader.makeCollectionMetadata(catalogManager(),
- "test.foo",
- "shard0000",
- NULL, /* no old metadata */
- &metadata);
-
- // NSNotFound because we're reloading with no old metadata
- ASSERT_EQUALS( status.code(), ErrorCodes::NamespaceNotFound );
- }
+TEST_F(ConfigServerFixture, SingleChunkGetMaxShardVersion) {
+ MetadataLoader loader;
+ CollectionMetadata metadata;
+ loader.makeCollectionMetadata(catalogManager(),
+ "test.foo",
+ "shard0000",
+ NULL, /* no old metadata */
+ &metadata);
- class MultipleMetadataFixture : public MetadataLoaderFixture {
- protected:
- void setUp() {
- MetadataLoaderFixture::setUp();
+ ASSERT_TRUE(getMaxShardVersion().equals(metadata.getShardVersion()));
+}
- _dummyConfig.reset( new MockRemoteDBServer( CONFIG_HOST_PORT ) );
- mongo::ConnectionString::setConnectionHook( MockConnRegistry::get()->getConnStrHook() );
- MockConnRegistry::get()->addServer( _dummyConfig.get() );
+TEST_F(ConfigServerFixture, NoChunks) {
+ getConfigServer()->remove(ChunkType::ConfigNS, BSONObj());
- ConnectionString confServerStr((HostAndPort(CONFIG_HOST_PORT)));
- ConnectionString configLoc( confServerStr );
- _loader.reset(new MetadataLoader);
- }
+ MetadataLoader loader;
+ CollectionMetadata metadata;
+ Status status = loader.makeCollectionMetadata(catalogManager(),
+ "test.foo",
+ "shard0000",
+ NULL, /* no old metadata */
+ &metadata);
- MetadataLoader& loader() {
- return *_loader;
- }
+ // NSNotFound because we're reloading with no old metadata
+ ASSERT_EQUALS(status.code(), ErrorCodes::NamespaceNotFound);
+}
- void getMetadataFor( const OwnedPointerVector<ChunkType>& chunks,
- CollectionMetadata* metadata ) {
+class MultipleMetadataFixture : public MetadataLoaderFixture {
+protected:
+ void setUp() {
+ MetadataLoaderFixture::setUp();
- // Infer namespace, shard, epoch, keypattern from first chunk
- const ChunkType* firstChunk = *( chunks.vector().begin() );
+ _dummyConfig.reset(new MockRemoteDBServer(CONFIG_HOST_PORT));
+ mongo::ConnectionString::setConnectionHook(MockConnRegistry::get()->getConnStrHook());
+ MockConnRegistry::get()->addServer(_dummyConfig.get());
- const string ns = firstChunk->getNS();
- const string shardName = firstChunk->getShard();
- const OID epoch = firstChunk->getVersion().epoch();
+ ConnectionString confServerStr((HostAndPort(CONFIG_HOST_PORT)));
+ ConnectionString configLoc(confServerStr);
+ _loader.reset(new MetadataLoader);
+ }
- BSONObjBuilder keyPatternB;
- BSONObjIterator keyPatternIt( firstChunk->getMin() );
- while ( keyPatternIt.more() )
- keyPatternB.append( keyPatternIt.next().fieldName(), 1 );
- BSONObj keyPattern = keyPatternB.obj();
+ MetadataLoader& loader() {
+ return *_loader;
+ }
- _dummyConfig->remove( CollectionType::ConfigNS, BSONObj() );
- _dummyConfig->remove( ChunkType::ConfigNS, BSONObj() );
+ void getMetadataFor(const OwnedPointerVector<ChunkType>& chunks, CollectionMetadata* metadata) {
+ // Infer namespace, shard, epoch, keypattern from first chunk
+ const ChunkType* firstChunk = *(chunks.vector().begin());
+
+ const string ns = firstChunk->getNS();
+ const string shardName = firstChunk->getShard();
+ const OID epoch = firstChunk->getVersion().epoch();
- CollectionType coll;
- coll.setNs(NamespaceString{ns});
- coll.setKeyPattern( BSON( "a" << 1 ) );
- coll.setUpdatedAt( Date_t::fromMillisSinceEpoch(1) );
- coll.setEpoch( epoch );
- ASSERT_OK(coll.validate());
+ BSONObjBuilder keyPatternB;
+ BSONObjIterator keyPatternIt(firstChunk->getMin());
+ while (keyPatternIt.more())
+ keyPatternB.append(keyPatternIt.next().fieldName(), 1);
+ BSONObj keyPattern = keyPatternB.obj();
- _dummyConfig->insert( CollectionType::ConfigNS, coll.toBSON() );
+ _dummyConfig->remove(CollectionType::ConfigNS, BSONObj());
+ _dummyConfig->remove(ChunkType::ConfigNS, BSONObj());
- ChunkVersion version( 1, 0, epoch );
- for (const auto chunkVal : chunks.vector()) {
- ChunkType chunk(*chunkVal);
+ CollectionType coll;
+ coll.setNs(NamespaceString{ns});
+ coll.setKeyPattern(BSON("a" << 1));
+ coll.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
+ coll.setEpoch(epoch);
+ ASSERT_OK(coll.validate());
- chunk.setName(OID::gen().toString());
- if (!chunk.isVersionSet()) {
- chunk.setVersion(version);
- version.incMajor();
- }
+ _dummyConfig->insert(CollectionType::ConfigNS, coll.toBSON());
- ASSERT(chunk.validate().isOK());
+ ChunkVersion version(1, 0, epoch);
+ for (const auto chunkVal : chunks.vector()) {
+ ChunkType chunk(*chunkVal);
- _dummyConfig->insert(ChunkType::ConfigNS, chunk.toBSON());
+ chunk.setName(OID::gen().toString());
+ if (!chunk.isVersionSet()) {
+ chunk.setVersion(version);
+ version.incMajor();
}
- Status status = loader().makeCollectionMetadata(catalogManager(),
- ns,
- shardName,
- NULL,
- metadata);
- ASSERT(status.isOK());
- }
+ ASSERT(chunk.validate().isOK());
- void tearDown() {
- MockConnRegistry::get()->clear();
+ _dummyConfig->insert(ChunkType::ConfigNS, chunk.toBSON());
}
- private:
- unique_ptr<MockRemoteDBServer> _dummyConfig;
- unique_ptr<MetadataLoader> _loader;
- };
+ Status status =
+ loader().makeCollectionMetadata(catalogManager(), ns, shardName, NULL, metadata);
+ ASSERT(status.isOK());
+ }
- TEST_F(MultipleMetadataFixture, PromotePendingNA) {
- unique_ptr<ChunkType> chunk( new ChunkType() );
- chunk->setNS("foo.bar");
- chunk->setShard("shard0000");
- chunk->setMin( BSON( "x" << MINKEY ) );
- chunk->setMax( BSON( "x" << 0 ) );
- chunk->setVersion( ChunkVersion( 1, 0, OID::gen() ) );
+ void tearDown() {
+ MockConnRegistry::get()->clear();
+ }
- OwnedPointerVector<ChunkType> chunks;
- chunks.mutableVector().push_back( chunk.release() );
+private:
+ unique_ptr<MockRemoteDBServer> _dummyConfig;
+ unique_ptr<MetadataLoader> _loader;
+};
- CollectionMetadata afterMetadata;
- getMetadataFor( chunks, &afterMetadata );
+TEST_F(MultipleMetadataFixture, PromotePendingNA) {
+ unique_ptr<ChunkType> chunk(new ChunkType());
+ chunk->setNS("foo.bar");
+ chunk->setShard("shard0000");
+ chunk->setMin(BSON("x" << MINKEY));
+ chunk->setMax(BSON("x" << 0));
+ chunk->setVersion(ChunkVersion(1, 0, OID::gen()));
- // Metadata of different epoch
- ( *chunks.vector().begin() )->setVersion( ChunkVersion( 1, 0, OID::gen() ) );
+ OwnedPointerVector<ChunkType> chunks;
+ chunks.mutableVector().push_back(chunk.release());
- CollectionMetadata remoteMetadata;
- getMetadataFor( chunks, &remoteMetadata );
+ CollectionMetadata afterMetadata;
+ getMetadataFor(chunks, &afterMetadata);
- Status status = loader().promotePendingChunks( &afterMetadata, &remoteMetadata );
- ASSERT( status.isOK() );
+ // Metadata of different epoch
+ (*chunks.vector().begin())->setVersion(ChunkVersion(1, 0, OID::gen()));
- string errMsg;
- ChunkType pending;
- pending.setMin( BSON( "x" << 0 ) );
- pending.setMax( BSON( "x" << 10 ) );
+ CollectionMetadata remoteMetadata;
+ getMetadataFor(chunks, &remoteMetadata);
- unique_ptr<CollectionMetadata> cloned( afterMetadata.clonePlusPending( pending, &errMsg ) );
- ASSERT( cloned != NULL );
+ Status status = loader().promotePendingChunks(&afterMetadata, &remoteMetadata);
+ ASSERT(status.isOK());
- status = loader().promotePendingChunks( cloned.get(), &remoteMetadata );
- ASSERT( status.isOK() );
- ASSERT_EQUALS( remoteMetadata.getNumPending(), 0u );
- }
+ string errMsg;
+ ChunkType pending;
+ pending.setMin(BSON("x" << 0));
+ pending.setMax(BSON("x" << 10));
- TEST_F(MultipleMetadataFixture, PromotePendingNAVersion) {
- OID epoch = OID::gen();
+ unique_ptr<CollectionMetadata> cloned(afterMetadata.clonePlusPending(pending, &errMsg));
+ ASSERT(cloned != NULL);
- unique_ptr<ChunkType> chunk( new ChunkType() );
- chunk->setNS("foo.bar");
- chunk->setShard("shard0000");
- chunk->setMin( BSON( "x" << MINKEY ) );
- chunk->setMax( BSON( "x" << 0 ) );
- chunk->setVersion( ChunkVersion( 1, 1, epoch ) );
+ status = loader().promotePendingChunks(cloned.get(), &remoteMetadata);
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(remoteMetadata.getNumPending(), 0u);
+}
- OwnedPointerVector<ChunkType> chunks;
- chunks.mutableVector().push_back( chunk.release() );
+TEST_F(MultipleMetadataFixture, PromotePendingNAVersion) {
+ OID epoch = OID::gen();
- CollectionMetadata afterMetadata;
- getMetadataFor( chunks, &afterMetadata );
+ unique_ptr<ChunkType> chunk(new ChunkType());
+ chunk->setNS("foo.bar");
+ chunk->setShard("shard0000");
+ chunk->setMin(BSON("x" << MINKEY));
+ chunk->setMax(BSON("x" << 0));
+ chunk->setVersion(ChunkVersion(1, 1, epoch));
- // Metadata of same epoch, but lower version
- ( *chunks.vector().begin() )->setVersion( ChunkVersion( 1, 0, epoch ) );
+ OwnedPointerVector<ChunkType> chunks;
+ chunks.mutableVector().push_back(chunk.release());
- CollectionMetadata remoteMetadata;
- getMetadataFor( chunks, &remoteMetadata );
+ CollectionMetadata afterMetadata;
+ getMetadataFor(chunks, &afterMetadata);
- Status status = loader().promotePendingChunks( &afterMetadata, &remoteMetadata );
- ASSERT( status.isOK() );
+ // Metadata of same epoch, but lower version
+ (*chunks.vector().begin())->setVersion(ChunkVersion(1, 0, epoch));
- string errMsg;
- ChunkType pending;
- pending.setMin( BSON( "x" << 0 ) );
- pending.setMax( BSON( "x" << 10 ) );
+ CollectionMetadata remoteMetadata;
+ getMetadataFor(chunks, &remoteMetadata);
- unique_ptr<CollectionMetadata> cloned( afterMetadata.clonePlusPending( pending, &errMsg ) );
- ASSERT( cloned != NULL );
+ Status status = loader().promotePendingChunks(&afterMetadata, &remoteMetadata);
+ ASSERT(status.isOK());
- status = loader().promotePendingChunks( cloned.get(), &remoteMetadata );
- ASSERT( status.isOK() );
- ASSERT_EQUALS( remoteMetadata.getNumPending(), 0u );
+ string errMsg;
+ ChunkType pending;
+ pending.setMin(BSON("x" << 0));
+ pending.setMax(BSON("x" << 10));
- }
+ unique_ptr<CollectionMetadata> cloned(afterMetadata.clonePlusPending(pending, &errMsg));
+ ASSERT(cloned != NULL);
- TEST_F(MultipleMetadataFixture, PromotePendingGoodOverlap) {
- OID epoch = OID::gen();
+ status = loader().promotePendingChunks(cloned.get(), &remoteMetadata);
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(remoteMetadata.getNumPending(), 0u);
+}
- //
- // Setup chunk range for remote metadata
- //
+TEST_F(MultipleMetadataFixture, PromotePendingGoodOverlap) {
+ OID epoch = OID::gen();
- OwnedPointerVector<ChunkType> chunks;
+ //
+ // Setup chunk range for remote metadata
+ //
- unique_ptr<ChunkType> chunk( new ChunkType() );
- chunk->setNS("foo.bar");
- chunk->setShard("shard0000");
- chunk->setMin( BSON( "x" << MINKEY ) );
- chunk->setMax( BSON( "x" << 0 ) );
- chunk->setVersion( ChunkVersion( 1, 0, epoch ) );
- chunks.mutableVector().push_back( chunk.release() );
+ OwnedPointerVector<ChunkType> chunks;
- chunk.reset( new ChunkType() );
- chunk->setNS("foo.bar");
- chunk->setShard("shard0000");
- chunk->setMin( BSON( "x" << 10 ) );
- chunk->setMax( BSON( "x" << 20 ) );
- chunks.mutableVector().push_back( chunk.release() );
+ unique_ptr<ChunkType> chunk(new ChunkType());
+ chunk->setNS("foo.bar");
+ chunk->setShard("shard0000");
+ chunk->setMin(BSON("x" << MINKEY));
+ chunk->setMax(BSON("x" << 0));
+ chunk->setVersion(ChunkVersion(1, 0, epoch));
+ chunks.mutableVector().push_back(chunk.release());
- chunk.reset( new ChunkType() );
- chunk->setNS("foo.bar");
- chunk->setShard("shard0000");
- chunk->setMin( BSON( "x" << 30 ) );
- chunk->setMax( BSON( "x" << MAXKEY ) );\
- chunks.mutableVector().push_back( chunk.release() );
+ chunk.reset(new ChunkType());
+ chunk->setNS("foo.bar");
+ chunk->setShard("shard0000");
+ chunk->setMin(BSON("x" << 10));
+ chunk->setMax(BSON("x" << 20));
+ chunks.mutableVector().push_back(chunk.release());
- CollectionMetadata remoteMetadata;
- getMetadataFor( chunks, &remoteMetadata );
+ chunk.reset(new ChunkType());
+ chunk->setNS("foo.bar");
+ chunk->setShard("shard0000");
+ chunk->setMin(BSON("x" << 30));
+ chunk->setMax(BSON("x" << MAXKEY));
+ chunks.mutableVector().push_back(chunk.release());
- //
- // Setup chunk and pending range for afterMetadata
- //
+ CollectionMetadata remoteMetadata;
+ getMetadataFor(chunks, &remoteMetadata);
- chunks.clear();
+ //
+ // Setup chunk and pending range for afterMetadata
+ //
- chunk.reset( new ChunkType() );
- chunk->setNS("foo.bar");
- chunk->setShard("shard0000");
- chunk->setMin( BSON( "x" << 0 ) );
- chunk->setMax( BSON( "x" << 10 ) );
- chunk->setVersion( ChunkVersion( 1, 0, epoch ) );
+ chunks.clear();
- chunks.mutableVector().push_back( chunk.release() );
+ chunk.reset(new ChunkType());
+ chunk->setNS("foo.bar");
+ chunk->setShard("shard0000");
+ chunk->setMin(BSON("x" << 0));
+ chunk->setMax(BSON("x" << 10));
+ chunk->setVersion(ChunkVersion(1, 0, epoch));
- CollectionMetadata afterMetadata;
- getMetadataFor( chunks, &afterMetadata );
+ chunks.mutableVector().push_back(chunk.release());
- string errMsg;
- ChunkType pending;
- pending.setMin( BSON( "x" << MINKEY ) );
- pending.setMax( BSON( "x" << 0 ) );
+ CollectionMetadata afterMetadata;
+ getMetadataFor(chunks, &afterMetadata);
- unique_ptr<CollectionMetadata> cloned( afterMetadata.clonePlusPending( pending, &errMsg ) );
- ASSERT( cloned != NULL );
+ string errMsg;
+ ChunkType pending;
+ pending.setMin(BSON("x" << MINKEY));
+ pending.setMax(BSON("x" << 0));
- pending.setMin( BSON( "x" << 10 ) );
- pending.setMax( BSON( "x" << 20 ) );
+ unique_ptr<CollectionMetadata> cloned(afterMetadata.clonePlusPending(pending, &errMsg));
+ ASSERT(cloned != NULL);
- cloned.reset( cloned->clonePlusPending( pending, &errMsg ) );
- ASSERT( cloned != NULL );
+ pending.setMin(BSON("x" << 10));
+ pending.setMax(BSON("x" << 20));
- pending.setMin( BSON( "x" << 20 ) );
- pending.setMax( BSON( "x" << 30 ) );
+ cloned.reset(cloned->clonePlusPending(pending, &errMsg));
+ ASSERT(cloned != NULL);
- cloned.reset( cloned->clonePlusPending( pending, &errMsg ) );
- ASSERT( cloned != NULL );
+ pending.setMin(BSON("x" << 20));
+ pending.setMax(BSON("x" << 30));
- pending.setMin( BSON( "x" << 30 ) );
- pending.setMax( BSON( "x" << MAXKEY ) );
+ cloned.reset(cloned->clonePlusPending(pending, &errMsg));
+ ASSERT(cloned != NULL);
- cloned.reset( cloned->clonePlusPending( pending, &errMsg ) );
- ASSERT( cloned != NULL );
+ pending.setMin(BSON("x" << 30));
+ pending.setMax(BSON("x" << MAXKEY));
- Status status = loader().promotePendingChunks( cloned.get(), &remoteMetadata );
- ASSERT( status.isOK() );
+ cloned.reset(cloned->clonePlusPending(pending, &errMsg));
+ ASSERT(cloned != NULL);
- ASSERT_EQUALS( remoteMetadata.getNumPending(), 1u );
- ASSERT( remoteMetadata.keyIsPending( BSON( "x" << 25 ) ) );
- }
+ Status status = loader().promotePendingChunks(cloned.get(), &remoteMetadata);
+ ASSERT(status.isOK());
- TEST_F(MultipleMetadataFixture, PromotePendingBadOverlap) {
- OID epoch = OID::gen();
+ ASSERT_EQUALS(remoteMetadata.getNumPending(), 1u);
+ ASSERT(remoteMetadata.keyIsPending(BSON("x" << 25)));
+}
- //
- // Setup chunk range for remote metadata
- //
+TEST_F(MultipleMetadataFixture, PromotePendingBadOverlap) {
+ OID epoch = OID::gen();
- OwnedPointerVector<ChunkType> chunks;
+ //
+ // Setup chunk range for remote metadata
+ //
- unique_ptr<ChunkType> chunk( new ChunkType() );
- chunk->setNS("foo.bar");
- chunk->setShard("shard0000");
- chunk->setMin( BSON( "x" << MINKEY ) );
- chunk->setMax( BSON( "x" << 0 ) );
- chunk->setVersion( ChunkVersion( 1, 0, epoch ) );
+ OwnedPointerVector<ChunkType> chunks;
- chunks.mutableVector().push_back( chunk.release() );
+ unique_ptr<ChunkType> chunk(new ChunkType());
+ chunk->setNS("foo.bar");
+ chunk->setShard("shard0000");
+ chunk->setMin(BSON("x" << MINKEY));
+ chunk->setMax(BSON("x" << 0));
+ chunk->setVersion(ChunkVersion(1, 0, epoch));
- CollectionMetadata remoteMetadata;
- getMetadataFor( chunks, &remoteMetadata );
+ chunks.mutableVector().push_back(chunk.release());
- //
- // Setup chunk and pending range for afterMetadata
- //
+ CollectionMetadata remoteMetadata;
+ getMetadataFor(chunks, &remoteMetadata);
- chunks.clear();
+ //
+ // Setup chunk and pending range for afterMetadata
+ //
- chunk.reset( new ChunkType() );
- chunk->setNS("foo.bar");
- chunk->setShard("shard0000");
- chunk->setMin( BSON( "x" << 15 ) );
- chunk->setMax( BSON( "x" << MAXKEY ) );
- chunk->setVersion( ChunkVersion( 1, 0, epoch ) );
+ chunks.clear();
- chunks.mutableVector().push_back( chunk.release() );
+ chunk.reset(new ChunkType());
+ chunk->setNS("foo.bar");
+ chunk->setShard("shard0000");
+ chunk->setMin(BSON("x" << 15));
+ chunk->setMax(BSON("x" << MAXKEY));
+ chunk->setVersion(ChunkVersion(1, 0, epoch));
- CollectionMetadata afterMetadata;
- getMetadataFor( chunks, &afterMetadata );
+ chunks.mutableVector().push_back(chunk.release());
- string errMsg;
- ChunkType pending;
- pending.setMin( BSON( "x" << MINKEY ) );
- pending.setMax( BSON( "x" << 1 ) );
+ CollectionMetadata afterMetadata;
+ getMetadataFor(chunks, &afterMetadata);
- unique_ptr<CollectionMetadata> cloned( afterMetadata.clonePlusPending( pending, &errMsg ) );
- ASSERT( cloned != NULL );
+ string errMsg;
+ ChunkType pending;
+ pending.setMin(BSON("x" << MINKEY));
+ pending.setMax(BSON("x" << 1));
- cloned.reset( cloned->clonePlusPending( pending, &errMsg ) );
- ASSERT( cloned != NULL );
+ unique_ptr<CollectionMetadata> cloned(afterMetadata.clonePlusPending(pending, &errMsg));
+ ASSERT(cloned != NULL);
- Status status = loader().promotePendingChunks( cloned.get(), &remoteMetadata );
- ASSERT_EQUALS( status.code(), ErrorCodes::RemoteChangeDetected );
- }
+ cloned.reset(cloned->clonePlusPending(pending, &errMsg));
+ ASSERT(cloned != NULL);
+
+ Status status = loader().promotePendingChunks(cloned.get(), &remoteMetadata);
+ ASSERT_EQUALS(status.code(), ErrorCodes::RemoteChangeDetected);
+}
#if 0
// TODO: MockServer functionality does not support selective query - consider
diff --git a/src/mongo/s/mock_ns_targeter.h b/src/mongo/s/mock_ns_targeter.h
index feb07602c69..6958c66f5a2 100644
--- a/src/mongo/s/mock_ns_targeter.h
+++ b/src/mongo/s/mock_ns_targeter.h
@@ -37,195 +37,180 @@
namespace mongo {
- /**
- * A MockRange represents a range with endpoint that a MockNSTargeter uses to direct writes to
- * a particular endpoint.
- */
- struct MockRange {
+/**
+ * A MockRange represents a range with endpoint that a MockNSTargeter uses to direct writes to
+ * a particular endpoint.
+ */
+struct MockRange {
+ MockRange(const ShardEndpoint& endpoint,
+ const NamespaceString nss,
+ const BSONObj& minKey,
+ const BSONObj& maxKey)
+ : endpoint(endpoint), range(nss, minKey, maxKey, getKeyPattern(minKey)) {}
+
+ MockRange(const ShardEndpoint& endpoint, const KeyRange& range)
+ : endpoint(endpoint), range(range) {}
+
+ static BSONObj getKeyPattern(const BSONObj& key) {
+ BSONObjIterator it(key);
+ BSONObjBuilder objB;
+ while (it.more())
+ objB.append(it.next().fieldName(), 1);
+ return objB.obj();
+ }
- MockRange( const ShardEndpoint& endpoint,
- const NamespaceString nss,
- const BSONObj& minKey,
- const BSONObj& maxKey ) :
- endpoint( endpoint ), range( nss, minKey, maxKey, getKeyPattern( minKey ) ) {
+ const ShardEndpoint endpoint;
+ const KeyRange range;
+};
- }
+/**
+ * A MockNSTargeter directs writes to particular endpoints based on a list of MockRanges given
+ * to the mock targeter on initialization.
+ *
+ * No refreshing behavior is currently supported.
+ */
+class MockNSTargeter : public NSTargeter {
+public:
+ void init(const std::vector<MockRange*> mockRanges) {
+ ASSERT(!mockRanges.empty());
+ _mockRanges.mutableVector().insert(
+ _mockRanges.mutableVector().end(), mockRanges.begin(), mockRanges.end());
+ _nss = NamespaceString(_mockRanges.vector().front()->range.ns);
+ }
- MockRange( const ShardEndpoint& endpoint, const KeyRange& range ) :
- endpoint( endpoint ), range( range ) {
- }
+ const NamespaceString& getNS() const {
+ return _nss;
+ }
- static BSONObj getKeyPattern( const BSONObj& key ) {
- BSONObjIterator it( key );
- BSONObjBuilder objB;
- while ( it.more() )
- objB.append( it.next().fieldName(), 1 );
- return objB.obj();
- }
+ /**
+ * Returns a ShardEndpoint for the doc from the mock ranges
+ */
+ Status targetInsert(const BSONObj& doc, ShardEndpoint** endpoint) const {
+ std::vector<ShardEndpoint*> endpoints;
+ Status status = targetQuery(doc, &endpoints);
+ if (!status.isOK())
+ return status;
+ if (!endpoints.empty())
+ *endpoint = endpoints.front();
+ return Status::OK();
+ }
- const ShardEndpoint endpoint;
- const KeyRange range;
- };
+ /**
+ * Returns the first ShardEndpoint for the query from the mock ranges. Only can handle
+ * queries of the form { field : { $gte : <value>, $lt : <value> } }.
+ */
+ Status targetUpdate(const BatchedUpdateDocument& updateDoc,
+ std::vector<ShardEndpoint*>* endpoints) const {
+ return targetQuery(updateDoc.getQuery(), endpoints);
+ }
/**
- * A MockNSTargeter directs writes to particular endpoints based on a list of MockRanges given
- * to the mock targeter on initialization.
- *
- * No refreshing behavior is currently supported.
+ * Returns the first ShardEndpoint for the query from the mock ranges. Only can handle
+ * queries of the form { field : { $gte : <value>, $lt : <value> } }.
*/
- class MockNSTargeter : public NSTargeter {
- public:
-
- void init( const std::vector<MockRange*> mockRanges ) {
- ASSERT( !mockRanges.empty() );
- _mockRanges.mutableVector().insert( _mockRanges.mutableVector().end(),
- mockRanges.begin(),
- mockRanges.end() );
- _nss = NamespaceString( _mockRanges.vector().front()->range.ns );
- }
+ Status targetDelete(const BatchedDeleteDocument& deleteDoc,
+ std::vector<ShardEndpoint*>* endpoints) const {
+ return targetQuery(deleteDoc.getQuery(), endpoints);
+ }
- const NamespaceString& getNS() const {
- return _nss;
- }
+ Status targetCollection(std::vector<ShardEndpoint*>* endpoints) const {
+ // TODO: XXX
+ // No-op
+ return Status::OK();
+ }
- /**
- * Returns a ShardEndpoint for the doc from the mock ranges
- */
- Status targetInsert( const BSONObj& doc, ShardEndpoint** endpoint ) const {
- std::vector<ShardEndpoint*> endpoints;
- Status status = targetQuery( doc, &endpoints );
- if ( !status.isOK() )
- return status;
- if ( !endpoints.empty() )
- *endpoint = endpoints.front();
- return Status::OK();
+ Status targetAllShards(std::vector<ShardEndpoint*>* endpoints) const {
+ const std::vector<MockRange*>& ranges = getRanges();
+ for (std::vector<MockRange*>::const_iterator it = ranges.begin(); it != ranges.end();
+ ++it) {
+ const MockRange* range = *it;
+ endpoints->push_back(new ShardEndpoint(range->endpoint));
}
- /**
- * Returns the first ShardEndpoint for the query from the mock ranges. Only can handle
- * queries of the form { field : { $gte : <value>, $lt : <value> } }.
- */
- Status targetUpdate( const BatchedUpdateDocument& updateDoc,
- std::vector<ShardEndpoint*>* endpoints ) const {
- return targetQuery( updateDoc.getQuery(), endpoints );
- }
+ return Status::OK();
+ }
- /**
- * Returns the first ShardEndpoint for the query from the mock ranges. Only can handle
- * queries of the form { field : { $gte : <value>, $lt : <value> } }.
- */
- Status targetDelete( const BatchedDeleteDocument& deleteDoc,
- std::vector<ShardEndpoint*>* endpoints ) const {
- return targetQuery( deleteDoc.getQuery(), endpoints );
- }
+ void noteCouldNotTarget() {
+ // No-op
+ }
- Status targetCollection( std::vector<ShardEndpoint*>* endpoints ) const {
- // TODO: XXX
- // No-op
- return Status::OK();
- }
+ void noteStaleResponse(const ShardEndpoint& endpoint, const BSONObj& staleInfo) {
+ // No-op
+ }
- Status targetAllShards( std::vector<ShardEndpoint*>* endpoints ) const {
- const std::vector<MockRange*>& ranges = getRanges();
- for ( std::vector<MockRange*>::const_iterator it = ranges.begin(); it != ranges.end();
- ++it ) {
+ Status refreshIfNeeded(bool* wasChanged) {
+ // No-op
+ if (wasChanged)
+ *wasChanged = false;
+ return Status::OK();
+ }
- const MockRange* range = *it;
- endpoints->push_back( new ShardEndpoint( range->endpoint ) );
- }
+ const std::vector<MockRange*>& getRanges() const {
+ return _mockRanges.vector();
+ }
- return Status::OK();
- }
+private:
+ KeyRange parseRange(const BSONObj& query) const {
+ std::string fieldName = query.firstElement().fieldName();
- void noteCouldNotTarget() {
- // No-op
- }
+ if (query.firstElement().isNumber()) {
+ return KeyRange("",
+ BSON(fieldName << query.firstElement().numberInt()),
+ BSON(fieldName << query.firstElement().numberInt() + 1),
+ BSON(fieldName << 1));
+ } else if (query.firstElement().type() == Object) {
+ BSONObj queryRange = query.firstElement().Obj();
- void noteStaleResponse( const ShardEndpoint& endpoint, const BSONObj& staleInfo ) {
- // No-op
- }
+ ASSERT(!queryRange[GTE.l_].eoo());
+ ASSERT(!queryRange[LT.l_].eoo());
- Status refreshIfNeeded( bool* wasChanged ) {
- // No-op
- if ( wasChanged )
- *wasChanged = false;
- return Status::OK();
- }
+ BSONObjBuilder minKeyB;
+ minKeyB.appendAs(queryRange[GTE.l_], fieldName);
+ BSONObjBuilder maxKeyB;
+ maxKeyB.appendAs(queryRange[LT.l_], fieldName);
- const std::vector<MockRange*>& getRanges() const {
- return _mockRanges.vector();
+ return KeyRange("", minKeyB.obj(), maxKeyB.obj(), BSON(fieldName << 1));
}
- private:
-
- KeyRange parseRange( const BSONObj& query ) const {
-
- std::string fieldName = query.firstElement().fieldName();
-
- if ( query.firstElement().isNumber() ) {
-
- return KeyRange( "",
- BSON( fieldName << query.firstElement().numberInt() ),
- BSON( fieldName << query.firstElement().numberInt() + 1 ),
- BSON( fieldName << 1 ) );
- }
- else if ( query.firstElement().type() == Object ) {
-
- BSONObj queryRange = query.firstElement().Obj();
-
- ASSERT( !queryRange[GTE.l_].eoo() );
- ASSERT( !queryRange[LT.l_].eoo() );
-
- BSONObjBuilder minKeyB;
- minKeyB.appendAs( queryRange[GTE.l_], fieldName );
- BSONObjBuilder maxKeyB;
- maxKeyB.appendAs( queryRange[LT.l_], fieldName );
+ ASSERT(false);
+ return KeyRange("", BSONObj(), BSONObj(), BSONObj());
+ }
- return KeyRange( "", minKeyB.obj(), maxKeyB.obj(), BSON( fieldName << 1 ) );
+ /**
+ * Returns the first ShardEndpoint for the query from the mock ranges. Only can handle
+ * queries of the form { field : { $gte : <value>, $lt : <value> } }.
+ */
+ Status targetQuery(const BSONObj& query, std::vector<ShardEndpoint*>* endpoints) const {
+ KeyRange queryRange = parseRange(query);
+
+ const std::vector<MockRange*>& ranges = getRanges();
+ for (std::vector<MockRange*>::const_iterator it = ranges.begin(); it != ranges.end();
+ ++it) {
+ const MockRange* range = *it;
+
+ if (rangeOverlaps(queryRange.minKey,
+ queryRange.maxKey,
+ range->range.minKey,
+ range->range.maxKey)) {
+ endpoints->push_back(new ShardEndpoint(range->endpoint));
}
-
- ASSERT( false );
- return KeyRange( "", BSONObj(), BSONObj(), BSONObj() );
}
- /**
- * Returns the first ShardEndpoint for the query from the mock ranges. Only can handle
- * queries of the form { field : { $gte : <value>, $lt : <value> } }.
- */
- Status targetQuery( const BSONObj& query, std::vector<ShardEndpoint*>* endpoints ) const {
-
- KeyRange queryRange = parseRange( query );
-
- const std::vector<MockRange*>& ranges = getRanges();
- for ( std::vector<MockRange*>::const_iterator it = ranges.begin(); it != ranges.end();
- ++it ) {
-
- const MockRange* range = *it;
-
- if ( rangeOverlaps( queryRange.minKey,
- queryRange.maxKey,
- range->range.minKey,
- range->range.maxKey ) ) {
- endpoints->push_back( new ShardEndpoint( range->endpoint ) );
- }
- }
-
- if ( endpoints->empty() ) return Status( ErrorCodes::UnknownError,
- "no mock ranges found for query" );
- return Status::OK();
- }
+ if (endpoints->empty())
+ return Status(ErrorCodes::UnknownError, "no mock ranges found for query");
+ return Status::OK();
+ }
- NamespaceString _nss;
+ NamespaceString _nss;
- // Manually-stored ranges
- OwnedPointerVector<MockRange> _mockRanges;
- };
+ // Manually-stored ranges
+ OwnedPointerVector<MockRange> _mockRanges;
+};
- inline void assertEndpointsEqual( const ShardEndpoint& endpointA,
- const ShardEndpoint& endpointB ) {
- ASSERT_EQUALS( endpointA.shardName, endpointB.shardName );
- ASSERT_EQUALS( endpointA.shardVersion.toLong(), endpointB.shardVersion.toLong() );
- ASSERT_EQUALS( endpointA.shardVersion.epoch(), endpointB.shardVersion.epoch() );
- }
+inline void assertEndpointsEqual(const ShardEndpoint& endpointA, const ShardEndpoint& endpointB) {
+ ASSERT_EQUALS(endpointA.shardName, endpointB.shardName);
+ ASSERT_EQUALS(endpointA.shardVersion.toLong(), endpointB.shardVersion.toLong());
+ ASSERT_EQUALS(endpointA.shardVersion.epoch(), endpointB.shardVersion.epoch());
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/mock_shard_resolver.h b/src/mongo/s/mock_shard_resolver.h
index a37c6cfe905..23e676e5836 100644
--- a/src/mongo/s/mock_shard_resolver.h
+++ b/src/mongo/s/mock_shard_resolver.h
@@ -37,19 +37,16 @@
namespace mongo {
- class MockShardResolver : public ShardResolver {
- public:
-
- virtual ~MockShardResolver() {
- }
-
- Status chooseWriteHost( const std::string& shardName, ConnectionString* shardHost ) const {
- std::string errMsg;
- *shardHost = ConnectionString::parse( std::string( "$" ) + shardName + ":12345", errMsg );
- ASSERT_EQUALS( errMsg, "" );
- return Status::OK();
- }
-
- };
-
-} // namespace mongo
+class MockShardResolver : public ShardResolver {
+public:
+ virtual ~MockShardResolver() {}
+
+ Status chooseWriteHost(const std::string& shardName, ConnectionString* shardHost) const {
+ std::string errMsg;
+ *shardHost = ConnectionString::parse(std::string("$") + shardName + ":12345", errMsg);
+ ASSERT_EQUALS(errMsg, "");
+ return Status::OK();
+ }
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/mongo_version_range.cpp b/src/mongo/s/mongo_version_range.cpp
index 82870e20418..7ac4b2c4474 100644
--- a/src/mongo/s/mongo_version_range.cpp
+++ b/src/mongo/s/mongo_version_range.cpp
@@ -32,147 +32,135 @@
namespace mongo {
- using std::string;
- using std::vector;
-
- bool MongoVersionRange::parseBSONArray(const BSONArray& arr,
- vector<MongoVersionRange>* excludes,
- std::string* errMsg)
- {
- string dummy;
- if (!errMsg) errMsg = &dummy;
-
- BSONObjIterator it(arr);
-
- while (it.more()) {
- MongoVersionRange range;
- if (!range.parseBSONElement(it.next(), errMsg)) return false;
- excludes->push_back(range);
- }
-
- return true;
- }
-
- BSONArray MongoVersionRange::toBSONArray(const vector<MongoVersionRange>& ranges) {
+using std::string;
+using std::vector;
- BSONArrayBuilder barr;
+bool MongoVersionRange::parseBSONArray(const BSONArray& arr,
+ vector<MongoVersionRange>* excludes,
+ std::string* errMsg) {
+ string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
- for (vector<MongoVersionRange>::const_iterator it = ranges.begin(); it != ranges.end();
- ++it)
- {
- const MongoVersionRange& range = *it;
- range.toBSONElement(&barr);
- }
+ BSONObjIterator it(arr);
- return barr.arr();
+ while (it.more()) {
+ MongoVersionRange range;
+ if (!range.parseBSONElement(it.next(), errMsg))
+ return false;
+ excludes->push_back(range);
}
- bool MongoVersionRange::parseBSONElement(const BSONElement& el, string* errMsg) {
-
- string dummy;
- if (!errMsg) errMsg = &dummy;
+ return true;
+}
- if (el.type() == String) {
- minVersion = el.String();
- if (minVersion == "") {
- *errMsg = (string) "cannot parse single empty mongo version (" + el.toString()
- + ")";
- return false;
- }
- return true;
- }
- else if (el.type() == Array || el.type() == Object) {
+BSONArray MongoVersionRange::toBSONArray(const vector<MongoVersionRange>& ranges) {
+ BSONArrayBuilder barr;
- BSONObj range = el.Obj();
+ for (vector<MongoVersionRange>::const_iterator it = ranges.begin(); it != ranges.end(); ++it) {
+ const MongoVersionRange& range = *it;
+ range.toBSONElement(&barr);
+ }
- if (range.nFields() != 2) {
- *errMsg = (string) "not enough fields in mongo version range (" + el.toString()
- + ")";
- return false;
- }
+ return barr.arr();
+}
- BSONObjIterator it(range);
+bool MongoVersionRange::parseBSONElement(const BSONElement& el, string* errMsg) {
+ string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
- BSONElement subElA = it.next();
- BSONElement subElB = it.next();
+ if (el.type() == String) {
+ minVersion = el.String();
+ if (minVersion == "") {
+ *errMsg = (string) "cannot parse single empty mongo version (" + el.toString() + ")";
+ return false;
+ }
+ return true;
+ } else if (el.type() == Array || el.type() == Object) {
+ BSONObj range = el.Obj();
- if (subElA.type() != String || subElB.type() != String) {
- *errMsg = (string) "wrong field type for mongo version range (" + el.toString()
- + ")";
- return false;
- }
+ if (range.nFields() != 2) {
+ *errMsg = (string) "not enough fields in mongo version range (" + el.toString() + ")";
+ return false;
+ }
- minVersion = subElA.String();
- maxVersion = subElB.String();
+ BSONObjIterator it(range);
- if (minVersion == "") {
- *errMsg = (string) "cannot parse first empty mongo version (" + el.toString() + ")";
- return false;
- }
+ BSONElement subElA = it.next();
+ BSONElement subElB = it.next();
- if (maxVersion == "") {
- *errMsg = (string) "cannot parse second empty mongo version (" + el.toString()
- + ")";
- return false;
- }
+ if (subElA.type() != String || subElB.type() != String) {
+ *errMsg = (string) "wrong field type for mongo version range (" + el.toString() + ")";
+ return false;
+ }
- if (versionCmp(minVersion, maxVersion) > 0) {
- string swap = minVersion;
- minVersion = maxVersion;
- maxVersion = swap;
- }
+ minVersion = subElA.String();
+ maxVersion = subElB.String();
- return true;
- }
- else {
- *errMsg = (string) "wrong type for mongo version range " + el.toString();
+ if (minVersion == "") {
+ *errMsg = (string) "cannot parse first empty mongo version (" + el.toString() + ")";
return false;
}
- }
- void MongoVersionRange::toBSONElement(BSONArrayBuilder* barr) const {
if (maxVersion == "") {
- barr->append(minVersion);
+ *errMsg = (string) "cannot parse second empty mongo version (" + el.toString() + ")";
+ return false;
}
- else {
- BSONArrayBuilder rangeB(barr->subarrayStart());
-
- rangeB.append(minVersion);
- rangeB.append(maxVersion);
- rangeB.done();
+ if (versionCmp(minVersion, maxVersion) > 0) {
+ string swap = minVersion;
+ minVersion = maxVersion;
+ maxVersion = swap;
}
+
+ return true;
+ } else {
+ *errMsg = (string) "wrong type for mongo version range " + el.toString();
+ return false;
}
+}
- bool MongoVersionRange::isInRange(StringData version) const {
+void MongoVersionRange::toBSONElement(BSONArrayBuilder* barr) const {
+ if (maxVersion == "") {
+ barr->append(minVersion);
+ } else {
+ BSONArrayBuilder rangeB(barr->subarrayStart());
- if (maxVersion == "") {
- // If a prefix of the version specified is excluded, the specified version is
- // excluded
- if (version.find(minVersion) == 0) return true;
- }
- else {
- // Range is inclusive, so make sure the end and beginning prefix excludes all
- // prefixed versions as above
- if (version.find(minVersion) == 0) return true;
- if (version.find(maxVersion) == 0) return true;
- if (versionCmp(minVersion, version) <= 0 && versionCmp(maxVersion, version) >= 0) {
- return true;
- }
- }
+ rangeB.append(minVersion);
+ rangeB.append(maxVersion);
- return false;
+ rangeB.done();
}
+}
- bool isInMongoVersionRanges(StringData version, const vector<MongoVersionRange>& ranges)
- {
- for (vector<MongoVersionRange>::const_iterator it = ranges.begin(); it != ranges.end();
- ++it)
- {
- if (it->isInRange(version)) return true;
+bool MongoVersionRange::isInRange(StringData version) const {
+ if (maxVersion == "") {
+ // If a prefix of the version specified is excluded, the specified version is
+ // excluded
+ if (version.find(minVersion) == 0)
+ return true;
+ } else {
+ // Range is inclusive, so make sure the end and beginning prefix excludes all
+ // prefixed versions as above
+ if (version.find(minVersion) == 0)
+ return true;
+ if (version.find(maxVersion) == 0)
+ return true;
+ if (versionCmp(minVersion, version) <= 0 && versionCmp(maxVersion, version) >= 0) {
+ return true;
}
+ }
- return false;
+ return false;
+}
+
+bool isInMongoVersionRanges(StringData version, const vector<MongoVersionRange>& ranges) {
+ for (vector<MongoVersionRange>::const_iterator it = ranges.begin(); it != ranges.end(); ++it) {
+ if (it->isInRange(version))
+ return true;
}
+ return false;
+}
}
diff --git a/src/mongo/s/mongo_version_range.h b/src/mongo/s/mongo_version_range.h
index fcdc4dca4c2..2e5c443fcc9 100644
--- a/src/mongo/s/mongo_version_range.h
+++ b/src/mongo/s/mongo_version_range.h
@@ -36,33 +36,31 @@
namespace mongo {
- /**
- * The MongoVersionRange represents a min/max of MongoDB versions, useful for
- * excluding/including particular versions.
- *
- * The ranges may be single-version, in which case maxVersion == "", where only exact prefix
- * matches are included in the range. Alternately, the range may have a min and max version
- * and include any version with a prefix of the min and max version as well as all versions
- * between the two.
- */
- struct MongoVersionRange {
-
- static bool parseBSONArray(const BSONArray& arr,
- std::vector<MongoVersionRange>* excludes,
- std::string* errMsg);
+/**
+ * The MongoVersionRange represents a min/max of MongoDB versions, useful for
+ * excluding/including particular versions.
+ *
+ * The ranges may be single-version, in which case maxVersion == "", where only exact prefix
+ * matches are included in the range. Alternately, the range may have a min and max version
+ * and include any version with a prefix of the min and max version as well as all versions
+ * between the two.
+ */
+struct MongoVersionRange {
+ static bool parseBSONArray(const BSONArray& arr,
+ std::vector<MongoVersionRange>* excludes,
+ std::string* errMsg);
- static BSONArray toBSONArray(const std::vector<MongoVersionRange>& ranges);
+ static BSONArray toBSONArray(const std::vector<MongoVersionRange>& ranges);
- bool parseBSONElement(const BSONElement& el, std::string* errMsg);
+ bool parseBSONElement(const BSONElement& el, std::string* errMsg);
- void toBSONElement(BSONArrayBuilder* barr) const;
+ void toBSONElement(BSONArrayBuilder* barr) const;
- bool isInRange(StringData version) const;
+ bool isInRange(StringData version) const;
- std::string minVersion;
- std::string maxVersion;
- };
+ std::string minVersion;
+ std::string maxVersion;
+};
- bool isInMongoVersionRanges(StringData version,
- const std::vector<MongoVersionRange>& ranges);
+bool isInMongoVersionRanges(StringData version, const std::vector<MongoVersionRange>& ranges);
}
diff --git a/src/mongo/s/mongo_version_range_test.cpp b/src/mongo/s/mongo_version_range_test.cpp
index 6aedd0168af..35397b1d486 100644
--- a/src/mongo/s/mongo_version_range_test.cpp
+++ b/src/mongo/s/mongo_version_range_test.cpp
@@ -41,189 +41,183 @@
namespace mongo {
namespace {
- using std::string;
- using std::vector;
+using std::string;
+using std::vector;
+//
+// Test mongo version ranges
+//
+
+TEST(Excludes, Empty) {
//
- // Test mongo version ranges
+ // Tests basic empty range
//
- TEST(Excludes, Empty) {
-
- //
- // Tests basic empty range
- //
-
- BSONArray includeArr;
- vector<MongoVersionRange> includes;
-
- string errMsg;
- MongoVersionRange::parseBSONArray(includeArr, &includes, &errMsg);
- ASSERT_EQUALS(errMsg, "");
-
- // Make sure nothing is included
- ASSERT(!isInMongoVersionRanges("1.2.3", includes));
- ASSERT(!isInMongoVersionRanges("1.2.3-pre", includes));
- ASSERT(!isInMongoVersionRanges("1.2.3-rc0", includes));
- }
-
- TEST(Excludes, SinglePointRange) {
-
- //
- // Tests single string range
- //
+ BSONArray includeArr;
+ vector<MongoVersionRange> includes;
- BSONArrayBuilder bab;
- bab << "1.2.3";
- BSONArray includeArr = bab.arr();
+ string errMsg;
+ MongoVersionRange::parseBSONArray(includeArr, &includes, &errMsg);
+ ASSERT_EQUALS(errMsg, "");
- vector<MongoVersionRange> includes;
-
- string errMsg;
- MongoVersionRange::parseBSONArray(includeArr, &includes, &errMsg);
- ASSERT_EQUALS(errMsg, "");
-
- ASSERT(isInMongoVersionRanges("1.2.3", includes));
-
- ASSERT(!isInMongoVersionRanges("1.2.2-rc0", includes));
- ASSERT(!isInMongoVersionRanges("1.2.2", includes));
+ // Make sure nothing is included
+ ASSERT(!isInMongoVersionRanges("1.2.3", includes));
+ ASSERT(!isInMongoVersionRanges("1.2.3-pre", includes));
+ ASSERT(!isInMongoVersionRanges("1.2.3-rc0", includes));
+}
- ASSERT(isInMongoVersionRanges("1.2.3-pre", includes));
- ASSERT(isInMongoVersionRanges("1.2.3-rc0", includes));
+TEST(Excludes, SinglePointRange) {
+ //
+ // Tests single string range
+ //
- ASSERT(!isInMongoVersionRanges("1.2.4-rc0", includes));
- ASSERT(!isInMongoVersionRanges("1.2.4", includes));
- }
+ BSONArrayBuilder bab;
+ bab << "1.2.3";
+ BSONArray includeArr = bab.arr();
- TEST(Excludes, BetweenRange) {
+ vector<MongoVersionRange> includes;
- //
- // Tests range with two endpoints
- //
+ string errMsg;
+ MongoVersionRange::parseBSONArray(includeArr, &includes, &errMsg);
+ ASSERT_EQUALS(errMsg, "");
- BSONArrayBuilder bab;
- bab << BSON_ARRAY("7.8.9" << "10.11.12");
- BSONArray includeArr = bab.arr();
+ ASSERT(isInMongoVersionRanges("1.2.3", includes));
- vector<MongoVersionRange> includes;
+ ASSERT(!isInMongoVersionRanges("1.2.2-rc0", includes));
+ ASSERT(!isInMongoVersionRanges("1.2.2", includes));
- string errMsg;
- MongoVersionRange::parseBSONArray(includeArr, &includes, &errMsg);
- ASSERT_EQUALS(errMsg, "");
+ ASSERT(isInMongoVersionRanges("1.2.3-pre", includes));
+ ASSERT(isInMongoVersionRanges("1.2.3-rc0", includes));
- ASSERT(isInMongoVersionRanges("7.8.9", includes));
- ASSERT(isInMongoVersionRanges("10.11.12", includes));
+ ASSERT(!isInMongoVersionRanges("1.2.4-rc0", includes));
+ ASSERT(!isInMongoVersionRanges("1.2.4", includes));
+}
- // Before
- ASSERT(!isInMongoVersionRanges("7.8.8-rc0", includes));
- ASSERT(!isInMongoVersionRanges("7.8.8", includes));
+TEST(Excludes, BetweenRange) {
+ //
+ // Tests range with two endpoints
+ //
- // Boundary
- ASSERT(isInMongoVersionRanges("7.8.9-pre", includes));
- ASSERT(isInMongoVersionRanges("7.8.9-rc0", includes));
+ BSONArrayBuilder bab;
+ bab << BSON_ARRAY("7.8.9"
+ << "10.11.12");
+ BSONArray includeArr = bab.arr();
- ASSERT(isInMongoVersionRanges("7.8.10-rc0", includes));
- ASSERT(isInMongoVersionRanges("7.8.10", includes));
+ vector<MongoVersionRange> includes;
- // Between
- ASSERT(isInMongoVersionRanges("8.9.10", includes));
- ASSERT(isInMongoVersionRanges("9.10.11", includes));
+ string errMsg;
+ MongoVersionRange::parseBSONArray(includeArr, &includes, &errMsg);
+ ASSERT_EQUALS(errMsg, "");
- // Boundary
- ASSERT(isInMongoVersionRanges("10.11.11-rc0", includes));
- ASSERT(isInMongoVersionRanges("10.11.11", includes));
+ ASSERT(isInMongoVersionRanges("7.8.9", includes));
+ ASSERT(isInMongoVersionRanges("10.11.12", includes));
- ASSERT(isInMongoVersionRanges("10.11.12-pre", includes));
- ASSERT(isInMongoVersionRanges("10.11.12-rc0", includes));
+ // Before
+ ASSERT(!isInMongoVersionRanges("7.8.8-rc0", includes));
+ ASSERT(!isInMongoVersionRanges("7.8.8", includes));
- // After
- ASSERT(!isInMongoVersionRanges("10.11.13-rc0", includes));
- ASSERT(!isInMongoVersionRanges("10.11.13", includes));
+ // Boundary
+ ASSERT(isInMongoVersionRanges("7.8.9-pre", includes));
+ ASSERT(isInMongoVersionRanges("7.8.9-rc0", includes));
- }
+ ASSERT(isInMongoVersionRanges("7.8.10-rc0", includes));
+ ASSERT(isInMongoVersionRanges("7.8.10", includes));
- TEST(Excludes, WeirdRange) {
+ // Between
+ ASSERT(isInMongoVersionRanges("8.9.10", includes));
+ ASSERT(isInMongoVersionRanges("9.10.11", includes));
- //
- // Tests range with rc/pre endpoints
- //
+ // Boundary
+ ASSERT(isInMongoVersionRanges("10.11.11-rc0", includes));
+ ASSERT(isInMongoVersionRanges("10.11.11", includes));
- BSONArrayBuilder bab;
- bab << BSON_ARRAY("7.8.9-rc0" << "10.11.12-pre");
- BSONArray includeArr = bab.arr();
+ ASSERT(isInMongoVersionRanges("10.11.12-pre", includes));
+ ASSERT(isInMongoVersionRanges("10.11.12-rc0", includes));
- vector<MongoVersionRange> includes;
+ // After
+ ASSERT(!isInMongoVersionRanges("10.11.13-rc0", includes));
+ ASSERT(!isInMongoVersionRanges("10.11.13", includes));
+}
- string errMsg;
- MongoVersionRange::parseBSONArray(includeArr, &includes, &errMsg);
- ASSERT_EQUALS(errMsg, "");
+TEST(Excludes, WeirdRange) {
+ //
+ // Tests range with rc/pre endpoints
+ //
- // Near endpoints
- ASSERT(isInMongoVersionRanges("7.8.9", includes));
- ASSERT(!isInMongoVersionRanges("10.11.12", includes));
+ BSONArrayBuilder bab;
+ bab << BSON_ARRAY("7.8.9-rc0"
+ << "10.11.12-pre");
+ BSONArray includeArr = bab.arr();
- // Before
- ASSERT(!isInMongoVersionRanges("7.8.8-rc0", includes));
- ASSERT(!isInMongoVersionRanges("7.8.8", includes));
+ vector<MongoVersionRange> includes;
- // Boundary
- ASSERT(!isInMongoVersionRanges("7.8.9-pre", includes));
- ASSERT(isInMongoVersionRanges("7.8.9-rc0", includes));
+ string errMsg;
+ MongoVersionRange::parseBSONArray(includeArr, &includes, &errMsg);
+ ASSERT_EQUALS(errMsg, "");
- ASSERT(isInMongoVersionRanges("7.8.10-rc0", includes));
- ASSERT(isInMongoVersionRanges("7.8.10", includes));
+ // Near endpoints
+ ASSERT(isInMongoVersionRanges("7.8.9", includes));
+ ASSERT(!isInMongoVersionRanges("10.11.12", includes));
- // Between
- ASSERT(isInMongoVersionRanges("8.9.10", includes));
- ASSERT(isInMongoVersionRanges("9.10.11", includes));
+ // Before
+ ASSERT(!isInMongoVersionRanges("7.8.8-rc0", includes));
+ ASSERT(!isInMongoVersionRanges("7.8.8", includes));
- // Boundary
- ASSERT(isInMongoVersionRanges("10.11.11-rc0", includes));
- ASSERT(isInMongoVersionRanges("10.11.11", includes));
+ // Boundary
+ ASSERT(!isInMongoVersionRanges("7.8.9-pre", includes));
+ ASSERT(isInMongoVersionRanges("7.8.9-rc0", includes));
- ASSERT(isInMongoVersionRanges("10.11.12-pre", includes));
- ASSERT(!isInMongoVersionRanges("10.11.12-rc0", includes));
+ ASSERT(isInMongoVersionRanges("7.8.10-rc0", includes));
+ ASSERT(isInMongoVersionRanges("7.8.10", includes));
- // After
- ASSERT(!isInMongoVersionRanges("10.11.13-rc0", includes));
- ASSERT(!isInMongoVersionRanges("10.11.13", includes));
- }
+ // Between
+ ASSERT(isInMongoVersionRanges("8.9.10", includes));
+ ASSERT(isInMongoVersionRanges("9.10.11", includes));
- TEST(Excludes, BadRangeType) {
+ // Boundary
+ ASSERT(isInMongoVersionRanges("10.11.11-rc0", includes));
+ ASSERT(isInMongoVersionRanges("10.11.11", includes));
- //
- // Tests range with bad endpoint types
- //
+ ASSERT(isInMongoVersionRanges("10.11.12-pre", includes));
+ ASSERT(!isInMongoVersionRanges("10.11.12-rc0", includes));
- BSONArrayBuilder bab;
- bab << "1.2.3";
- bab << 2; // integer is not version
- BSONArray includeArr = bab.arr();
+ // After
+ ASSERT(!isInMongoVersionRanges("10.11.13-rc0", includes));
+ ASSERT(!isInMongoVersionRanges("10.11.13", includes));
+}
- vector<MongoVersionRange> includes;
+TEST(Excludes, BadRangeType) {
+ //
+ // Tests range with bad endpoint types
+ //
- string errMsg;
- MongoVersionRange::parseBSONArray(includeArr, &includes, &errMsg);
- ASSERT_NOT_EQUALS(errMsg, "");
- }
+ BSONArrayBuilder bab;
+ bab << "1.2.3";
+ bab << 2; // integer is not version
+ BSONArray includeArr = bab.arr();
- TEST(Excludes, BadRangeArray) {
+ vector<MongoVersionRange> includes;
- //
- // Tests range with bad array
- //
+ string errMsg;
+ MongoVersionRange::parseBSONArray(includeArr, &includes, &errMsg);
+ ASSERT_NOT_EQUALS(errMsg, "");
+}
- BSONArrayBuilder bab;
- bab << BSON_ARRAY("" << "1.2.3"); // empty bound
- BSONArray includeArr = bab.arr();
+TEST(Excludes, BadRangeArray) {
+ //
+ // Tests range with bad array
+ //
- vector<MongoVersionRange> includes;
+ BSONArrayBuilder bab;
+ bab << BSON_ARRAY(""
+ << "1.2.3"); // empty bound
+ BSONArray includeArr = bab.arr();
- string errMsg;
- MongoVersionRange::parseBSONArray(includeArr, &includes, &errMsg);
- ASSERT_NOT_EQUALS(errMsg, "");
- }
+ vector<MongoVersionRange> includes;
+ string errMsg;
+ MongoVersionRange::parseBSONArray(includeArr, &includes, &errMsg);
+ ASSERT_NOT_EQUALS(errMsg, "");
}
-} // namespace mongo
-
+}
+} // namespace mongo
diff --git a/src/mongo/s/mongos_options.cpp b/src/mongo/s/mongos_options.cpp
index 02fffb8230a..489235cc749 100644
--- a/src/mongo/s/mongos_options.cpp
+++ b/src/mongo/s/mongos_options.cpp
@@ -52,241 +52,239 @@
namespace mongo {
- MongosGlobalParams mongosGlobalParams;
+MongosGlobalParams mongosGlobalParams;
- Status addMongosOptions(moe::OptionSection* options) {
+Status addMongosOptions(moe::OptionSection* options) {
+ moe::OptionSection general_options("General options");
- moe::OptionSection general_options("General options");
-
- Status ret = addGeneralServerOptions(&general_options);
- if (!ret.isOK()) {
- return ret;
- }
+ Status ret = addGeneralServerOptions(&general_options);
+ if (!ret.isOK()) {
+ return ret;
+ }
#if defined(_WIN32)
- moe::OptionSection windows_scm_options("Windows Service Control Manager options");
+ moe::OptionSection windows_scm_options("Windows Service Control Manager options");
- ret = addWindowsServerOptions(&windows_scm_options);
- if (!ret.isOK()) {
- return ret;
- }
+ ret = addWindowsServerOptions(&windows_scm_options);
+ if (!ret.isOK()) {
+ return ret;
+ }
#endif
#ifdef MONGO_CONFIG_SSL
- moe::OptionSection ssl_options("SSL options");
+ moe::OptionSection ssl_options("SSL options");
- ret = addSSLServerOptions(&ssl_options);
- if (!ret.isOK()) {
- return ret;
- }
+ ret = addSSLServerOptions(&ssl_options);
+ if (!ret.isOK()) {
+ return ret;
+ }
#endif
- moe::OptionSection sharding_options("Sharding options");
+ moe::OptionSection sharding_options("Sharding options");
- sharding_options.addOptionChaining("sharding.configDB", "configdb", moe::String,
- "1 or 3 comma separated config servers");
+ sharding_options.addOptionChaining(
+ "sharding.configDB", "configdb", moe::String, "1 or 3 comma separated config servers");
- sharding_options.addOptionChaining("replication.localPingThresholdMs", "localThreshold",
- moe::Int, "ping time (in ms) for a node to be considered local (default 15ms)");
+ sharding_options.addOptionChaining(
+ "replication.localPingThresholdMs",
+ "localThreshold",
+ moe::Int,
+ "ping time (in ms) for a node to be considered local (default 15ms)");
- sharding_options.addOptionChaining("test", "test", moe::Switch, "just run unit tests")
- .setSources(moe::SourceAllLegacy);
+ sharding_options.addOptionChaining("test", "test", moe::Switch, "just run unit tests")
+ .setSources(moe::SourceAllLegacy);
- sharding_options.addOptionChaining("upgrade", "upgrade", moe::Switch,
- "upgrade meta data version")
- .setSources(moe::SourceAllLegacy);
+ sharding_options.addOptionChaining(
+ "upgrade", "upgrade", moe::Switch, "upgrade meta data version")
+ .setSources(moe::SourceAllLegacy);
- sharding_options.addOptionChaining("sharding.chunkSize", "chunkSize", moe::Int,
- "maximum amount of data per chunk");
+ sharding_options.addOptionChaining(
+ "sharding.chunkSize", "chunkSize", moe::Int, "maximum amount of data per chunk");
- sharding_options.addOptionChaining("net.http.JSONPEnabled", "jsonp", moe::Switch,
- "allow JSONP access via http (has security implications)")
- .setSources(moe::SourceAllLegacy);
+ sharding_options.addOptionChaining("net.http.JSONPEnabled",
+ "jsonp",
+ moe::Switch,
+ "allow JSONP access via http (has security implications)")
+ .setSources(moe::SourceAllLegacy);
- sharding_options.addOptionChaining("noscripting", "noscripting", moe::Switch,
- "disable scripting engine")
- .setSources(moe::SourceAllLegacy);
+ sharding_options.addOptionChaining(
+ "noscripting", "noscripting", moe::Switch, "disable scripting engine")
+ .setSources(moe::SourceAllLegacy);
- options->addSection(general_options);
+ options->addSection(general_options);
#if defined(_WIN32)
- options->addSection(windows_scm_options);
+ options->addSection(windows_scm_options);
#endif
- options->addSection(sharding_options);
+ options->addSection(sharding_options);
#ifdef MONGO_CONFIG_SSL
- options->addSection(ssl_options);
+ options->addSection(ssl_options);
#endif
- options->addOptionChaining("noAutoSplit", "noAutoSplit", moe::Switch,
- "do not send split commands with writes")
- .hidden()
- .setSources(moe::SourceAllLegacy);
+ options->addOptionChaining("noAutoSplit",
+ "noAutoSplit",
+ moe::Switch,
+ "do not send split commands with writes")
+ .hidden()
+ .setSources(moe::SourceAllLegacy);
- options->addOptionChaining("sharding.autoSplit", "", moe::Bool,
- "send split commands with writes")
- .setSources(moe::SourceYAMLConfig);
+ options->addOptionChaining(
+ "sharding.autoSplit", "", moe::Bool, "send split commands with writes")
+ .setSources(moe::SourceYAMLConfig);
- return Status::OK();
- }
+ return Status::OK();
+}
- void printMongosHelp(const moe::OptionSection& options) {
- std::cout << options.helpString() << std::endl;
- };
+void printMongosHelp(const moe::OptionSection& options) {
+ std::cout << options.helpString() << std::endl;
+};
- bool handlePreValidationMongosOptions(const moe::Environment& params,
- const std::vector<std::string>& args) {
- if (params.count("help") &&
- params["help"].as<bool>() == true) {
- printMongosHelp(moe::startupOptions);
- return false;
- }
- if (params.count("version") &&
- params["version"].as<bool>() == true) {
- printShardingVersionInfo(true);
- return false;
- }
- if (params.count("test") &&
- params["test"].as<bool>() == true) {
- ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity(
- ::mongo::logger::LogSeverity::Debug(5));
- StartupTest::runTests();
- return false;
- }
-
- return true;
+bool handlePreValidationMongosOptions(const moe::Environment& params,
+ const std::vector<std::string>& args) {
+ if (params.count("help") && params["help"].as<bool>() == true) {
+ printMongosHelp(moe::startupOptions);
+ return false;
+ }
+ if (params.count("version") && params["version"].as<bool>() == true) {
+ printShardingVersionInfo(true);
+ return false;
+ }
+ if (params.count("test") && params["test"].as<bool>() == true) {
+ ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity(
+ ::mongo::logger::LogSeverity::Debug(5));
+ StartupTest::runTests();
+ return false;
}
- Status validateMongosOptions(const moe::Environment& params) {
+ return true;
+}
- Status ret = validateServerOptions(params);
- if (!ret.isOK()) {
- return ret;
- }
+Status validateMongosOptions(const moe::Environment& params) {
+ Status ret = validateServerOptions(params);
+ if (!ret.isOK()) {
+ return ret;
+ }
- return Status::OK();
+ return Status::OK();
+}
+
+Status canonicalizeMongosOptions(moe::Environment* params) {
+ Status ret = canonicalizeServerOptions(params);
+ if (!ret.isOK()) {
+ return ret;
}
- Status canonicalizeMongosOptions(moe::Environment* params) {
+#ifdef MONGO_CONFIG_SSL
+ ret = canonicalizeSSLServerOptions(params);
+ if (!ret.isOK()) {
+ return ret;
+ }
+#endif
- Status ret = canonicalizeServerOptions(params);
+ // "sharding.autoSplit" comes from the config file, so override it if "noAutoSplit" is set
+ // since that comes from the command line.
+ if (params->count("noAutoSplit")) {
+ Status ret =
+ params->set("sharding.autoSplit", moe::Value(!(*params)["noAutoSplit"].as<bool>()));
if (!ret.isOK()) {
return ret;
}
-
-#ifdef MONGO_CONFIG_SSL
- ret = canonicalizeSSLServerOptions(params);
+ ret = params->remove("noAutoSplit");
if (!ret.isOK()) {
return ret;
}
-#endif
-
- // "sharding.autoSplit" comes from the config file, so override it if "noAutoSplit" is set
- // since that comes from the command line.
- if (params->count("noAutoSplit")) {
- Status ret = params->set("sharding.autoSplit",
- moe::Value(!(*params)["noAutoSplit"].as<bool>()));
- if (!ret.isOK()) {
- return ret;
- }
- ret = params->remove("noAutoSplit");
- if (!ret.isOK()) {
- return ret;
- }
- }
-
- return Status::OK();
}
- Status storeMongosOptions(const moe::Environment& params,
- const std::vector<std::string>& args) {
+ return Status::OK();
+}
- Status ret = storeServerOptions(params, args);
- if (!ret.isOK()) {
- return ret;
- }
-
- if ( params.count( "sharding.chunkSize" ) ) {
- int csize = params["sharding.chunkSize"].as<int>();
+Status storeMongosOptions(const moe::Environment& params, const std::vector<std::string>& args) {
+ Status ret = storeServerOptions(params, args);
+ if (!ret.isOK()) {
+ return ret;
+ }
- // validate chunksize before proceeding
- if ( csize == 0 ) {
- return Status(ErrorCodes::BadValue, "error: need a non-zero chunksize");
- }
+ if (params.count("sharding.chunkSize")) {
+ int csize = params["sharding.chunkSize"].as<int>();
- if ( !Chunk::setMaxChunkSizeSizeMB( csize ) ) {
- return Status(ErrorCodes::BadValue, "MaxChunkSize invalid");
- }
+ // validate chunksize before proceeding
+ if (csize == 0) {
+ return Status(ErrorCodes::BadValue, "error: need a non-zero chunksize");
}
- if (params.count( "net.port" ) ) {
- int port = params["net.port"].as<int>();
- if ( port <= 0 || port > 65535 ) {
- return Status(ErrorCodes::BadValue,
- "error: port number must be between 1 and 65535");
- }
+ if (!Chunk::setMaxChunkSizeSizeMB(csize)) {
+ return Status(ErrorCodes::BadValue, "MaxChunkSize invalid");
}
+ }
- if ( params.count( "replication.localPingThresholdMs" ) ) {
- serverGlobalParams.defaultLocalThresholdMillis =
- params["replication.localPingThresholdMs"].as<int>();
+ if (params.count("net.port")) {
+ int port = params["net.port"].as<int>();
+ if (port <= 0 || port > 65535) {
+ return Status(ErrorCodes::BadValue, "error: port number must be between 1 and 65535");
}
+ }
- if (params.count("net.http.JSONPEnabled")) {
- serverGlobalParams.jsonp = params["net.http.JSONPEnabled"].as<bool>();
- }
+ if (params.count("replication.localPingThresholdMs")) {
+ serverGlobalParams.defaultLocalThresholdMillis =
+ params["replication.localPingThresholdMs"].as<int>();
+ }
- if (params.count("noscripting")) {
- // This option currently has no effect for mongos
- }
+ if (params.count("net.http.JSONPEnabled")) {
+ serverGlobalParams.jsonp = params["net.http.JSONPEnabled"].as<bool>();
+ }
- if (params.count("sharding.autoSplit")) {
- Chunk::ShouldAutoSplit = params["sharding.autoSplit"].as<bool>();
- if (Chunk::ShouldAutoSplit == false) {
- warning() << "running with auto-splitting disabled";
- }
- }
+ if (params.count("noscripting")) {
+ // This option currently has no effect for mongos
+ }
- if ( ! params.count( "sharding.configDB" ) ) {
- return Status(ErrorCodes::BadValue, "error: no args for --configdb");
+ if (params.count("sharding.autoSplit")) {
+ Chunk::ShouldAutoSplit = params["sharding.autoSplit"].as<bool>();
+ if (Chunk::ShouldAutoSplit == false) {
+ warning() << "running with auto-splitting disabled";
}
+ }
- {
- std::string configdbString = params["sharding.configDB"].as<std::string>();
+ if (!params.count("sharding.configDB")) {
+ return Status(ErrorCodes::BadValue, "error: no args for --configdb");
+ }
- auto configdbConnectionString = ConnectionString::parse(configdbString);
- if (!configdbConnectionString.isOK()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "Invalid configdb connection string: "
- << configdbConnectionString.getStatus().toString());
- }
+ {
+ std::string configdbString = params["sharding.configDB"].as<std::string>();
- mongosGlobalParams.configdbs = configdbConnectionString.getValue();
+ auto configdbConnectionString = ConnectionString::parse(configdbString);
+ if (!configdbConnectionString.isOK()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Invalid configdb connection string: "
+ << configdbConnectionString.getStatus().toString());
}
- std::vector<HostAndPort> configServers = mongosGlobalParams.configdbs.getServers();
+ mongosGlobalParams.configdbs = configdbConnectionString.getValue();
+ }
- if (!(mongosGlobalParams.configdbs.type() == ConnectionString::SYNC) &&
- !(mongosGlobalParams.configdbs.type() == ConnectionString::SET &&
- configServers.size() == 1)) {
- return Status(ErrorCodes::BadValue,
- "Must have either 3 node old-style config servers, or a single server "
- "replica set config server");
- }
+ std::vector<HostAndPort> configServers = mongosGlobalParams.configdbs.getServers();
- if (configServers.size() < 3) {
- warning() << "running with less than 3 config servers should be done only for testing "
- "purposes and is not recommended for production";
- }
+ if (!(mongosGlobalParams.configdbs.type() == ConnectionString::SYNC) &&
+ !(mongosGlobalParams.configdbs.type() == ConnectionString::SET &&
+ configServers.size() == 1)) {
+ return Status(ErrorCodes::BadValue,
+ "Must have either 3 node old-style config servers, or a single server "
+ "replica set config server");
+ }
- if (params.count("upgrade")) {
- mongosGlobalParams.upgrade = params["upgrade"].as<bool>();
- }
+ if (configServers.size() < 3) {
+ warning() << "running with less than 3 config servers should be done only for testing "
+ "purposes and is not recommended for production";
+ }
- return Status::OK();
+ if (params.count("upgrade")) {
+ mongosGlobalParams.upgrade = params["upgrade"].as<bool>();
}
-} // namespace mongo
+ return Status::OK();
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/mongos_options.h b/src/mongo/s/mongos_options.h
index 81089910629..873ab6aae33 100644
--- a/src/mongo/s/mongos_options.h
+++ b/src/mongo/s/mongos_options.h
@@ -36,54 +36,52 @@
namespace mongo {
- namespace optionenvironment {
- class OptionSection;
- class Environment;
- } // namespace optionenvironment
+namespace optionenvironment {
+class OptionSection;
+class Environment;
+} // namespace optionenvironment
- namespace moe = mongo::optionenvironment;
+namespace moe = mongo::optionenvironment;
- struct MongosGlobalParams {
- ConnectionString configdbs;
- bool upgrade;
+struct MongosGlobalParams {
+ ConnectionString configdbs;
+ bool upgrade;
- MongosGlobalParams() :
- upgrade(false)
- { }
- };
+ MongosGlobalParams() : upgrade(false) {}
+};
- extern MongosGlobalParams mongosGlobalParams;
+extern MongosGlobalParams mongosGlobalParams;
- Status addMongosOptions(moe::OptionSection* options);
+Status addMongosOptions(moe::OptionSection* options);
- void printMongosHelp(const moe::OptionSection& options);
+void printMongosHelp(const moe::OptionSection& options);
- /**
- * Handle options that should come before validation, such as "help".
- *
- * Returns false if an option was found that implies we should prematurely exit with success.
- */
- bool handlePreValidationMongosOptions(const moe::Environment& params,
- const std::vector<std::string>& args);
+/**
+ * Handle options that should come before validation, such as "help".
+ *
+ * Returns false if an option was found that implies we should prematurely exit with success.
+ */
+bool handlePreValidationMongosOptions(const moe::Environment& params,
+ const std::vector<std::string>& args);
- /**
- * Handle custom validation of mongos options that can not currently be done by using
- * Constraints in the Environment. See the "validate" function in the Environment class for
- * more details.
- */
- Status validateMongosOptions(const moe::Environment& params);
+/**
+ * Handle custom validation of mongos options that can not currently be done by using
+ * Constraints in the Environment. See the "validate" function in the Environment class for
+ * more details.
+ */
+Status validateMongosOptions(const moe::Environment& params);
- /**
- * Canonicalize mongos options for the given environment.
- *
- * For example, the options "dur", "nodur", "journal", "nojournal", and
- * "storage.journaling.enabled" should all be merged into "storage.journaling.enabled".
- */
- Status canonicalizeMongosOptions(moe::Environment* params);
+/**
+ * Canonicalize mongos options for the given environment.
+ *
+ * For example, the options "dur", "nodur", "journal", "nojournal", and
+ * "storage.journaling.enabled" should all be merged into "storage.journaling.enabled".
+ */
+Status canonicalizeMongosOptions(moe::Environment* params);
- Status storeMongosOptions(const moe::Environment& params, const std::vector<std::string>& args);
+Status storeMongosOptions(const moe::Environment& params, const std::vector<std::string>& args);
- // This function should eventually go away, but needs to be here now because the sorter and
- // the version manager must know at runtime which binary it is in.
- bool isMongos();
+// This function should eventually go away, but needs to be here now because the sorter and
+// the version manager must know at runtime which binary it is in.
+bool isMongos();
}
diff --git a/src/mongo/s/mongos_options_init.cpp b/src/mongo/s/mongos_options_init.cpp
index 0ec9bca0b91..335ddef25d8 100644
--- a/src/mongo/s/mongos_options_init.cpp
+++ b/src/mongo/s/mongos_options_init.cpp
@@ -33,47 +33,46 @@
#include "mongo/util/quick_exit.h"
namespace mongo {
- MONGO_GENERAL_STARTUP_OPTIONS_REGISTER(MongosOptions)(InitializerContext* context) {
- return addMongosOptions(&moe::startupOptions);
- }
+MONGO_GENERAL_STARTUP_OPTIONS_REGISTER(MongosOptions)(InitializerContext* context) {
+ return addMongosOptions(&moe::startupOptions);
+}
- MONGO_STARTUP_OPTIONS_VALIDATE(MongosOptions)(InitializerContext* context) {
- if (!handlePreValidationMongosOptions(moe::startupOptionsParsed, context->args())) {
- quickExit(EXIT_SUCCESS);
- }
- // Run validation, but tell the Environment that we don't want it to be set as "valid",
- // since we may be making it invalid in the canonicalization process.
- Status ret = moe::startupOptionsParsed.validate(false/*setValid*/);
- if (!ret.isOK()) {
- return ret;
- }
- ret = validateMongosOptions(moe::startupOptionsParsed);
- if (!ret.isOK()) {
- return ret;
- }
- ret = canonicalizeMongosOptions(&moe::startupOptionsParsed);
- if (!ret.isOK()) {
- return ret;
- }
- ret = moe::startupOptionsParsed.validate();
- if (!ret.isOK()) {
- return ret;
- }
- return Status::OK();
+MONGO_STARTUP_OPTIONS_VALIDATE(MongosOptions)(InitializerContext* context) {
+ if (!handlePreValidationMongosOptions(moe::startupOptionsParsed, context->args())) {
+ quickExit(EXIT_SUCCESS);
+ }
+ // Run validation, but tell the Environment that we don't want it to be set as "valid",
+ // since we may be making it invalid in the canonicalization process.
+ Status ret = moe::startupOptionsParsed.validate(false /*setValid*/);
+ if (!ret.isOK()) {
+ return ret;
+ }
+ ret = validateMongosOptions(moe::startupOptionsParsed);
+ if (!ret.isOK()) {
+ return ret;
+ }
+ ret = canonicalizeMongosOptions(&moe::startupOptionsParsed);
+ if (!ret.isOK()) {
+ return ret;
+ }
+ ret = moe::startupOptionsParsed.validate();
+ if (!ret.isOK()) {
+ return ret;
}
+ return Status::OK();
+}
- MONGO_INITIALIZER_GENERAL(MongosOptions_Store,
- ("BeginStartupOptionStorage"),
- ("EndStartupOptionStorage"))
- (InitializerContext* context) {
- Status ret = storeMongosOptions(moe::startupOptionsParsed, context->args());
- if (!ret.isOK()) {
- std::cerr << ret.toString() << std::endl;
- std::cerr << "try '" << context->args()[0] << " --help' for more information"
- << std::endl;
- quickExit(EXIT_BADOPTIONS);
- }
- return Status::OK();
+MONGO_INITIALIZER_GENERAL(MongosOptions_Store,
+ ("BeginStartupOptionStorage"),
+ ("EndStartupOptionStorage"))
+(InitializerContext* context) {
+ Status ret = storeMongosOptions(moe::startupOptionsParsed, context->args());
+ if (!ret.isOK()) {
+ std::cerr << ret.toString() << std::endl;
+ std::cerr << "try '" << context->args()[0] << " --help' for more information" << std::endl;
+ quickExit(EXIT_BADOPTIONS);
}
+ return Status::OK();
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/ns_targeter.h b/src/mongo/s/ns_targeter.h
index 9212b191370..0b8e513f7fa 100644
--- a/src/mongo/s/ns_targeter.h
+++ b/src/mongo/s/ns_targeter.h
@@ -40,149 +40,140 @@
namespace mongo {
- struct ShardEndpoint;
+struct ShardEndpoint;
+
+/**
+ * The NSTargeter interface is used by a WriteOp to generate and target child write operations
+ * to a particular collection.
+ *
+ * The lifecyle of a NSTargeter is:
+ *
+ * 0. targetDoc/targetQuery as many times as is required
+ *
+ * 1a. On targeting failure we may need to refresh, note that it happened.
+ * 1b. On stale config from a child write operation we may need to refresh, note the error.
+ *
+ * 2. RefreshIfNeeded() to get newer targeting information
+ *
+ * 3. Goto 0.
+ *
+ * The refreshIfNeeded() operation must try to make progress against noted targeting or stale
+ * config failures, see comments below. No functions may block for shared resources or network
+ * calls except refreshIfNeeded().
+ *
+ * Implementers are free to define more specific targeting error codes to allow more complex
+ * error handling.
+ *
+ * Interface must be externally synchronized if used in multiple threads, for now.
+ * TODO: Determine if we should internally synchronize.
+ */
+class NSTargeter {
+public:
+ virtual ~NSTargeter() {}
/**
- * The NSTargeter interface is used by a WriteOp to generate and target child write operations
- * to a particular collection.
- *
- * The lifecyle of a NSTargeter is:
+ * Returns the namespace targeted.
+ */
+ virtual const NamespaceString& getNS() const = 0;
+
+ /**
+ * Returns a ShardEndpoint for a single document write.
*
- * 0. targetDoc/targetQuery as many times as is required
+ * Returns !OK with message if document could not be targeted for other reasons.
+ */
+ virtual Status targetInsert(const BSONObj& doc, ShardEndpoint** endpoint) const = 0;
+
+ /**
+ * Returns a vector of ShardEndpoints for a potentially multi-shard update.
*
- * 1a. On targeting failure we may need to refresh, note that it happened.
- * 1b. On stale config from a child write operation we may need to refresh, note the error.
+ * Returns OK and fills the endpoints; returns a status describing the error otherwise.
+ */
+ virtual Status targetUpdate(const BatchedUpdateDocument& updateDoc,
+ std::vector<ShardEndpoint*>* endpoints) const = 0;
+
+ /**
+ * Returns a vector of ShardEndpoints for a potentially multi-shard delete.
*
- * 2. RefreshIfNeeded() to get newer targeting information
+ * Returns OK and fills the endpoints; returns a status describing the error otherwise.
+ */
+ virtual Status targetDelete(const BatchedDeleteDocument& deleteDoc,
+ std::vector<ShardEndpoint*>* endpoints) const = 0;
+
+ /**
+ * Returns a vector of ShardEndpoints for the entire collection.
*
- * 3. Goto 0.
+ * Returns !OK with message if the full collection could not be targeted.
+ */
+ virtual Status targetCollection(std::vector<ShardEndpoint*>* endpoints) const = 0;
+
+ /**
+ * Returns a vector of ShardEndpoints for all shards.
*
- * The refreshIfNeeded() operation must try to make progress against noted targeting or stale
- * config failures, see comments below. No functions may block for shared resources or network
- * calls except refreshIfNeeded().
+ * Returns !OK with message if all shards could not be targeted.
+ */
+ virtual Status targetAllShards(std::vector<ShardEndpoint*>* endpoints) const = 0;
+
+ /**
+ * Informs the targeter that a targeting failure occurred during one of the last targeting
+ * operations. If this is noted, we cannot note stale responses.
+ */
+ virtual void noteCouldNotTarget() = 0;
+
+ /**
+ * Informs the targeter of stale config responses for this namespace from an endpoint, with
+ * further information available in the returned staleInfo.
*
- * Implementers are free to define more specific targeting error codes to allow more complex
- * error handling.
+ * Any stale responses noted here will be taken into account on the next refresh.
*
- * Interface must be externally synchronized if used in multiple threads, for now.
- * TODO: Determine if we should internally synchronize.
+ * If stale responses are is noted, we must not have noted that we cannot target.
*/
- class NSTargeter {
- public:
-
- virtual ~NSTargeter() {
- }
-
- /**
- * Returns the namespace targeted.
- */
- virtual const NamespaceString& getNS() const = 0;
-
- /**
- * Returns a ShardEndpoint for a single document write.
- *
- * Returns !OK with message if document could not be targeted for other reasons.
- */
- virtual Status targetInsert( const BSONObj& doc, ShardEndpoint** endpoint ) const = 0;
-
- /**
- * Returns a vector of ShardEndpoints for a potentially multi-shard update.
- *
- * Returns OK and fills the endpoints; returns a status describing the error otherwise.
- */
- virtual Status targetUpdate( const BatchedUpdateDocument& updateDoc,
- std::vector<ShardEndpoint*>* endpoints ) const = 0;
-
- /**
- * Returns a vector of ShardEndpoints for a potentially multi-shard delete.
- *
- * Returns OK and fills the endpoints; returns a status describing the error otherwise.
- */
- virtual Status targetDelete( const BatchedDeleteDocument& deleteDoc,
- std::vector<ShardEndpoint*>* endpoints ) const = 0;
-
- /**
- * Returns a vector of ShardEndpoints for the entire collection.
- *
- * Returns !OK with message if the full collection could not be targeted.
- */
- virtual Status targetCollection( std::vector<ShardEndpoint*>* endpoints ) const = 0;
-
- /**
- * Returns a vector of ShardEndpoints for all shards.
- *
- * Returns !OK with message if all shards could not be targeted.
- */
- virtual Status targetAllShards( std::vector<ShardEndpoint*>* endpoints ) const = 0;
-
- /**
- * Informs the targeter that a targeting failure occurred during one of the last targeting
- * operations. If this is noted, we cannot note stale responses.
- */
- virtual void noteCouldNotTarget() = 0;
-
- /**
- * Informs the targeter of stale config responses for this namespace from an endpoint, with
- * further information available in the returned staleInfo.
- *
- * Any stale responses noted here will be taken into account on the next refresh.
- *
- * If stale responses are is noted, we must not have noted that we cannot target.
- */
- virtual void noteStaleResponse( const ShardEndpoint& endpoint,
- const BSONObj& staleInfo ) = 0;
-
- /**
- * Refreshes the targeting metadata for the namespace if needed, based on previously-noted
- * stale responses and targeting failures.
- *
- * After this function is called, the targeter should be in a state such that the noted
- * stale responses are not seen again and if a targeting failure occurred it reloaded -
- * it should try to make progress. If provided, wasChanged is set to true if the targeting
- * information used here was changed.
- *
- * NOTE: This function may block for shared resources or network calls.
- * Returns !OK with message if could not refresh
- */
- virtual Status refreshIfNeeded( bool* wasChanged ) = 0;
-
- };
+ virtual void noteStaleResponse(const ShardEndpoint& endpoint, const BSONObj& staleInfo) = 0;
/**
- * A ShardEndpoint represents a destination for a targeted query or document. It contains both
- * the logical target (shard name/version/broadcast) and the physical target (host name).
+ * Refreshes the targeting metadata for the namespace if needed, based on previously-noted
+ * stale responses and targeting failures.
+ *
+ * After this function is called, the targeter should be in a state such that the noted
+ * stale responses are not seen again and if a targeting failure occurred it reloaded -
+ * it should try to make progress. If provided, wasChanged is set to true if the targeting
+ * information used here was changed.
+ *
+ * NOTE: This function may block for shared resources or network calls.
+ * Returns !OK with message if could not refresh
*/
- struct ShardEndpoint {
+ virtual Status refreshIfNeeded(bool* wasChanged) = 0;
+};
- ShardEndpoint() {
- }
+/**
+ * A ShardEndpoint represents a destination for a targeted query or document. It contains both
+ * the logical target (shard name/version/broadcast) and the physical target (host name).
+ */
+struct ShardEndpoint {
+ ShardEndpoint() {}
- ShardEndpoint( const ShardEndpoint& other ) :
- shardName( other.shardName ), shardVersion( other.shardVersion ) {
- }
+ ShardEndpoint(const ShardEndpoint& other)
+ : shardName(other.shardName), shardVersion(other.shardVersion) {}
- ShardEndpoint( const std::string& shardName,
- const ChunkVersion& shardVersion ) :
- shardName( shardName ), shardVersion( shardVersion ) {
- }
+ ShardEndpoint(const std::string& shardName, const ChunkVersion& shardVersion)
+ : shardName(shardName), shardVersion(shardVersion) {}
- const std::string shardName;
- const ChunkVersion shardVersion;
+ const std::string shardName;
+ const ChunkVersion shardVersion;
- //
- // For testing *only* - do not use as part of API
- //
+ //
+ // For testing *only* - do not use as part of API
+ //
- BSONObj toBSON() const {
- BSONObjBuilder b;
- appendBSON( &b );
- return b.obj();
- }
+ BSONObj toBSON() const {
+ BSONObjBuilder b;
+ appendBSON(&b);
+ return b.obj();
+ }
- void appendBSON( BSONObjBuilder* builder ) const {
- builder->append( "shardName", shardName );
- shardVersion.addToBSON( *builder, "shardVersion" );
- }
- };
+ void appendBSON(BSONObjBuilder* builder) const {
+ builder->append("shardName", shardName);
+ shardVersion.addToBSON(*builder, "shardVersion");
+ }
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/request.cpp b/src/mongo/s/request.cpp
index 6ece1659e93..89fd65e079c 100644
--- a/src/mongo/s/request.cpp
+++ b/src/mongo/s/request.cpp
@@ -48,115 +48,98 @@
namespace mongo {
- using std::endl;
- using std::string;
-
- Request::Request(Message& m, AbstractMessagingPort* p)
- : _clientInfo(&cc()),
- _m(m),
- _d(m),
- _p(p),
- _id(_m.header().getId()),
- _didInit(false) {
-
- ClusterLastErrorInfo::get(_clientInfo).newRequest();
- }
-
- void Request::init() {
- if (_didInit) {
- return;
- }
+using std::endl;
+using std::string;
- _m.header().setId(_id);
- LastError::get(_clientInfo).startRequest();
- ClusterLastErrorInfo::get(_clientInfo).clearRequestInfo();
+Request::Request(Message& m, AbstractMessagingPort* p)
+ : _clientInfo(&cc()), _m(m), _d(m), _p(p), _id(_m.header().getId()), _didInit(false) {
+ ClusterLastErrorInfo::get(_clientInfo).newRequest();
+}
- if (_d.messageShouldHaveNs()) {
- const NamespaceString nss(getns());
-
- uassert(ErrorCodes::IllegalOperation,
- "can't use 'local' database through mongos",
- nss.db() != "local");
-
- uassert(ErrorCodes::InvalidNamespace,
- str::stream() << "Invalid ns [" << nss.ns() << "]",
- nss.isValid());
- }
-
- AuthorizationSession::get(_clientInfo)->startRequest(NULL);
- _didInit = true;
+void Request::init() {
+ if (_didInit) {
+ return;
}
- void Request::process( int attempt ) {
- init();
- int op = _m.operation();
- verify( op > dbMsg );
+ _m.header().setId(_id);
+ LastError::get(_clientInfo).startRequest();
+ ClusterLastErrorInfo::get(_clientInfo).clearRequestInfo();
- const MSGID msgId = _m.header().getId();
+ if (_d.messageShouldHaveNs()) {
+ const NamespaceString nss(getns());
- Timer t;
- LOG(3) << "Request::process begin ns: " << getns()
- << " msg id: " << msgId
- << " op: " << op
- << " attempt: " << attempt
- << endl;
+ uassert(ErrorCodes::IllegalOperation,
+ "can't use 'local' database through mongos",
+ nss.db() != "local");
- _d.markSet();
+ uassert(ErrorCodes::InvalidNamespace,
+ str::stream() << "Invalid ns [" << nss.ns() << "]",
+ nss.isValid());
+ }
- bool iscmd = false;
- if ( op == dbKillCursors ) {
- cursorCache.gotKillCursors( _m );
- globalOpCounters.gotOp( op , iscmd );
- }
- else if ( op == dbQuery ) {
- NamespaceString nss(getns());
- iscmd = nss.isCommand() || nss.isSpecialCommand();
-
- if (iscmd) {
- int n = _d.getQueryNToReturn();
- uassert( 16978, str::stream() << "bad numberToReturn (" << n
- << ") for $cmd type ns - can only be 1 or -1",
- n == 1 || n == -1 );
-
- Strategy::clientCommandOp(*this);
- }
- else {
- Strategy::queryOp( *this );
- }
-
- globalOpCounters.gotOp( op , iscmd );
- }
- else if ( op == dbGetMore ) {
- Strategy::getMore( *this );
- globalOpCounters.gotOp( op , iscmd );
- }
- else {
- Strategy::writeOp( op, *this );
- // globalOpCounters are handled by write commands.
+ AuthorizationSession::get(_clientInfo)->startRequest(NULL);
+ _didInit = true;
+}
+
+void Request::process(int attempt) {
+ init();
+ int op = _m.operation();
+ verify(op > dbMsg);
+
+ const MSGID msgId = _m.header().getId();
+
+ Timer t;
+ LOG(3) << "Request::process begin ns: " << getns() << " msg id: " << msgId << " op: " << op
+ << " attempt: " << attempt << endl;
+
+ _d.markSet();
+
+ bool iscmd = false;
+ if (op == dbKillCursors) {
+ cursorCache.gotKillCursors(_m);
+ globalOpCounters.gotOp(op, iscmd);
+ } else if (op == dbQuery) {
+ NamespaceString nss(getns());
+ iscmd = nss.isCommand() || nss.isSpecialCommand();
+
+ if (iscmd) {
+ int n = _d.getQueryNToReturn();
+ uassert(16978,
+ str::stream() << "bad numberToReturn (" << n
+ << ") for $cmd type ns - can only be 1 or -1",
+ n == 1 || n == -1);
+
+ Strategy::clientCommandOp(*this);
+ } else {
+ Strategy::queryOp(*this);
}
- LOG(3) << "Request::process end ns: " << getns()
- << " msg id: " << msgId
- << " op: " << op
- << " attempt: " << attempt
- << " " << t.millis() << "ms"
- << endl;
+ globalOpCounters.gotOp(op, iscmd);
+ } else if (op == dbGetMore) {
+ Strategy::getMore(*this);
+ globalOpCounters.gotOp(op, iscmd);
+ } else {
+ Strategy::writeOp(op, *this);
+ // globalOpCounters are handled by write commands.
}
- void Request::reply( Message & response , const string& fromServer ) {
- verify( _didInit );
- long long cursor = response.header().getCursor();
- if ( cursor ) {
- if ( fromServer.size() ) {
- cursorCache.storeRef(fromServer, cursor, getns());
- }
- else {
- // probably a getMore
- // make sure we have a ref for this
- verify( cursorCache.getRef( cursor ).size() );
- }
+ LOG(3) << "Request::process end ns: " << getns() << " msg id: " << msgId << " op: " << op
+ << " attempt: " << attempt << " " << t.millis() << "ms" << endl;
+}
+
+void Request::reply(Message& response, const string& fromServer) {
+ verify(_didInit);
+ long long cursor = response.header().getCursor();
+ if (cursor) {
+ if (fromServer.size()) {
+ cursorCache.storeRef(fromServer, cursor, getns());
+ } else {
+ // probably a getMore
+ // make sure we have a ref for this
+ verify(cursorCache.getRef(cursor).size());
}
- _p->reply( _m , response , _id );
}
+ _p->reply(_m, response, _id);
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/request.h b/src/mongo/s/request.h
index 4bdf7a816bb..775b44f6511 100644
--- a/src/mongo/s/request.h
+++ b/src/mongo/s/request.h
@@ -35,51 +35,57 @@
namespace mongo {
- class Client;
+class Client;
- class Request {
- MONGO_DISALLOW_COPYING(Request);
- public:
- Request(Message& m, AbstractMessagingPort* p);
+class Request {
+ MONGO_DISALLOW_COPYING(Request);
- const char* getns() const {
- return _d.getns();
- }
+public:
+ Request(Message& m, AbstractMessagingPort* p);
- int op() const {
- return _m.operation();
- }
+ const char* getns() const {
+ return _d.getns();
+ }
- bool expectResponse() const {
- return op() == dbQuery || op() == dbGetMore;
- }
+ int op() const {
+ return _m.operation();
+ }
- bool isCommand() const;
+ bool expectResponse() const {
+ return op() == dbQuery || op() == dbGetMore;
+ }
- MSGID id() const {
- return _id;
- }
+ bool isCommand() const;
- void reply(Message & response, const std::string& fromServer);
+ MSGID id() const {
+ return _id;
+ }
- Message& m() { return _m; }
- DbMessage& d() { return _d; }
- AbstractMessagingPort* p() const { return _p; }
+ void reply(Message& response, const std::string& fromServer);
- void process( int attempt = 0 );
+ Message& m() {
+ return _m;
+ }
+ DbMessage& d() {
+ return _d;
+ }
+ AbstractMessagingPort* p() const {
+ return _p;
+ }
- void init();
+ void process(int attempt = 0);
- private:
- Client* const _clientInfo;
+ void init();
- Message& _m;
- DbMessage _d;
- AbstractMessagingPort* const _p;
+private:
+ Client* const _clientInfo;
- MSGID _id;
+ Message& _m;
+ DbMessage _d;
+ AbstractMessagingPort* const _p;
- bool _didInit;
- };
+ MSGID _id;
+ bool _didInit;
+};
}
diff --git a/src/mongo/s/s_only.cpp b/src/mongo/s/s_only.cpp
index bd13778506a..671e4708864 100644
--- a/src/mongo/s/s_only.cpp
+++ b/src/mongo/s/s_only.cpp
@@ -49,130 +49,121 @@
namespace mongo {
- using std::string;
- using std::stringstream;
-
-
- bool isMongos() {
- return true;
- }
-
- /** When this callback is run, we record a shard that we've used for useful work
- * in an operation to be read later by getLastError()
- */
- void usingAShardConnection(const std::string& addr) {
- ClusterLastErrorInfo::get(cc()).addShardHost(addr);
+using std::string;
+using std::stringstream;
+
+
+bool isMongos() {
+ return true;
+}
+
+/** When this callback is run, we record a shard that we've used for useful work
+ * in an operation to be read later by getLastError()
+*/
+void usingAShardConnection(const std::string& addr) {
+ ClusterLastErrorInfo::get(cc()).addShardHost(addr);
+}
+
+// called into by the web server. For now we just translate the parameters
+// to their old style equivalents.
+void Command::execCommand(OperationContext* txn,
+ Command* command,
+ const rpc::RequestInterface& request,
+ rpc::ReplyBuilderInterface* replyBuilder) {
+ int queryFlags = 0;
+ BSONObj cmdObj;
+
+ std::tie(cmdObj, queryFlags) = uassertStatusOK(
+ rpc::downconvertRequestMetadata(request.getCommandArgs(), request.getMetadata()));
+
+ std::string db = request.getDatabase().rawData();
+ BSONObjBuilder result;
+
+ execCommandClientBasic(txn,
+ command,
+ *txn->getClient(),
+ queryFlags,
+ request.getDatabase().rawData(),
+ cmdObj,
+ result);
+
+ replyBuilder->setMetadata(rpc::makeEmptyMetadata()).setCommandReply(result.done());
+}
+
+void Command::execCommandClientBasic(OperationContext* txn,
+ Command* c,
+ ClientBasic& client,
+ int queryOptions,
+ const char* ns,
+ BSONObj& cmdObj,
+ BSONObjBuilder& result) {
+ std::string dbname = nsToDatabase(ns);
+
+ if (cmdObj.getBoolField("help")) {
+ stringstream help;
+ help << "help for: " << c->name << " ";
+ c->help(help);
+ result.append("help", help.str());
+ result.append("lockType", c->isWriteCommandForConfigServer() ? 1 : 0);
+ appendCommandStatus(result, true, "");
+ return;
}
- // called into by the web server. For now we just translate the parameters
- // to their old style equivalents.
- void Command::execCommand(OperationContext* txn,
- Command* command,
- const rpc::RequestInterface& request,
- rpc::ReplyBuilderInterface* replyBuilder) {
-
- int queryFlags = 0;
- BSONObj cmdObj;
-
- std::tie(cmdObj, queryFlags) = uassertStatusOK(
- rpc::downconvertRequestMetadata(request.getCommandArgs(),
- request.getMetadata())
- );
-
- std::string db = request.getDatabase().rawData();
- BSONObjBuilder result;
-
- execCommandClientBasic(txn,
- command,
- *txn->getClient(),
- queryFlags,
- request.getDatabase().rawData(),
- cmdObj,
- result);
-
- replyBuilder
- ->setMetadata(rpc::makeEmptyMetadata())
- .setCommandReply(result.done());
+ Status status = _checkAuthorization(c, &client, dbname, cmdObj);
+ if (!status.isOK()) {
+ appendCommandStatus(result, status);
+ return;
}
- void Command::execCommandClientBasic(OperationContext* txn,
- Command * c ,
- ClientBasic& client,
- int queryOptions,
- const char *ns,
- BSONObj& cmdObj,
- BSONObjBuilder& result) {
- std::string dbname = nsToDatabase(ns);
-
- if (cmdObj.getBoolField("help")) {
- stringstream help;
- help << "help for: " << c->name << " ";
- c->help( help );
- result.append( "help" , help.str() );
- result.append("lockType", c->isWriteCommandForConfigServer() ? 1 : 0);
- appendCommandStatus(result, true, "");
- return;
- }
-
- Status status = _checkAuthorization(c, &client, dbname, cmdObj);
- if (!status.isOK()) {
- appendCommandStatus(result, status);
- return;
- }
-
- c->_commandsExecuted.increment();
-
- std::string errmsg;
- bool ok;
- try {
- ok = c->run(txn, dbname , cmdObj, queryOptions, errmsg, result);
- }
- catch (const DBException& e) {
- ok = false;
- int code = e.getCode();
- if (code == RecvStaleConfigCode) { // code for StaleConfigException
- throw;
- }
-
- errmsg = e.what();
- result.append("code", code);
+ c->_commandsExecuted.increment();
+
+ std::string errmsg;
+ bool ok;
+ try {
+ ok = c->run(txn, dbname, cmdObj, queryOptions, errmsg, result);
+ } catch (const DBException& e) {
+ ok = false;
+ int code = e.getCode();
+ if (code == RecvStaleConfigCode) { // code for StaleConfigException
+ throw;
}
- if ( !ok ) {
- c->_commandsFailed.increment();
- }
+ errmsg = e.what();
+ result.append("code", code);
+ }
- appendCommandStatus(result, ok, errmsg);
+ if (!ok) {
+ c->_commandsFailed.increment();
}
- void Command::runAgainstRegistered(const char *ns,
- BSONObj& jsobj,
- BSONObjBuilder& anObjBuilder,
- int queryOptions) {
+ appendCommandStatus(result, ok, errmsg);
+}
- // It should be impossible for this uassert to fail since there should be no way to get
- // into this function with any other collection name.
- uassert(16618,
+void Command::runAgainstRegistered(const char* ns,
+ BSONObj& jsobj,
+ BSONObjBuilder& anObjBuilder,
+ int queryOptions) {
+ // It should be impossible for this uassert to fail since there should be no way to get
+ // into this function with any other collection name.
+ uassert(16618,
"Illegal attempt to run a command against a namespace other than $cmd.",
nsToCollectionSubstring(ns) == "$cmd");
- BSONElement e = jsobj.firstElement();
- std::string commandName = e.fieldName();
- Command* c = e.type() ? Command::findCommand(commandName) : NULL;
- if (!c) {
- Command::appendCommandStatus(anObjBuilder,
- false,
- str::stream() << "no such cmd: " << commandName);
- anObjBuilder.append("code", ErrorCodes::CommandNotFound);
- Command::unknownCommands.increment();
- return;
- }
-
- auto txn = cc().makeOperationContext();
- execCommandClientBasic(txn.get(), c, cc(), queryOptions, ns, jsobj, anObjBuilder);
+ BSONElement e = jsobj.firstElement();
+ std::string commandName = e.fieldName();
+ Command* c = e.type() ? Command::findCommand(commandName) : NULL;
+ if (!c) {
+ Command::appendCommandStatus(
+ anObjBuilder, false, str::stream() << "no such cmd: " << commandName);
+ anObjBuilder.append("code", ErrorCodes::CommandNotFound);
+ Command::unknownCommands.increment();
+ return;
}
- void Command::registerError(OperationContext* txn, const DBException& exception) {
- }
+ auto txn = cc().makeOperationContext();
+ execCommandClientBasic(txn.get(), c, cc(), queryOptions, ns, jsobj, anObjBuilder);
+}
+
+void Command::registerError(OperationContext* txn, const DBException& exception) {}
-} //namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/server.cpp b/src/mongo/s/server.cpp
index 092a7339d67..94ed60e91de 100644
--- a/src/mongo/s/server.cpp
+++ b/src/mongo/s/server.cpp
@@ -92,125 +92,119 @@
namespace mongo {
- using std::string;
- using std::vector;
+using std::string;
+using std::vector;
- using logger::LogComponent;
+using logger::LogComponent;
#if defined(_WIN32)
- ntservice::NtServiceDefaultStrings defaultServiceStrings = {
- L"MongoS",
- L"MongoDB Router",
- L"MongoDB Sharding Router"
- };
- static ExitCode initService();
+ntservice::NtServiceDefaultStrings defaultServiceStrings = {
+ L"MongoS", L"MongoDB Router", L"MongoDB Sharding Router"};
+static ExitCode initService();
#endif
- bool dbexitCalled = false;
+bool dbexitCalled = false;
- bool inShutdown() {
- return dbexitCalled;
- }
+bool inShutdown() {
+ return dbexitCalled;
+}
- bool haveLocalShardingInfo( Client* client, const string& ns ) {
- verify( 0 );
- return false;
- }
+bool haveLocalShardingInfo(Client* client, const string& ns) {
+ verify(0);
+ return false;
+}
- static BSONObj buildErrReply( const DBException& ex ) {
- BSONObjBuilder errB;
- errB.append( "$err", ex.what() );
- errB.append( "code", ex.getCode() );
- if ( !ex._shard.empty() ) {
- errB.append( "shard", ex._shard );
- }
- return errB.obj();
+static BSONObj buildErrReply(const DBException& ex) {
+ BSONObjBuilder errB;
+ errB.append("$err", ex.what());
+ errB.append("code", ex.getCode());
+ if (!ex._shard.empty()) {
+ errB.append("shard", ex._shard);
}
+ return errB.obj();
+}
- class ShardedMessageHandler : public MessageHandler {
- public:
- virtual ~ShardedMessageHandler() {}
-
- virtual void connected(AbstractMessagingPort* p) {
- Client::initThread("conn", getGlobalServiceContext(), p);
- }
-
- virtual void process(Message& m, AbstractMessagingPort* p) {
- verify( p );
- Request r( m , p );
-
- try {
- r.init();
- r.process();
- }
- catch ( const AssertionException& ex ) {
-
- LOG( ex.isUserAssertion() ? 1 : 0 ) << "Assertion failed"
- << " while processing " << opToString( m.operation() ) << " op"
- << " for " << r.getns() << causedBy( ex );
+class ShardedMessageHandler : public MessageHandler {
+public:
+ virtual ~ShardedMessageHandler() {}
- if ( r.expectResponse() ) {
- m.header().setId(r.id());
- replyToQuery( ResultFlag_ErrSet, p , m , buildErrReply( ex ) );
- }
+ virtual void connected(AbstractMessagingPort* p) {
+ Client::initThread("conn", getGlobalServiceContext(), p);
+ }
- // We *always* populate the last error for now
- LastError::get(cc()).setLastError(ex.getCode(), ex.what());
+ virtual void process(Message& m, AbstractMessagingPort* p) {
+ verify(p);
+ Request r(m, p);
+
+ try {
+ r.init();
+ r.process();
+ } catch (const AssertionException& ex) {
+ LOG(ex.isUserAssertion() ? 1 : 0) << "Assertion failed"
+ << " while processing " << opToString(m.operation())
+ << " op"
+ << " for " << r.getns() << causedBy(ex);
+
+ if (r.expectResponse()) {
+ m.header().setId(r.id());
+ replyToQuery(ResultFlag_ErrSet, p, m, buildErrReply(ex));
}
- catch ( const DBException& ex ) {
- log() << "Exception thrown"
- << " while processing " << opToString( m.operation() ) << " op"
- << " for " << r.getns() << causedBy( ex );
+ // We *always* populate the last error for now
+ LastError::get(cc()).setLastError(ex.getCode(), ex.what());
+ } catch (const DBException& ex) {
+ log() << "Exception thrown"
+ << " while processing " << opToString(m.operation()) << " op"
+ << " for " << r.getns() << causedBy(ex);
- if ( r.expectResponse() ) {
- m.header().setId(r.id());
- replyToQuery( ResultFlag_ErrSet, p , m , buildErrReply( ex ) );
- }
-
- // We *always* populate the last error for now
- LastError::get(cc()).setLastError(ex.getCode(), ex.what());
+ if (r.expectResponse()) {
+ m.header().setId(r.id());
+ replyToQuery(ResultFlag_ErrSet, p, m, buildErrReply(ex));
}
- // Release connections back to pool, if any still cached
- ShardConnection::releaseMyConnections();
+ // We *always* populate the last error for now
+ LastError::get(cc()).setLastError(ex.getCode(), ex.what());
}
- };
- void start( const MessageServer::Options& opts ) {
- balancer.go();
- cursorCache.startTimeoutThread();
- UserCacheInvalidator cacheInvalidatorThread(getGlobalAuthorizationManager());
- cacheInvalidatorThread.go();
+ // Release connections back to pool, if any still cached
+ ShardConnection::releaseMyConnections();
+ }
+};
+
+void start(const MessageServer::Options& opts) {
+ balancer.go();
+ cursorCache.startTimeoutThread();
+ UserCacheInvalidator cacheInvalidatorThread(getGlobalAuthorizationManager());
+ cacheInvalidatorThread.go();
- PeriodicTask::startRunningPeriodicTasks();
+ PeriodicTask::startRunningPeriodicTasks();
- ShardedMessageHandler handler;
- MessageServer * server = createServer( opts , &handler );
- server->setAsTimeTracker();
- server->setupSockets();
- server->run();
- }
+ ShardedMessageHandler handler;
+ MessageServer* server = createServer(opts, &handler);
+ server->setAsTimeTracker();
+ server->setupSockets();
+ server->run();
+}
- DBClientBase* createDirectClient(OperationContext* txn) {
- uassert( 10197 , "createDirectClient not implemented for sharding yet" , 0 );
- return 0;
- }
+DBClientBase* createDirectClient(OperationContext* txn) {
+ uassert(10197, "createDirectClient not implemented for sharding yet", 0);
+ return 0;
+}
-} // namespace mongo
+} // namespace mongo
using namespace mongo;
-static ExitCode runMongosServer( bool doUpgrade ) {
- setThreadName( "mongosMain" );
- printShardingVersionInfo( false );
+static ExitCode runMongosServer(bool doUpgrade) {
+ setThreadName("mongosMain");
+ printShardingVersionInfo(false);
// Add sharding hooks to both connection pools - ShardingConnectionHook includes auth hooks
globalConnPool.addHook(new ShardingConnectionHook(false));
shardConnectionPool.addHook(new ShardingConnectionHook(true));
// Mongos shouldn't lazily kill cursors, otherwise we can end up with extras from migration
- DBClientConnection::setLazyKillCursor( false );
+ DBClientConnection::setLazyKillCursor(false);
ReplicaSetMonitor::setConfigChangeHook(&ConfigServer::replicaSetChange);
@@ -232,11 +226,11 @@ static ExitCode runMongosServer( bool doUpgrade ) {
}
}
- auto shardRegistry = stdx::make_unique<ShardRegistry>(
- stdx::make_unique<RemoteCommandTargeterFactoryImpl>(),
- stdx::make_unique<RemoteCommandRunnerImpl>(0),
- std::unique_ptr<executor::TaskExecutor>{nullptr},
- catalogManager.get());
+ auto shardRegistry =
+ stdx::make_unique<ShardRegistry>(stdx::make_unique<RemoteCommandTargeterFactoryImpl>(),
+ stdx::make_unique<RemoteCommandRunnerImpl>(0),
+ std::unique_ptr<executor::TaskExecutor>{nullptr},
+ catalogManager.get());
grid.init(std::move(catalogManager), std::move(shardRegistry));
@@ -259,10 +253,8 @@ static ExitCode runMongosServer( bool doUpgrade ) {
#endif
if (serverGlobalParams.isHttpInterfaceEnabled) {
- std::shared_ptr<DbWebServer> dbWebServer(
- new DbWebServer(serverGlobalParams.bind_ip,
- serverGlobalParams.port + 1000,
- new NoAdminAccess()));
+ std::shared_ptr<DbWebServer> dbWebServer(new DbWebServer(
+ serverGlobalParams.bind_ip, serverGlobalParams.port + 1000, new NoAdminAccess()));
dbWebServer->setupSockets();
stdx::thread web(stdx::bind(&webServerListenThread, dbWebServer));
@@ -298,12 +290,9 @@ MONGO_INITIALIZER_GENERAL(ForkServer,
static void startupConfigActions(const std::vector<std::string>& argv) {
#if defined(_WIN32)
vector<string> disallowedOptions;
- disallowedOptions.push_back( "upgrade" );
- ntservice::configureService(initService,
- moe::startupOptionsParsed,
- defaultServiceStrings,
- disallowedOptions,
- argv);
+ disallowedOptions.push_back("upgrade");
+ ntservice::configureService(
+ initService, moe::startupOptionsParsed, defaultServiceStrings, disallowedOptions, argv);
#endif
}
@@ -316,15 +305,15 @@ static int _main() {
// we either have a setting where all processes are in localhost or none are
std::vector<HostAndPort> configServers = mongosGlobalParams.configdbs.getServers();
for (std::vector<HostAndPort>::const_iterator it = configServers.begin();
- it != configServers.end(); ++it) {
-
+ it != configServers.end();
+ ++it) {
const HostAndPort& configAddr = *it;
if (it == configServers.begin()) {
- grid.setAllowLocalHost( configAddr.isLocalHost() );
+ grid.setAllowLocalHost(configAddr.isLocalHost());
}
- if ( configAddr.isLocalHost() != grid.allowLocalHost() ) {
+ if (configAddr.isLocalHost() != grid.allowLocalHost()) {
mongo::log(LogComponent::kDefault)
<< "cannot mix localhost and ip addresses in configdbs";
return 10;
@@ -343,7 +332,7 @@ static int _main() {
// To maintain backwards compatibility, we exit with EXIT_NET_ERROR if the listener loop returns.
if (exitCode == EXIT_NET_ERROR) {
- dbexit( EXIT_NET_ERROR );
+ dbexit(EXIT_NET_ERROR);
}
return (exitCode == EXIT_CLEAN) ? 0 : 1;
@@ -351,43 +340,43 @@ static int _main() {
#if defined(_WIN32)
namespace mongo {
- static ExitCode initService() {
- ntservice::reportStatus( SERVICE_RUNNING );
- log() << "Service running";
+static ExitCode initService() {
+ ntservice::reportStatus(SERVICE_RUNNING);
+ log() << "Service running";
- ExitCode exitCode = runMongosServer(mongosGlobalParams.upgrade);
+ ExitCode exitCode = runMongosServer(mongosGlobalParams.upgrade);
- // ignore EXIT_NET_ERROR on clean shutdown since we return this when the listening socket
- // is closed
- return (exitCode == EXIT_NET_ERROR && inShutdown()) ? EXIT_CLEAN : exitCode;
- }
+ // ignore EXIT_NET_ERROR on clean shutdown since we return this when the listening socket
+ // is closed
+ return (exitCode == EXIT_NET_ERROR && inShutdown()) ? EXIT_CLEAN : exitCode;
+}
} // namespace mongo
#endif
namespace {
- std::unique_ptr<AuthzManagerExternalState> createAuthzManagerExternalStateMongos() {
- return stdx::make_unique<AuthzManagerExternalStateMongos>();
- }
+std::unique_ptr<AuthzManagerExternalState> createAuthzManagerExternalStateMongos() {
+ return stdx::make_unique<AuthzManagerExternalStateMongos>();
+}
- MONGO_INITIALIZER(CreateAuthorizationExternalStateFactory) (InitializerContext* context) {
- AuthzManagerExternalState::create = &createAuthzManagerExternalStateMongos;
- return Status::OK();
- }
+MONGO_INITIALIZER(CreateAuthorizationExternalStateFactory)(InitializerContext* context) {
+ AuthzManagerExternalState::create = &createAuthzManagerExternalStateMongos;
+ return Status::OK();
+}
- MONGO_INITIALIZER(SetGlobalEnvironment)(InitializerContext* context) {
- setGlobalServiceContext(stdx::make_unique<ServiceContextNoop>());
- return Status::OK();
- }
+MONGO_INITIALIZER(SetGlobalEnvironment)(InitializerContext* context) {
+ setGlobalServiceContext(stdx::make_unique<ServiceContextNoop>());
+ return Status::OK();
+}
#ifdef MONGO_CONFIG_SSL
- MONGO_INITIALIZER_GENERAL(setSSLManagerType,
- MONGO_NO_PREREQUISITES,
- ("SSLManager"))(InitializerContext* context) {
- isSSLServer = true;
- return Status::OK();
- }
+MONGO_INITIALIZER_GENERAL(setSSLManagerType,
+ MONGO_NO_PREREQUISITES,
+ ("SSLManager"))(InitializerContext* context) {
+ isSSLServer = true;
+ return Status::OK();
+}
#endif
-} // namespace
+} // namespace
int mongoSMain(int argc, char* argv[], char** envp) {
static StaticObserver staticObserver;
@@ -410,17 +399,13 @@ int mongoSMain(int argc, char* argv[], char** envp) {
try {
int exitCode = _main();
return exitCode;
- }
- catch(const SocketException& e) {
+ } catch (const SocketException& e) {
error() << "uncaught SocketException in mongos main: " << e.toString();
- }
- catch (const DBException& e) {
+ } catch (const DBException& e) {
error() << "uncaught DBException in mongos main: " << e.toString();
- }
- catch (const std::exception& e) {
+ } catch (const std::exception& e) {
error() << "uncaught std::exception in mongos main:" << e.what();
- }
- catch (...) {
+ } catch (...) {
error() << "uncaught unknown exception in mongos main";
}
@@ -453,10 +438,10 @@ void mongo::signalShutdown() {
void mongo::exitCleanly(ExitCode code) {
// TODO: do we need to add anything?
grid.catalogManager()->shutDown();
- mongo::dbexit( code );
+ mongo::dbexit(code);
}
-void mongo::dbexit(ExitCode rc, const char *why) {
+void mongo::dbexit(ExitCode rc, const char* why) {
dbexitCalled = true;
audit::logShutdown(ClientBasic::getCurrent());
diff --git a/src/mongo/s/server.h b/src/mongo/s/server.h
index 3594cb67b59..1b76ac71a15 100644
--- a/src/mongo/s/server.h
+++ b/src/mongo/s/server.h
@@ -35,8 +35,8 @@
namespace mongo {
- extern OID serverID;
+extern OID serverID;
- // from request.cpp
- void processRequest(Message& m, MessagingPort& p);
+// from request.cpp
+void processRequest(Message& m, MessagingPort& p);
}
diff --git a/src/mongo/s/shard_key_pattern.cpp b/src/mongo/s/shard_key_pattern.cpp
index f48088e9837..f411461b925 100644
--- a/src/mongo/s/shard_key_pattern.cpp
+++ b/src/mongo/s/shard_key_pattern.cpp
@@ -40,400 +40,379 @@
namespace mongo {
- using std::make_pair;
- using std::pair;
- using std::shared_ptr;
- using std::string;
- using std::unique_ptr;
- using std::vector;
-
- using pathsupport::EqualityMatches;
- using mongoutils::str::stream;
-
- const int ShardKeyPattern::kMaxShardKeySizeBytes = 512;
- const unsigned int ShardKeyPattern::kMaxFlattenedInCombinations = 4000000;
-
- Status ShardKeyPattern::checkShardKeySize(const BSONObj& shardKey) {
- if (shardKey.objsize() <= kMaxShardKeySizeBytes)
- return Status::OK();
-
- return Status(ErrorCodes::ShardKeyTooBig,
- stream() << "shard keys must be less than " << kMaxShardKeySizeBytes
- << " bytes, but key " << shardKey << " is " << shardKey.objsize()
- << " bytes");
- }
-
- static bool isHashedPatternEl(const BSONElement& el) {
- return el.type() == String && el.String() == IndexNames::HASHED;
- }
-
- /**
- * Currently the allowable shard keys are either
- * i) a hashed single field, e.g. { a : "hashed" }, or
- * ii) a compound list of ascending, potentially-nested field paths, e.g. { a : 1 , b.c : 1 }
- */
- static vector<FieldRef*> parseShardKeyPattern(const BSONObj& keyPattern) {
-
- OwnedPointerVector<FieldRef> parsedPaths;
- static const vector<FieldRef*> empty;
-
- BSONObjIterator patternIt(keyPattern);
- while (patternIt.more()) {
-
- BSONElement patternEl = patternIt.next();
- parsedPaths.push_back(new FieldRef(patternEl.fieldNameStringData()));
- const FieldRef& patternPath = *parsedPaths.back();
+using std::make_pair;
+using std::pair;
+using std::shared_ptr;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+using pathsupport::EqualityMatches;
+using mongoutils::str::stream;
+
+const int ShardKeyPattern::kMaxShardKeySizeBytes = 512;
+const unsigned int ShardKeyPattern::kMaxFlattenedInCombinations = 4000000;
+
+Status ShardKeyPattern::checkShardKeySize(const BSONObj& shardKey) {
+ if (shardKey.objsize() <= kMaxShardKeySizeBytes)
+ return Status::OK();
+
+ return Status(ErrorCodes::ShardKeyTooBig,
+ stream() << "shard keys must be less than " << kMaxShardKeySizeBytes
+ << " bytes, but key " << shardKey << " is " << shardKey.objsize()
+ << " bytes");
+}
- // Empty path
- if (patternPath.numParts() == 0)
- return empty;
+static bool isHashedPatternEl(const BSONElement& el) {
+ return el.type() == String && el.String() == IndexNames::HASHED;
+}
- // Extra "." in path?
- if (patternPath.dottedField() != patternEl.fieldNameStringData())
+/**
+ * Currently the allowable shard keys are either
+ * i) a hashed single field, e.g. { a : "hashed" }, or
+ * ii) a compound list of ascending, potentially-nested field paths, e.g. { a : 1 , b.c : 1 }
+ */
+static vector<FieldRef*> parseShardKeyPattern(const BSONObj& keyPattern) {
+ OwnedPointerVector<FieldRef> parsedPaths;
+ static const vector<FieldRef*> empty;
+
+ BSONObjIterator patternIt(keyPattern);
+ while (patternIt.more()) {
+ BSONElement patternEl = patternIt.next();
+ parsedPaths.push_back(new FieldRef(patternEl.fieldNameStringData()));
+ const FieldRef& patternPath = *parsedPaths.back();
+
+ // Empty path
+ if (patternPath.numParts() == 0)
+ return empty;
+
+ // Extra "." in path?
+ if (patternPath.dottedField() != patternEl.fieldNameStringData())
+ return empty;
+
+ // Empty parts of the path, ".."?
+ for (size_t i = 0; i < patternPath.numParts(); ++i) {
+ if (patternPath.getPart(i).size() == 0)
return empty;
+ }
- // Empty parts of the path, ".."?
- for (size_t i = 0; i < patternPath.numParts(); ++i) {
- if (patternPath.getPart(i).size() == 0)
- return empty;
- }
-
- // Numeric and ascending (1.0), or "hashed" and single field
- if (!patternEl.isNumber()) {
- if (keyPattern.nFields() != 1 || !isHashedPatternEl(patternEl))
- return empty;
- }
- else if (patternEl.numberInt() != 1) {
+ // Numeric and ascending (1.0), or "hashed" and single field
+ if (!patternEl.isNumber()) {
+ if (keyPattern.nFields() != 1 || !isHashedPatternEl(patternEl))
return empty;
- }
+ } else if (patternEl.numberInt() != 1) {
+ return empty;
}
-
- return parsedPaths.release();
}
- ShardKeyPattern::ShardKeyPattern(const BSONObj& keyPattern)
- : _keyPatternPaths(parseShardKeyPattern(keyPattern)),
- _keyPattern(_keyPatternPaths.empty() ? BSONObj() : keyPattern) {
- }
-
- ShardKeyPattern::ShardKeyPattern(const KeyPattern& keyPattern)
- : _keyPatternPaths(parseShardKeyPattern(keyPattern.toBSON())),
- _keyPattern(_keyPatternPaths.empty() ? KeyPattern(BSONObj()) : keyPattern) {
- }
-
- bool ShardKeyPattern::isValid() const {
- return !_keyPattern.toBSON().isEmpty();
- }
+ return parsedPaths.release();
+}
- bool ShardKeyPattern::isHashedPattern() const {
- return isHashedPatternEl(_keyPattern.toBSON().firstElement());
- }
+ShardKeyPattern::ShardKeyPattern(const BSONObj& keyPattern)
+ : _keyPatternPaths(parseShardKeyPattern(keyPattern)),
+ _keyPattern(_keyPatternPaths.empty() ? BSONObj() : keyPattern) {}
- const KeyPattern& ShardKeyPattern::getKeyPattern() const {
- return _keyPattern;
- }
+ShardKeyPattern::ShardKeyPattern(const KeyPattern& keyPattern)
+ : _keyPatternPaths(parseShardKeyPattern(keyPattern.toBSON())),
+ _keyPattern(_keyPatternPaths.empty() ? KeyPattern(BSONObj()) : keyPattern) {}
- const BSONObj& ShardKeyPattern::toBSON() const {
- return _keyPattern.toBSON();
- }
+bool ShardKeyPattern::isValid() const {
+ return !_keyPattern.toBSON().isEmpty();
+}
- string ShardKeyPattern::toString() const {
- return toBSON().toString();
- }
+bool ShardKeyPattern::isHashedPattern() const {
+ return isHashedPatternEl(_keyPattern.toBSON().firstElement());
+}
- static bool isShardKeyElement(const BSONElement& element, bool allowRegex) {
- // TODO: Disallow regex all the time
- if (element.eoo() || element.type() == Array || (!allowRegex && element.type() == RegEx)
- || (element.type() == Object && !element.embeddedObject().okForStorage()))
- return false;
- return true;
- }
+const KeyPattern& ShardKeyPattern::getKeyPattern() const {
+ return _keyPattern;
+}
- bool ShardKeyPattern::isShardKey(const BSONObj& shardKey) const {
+const BSONObj& ShardKeyPattern::toBSON() const {
+ return _keyPattern.toBSON();
+}
- // Shard keys are always of the form: { 'nested.path' : value, 'nested.path2' : value }
+string ShardKeyPattern::toString() const {
+ return toBSON().toString();
+}
- if (!isValid())
- return false;
+static bool isShardKeyElement(const BSONElement& element, bool allowRegex) {
+ // TODO: Disallow regex all the time
+ if (element.eoo() || element.type() == Array || (!allowRegex && element.type() == RegEx) ||
+ (element.type() == Object && !element.embeddedObject().okForStorage()))
+ return false;
+ return true;
+}
- BSONObjIterator patternIt(_keyPattern.toBSON());
- while (patternIt.more()) {
+bool ShardKeyPattern::isShardKey(const BSONObj& shardKey) const {
+ // Shard keys are always of the form: { 'nested.path' : value, 'nested.path2' : value }
- BSONElement patternEl = patternIt.next();
+ if (!isValid())
+ return false;
- BSONElement keyEl = shardKey[patternEl.fieldNameStringData()];
- if (!isShardKeyElement(keyEl, true))
- return false;
- }
+ BSONObjIterator patternIt(_keyPattern.toBSON());
+ while (patternIt.more()) {
+ BSONElement patternEl = patternIt.next();
- return true;
+ BSONElement keyEl = shardKey[patternEl.fieldNameStringData()];
+ if (!isShardKeyElement(keyEl, true))
+ return false;
}
- BSONObj ShardKeyPattern::normalizeShardKey(const BSONObj& shardKey) const {
-
- // Shard keys are always of the form: { 'nested.path' : value, 'nested.path2' : value }
- // and in the same order as the key pattern
-
- if (!isValid())
- return BSONObj();
+ return true;
+}
- // We want to return an empty key if users pass us something that's not a shard key
- if (shardKey.nFields() > _keyPattern.toBSON().nFields())
- return BSONObj();
+BSONObj ShardKeyPattern::normalizeShardKey(const BSONObj& shardKey) const {
+ // Shard keys are always of the form: { 'nested.path' : value, 'nested.path2' : value }
+ // and in the same order as the key pattern
- BSONObjBuilder keyBuilder;
- BSONObjIterator patternIt(_keyPattern.toBSON());
- while (patternIt.more()) {
+ if (!isValid())
+ return BSONObj();
- BSONElement patternEl = patternIt.next();
+ // We want to return an empty key if users pass us something that's not a shard key
+ if (shardKey.nFields() > _keyPattern.toBSON().nFields())
+ return BSONObj();
- BSONElement keyEl = shardKey[patternEl.fieldNameStringData()];
+ BSONObjBuilder keyBuilder;
+ BSONObjIterator patternIt(_keyPattern.toBSON());
+ while (patternIt.more()) {
+ BSONElement patternEl = patternIt.next();
- if (!isShardKeyElement(keyEl, true))
- return BSONObj();
+ BSONElement keyEl = shardKey[patternEl.fieldNameStringData()];
- keyBuilder.appendAs(keyEl, patternEl.fieldName());
- }
+ if (!isShardKeyElement(keyEl, true))
+ return BSONObj();
- dassert(isShardKey(keyBuilder.asTempObj()));
- return keyBuilder.obj();
+ keyBuilder.appendAs(keyEl, patternEl.fieldName());
}
- static BSONElement extractKeyElementFromMatchable(const MatchableDocument& matchable,
- StringData pathStr) {
- ElementPath path;
- path.init(pathStr);
- path.setTraverseNonleafArrays(false);
- path.setTraverseLeafArray(false);
-
- MatchableDocument::IteratorHolder matchIt(&matchable, &path);
- if (!matchIt->more())
- return BSONElement();
+ dassert(isShardKey(keyBuilder.asTempObj()));
+ return keyBuilder.obj();
+}
- BSONElement matchEl = matchIt->next().element();
- // We shouldn't have more than one element - we don't expand arrays
- dassert(!matchIt->more());
+static BSONElement extractKeyElementFromMatchable(const MatchableDocument& matchable,
+ StringData pathStr) {
+ ElementPath path;
+ path.init(pathStr);
+ path.setTraverseNonleafArrays(false);
+ path.setTraverseLeafArray(false);
- return matchEl;
- }
+ MatchableDocument::IteratorHolder matchIt(&matchable, &path);
+ if (!matchIt->more())
+ return BSONElement();
- BSONObj //
- ShardKeyPattern::extractShardKeyFromMatchable(const MatchableDocument& matchable) const {
+ BSONElement matchEl = matchIt->next().element();
+ // We shouldn't have more than one element - we don't expand arrays
+ dassert(!matchIt->more());
- if (!isValid())
- return BSONObj();
+ return matchEl;
+}
- BSONObjBuilder keyBuilder;
+BSONObj //
+ ShardKeyPattern::extractShardKeyFromMatchable(const MatchableDocument& matchable) const {
+ if (!isValid())
+ return BSONObj();
- BSONObjIterator patternIt(_keyPattern.toBSON());
- while (patternIt.more()) {
+ BSONObjBuilder keyBuilder;
- BSONElement patternEl = patternIt.next();
- BSONElement matchEl = extractKeyElementFromMatchable(matchable,
- patternEl.fieldNameStringData());
+ BSONObjIterator patternIt(_keyPattern.toBSON());
+ while (patternIt.more()) {
+ BSONElement patternEl = patternIt.next();
+ BSONElement matchEl =
+ extractKeyElementFromMatchable(matchable, patternEl.fieldNameStringData());
- if (!isShardKeyElement(matchEl, true))
- return BSONObj();
+ if (!isShardKeyElement(matchEl, true))
+ return BSONObj();
- if (isHashedPatternEl(patternEl)) {
- keyBuilder.append(patternEl.fieldName(),
- BSONElementHasher::hash64(matchEl,
- BSONElementHasher::DEFAULT_HASH_SEED));
- }
- else {
- // NOTE: The matched element may *not* have the same field name as the path -
- // index keys don't contain field names, for example
- keyBuilder.appendAs(matchEl, patternEl.fieldName());
- }
+ if (isHashedPatternEl(patternEl)) {
+ keyBuilder.append(
+ patternEl.fieldName(),
+ BSONElementHasher::hash64(matchEl, BSONElementHasher::DEFAULT_HASH_SEED));
+ } else {
+ // NOTE: The matched element may *not* have the same field name as the path -
+ // index keys don't contain field names, for example
+ keyBuilder.appendAs(matchEl, patternEl.fieldName());
}
-
- dassert(isShardKey(keyBuilder.asTempObj()));
- return keyBuilder.obj();
- }
-
- BSONObj ShardKeyPattern::extractShardKeyFromDoc(const BSONObj& doc) const {
- BSONMatchableDocument matchable(doc);
- return extractShardKeyFromMatchable(matchable);
}
- static BSONElement findEqualityElement(const EqualityMatches& equalities,
- const FieldRef& path) {
+ dassert(isShardKey(keyBuilder.asTempObj()));
+ return keyBuilder.obj();
+}
- int parentPathPart;
- const BSONElement& parentEl = pathsupport::findParentEqualityElement(equalities,
- path,
- &parentPathPart);
+BSONObj ShardKeyPattern::extractShardKeyFromDoc(const BSONObj& doc) const {
+ BSONMatchableDocument matchable(doc);
+ return extractShardKeyFromMatchable(matchable);
+}
- if (parentPathPart == static_cast<int>(path.numParts()))
- return parentEl;
+static BSONElement findEqualityElement(const EqualityMatches& equalities, const FieldRef& path) {
+ int parentPathPart;
+ const BSONElement& parentEl =
+ pathsupport::findParentEqualityElement(equalities, path, &parentPathPart);
- if (parentEl.type() != Object)
- return BSONElement();
+ if (parentPathPart == static_cast<int>(path.numParts()))
+ return parentEl;
- StringData suffixStr = path.dottedSubstring(parentPathPart, path.numParts());
- BSONMatchableDocument matchable(parentEl.Obj());
- return extractKeyElementFromMatchable(matchable, suffixStr);
- }
+ if (parentEl.type() != Object)
+ return BSONElement();
- StatusWith<BSONObj> ShardKeyPattern::extractShardKeyFromQuery(const BSONObj& basicQuery) const {
-
- if (!isValid())
- return StatusWith<BSONObj>(BSONObj());
+ StringData suffixStr = path.dottedSubstring(parentPathPart, path.numParts());
+ BSONMatchableDocument matchable(parentEl.Obj());
+ return extractKeyElementFromMatchable(matchable, suffixStr);
+}
- // Extract equalities from query
- CanonicalQuery* rawQuery;
- Status queryStatus =
- CanonicalQuery::canonicalize("", basicQuery, &rawQuery, WhereCallbackNoop());
- if (!queryStatus.isOK())
- return StatusWith<BSONObj>(queryStatus);
- unique_ptr<CanonicalQuery> query(rawQuery);
-
- EqualityMatches equalities;
- // TODO: Build the path set initially?
- FieldRefSet keyPatternPathSet(_keyPatternPaths.vector());
- // We only care about extracting the full key pattern paths - if they don't exist (or are
- // conflicting), we don't contain the shard key.
- Status eqStatus = pathsupport::extractFullEqualityMatches(*query->root(),
- keyPatternPathSet,
- &equalities);
- // NOTE: Failure to extract equality matches just means we return no shard key - it's not
- // an error we propagate
- if (!eqStatus.isOK())
+StatusWith<BSONObj> ShardKeyPattern::extractShardKeyFromQuery(const BSONObj& basicQuery) const {
+ if (!isValid())
+ return StatusWith<BSONObj>(BSONObj());
+
+ // Extract equalities from query
+ CanonicalQuery* rawQuery;
+ Status queryStatus =
+ CanonicalQuery::canonicalize("", basicQuery, &rawQuery, WhereCallbackNoop());
+ if (!queryStatus.isOK())
+ return StatusWith<BSONObj>(queryStatus);
+ unique_ptr<CanonicalQuery> query(rawQuery);
+
+ EqualityMatches equalities;
+ // TODO: Build the path set initially?
+ FieldRefSet keyPatternPathSet(_keyPatternPaths.vector());
+ // We only care about extracting the full key pattern paths - if they don't exist (or are
+ // conflicting), we don't contain the shard key.
+ Status eqStatus =
+ pathsupport::extractFullEqualityMatches(*query->root(), keyPatternPathSet, &equalities);
+ // NOTE: Failure to extract equality matches just means we return no shard key - it's not
+ // an error we propagate
+ if (!eqStatus.isOK())
+ return StatusWith<BSONObj>(BSONObj());
+
+ // Extract key from equalities
+ // NOTE: The method below is equivalent to constructing a BSONObj and running
+ // extractShardKeyFromMatchable, but doesn't require creating the doc.
+
+ BSONObjBuilder keyBuilder;
+ // Iterate the parsed paths to avoid re-parsing
+ for (OwnedPointerVector<FieldRef>::const_iterator it = _keyPatternPaths.begin();
+ it != _keyPatternPaths.end();
+ ++it) {
+ const FieldRef& patternPath = **it;
+ BSONElement equalEl = findEqualityElement(equalities, patternPath);
+
+ if (!isShardKeyElement(equalEl, false))
return StatusWith<BSONObj>(BSONObj());
- // Extract key from equalities
- // NOTE: The method below is equivalent to constructing a BSONObj and running
- // extractShardKeyFromMatchable, but doesn't require creating the doc.
-
- BSONObjBuilder keyBuilder;
- // Iterate the parsed paths to avoid re-parsing
- for (OwnedPointerVector<FieldRef>::const_iterator it = _keyPatternPaths.begin();
- it != _keyPatternPaths.end(); ++it) {
-
- const FieldRef& patternPath = **it;
- BSONElement equalEl = findEqualityElement(equalities, patternPath);
-
- if (!isShardKeyElement(equalEl, false))
- return StatusWith<BSONObj>(BSONObj());
-
- if (isHashedPattern()) {
- keyBuilder.append(patternPath.dottedField(),
- BSONElementHasher::hash64(equalEl,
- BSONElementHasher::DEFAULT_HASH_SEED));
- }
- else {
- // NOTE: The equal element may *not* have the same field name as the path -
- // nested $and, $eq, for example
- keyBuilder.appendAs(equalEl, patternPath.dottedField());
- }
+ if (isHashedPattern()) {
+ keyBuilder.append(
+ patternPath.dottedField(),
+ BSONElementHasher::hash64(equalEl, BSONElementHasher::DEFAULT_HASH_SEED));
+ } else {
+ // NOTE: The equal element may *not* have the same field name as the path -
+ // nested $and, $eq, for example
+ keyBuilder.appendAs(equalEl, patternPath.dottedField());
}
-
- dassert(isShardKey(keyBuilder.asTempObj()));
- return StatusWith<BSONObj>(keyBuilder.obj());
}
- bool ShardKeyPattern::isUniqueIndexCompatible(const BSONObj& uniqueIndexPattern) const {
-
- dassert(!KeyPattern::isHashedKeyPattern(uniqueIndexPattern));
+ dassert(isShardKey(keyBuilder.asTempObj()));
+ return StatusWith<BSONObj>(keyBuilder.obj());
+}
- if (!uniqueIndexPattern.isEmpty()
- && string("_id") == uniqueIndexPattern.firstElementFieldName()) {
- return true;
- }
+bool ShardKeyPattern::isUniqueIndexCompatible(const BSONObj& uniqueIndexPattern) const {
+ dassert(!KeyPattern::isHashedKeyPattern(uniqueIndexPattern));
- return _keyPattern.toBSON().isFieldNamePrefixOf(uniqueIndexPattern);
+ if (!uniqueIndexPattern.isEmpty() &&
+ string("_id") == uniqueIndexPattern.firstElementFieldName()) {
+ return true;
}
- BoundList ShardKeyPattern::flattenBounds(const IndexBounds& indexBounds) const {
+ return _keyPattern.toBSON().isFieldNamePrefixOf(uniqueIndexPattern);
+}
- invariant(indexBounds.fields.size() == (size_t)_keyPattern.toBSON().nFields());
+BoundList ShardKeyPattern::flattenBounds(const IndexBounds& indexBounds) const {
+ invariant(indexBounds.fields.size() == (size_t)_keyPattern.toBSON().nFields());
- // If any field is unsatisfied, return empty bound list.
- for (vector<OrderedIntervalList>::const_iterator it = indexBounds.fields.begin();
- it != indexBounds.fields.end(); it++) {
- if (it->intervals.size() == 0) {
- return BoundList();
- }
+ // If any field is unsatisfied, return empty bound list.
+ for (vector<OrderedIntervalList>::const_iterator it = indexBounds.fields.begin();
+ it != indexBounds.fields.end();
+ it++) {
+ if (it->intervals.size() == 0) {
+ return BoundList();
}
- // To construct our bounds we will generate intervals based on bounds for
- // the first field, then compound intervals based on constraints for the first
- // 2 fields, then compound intervals for the first 3 fields, etc.
- // As we loop through the fields, we start generating new intervals that will later
- // get extended in another iteration of the loop. We define these partially constructed
- // intervals using pairs of BSONObjBuilders (shared_ptrs, since after one iteration of the
- // loop they still must exist outside their scope).
- typedef vector<pair<shared_ptr<BSONObjBuilder>, shared_ptr<BSONObjBuilder> > > BoundBuilders;
-
- BoundBuilders builders;
- builders.push_back(make_pair(shared_ptr<BSONObjBuilder>(new BSONObjBuilder()),
- shared_ptr<BSONObjBuilder>(new BSONObjBuilder())));
- BSONObjIterator keyIter(_keyPattern.toBSON());
- // until equalityOnly is false, we are just dealing with equality (no range or $in queries).
- bool equalityOnly = true;
-
- for (size_t i = 0; i < indexBounds.fields.size(); i++) {
- BSONElement e = keyIter.next();
-
- StringData fieldName = e.fieldNameStringData();
-
- // get the relevant intervals for this field, but we may have to transform the
- // list of what's relevant according to the expression for this field
- const OrderedIntervalList& oil = indexBounds.fields[i];
- const vector<Interval>& intervals = oil.intervals;
-
- if (equalityOnly) {
- if (intervals.size() == 1 && intervals.front().isPoint()) {
- // this field is only a single point-interval
- BoundBuilders::const_iterator j;
- for (j = builders.begin(); j != builders.end(); ++j) {
- j->first->appendAs(intervals.front().start, fieldName);
- j->second->appendAs(intervals.front().end, fieldName);
- }
+ }
+ // To construct our bounds we will generate intervals based on bounds for
+ // the first field, then compound intervals based on constraints for the first
+ // 2 fields, then compound intervals for the first 3 fields, etc.
+ // As we loop through the fields, we start generating new intervals that will later
+ // get extended in another iteration of the loop. We define these partially constructed
+ // intervals using pairs of BSONObjBuilders (shared_ptrs, since after one iteration of the
+ // loop they still must exist outside their scope).
+ typedef vector<pair<shared_ptr<BSONObjBuilder>, shared_ptr<BSONObjBuilder>>> BoundBuilders;
+
+ BoundBuilders builders;
+ builders.push_back(make_pair(shared_ptr<BSONObjBuilder>(new BSONObjBuilder()),
+ shared_ptr<BSONObjBuilder>(new BSONObjBuilder())));
+ BSONObjIterator keyIter(_keyPattern.toBSON());
+ // until equalityOnly is false, we are just dealing with equality (no range or $in queries).
+ bool equalityOnly = true;
+
+ for (size_t i = 0; i < indexBounds.fields.size(); i++) {
+ BSONElement e = keyIter.next();
+
+ StringData fieldName = e.fieldNameStringData();
+
+ // get the relevant intervals for this field, but we may have to transform the
+ // list of what's relevant according to the expression for this field
+ const OrderedIntervalList& oil = indexBounds.fields[i];
+ const vector<Interval>& intervals = oil.intervals;
+
+ if (equalityOnly) {
+ if (intervals.size() == 1 && intervals.front().isPoint()) {
+ // this field is only a single point-interval
+ BoundBuilders::const_iterator j;
+ for (j = builders.begin(); j != builders.end(); ++j) {
+ j->first->appendAs(intervals.front().start, fieldName);
+ j->second->appendAs(intervals.front().end, fieldName);
}
- else {
- // This clause is the first to generate more than a single point.
- // We only execute this clause once. After that, we simplify the bound
- // extensions to prevent combinatorial explosion.
- equalityOnly = false;
-
- BoundBuilders newBuilders;
-
- for (BoundBuilders::const_iterator it = builders.begin(); it != builders.end();
- ++it) {
- BSONObj first = it->first->obj();
- BSONObj second = it->second->obj();
-
- for (vector<Interval>::const_iterator interval = intervals.begin();
- interval != intervals.end(); ++interval) {
- uassert( 17439,
- "combinatorial limit of $in partitioning of results exceeded" ,
- newBuilders.size() < kMaxFlattenedInCombinations );
- newBuilders.push_back( //
+ } else {
+ // This clause is the first to generate more than a single point.
+ // We only execute this clause once. After that, we simplify the bound
+ // extensions to prevent combinatorial explosion.
+ equalityOnly = false;
+
+ BoundBuilders newBuilders;
+
+ for (BoundBuilders::const_iterator it = builders.begin(); it != builders.end();
+ ++it) {
+ BSONObj first = it->first->obj();
+ BSONObj second = it->second->obj();
+
+ for (vector<Interval>::const_iterator interval = intervals.begin();
+ interval != intervals.end();
+ ++interval) {
+ uassert(17439,
+ "combinatorial limit of $in partitioning of results exceeded",
+ newBuilders.size() < kMaxFlattenedInCombinations);
+ newBuilders.push_back( //
make_pair(shared_ptr<BSONObjBuilder>(new BSONObjBuilder()),
shared_ptr<BSONObjBuilder>(new BSONObjBuilder())));
- newBuilders.back().first->appendElements(first);
- newBuilders.back().second->appendElements(second);
- newBuilders.back().first->appendAs(interval->start, fieldName);
- newBuilders.back().second->appendAs(interval->end, fieldName);
- }
+ newBuilders.back().first->appendElements(first);
+ newBuilders.back().second->appendElements(second);
+ newBuilders.back().first->appendAs(interval->start, fieldName);
+ newBuilders.back().second->appendAs(interval->end, fieldName);
}
- builders = newBuilders;
}
+ builders = newBuilders;
}
- else {
- // if we've already generated a range or multiple point-intervals
- // just extend what we've generated with min/max bounds for this field
- BoundBuilders::const_iterator j;
- for (j = builders.begin(); j != builders.end(); ++j) {
- j->first->appendAs(intervals.front().start, fieldName);
- j->second->appendAs(intervals.back().end, fieldName);
- }
+ } else {
+ // if we've already generated a range or multiple point-intervals
+ // just extend what we've generated with min/max bounds for this field
+ BoundBuilders::const_iterator j;
+ for (j = builders.begin(); j != builders.end(); ++j) {
+ j->first->appendAs(intervals.front().start, fieldName);
+ j->second->appendAs(intervals.back().end, fieldName);
}
}
- BoundList ret;
- for (BoundBuilders::const_iterator i = builders.begin(); i != builders.end(); ++i)
- ret.push_back(make_pair(i->first->obj(), i->second->obj()));
- return ret;
}
-
+ BoundList ret;
+ for (BoundBuilders::const_iterator i = builders.begin(); i != builders.end(); ++i)
+ ret.push_back(make_pair(i->first->obj(), i->second->obj()));
+ return ret;
+}
}
diff --git a/src/mongo/s/shard_key_pattern.h b/src/mongo/s/shard_key_pattern.h
index d6cc67f56df..de0c792c37e 100644
--- a/src/mongo/s/shard_key_pattern.h
+++ b/src/mongo/s/shard_key_pattern.h
@@ -38,192 +38,189 @@
namespace mongo {
- class FieldRef;
+class FieldRef;
+
+/**
+ * Helper struct when generating flattened bounds below
+ *
+ * A BoundList contains intervals specified by inclusive start
+ * and end bounds. The intervals should be nonoverlapping and occur in
+ * the specified direction of traversal. For example, given a simple index {i:1}
+ * and direction +1, one valid BoundList is: (1, 2); (4, 6). The same BoundList
+ * would be valid for index {i:-1} with direction -1.
+ */
+typedef std::vector<std::pair<BSONObj, BSONObj>> BoundList;
+
+/**
+ * A ShardKeyPattern represents the key pattern used to partition data in a collection between
+ * shards. Shard keys are extracted from documents, simple queries, or Matchable objects based
+ * on the paths within the key pattern.
+ *
+ * Shard key pattern paths may be nested, but are not traversable through arrays - this means
+ * a shard key pattern path always yields a single value.
+ */
+class ShardKeyPattern {
+public:
+ // Maximum size of shard key
+ static const int kMaxShardKeySizeBytes;
+
+ // Maximum number of intervals produced by $in queries.
+ static const unsigned int kMaxFlattenedInCombinations;
+
+ /**
+ * Helper to check shard key size and generate an appropriate error message.
+ */
+ static Status checkShardKeySize(const BSONObj& shardKey);
+
+ /**
+ * Constructs a shard key pattern from a BSON pattern document. If the document is not a
+ * valid shard key pattern, !isValid() will be true and key extraction will fail.
+ */
+ explicit ShardKeyPattern(const BSONObj& keyPattern);
+
+ /**
+ * Constructs a shard key pattern from a key pattern, see above.
+ */
+ explicit ShardKeyPattern(const KeyPattern& keyPattern);
+
+ bool isValid() const;
+
+ bool isHashedPattern() const;
+
+ const KeyPattern& getKeyPattern() const;
+
+ const BSONObj& toBSON() const;
+
+ std::string toString() const;
/**
- * Helper struct when generating flattened bounds below
+ * Returns true if the provided document is a shard key - i.e. has the same fields as the
+ * shard key pattern and valid shard key values.
+ */
+ bool isShardKey(const BSONObj& shardKey) const;
+
+ /**
+ * Given a shard key, return it in normal form where the fields are in the same order as
+ * the shard key pattern fields.
*
- * A BoundList contains intervals specified by inclusive start
- * and end bounds. The intervals should be nonoverlapping and occur in
- * the specified direction of traversal. For example, given a simple index {i:1}
- * and direction +1, one valid BoundList is: (1, 2); (4, 6). The same BoundList
- * would be valid for index {i:-1} with direction -1.
+ * If the shard key is invalid, returns BSONObj()
*/
- typedef std::vector<std::pair<BSONObj, BSONObj> > BoundList;
+ BSONObj normalizeShardKey(const BSONObj& shardKey) const;
/**
- * A ShardKeyPattern represents the key pattern used to partition data in a collection between
- * shards. Shard keys are extracted from documents, simple queries, or Matchable objects based
- * on the paths within the key pattern.
+ * Given a MatchableDocument, extracts the shard key corresponding to the key pattern.
+ * For each path in the shard key pattern, extracts a value from the matchable document.
+ *
+ * Paths to shard key fields must not contain arrays at any level, and shard keys may not
+ * be array fields, undefined, or non-storable sub-documents. If the shard key pattern is
+ * a hashed key pattern, this method performs the hashing.
+ *
+ * If a shard key cannot be extracted, returns an empty BSONObj().
*
- * Shard key pattern paths may be nested, but are not traversable through arrays - this means
- * a shard key pattern path always yields a single value.
+ * Examples:
+ * If 'this' KeyPattern is { a : 1 }
+ * { a: "hi" , b : 4} --> returns { a : "hi" }
+ * { c : 4 , a : 2 } --> returns { a : 2 }
+ * { b : 2 } -> returns {}
+ * { a : [1,2] } -> returns {}
+ * If 'this' KeyPattern is { a : "hashed" }
+ * { a: 1 } --> returns { a : NumberLong("5902408780260971510") }
+ * If 'this' KeyPattern is { 'a.b' : 1 }
+ * { a : { b : "hi" } } --> returns { 'a.b' : "hi" }
+ * { a : [{ b : "hi" }] } --> returns {}
*/
- class ShardKeyPattern {
- public:
-
- // Maximum size of shard key
- static const int kMaxShardKeySizeBytes;
-
- // Maximum number of intervals produced by $in queries.
- static const unsigned int kMaxFlattenedInCombinations;
-
- /**
- * Helper to check shard key size and generate an appropriate error message.
- */
- static Status checkShardKeySize(const BSONObj& shardKey);
-
- /**
- * Constructs a shard key pattern from a BSON pattern document. If the document is not a
- * valid shard key pattern, !isValid() will be true and key extraction will fail.
- */
- explicit ShardKeyPattern(const BSONObj& keyPattern);
-
- /**
- * Constructs a shard key pattern from a key pattern, see above.
- */
- explicit ShardKeyPattern(const KeyPattern& keyPattern);
-
- bool isValid() const;
-
- bool isHashedPattern() const;
-
- const KeyPattern& getKeyPattern() const;
-
- const BSONObj& toBSON() const;
-
- std::string toString() const;
-
- /**
- * Returns true if the provided document is a shard key - i.e. has the same fields as the
- * shard key pattern and valid shard key values.
- */
- bool isShardKey(const BSONObj& shardKey) const;
-
- /**
- * Given a shard key, return it in normal form where the fields are in the same order as
- * the shard key pattern fields.
- *
- * If the shard key is invalid, returns BSONObj()
- */
- BSONObj normalizeShardKey(const BSONObj& shardKey) const;
-
- /**
- * Given a MatchableDocument, extracts the shard key corresponding to the key pattern.
- * For each path in the shard key pattern, extracts a value from the matchable document.
- *
- * Paths to shard key fields must not contain arrays at any level, and shard keys may not
- * be array fields, undefined, or non-storable sub-documents. If the shard key pattern is
- * a hashed key pattern, this method performs the hashing.
- *
- * If a shard key cannot be extracted, returns an empty BSONObj().
- *
- * Examples:
- * If 'this' KeyPattern is { a : 1 }
- * { a: "hi" , b : 4} --> returns { a : "hi" }
- * { c : 4 , a : 2 } --> returns { a : 2 }
- * { b : 2 } -> returns {}
- * { a : [1,2] } -> returns {}
- * If 'this' KeyPattern is { a : "hashed" }
- * { a: 1 } --> returns { a : NumberLong("5902408780260971510") }
- * If 'this' KeyPattern is { 'a.b' : 1 }
- * { a : { b : "hi" } } --> returns { 'a.b' : "hi" }
- * { a : [{ b : "hi" }] } --> returns {}
- */
- BSONObj extractShardKeyFromMatchable(const MatchableDocument& matchable) const;
-
- /**
- * Given a document, extracts the shard key corresponding to the key pattern.
- * See above.
- */
- BSONObj extractShardKeyFromDoc(const BSONObj& doc) const;
-
- /**
- * Given a simple BSON query, extracts the shard key corresponding to the key pattern
- * from equality matches in the query. The query expression *must not* be a complex query
- * with sorts or other attributes.
- *
- * Logically, the equalities in the BSON query can be serialized into a BSON document and
- * then a shard key is extracted from this equality document.
- *
- * NOTE: BSON queries and BSON documents look similar but are different languages. Use the
- * correct shard key extraction function.
- *
- * Returns !OK status if the query cannot be parsed. Returns an empty BSONObj() if there is
- * no shard key found in the query equalities.
- *
- * Examples:
- * If the key pattern is { a : 1 }
- * { a : "hi", b : 4 } --> returns { a : "hi" }
- * { a : { $eq : "hi" }, b : 4 } --> returns { a : "hi" }
- * { $and : [{a : { $eq : "hi" }}, { b : 4 }] } --> returns { a : "hi" }
- * If the key pattern is { 'a.b' : 1 }
- * { a : { b : "hi" } } --> returns { 'a.b' : "hi" }
- * { 'a.b' : "hi" } --> returns { 'a.b' : "hi" }
- * { a : { b : { $eq : "hi" } } } --> returns {} because the query language treats this as
- * a : { $eq : { b : ... } }
- */
- StatusWith<BSONObj> extractShardKeyFromQuery(const BSONObj& basicQuery) const;
-
- /**
- * Returns true if the shard key pattern can ensure that the unique index pattern is
- * respected across all shards.
- *
- * Primarily this just checks whether the shard key pattern field names are equal to or a
- * prefix of the unique index pattern field names. Since documents with the same fields in
- * the shard key pattern are guaranteed to go to the same shard, and all documents must
- * contain the full shard key, a unique index with a shard key pattern prefix can be sure
- * when resolving duplicates that documents on other shards will have different shard keys,
- * and so are not duplicates.
- *
- * Hashed shard key patterns are similar to ordinary patterns in that they guarantee similar
- * shard keys go to the same shard.
- *
- * Examples:
- * shard key {a : 1} is compatible with a unique index on {_id : 1}
- * shard key {a : 1} is compatible with a unique index on {a : 1 , b : 1}
- * shard key {a : 1} is compatible with a unique index on {a : -1 , b : 1 }
- * shard key {a : "hashed"} is compatible with a unique index on {a : 1}
- * shard key {a : 1} is not compatible with a unique index on {b : 1}
- * shard key {a : "hashed" , b : 1 } is not compatible with unique index on { b : 1 }
- *
- * All unique index patterns starting with _id are assumed to be enforceable by the fact
- * that _ids must be unique, and so all unique _id prefixed indexes are compatible with
- * any shard key pattern.
- *
- * NOTE: We assume 'uniqueIndexPattern' is a valid unique index pattern - a pattern like
- * { k : "hashed" } is not capable of being a unique index and is an invalid argument to
- * this method.
- */
- bool isUniqueIndexCompatible(const BSONObj& uniqueIndexPattern) const;
-
- /**
- * Return an ordered list of bounds generated using this KeyPattern and the
- * bounds from the IndexBounds. This function is used in sharding to
- * determine where to route queries according to the shard key pattern.
- *
- * Examples:
- *
- * Key { a: 1 }, Bounds a: [0] => { a: 0 } -> { a: 0 }
- * Key { a: 1 }, Bounds a: [2, 3) => { a: 2 } -> { a: 3 } // bound inclusion ignored.
- *
- * The bounds returned by this function may be a superset of those defined
- * by the constraints. For instance, if this KeyPattern is {a : 1, b: 1}
- * Bounds: { a : {$in : [1,2]} , b : {$in : [3,4,5]} }
- * => {a : 1 , b : 3} -> {a : 1 , b : 5}, {a : 2 , b : 3} -> {a : 2 , b : 5}
- *
- * If the IndexBounds are not defined for all the fields in this keypattern, which
- * means some fields are unsatisfied, an empty BoundList could return.
- *
- */
- BoundList flattenBounds(const IndexBounds& indexBounds) const;
-
- private:
-
- // Ordered, parsed paths
- const OwnedPointerVector<FieldRef> _keyPatternPaths;
-
- const KeyPattern _keyPattern;
- };
+ BSONObj extractShardKeyFromMatchable(const MatchableDocument& matchable) const;
+
+ /**
+ * Given a document, extracts the shard key corresponding to the key pattern.
+ * See above.
+ */
+ BSONObj extractShardKeyFromDoc(const BSONObj& doc) const;
+
+ /**
+ * Given a simple BSON query, extracts the shard key corresponding to the key pattern
+ * from equality matches in the query. The query expression *must not* be a complex query
+ * with sorts or other attributes.
+ *
+ * Logically, the equalities in the BSON query can be serialized into a BSON document and
+ * then a shard key is extracted from this equality document.
+ *
+ * NOTE: BSON queries and BSON documents look similar but are different languages. Use the
+ * correct shard key extraction function.
+ *
+ * Returns !OK status if the query cannot be parsed. Returns an empty BSONObj() if there is
+ * no shard key found in the query equalities.
+ *
+ * Examples:
+ * If the key pattern is { a : 1 }
+ * { a : "hi", b : 4 } --> returns { a : "hi" }
+ * { a : { $eq : "hi" }, b : 4 } --> returns { a : "hi" }
+ * { $and : [{a : { $eq : "hi" }}, { b : 4 }] } --> returns { a : "hi" }
+ * If the key pattern is { 'a.b' : 1 }
+ * { a : { b : "hi" } } --> returns { 'a.b' : "hi" }
+ * { 'a.b' : "hi" } --> returns { 'a.b' : "hi" }
+ * { a : { b : { $eq : "hi" } } } --> returns {} because the query language treats this as
+ * a : { $eq : { b : ... } }
+ */
+ StatusWith<BSONObj> extractShardKeyFromQuery(const BSONObj& basicQuery) const;
+
+ /**
+ * Returns true if the shard key pattern can ensure that the unique index pattern is
+ * respected across all shards.
+ *
+ * Primarily this just checks whether the shard key pattern field names are equal to or a
+ * prefix of the unique index pattern field names. Since documents with the same fields in
+ * the shard key pattern are guaranteed to go to the same shard, and all documents must
+ * contain the full shard key, a unique index with a shard key pattern prefix can be sure
+ * when resolving duplicates that documents on other shards will have different shard keys,
+ * and so are not duplicates.
+ *
+ * Hashed shard key patterns are similar to ordinary patterns in that they guarantee similar
+ * shard keys go to the same shard.
+ *
+ * Examples:
+ * shard key {a : 1} is compatible with a unique index on {_id : 1}
+ * shard key {a : 1} is compatible with a unique index on {a : 1 , b : 1}
+ * shard key {a : 1} is compatible with a unique index on {a : -1 , b : 1 }
+ * shard key {a : "hashed"} is compatible with a unique index on {a : 1}
+ * shard key {a : 1} is not compatible with a unique index on {b : 1}
+ * shard key {a : "hashed" , b : 1 } is not compatible with unique index on { b : 1 }
+ *
+ * All unique index patterns starting with _id are assumed to be enforceable by the fact
+ * that _ids must be unique, and so all unique _id prefixed indexes are compatible with
+ * any shard key pattern.
+ *
+ * NOTE: We assume 'uniqueIndexPattern' is a valid unique index pattern - a pattern like
+ * { k : "hashed" } is not capable of being a unique index and is an invalid argument to
+ * this method.
+ */
+ bool isUniqueIndexCompatible(const BSONObj& uniqueIndexPattern) const;
+
+ /**
+ * Return an ordered list of bounds generated using this KeyPattern and the
+ * bounds from the IndexBounds. This function is used in sharding to
+ * determine where to route queries according to the shard key pattern.
+ *
+ * Examples:
+ *
+ * Key { a: 1 }, Bounds a: [0] => { a: 0 } -> { a: 0 }
+ * Key { a: 1 }, Bounds a: [2, 3) => { a: 2 } -> { a: 3 } // bound inclusion ignored.
+ *
+ * The bounds returned by this function may be a superset of those defined
+ * by the constraints. For instance, if this KeyPattern is {a : 1, b: 1}
+ * Bounds: { a : {$in : [1,2]} , b : {$in : [3,4,5]} }
+ * => {a : 1 , b : 3} -> {a : 1 , b : 5}, {a : 2 , b : 3} -> {a : 2 , b : 5}
+ *
+ * If the IndexBounds are not defined for all the fields in this keypattern, which
+ * means some fields are unsatisfied, an empty BoundList could return.
+ *
+ */
+ BoundList flattenBounds(const IndexBounds& indexBounds) const;
+
+private:
+ // Ordered, parsed paths
+ const OwnedPointerVector<FieldRef> _keyPatternPaths;
+ const KeyPattern _keyPattern;
+};
}
diff --git a/src/mongo/s/shard_key_pattern_test.cpp b/src/mongo/s/shard_key_pattern_test.cpp
index c90e313ce26..57cb7bca521 100644
--- a/src/mongo/s/shard_key_pattern_test.cpp
+++ b/src/mongo/s/shard_key_pattern_test.cpp
@@ -33,457 +33,454 @@
namespace {
- using std::string;
+using std::string;
+
+using namespace mongo;
+
+TEST(ShardKeyPattern, ValidShardKeyPatternSingle) {
+ BSONObj empty;
+ ASSERT(!ShardKeyPattern(empty).isValid());
+
+ //
+ // Single field ShardKeyPatterns
+ //
+
+ ASSERT(ShardKeyPattern(BSON("a" << 1)).isValid());
+ ASSERT(ShardKeyPattern(BSON("a" << 1)).isValid());
+ ASSERT(ShardKeyPattern(BSON("a" << 1.0f)).isValid());
+ ASSERT(ShardKeyPattern(BSON("a" << (long long)1L)).isValid());
+
+ ASSERT(!ShardKeyPattern(BSON("a" << -1)).isValid());
+ ASSERT(!ShardKeyPattern(BSON("a" << -1.0)).isValid());
+ ASSERT(!ShardKeyPattern(BSON("a"
+ << "1")).isValid());
+
+ ASSERT(ShardKeyPattern(BSON("a"
+ << "hashed")).isValid());
+ ASSERT(!ShardKeyPattern(BSON("a"
+ << "hash")).isValid());
+ ASSERT(!ShardKeyPattern(BSON("" << 1)).isValid());
+ ASSERT(!ShardKeyPattern(BSON("." << 1)).isValid());
+}
- using namespace mongo;
+TEST(ShardKeyPattern, ValidShardKeyPatternComposite) {
+ //
+ // Composite ShardKeyPatterns
+ //
- TEST(ShardKeyPattern, ValidShardKeyPatternSingle) {
+ ASSERT(ShardKeyPattern(BSON("a" << 1 << "b" << 1)).isValid());
+ ASSERT(ShardKeyPattern(BSON("a" << 1.0f << "b" << 1.0)).isValid());
+ ASSERT(!ShardKeyPattern(BSON("a" << 1 << "b" << -1)).isValid());
+ ASSERT(!ShardKeyPattern(BSON("a" << 1 << "b"
+ << "1")).isValid());
- BSONObj empty;
- ASSERT(!ShardKeyPattern(empty).isValid());
+ ASSERT(ShardKeyPattern(BSON("a" << 1 << "b" << 1.0 << "c" << 1.0f)).isValid());
+ ASSERT(!ShardKeyPattern(BSON("a" << 1 << "b." << 1.0)).isValid());
+ ASSERT(!ShardKeyPattern(BSON("a" << 1 << "" << 1.0)).isValid());
+}
- //
- // Single field ShardKeyPatterns
- //
+TEST(ShardKeyPattern, ValidShardKeyPatternNested) {
+ //
+ // Nested ShardKeyPatterns
+ //
- ASSERT(ShardKeyPattern(BSON("a" << 1)).isValid());
- ASSERT(ShardKeyPattern(BSON("a" << 1)).isValid());
- ASSERT(ShardKeyPattern(BSON("a" << 1.0f)).isValid());
- ASSERT(ShardKeyPattern(BSON("a" << (long long)1L)).isValid());
+ ASSERT(ShardKeyPattern(BSON("a.b" << 1)).isValid());
+ ASSERT(!ShardKeyPattern(BSON("a.b" << -1)).isValid());
+ ASSERT(ShardKeyPattern(BSON("a.b.c.d" << 1.0)).isValid());
- ASSERT(!ShardKeyPattern(BSON("a" << -1)).isValid());
- ASSERT(!ShardKeyPattern(BSON("a" << -1.0)).isValid());
- ASSERT(!ShardKeyPattern(BSON("a" << "1")).isValid());
+ ASSERT(!ShardKeyPattern(BSON("a" << BSON("b" << 1))).isValid());
- ASSERT(ShardKeyPattern(BSON("a" << "hashed")).isValid());
- ASSERT(!ShardKeyPattern(BSON("a" << "hash")).isValid());
- ASSERT(!ShardKeyPattern(BSON("" << 1)).isValid());
- ASSERT(!ShardKeyPattern(BSON("." << 1)).isValid());
+ ASSERT(!ShardKeyPattern(BSON("a.b." << 1)).isValid());
+ ASSERT(!ShardKeyPattern(BSON("a.b.." << 1)).isValid());
+ ASSERT(!ShardKeyPattern(BSON("a..b" << 1)).isValid());
- }
+ ASSERT(ShardKeyPattern(BSON("a" << 1 << "c.d" << 1.0 << "e.f.g" << 1.0f)).isValid());
+ ASSERT(ShardKeyPattern(BSON("a" << 1 << "a.b" << 1.0 << "a.b.c" << 1.0f)).isValid());
- TEST(ShardKeyPattern, ValidShardKeyPatternComposite) {
+ ASSERT(!ShardKeyPattern(BSON("a" << 1 << "a.b." << 1.0)).isValid());
+ ASSERT(!ShardKeyPattern(BSON("a" << BSON("b" << 1) << "c.d" << 1.0)).isValid());
+}
- //
- // Composite ShardKeyPatterns
- //
+TEST(ShardKeyPattern, IsShardKey) {
+ ShardKeyPattern pattern(BSON("a.b" << 1 << "c" << 1.0f));
- ASSERT(ShardKeyPattern(BSON("a" << 1 << "b" << 1)).isValid());
- ASSERT(ShardKeyPattern(BSON("a" << 1.0f << "b" << 1.0)).isValid());
- ASSERT(!ShardKeyPattern(BSON("a" << 1 << "b" << -1)).isValid());
- ASSERT(!ShardKeyPattern(BSON("a" << 1 << "b" << "1")).isValid());
+ ASSERT(pattern.isShardKey(BSON("a.b" << 10 << "c" << 30)));
+ ASSERT(pattern.isShardKey(BSON("c" << 30 << "a.b" << 10)));
- ASSERT(ShardKeyPattern(BSON("a" << 1 << "b" << 1.0 << "c" << 1.0f)).isValid());
- ASSERT(!ShardKeyPattern(BSON("a" << 1 << "b." << 1.0)).isValid());
- ASSERT(!ShardKeyPattern(BSON("a" << 1 << "" << 1.0)).isValid());
+ ASSERT(!pattern.isShardKey(BSON("b" << 10)));
+ ASSERT(!pattern.isShardKey(BSON("a" << 10 << "c" << 30)));
+ ASSERT(!pattern.isShardKey(BSON("a" << BSON("b" << 10) << "c" << 30)));
+}
- }
+static BSONObj normKey(const ShardKeyPattern& pattern, const BSONObj& doc) {
+ return pattern.normalizeShardKey(doc);
+}
+
+TEST(ShardKeyPattern, NormalizeShardKey) {
+ ShardKeyPattern pattern(BSON("a.b" << 1 << "c" << 1.0f));
+
+ ASSERT_EQUALS(normKey(pattern, BSON("a.b" << 10 << "c" << 30)), BSON("a.b" << 10 << "c" << 30));
+ ASSERT_EQUALS(normKey(pattern, BSON("c" << 30 << "a.b" << 10)), BSON("a.b" << 10 << "c" << 30));
+
+ ASSERT_EQUALS(normKey(pattern, BSON("b" << 10)), BSONObj());
+ ASSERT_EQUALS(normKey(pattern, BSON("a" << 10 << "c" << 30)), BSONObj());
+ ASSERT_EQUALS(normKey(pattern, BSON("a.b" << BSON("$gt" << 10) << "c" << 30)), BSONObj());
+}
+
+static BSONObj docKey(const ShardKeyPattern& pattern, const BSONObj& doc) {
+ return pattern.extractShardKeyFromDoc(doc);
+}
+
+TEST(ShardKeyPattern, ExtractDocShardKeySingle) {
+ //
+ // Single field ShardKeyPatterns
+ //
+
+ ShardKeyPattern pattern(BSON("a" << 1));
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:10}")), fromjson("{a:10}"));
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:10, b:'20'}")), fromjson("{a:10}"));
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:{b:10}, c:30}")), fromjson("{a:{b:10}}"));
+ const BSONRegEx regex("abc");
+ ASSERT_EQUALS(docKey(pattern,
+ BSON("a" << regex << "b"
+ << "20")),
+ BSON("a" << regex));
+ const BSONObj ref = BSON("$ref"
+ << "coll"
+ << "$id" << 1);
+ ASSERT_EQUALS(docKey(pattern, BSON("a" << ref)), BSON("a" << ref));
+
+ ASSERT_EQUALS(docKey(pattern, BSONObj()), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, fromjson("{b:10}")), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, BSON("" << 10)), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:[1, 2]}")), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:{$invalid:true}}")), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:{$gt:10}}")), BSONObj());
+ // BSONObjIterator breaks this for now
+ // ASSERT_EQUALS(docKey(pattern, BSON("a" << 10 << "a" << 20)), BSONObj());
+}
+
+TEST(ShardKeyPattern, ExtractDocShardKeyCompound) {
+ //
+ // Compound ShardKeyPatterns
+ //
+
+ ShardKeyPattern pattern(BSON("a" << 1 << "b" << 1.0));
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:10, b:'20'}")), fromjson("{a:10, b:'20'}"));
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:10, b:'20', c:30}")), fromjson("{a:10, b:'20'}"));
+ ASSERT_EQUALS(docKey(pattern,
+ BSON("c" << 30 << "b"
+ << "20"
+ << "a" << 10)),
+ fromjson("{a:10, b:'20'}"));
+
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:10, b:[1, 2]}")), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:10, b:{$invalid:true}}")), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, fromjson("{b:20}")), BSONObj());
+ ASSERT_EQUALS(docKey(pattern,
+ BSON("" << 10 << "b"
+ << "20")),
+ BSONObj());
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:10, b:{$gt:20}}")), BSONObj());
+
+ // Ordering
+ ASSERT_EQUALS(docKey(pattern, BSON("b" << 20 << "a" << 10)).firstElement().numberInt(), 10);
+}
+
+TEST(ShardKeyPattern, ExtractDocShardKeyNested) {
+ //
+ // Nested ShardKeyPatterns
+ //
+
+ ShardKeyPattern pattern(BSON("a.b" << 1 << "c" << 1.0f));
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:{b:10}, c:30}")), fromjson("{'a.b':10, c:30}"));
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:{d:[1,2],b:10},c:30,d:40}")),
+ fromjson("{'a.b':10, c:30}"));
+ const BSONObj ref = BSON("$ref"
+ << "coll"
+ << "$id" << 1);
+ ASSERT_EQUALS(docKey(pattern, BSON("a" << BSON("b" << ref) << "c" << 30)),
+ BSON("a.b" << ref << "c" << 30));
+
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:10, c:30}")), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:{d:40}, c:30}")), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:[{b:10}, {b:20}], c:30}")), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:{b:[10, 20]}, c:30}")), BSONObj());
+}
- TEST(ShardKeyPattern, ValidShardKeyPatternNested) {
+TEST(ShardKeyPattern, ExtractDocShardKeyDeepNested) {
+ //
+ // Deeply nested ShardKeyPatterns
+ //
+
+ ShardKeyPattern pattern(BSON("a.b.c" << 1));
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:{b:{c:10}}}")), fromjson("{'a.b.c':10}"));
+
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:[{b:{c:10}}]}")), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:{b:[{c:10}]}}")), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:{b:{c:[10, 20]}}}")), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:{b:[{c:10}, {c:20}]}}")), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:[{b:{c:10}},{b:{c:20}}]}")), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:[{b:[{c:10},{c:20}]},{b:[{c:30},{c:40}]}]}}")),
+ BSONObj());
+}
- //
- // Nested ShardKeyPatterns
- //
+TEST(ShardKeyPattern, ExtractDocShardKeyHashed) {
+ //
+ // Hashed ShardKeyPattern
+ //
+
+ const string value = "12345";
+ const BSONObj bsonValue = BSON("" << value);
+ const long long hashValue =
+ BSONElementHasher::hash64(bsonValue.firstElement(), BSONElementHasher::DEFAULT_HASH_SEED);
+
+ ShardKeyPattern pattern(BSON("a.b"
+ << "hashed"));
+ ASSERT_EQUALS(docKey(pattern, BSON("a" << BSON("b" << value))), BSON("a.b" << hashValue));
+ ASSERT_EQUALS(docKey(pattern, BSON("a" << BSON("b" << value) << "c" << 30)),
+ BSON("a.b" << hashValue));
+ ASSERT_EQUALS(docKey(pattern, BSON("a" << BSON("c" << 30 << "b" << value))),
+ BSON("a.b" << hashValue));
+
+ ASSERT_EQUALS(docKey(pattern, BSON("a" << BSON("c" << value))), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, BSON("a" << BSON("b" << BSON_ARRAY(value)))), BSONObj());
+ ASSERT_EQUALS(docKey(pattern, BSON("a" << BSON_ARRAY(BSON("b" << value)))), BSONObj());
+}
- ASSERT(ShardKeyPattern(BSON("a.b" << 1)).isValid());
- ASSERT(!ShardKeyPattern(BSON("a.b" << -1)).isValid());
- ASSERT(ShardKeyPattern(BSON("a.b.c.d" << 1.0)).isValid());
+static BSONObj queryKey(const ShardKeyPattern& pattern, const BSONObj& query) {
+ StatusWith<BSONObj> status = pattern.extractShardKeyFromQuery(query);
+ if (!status.isOK())
+ return BSONObj();
+ return status.getValue();
+}
- ASSERT(!ShardKeyPattern(BSON("a" << BSON( "b" << 1 ))).isValid());
+TEST(ShardKeyPattern, ExtractQueryShardKeySingle) {
+ //
+ // Single field ShardKeyPatterns
+ //
+
+ ShardKeyPattern pattern(BSON("a" << 1));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10}")), fromjson("{a:10}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, b:'20'}")), fromjson("{a:10}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:10}, c:30}")), fromjson("{a:{b:10}}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, b:{$gt:20}}")), fromjson("{a:10}"));
+
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{$gt:10}}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, b:{$invalid:'20'}}")), BSONObj());
+
+ // Doc key extraction shouldn't work with query
+ ASSERT_EQUALS(docKey(pattern, fromjson("{a:{$eq:[10, 20]}, c:30}")), BSONObj());
+
+ // $eq/$or/$and/$all
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{$eq:10}}")), fromjson("{a:10}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{$or:[{a:{$eq:10}}]}")), fromjson("{a:10}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{$and:[{a:{$eq:10}},{b:'20'}]}")),
+ fromjson("{a:10}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{$all:[10]}}")), fromjson("{a:10}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{$or:[{a:{$eq:10}},{a:10}]}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{$and:[{a:10},{a:10}]}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{$all:[10,10]}}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{$or:[{a:{$eq:10}},{b:'20'}]}")), BSONObj());
+
+ // Regex can't be extracted from query
+ const BSONRegEx regex("abc");
+ ASSERT_EQUALS(queryKey(pattern,
+ BSON("a" << regex << "b"
+ << "20")),
+ BSONObj());
+}
- ASSERT(!ShardKeyPattern(BSON("a.b." << 1)).isValid());
- ASSERT(!ShardKeyPattern(BSON("a.b.." << 1)).isValid());
- ASSERT(!ShardKeyPattern(BSON("a..b" << 1)).isValid());
+TEST(ShardKeyPattern, ExtractQueryShardKeyCompound) {
+ //
+ // Compound ShardKeyPatterns
+ //
+
+ ShardKeyPattern pattern(BSON("a" << 1 << "b" << 1.0));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, b:'20'}")), fromjson("{a:10, b:'20'}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, b:'20', c:30}")), fromjson("{a:10, b:'20'}"));
+ ASSERT_EQUALS(queryKey(pattern,
+ BSON("c" << 30 << "b"
+ << "20"
+ << "a" << 10)),
+ fromjson("{a:10, b:'20'}"));
+
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, b:[1, 2]}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, b:{$invalid:true}}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{b:20}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern,
+ BSON("" << 10 << "b"
+ << "20")),
+ BSONObj());
+
+ // $eq/$or/$and/$all
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{$eq:10}, b:{$all:['20']}}")),
+ fromjson("{a:10, b:'20'}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{$and:[{a:{$eq:10},b:{$eq:'20'}}]}")),
+ fromjson("{a:10, b:'20'}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{$and:[{a:{$eq:10}},{b:{$eq:'20'}}]}")),
+ fromjson("{a:10, b:'20'}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, b:{$gt:20}}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{$or:[{a:{$eq:10}},{b:'20'}]}")), BSONObj());
+
+ // Ordering
+ ASSERT_EQUALS(queryKey(pattern, BSON("b" << 20 << "a" << 10)).firstElement().numberInt(), 10);
+}
- ASSERT(ShardKeyPattern(BSON("a" << 1 << "c.d" << 1.0 << "e.f.g" << 1.0f)).isValid());
- ASSERT(ShardKeyPattern(BSON("a" << 1 << "a.b" << 1.0 << "a.b.c" << 1.0f)).isValid());
+TEST(ShardKeyPattern, ExtractQueryShardKeyNested) {
+ //
+ // Nested ShardKeyPatterns
+ //
+
+ ShardKeyPattern pattern(BSON("a.b" << 1 << "c" << 1.0f));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:10}, c:30}")), fromjson("{'a.b':10, c:30}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{'a.b':{$eq:10}, c:30, d:40}")),
+ fromjson("{'a.b':10, c:30}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{$or:[{'a.b':10, c:30, d:40}]}")),
+ fromjson("{'a.b':10, c:30}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{'a.b':{$all:[10]}, c:30, d:40}")),
+ fromjson("{'a.b':10, c:30}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:10,d:40}, c:30}")),
+ fromjson("{'a.b':10, c:30}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{$and:[{'a.b':{$eq:10}}, {c:30}]}")),
+ fromjson("{'a.b':10, c:30}"));
+
+ // Nested $eq is actually a document element
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:{$eq:10}}, c:30}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{$and:[{a:{b:{$eq:10}}}, {c:30}]}")), BSONObj());
+
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{$or:[{a:{b:{$eq:10}}}, {c:30}]}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, c:30}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:10}, c:{$gt:30}}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{d:40}, c:30}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:[{b:10}, {b:20}], c:30}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:{$eq:[10, 20]}}, c:30}")), BSONObj());
+}
- ASSERT(!ShardKeyPattern(BSON("a" << 1 << "a.b." << 1.0)).isValid());
- ASSERT(!ShardKeyPattern(BSON("a" << BSON( "b" << 1 ) << "c.d" << 1.0)).isValid());
+TEST(ShardKeyPattern, ExtractQueryShardKeyDeepNested) {
+ //
+ // Deeply nested ShardKeyPatterns
+ //
+
+ ShardKeyPattern pattern(BSON("a.b.c" << 1));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:{c:10}}}")), fromjson("{'a.b.c':10}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{'a.b.c':10}")), fromjson("{'a.b.c':10}"));
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{'a.b.c':{$eq:10}}")), fromjson("{'a.b.c':10}"));
+
+ // Arrays at any nesting level means we can't extract a shard key
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{'a.b.c':[10]}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{'a.b':[{c:10}]}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:[{b:{c:10}}]}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:[{c:10}]}}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:{c:[10, 20]}}}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:[{c:10}, {c:20}]}}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:[{b:{c:10}},{b:{c:20}}]}")), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, fromjson("{a:[{b:[{c:10},{c:20}]},{b:[{c:30},{c:40}]}]}}")),
+ BSONObj());
+}
- }
+TEST(ShardKeyPattern, ExtractQueryShardKeyHashed) {
+ //
+ // Hashed ShardKeyPattern
+ //
+
+ const string value = "12345";
+ const BSONObj bsonValue = BSON("" << value);
+ const long long hashValue =
+ BSONElementHasher::hash64(bsonValue.firstElement(), BSONElementHasher::DEFAULT_HASH_SEED);
+
+ // Hashed works basically the same as non-hashed, but applies the hash function at the end
+ ShardKeyPattern pattern(BSON("a.b"
+ << "hashed"));
+ ASSERT_EQUALS(queryKey(pattern, BSON("a.b" << value)), BSON("a.b" << hashValue));
+ ASSERT_EQUALS(queryKey(pattern, BSON("a" << BSON("b" << value))), BSON("a.b" << hashValue));
+ ASSERT_EQUALS(queryKey(pattern, BSON("a.b" << BSON("$eq" << value))), BSON("a.b" << hashValue));
+ ASSERT_EQUALS(queryKey(pattern, BSON("a" << BSON("b" << value) << "c" << 30)),
+ BSON("a.b" << hashValue));
+ ASSERT_EQUALS(queryKey(pattern, BSON("a" << BSON("c" << 30 << "b" << value))),
+ BSON("a.b" << hashValue));
+ ASSERT_EQUALS(queryKey(pattern, //
+ BSON("$and" << BSON_ARRAY(BSON("a.b" << BSON("$eq" << value))))),
+ BSON("a.b" << hashValue));
+
+ ASSERT_EQUALS(queryKey(pattern, BSON("a" << BSON("b" << BSON("$eq" << value)))), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, BSON("a.b" << BSON("$gt" << value))), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, BSON("a" << BSON("c" << value))), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, BSON("a" << BSON("b" << BSON_ARRAY(value)))), BSONObj());
+ ASSERT_EQUALS(queryKey(pattern, BSON("a" << BSON_ARRAY(BSON("b" << value)))), BSONObj());
+}
- TEST(ShardKeyPattern, IsShardKey) {
+static bool indexComp(const ShardKeyPattern& pattern, const BSONObj& indexPattern) {
+ return pattern.isUniqueIndexCompatible(indexPattern);
+}
- ShardKeyPattern pattern(BSON("a.b" << 1 << "c" << 1.0f));
+TEST(ShardKeyPattern, UniqueIndexCompatibleSingle) {
+ //
+ // Single field ShardKeyPatterns
+ //
- ASSERT(pattern.isShardKey(BSON("a.b" << 10 << "c" << 30)));
- ASSERT(pattern.isShardKey(BSON("c" << 30 << "a.b" << 10)));
+ ShardKeyPattern pattern(BSON("a" << 1));
+ ASSERT(indexComp(pattern, BSON("a" << 1)));
+ ASSERT(indexComp(pattern, BSON("a" << -1)));
+ ASSERT(indexComp(pattern, BSON("a" << 1 << "b" << 1)));
+ ASSERT(indexComp(pattern, BSON("a" << -1 << "b" << 1)));
- ASSERT(!pattern.isShardKey(BSON("b" << 10)));
- ASSERT(!pattern.isShardKey(BSON("a" << 10 << "c" << 30)));
- ASSERT(!pattern.isShardKey(BSON("a" << BSON("b" << 10) << "c" << 30)));
- }
+ ASSERT(indexComp(pattern, BSON("_id" << 1)));
+ ASSERT(indexComp(pattern, BSON("_id" << -1 << "b" << 1)));
- static BSONObj normKey(const ShardKeyPattern& pattern, const BSONObj& doc) {
- return pattern.normalizeShardKey(doc);
- }
+ ASSERT(!indexComp(pattern, BSON("b" << 1)));
+ ASSERT(!indexComp(pattern, BSON("b" << -1 << "a" << 1)));
+}
- TEST(ShardKeyPattern, NormalizeShardKey) {
+TEST(ShardKeyPattern, UniqueIndexCompatibleCompound) {
+ //
+ // Compound ShardKeyPatterns
+ //
- ShardKeyPattern pattern(BSON("a.b" << 1 << "c" << 1.0f));
+ ShardKeyPattern pattern(BSON("a" << 1 << "b" << 1.0));
+ ASSERT(indexComp(pattern, BSON("a" << 1 << "b" << 1)));
+ ASSERT(indexComp(pattern, BSON("a" << 1 << "b" << -1.0)));
+ ASSERT(indexComp(pattern, BSON("a" << 1 << "b" << -1.0 << "c" << 1)));
- ASSERT_EQUALS(normKey(pattern, BSON("a.b" << 10 << "c" << 30)),
- BSON("a.b" << 10 << "c" << 30));
- ASSERT_EQUALS(normKey(pattern, BSON("c" << 30 << "a.b" << 10)),
- BSON("a.b" << 10 << "c" << 30));
+ ASSERT(indexComp(pattern, BSON("_id" << 1)));
+ ASSERT(indexComp(pattern, BSON("_id" << -1 << "c" << 1)));
- ASSERT_EQUALS(normKey(pattern, BSON("b" << 10)), BSONObj());
- ASSERT_EQUALS(normKey(pattern, BSON("a" << 10 << "c" << 30)), BSONObj());
- ASSERT_EQUALS(normKey(pattern, BSON("a.b" << BSON("$gt" << 10) << "c" << 30)), BSONObj());
- }
+ ASSERT(!indexComp(pattern, BSON("a" << 1)));
+ ASSERT(!indexComp(pattern, BSON("b" << 1)));
+ ASSERT(!indexComp(pattern, BSON("a" << 1 << "c" << 1.0f)));
+ ASSERT(!indexComp(pattern, BSON("b" << -1 << "a" << 1 << "c" << 1)));
+}
- static BSONObj docKey(const ShardKeyPattern& pattern, const BSONObj& doc) {
- return pattern.extractShardKeyFromDoc(doc);
- }
+TEST(ShardKeyPattern, UniqueIndexCompatibleNested) {
+ //
+ // Nested ShardKeyPatterns
+ //
- TEST(ShardKeyPattern, ExtractDocShardKeySingle) {
+ ShardKeyPattern pattern(BSON("a.b" << 1 << "c" << 1.0));
+ ASSERT(indexComp(pattern, BSON("a.b" << 1 << "c" << 1.0f)));
- //
- // Single field ShardKeyPatterns
- //
-
- ShardKeyPattern pattern(BSON("a" << 1));
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:10}")), fromjson("{a:10}"));
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:10, b:'20'}")), fromjson("{a:10}"));
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:{b:10}, c:30}")), fromjson("{a:{b:10}}"));
- const BSONRegEx regex("abc");
- ASSERT_EQUALS(docKey(pattern, BSON("a" << regex << "b" << "20")), BSON("a" << regex));
- const BSONObj ref = BSON("$ref" << "coll" << "$id" << 1);
- ASSERT_EQUALS(docKey(pattern, BSON("a" << ref)), BSON("a" << ref));
-
- ASSERT_EQUALS(docKey(pattern, BSONObj()), BSONObj());
- ASSERT_EQUALS(docKey(pattern, fromjson("{b:10}")), BSONObj());
- ASSERT_EQUALS(docKey(pattern, BSON("" << 10)), BSONObj());
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:[1, 2]}")), BSONObj());
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:{$invalid:true}}")), BSONObj());
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:{$gt:10}}")), BSONObj());
- // BSONObjIterator breaks this for now
- //ASSERT_EQUALS(docKey(pattern, BSON("a" << 10 << "a" << 20)), BSONObj());
- }
-
- TEST(ShardKeyPattern, ExtractDocShardKeyCompound) {
-
- //
- // Compound ShardKeyPatterns
- //
-
- ShardKeyPattern pattern(BSON("a" << 1 << "b" << 1.0));
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:10, b:'20'}")), fromjson("{a:10, b:'20'}"));
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:10, b:'20', c:30}")),
- fromjson("{a:10, b:'20'}"));
- ASSERT_EQUALS(docKey(pattern, BSON("c" << 30 << "b" << "20" << "a" << 10)),
- fromjson("{a:10, b:'20'}"));
-
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:10, b:[1, 2]}")), BSONObj());
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:10, b:{$invalid:true}}")), BSONObj());
- ASSERT_EQUALS(docKey(pattern, fromjson("{b:20}")), BSONObj());
- ASSERT_EQUALS(docKey(pattern, BSON("" << 10 << "b" << "20")), BSONObj());
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:10, b:{$gt:20}}")), BSONObj());
-
- // Ordering
- ASSERT_EQUALS(docKey(pattern, BSON("b" << 20 << "a" << 10)).firstElement().numberInt(), 10);
- }
-
- TEST(ShardKeyPattern, ExtractDocShardKeyNested) {
-
- //
- // Nested ShardKeyPatterns
- //
-
- ShardKeyPattern pattern(BSON("a.b" << 1 << "c" << 1.0f));
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:{b:10}, c:30}")), fromjson("{'a.b':10, c:30}"));
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:{d:[1,2],b:10},c:30,d:40}")),
- fromjson("{'a.b':10, c:30}"));
- const BSONObj ref = BSON("$ref" << "coll" << "$id" << 1);
- ASSERT_EQUALS(docKey(pattern, BSON("a" << BSON( "b" << ref) << "c" << 30)),
- BSON("a.b" << ref << "c" << 30));
-
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:10, c:30}")), BSONObj());
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:{d:40}, c:30}")), BSONObj());
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:[{b:10}, {b:20}], c:30}")), BSONObj());
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:{b:[10, 20]}, c:30}")), BSONObj());
- }
-
- TEST(ShardKeyPattern, ExtractDocShardKeyDeepNested) {
-
- //
- // Deeply nested ShardKeyPatterns
- //
-
- ShardKeyPattern pattern(BSON("a.b.c" << 1));
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:{b:{c:10}}}")), fromjson("{'a.b.c':10}"));
-
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:[{b:{c:10}}]}")), BSONObj());
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:{b:[{c:10}]}}")), BSONObj());
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:{b:{c:[10, 20]}}}")), BSONObj());
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:{b:[{c:10}, {c:20}]}}")), BSONObj());
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:[{b:{c:10}},{b:{c:20}}]}")), BSONObj());
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:[{b:[{c:10},{c:20}]},{b:[{c:30},{c:40}]}]}}")),
- BSONObj());
- }
-
- TEST(ShardKeyPattern, ExtractDocShardKeyHashed) {
-
- //
- // Hashed ShardKeyPattern
- //
-
- const string value = "12345";
- const BSONObj bsonValue = BSON("" << value);
- const long long hashValue = BSONElementHasher::hash64(bsonValue.firstElement(),
- BSONElementHasher::DEFAULT_HASH_SEED);
-
- ShardKeyPattern pattern(BSON("a.b" << "hashed"));
- ASSERT_EQUALS(docKey(pattern, BSON("a" << BSON("b" << value))), BSON("a.b" << hashValue));
- ASSERT_EQUALS(docKey(pattern, BSON("a" << BSON("b" << value) << "c" << 30)),
- BSON("a.b" << hashValue));
- ASSERT_EQUALS(docKey(pattern, BSON("a" << BSON("c" << 30 << "b" << value))),
- BSON("a.b" << hashValue));
-
- ASSERT_EQUALS(docKey(pattern, BSON("a" << BSON("c" << value))), BSONObj());
- ASSERT_EQUALS(docKey(pattern, BSON("a" << BSON("b" << BSON_ARRAY(value)))), BSONObj());
- ASSERT_EQUALS(docKey(pattern, BSON("a" << BSON_ARRAY(BSON("b" << value)))), BSONObj());
- }
-
- static BSONObj queryKey(const ShardKeyPattern& pattern, const BSONObj& query) {
- StatusWith<BSONObj> status = pattern.extractShardKeyFromQuery(query);
- if (!status.isOK())
- return BSONObj();
- return status.getValue();
- }
-
- TEST(ShardKeyPattern, ExtractQueryShardKeySingle) {
-
- //
- // Single field ShardKeyPatterns
- //
-
- ShardKeyPattern pattern(BSON("a" << 1));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10}")), fromjson("{a:10}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, b:'20'}")), fromjson("{a:10}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:10}, c:30}")), fromjson("{a:{b:10}}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, b:{$gt:20}}")), fromjson("{a:10}"));
-
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{$gt:10}}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, b:{$invalid:'20'}}")), BSONObj());
-
- // Doc key extraction shouldn't work with query
- ASSERT_EQUALS(docKey(pattern, fromjson("{a:{$eq:[10, 20]}, c:30}")), BSONObj());
-
- // $eq/$or/$and/$all
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{$eq:10}}")), fromjson("{a:10}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{$or:[{a:{$eq:10}}]}")), fromjson("{a:10}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{$and:[{a:{$eq:10}},{b:'20'}]}")),
- fromjson("{a:10}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{$all:[10]}}")), fromjson("{a:10}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{$or:[{a:{$eq:10}},{a:10}]}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{$and:[{a:10},{a:10}]}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{$all:[10,10]}}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{$or:[{a:{$eq:10}},{b:'20'}]}")), BSONObj());
-
- // Regex can't be extracted from query
- const BSONRegEx regex("abc");
- ASSERT_EQUALS(queryKey(pattern, BSON("a" << regex << "b" << "20")), BSONObj());
- }
-
- TEST(ShardKeyPattern, ExtractQueryShardKeyCompound) {
-
- //
- // Compound ShardKeyPatterns
- //
-
- ShardKeyPattern pattern(BSON("a" << 1 << "b" << 1.0));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, b:'20'}")), fromjson("{a:10, b:'20'}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, b:'20', c:30}")),
- fromjson("{a:10, b:'20'}"));
- ASSERT_EQUALS(queryKey(pattern, BSON("c" << 30 << "b" << "20" << "a" << 10)),
- fromjson("{a:10, b:'20'}"));
-
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, b:[1, 2]}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, b:{$invalid:true}}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{b:20}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, BSON("" << 10 << "b" << "20")), BSONObj());
-
- // $eq/$or/$and/$all
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{$eq:10}, b:{$all:['20']}}")),
- fromjson("{a:10, b:'20'}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{$and:[{a:{$eq:10},b:{$eq:'20'}}]}")),
- fromjson("{a:10, b:'20'}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{$and:[{a:{$eq:10}},{b:{$eq:'20'}}]}")),
- fromjson("{a:10, b:'20'}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, b:{$gt:20}}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{$or:[{a:{$eq:10}},{b:'20'}]}")), BSONObj());
-
- // Ordering
- ASSERT_EQUALS(queryKey(pattern, BSON("b" << 20 << "a" << 10)).firstElement().numberInt(),
- 10);
-
- }
-
- TEST(ShardKeyPattern, ExtractQueryShardKeyNested) {
-
- //
- // Nested ShardKeyPatterns
- //
-
- ShardKeyPattern pattern(BSON("a.b" << 1 << "c" << 1.0f));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:10}, c:30}")),
- fromjson("{'a.b':10, c:30}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{'a.b':{$eq:10}, c:30, d:40}")),
- fromjson("{'a.b':10, c:30}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{$or:[{'a.b':10, c:30, d:40}]}")),
- fromjson("{'a.b':10, c:30}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{'a.b':{$all:[10]}, c:30, d:40}")),
- fromjson("{'a.b':10, c:30}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:10,d:40}, c:30}")),
- fromjson("{'a.b':10, c:30}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{$and:[{'a.b':{$eq:10}}, {c:30}]}")),
- fromjson("{'a.b':10, c:30}"));
-
- // Nested $eq is actually a document element
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:{$eq:10}}, c:30}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{$and:[{a:{b:{$eq:10}}}, {c:30}]}")), BSONObj());
-
- ASSERT_EQUALS(queryKey(pattern, fromjson("{$or:[{a:{b:{$eq:10}}}, {c:30}]}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:10, c:30}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:10}, c:{$gt:30}}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{d:40}, c:30}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:[{b:10}, {b:20}], c:30}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:{$eq:[10, 20]}}, c:30}")), BSONObj());
- }
-
- TEST(ShardKeyPattern, ExtractQueryShardKeyDeepNested) {
-
- //
- // Deeply nested ShardKeyPatterns
- //
-
- ShardKeyPattern pattern(BSON("a.b.c" << 1));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:{c:10}}}")), fromjson("{'a.b.c':10}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{'a.b.c':10}")), fromjson("{'a.b.c':10}"));
- ASSERT_EQUALS(queryKey(pattern, fromjson("{'a.b.c':{$eq:10}}")), fromjson("{'a.b.c':10}"));
-
- // Arrays at any nesting level means we can't extract a shard key
- ASSERT_EQUALS(queryKey(pattern, fromjson("{'a.b.c':[10]}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{'a.b':[{c:10}]}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:[{b:{c:10}}]}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:[{c:10}]}}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:{c:[10, 20]}}}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:{b:[{c:10}, {c:20}]}}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:[{b:{c:10}},{b:{c:20}}]}")), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, fromjson("{a:[{b:[{c:10},{c:20}]},{b:[{c:30},{c:40}]}]}}")),
- BSONObj());
-
- }
-
- TEST(ShardKeyPattern, ExtractQueryShardKeyHashed) {
-
- //
- // Hashed ShardKeyPattern
- //
-
- const string value = "12345";
- const BSONObj bsonValue = BSON("" << value);
- const long long hashValue = BSONElementHasher::hash64(bsonValue.firstElement(),
- BSONElementHasher::DEFAULT_HASH_SEED);
-
- // Hashed works basically the same as non-hashed, but applies the hash function at the end
- ShardKeyPattern pattern(BSON("a.b" << "hashed"));
- ASSERT_EQUALS(queryKey(pattern, BSON("a.b" << value)), BSON("a.b" << hashValue));
- ASSERT_EQUALS(queryKey(pattern, BSON("a" << BSON("b" << value))), BSON("a.b" << hashValue));
- ASSERT_EQUALS(queryKey(pattern, BSON("a.b" << BSON("$eq" << value))),
- BSON("a.b" << hashValue));
- ASSERT_EQUALS(queryKey(pattern, BSON("a" << BSON("b" << value) << "c" << 30)),
- BSON("a.b" << hashValue));
- ASSERT_EQUALS(queryKey(pattern, BSON("a" << BSON("c" << 30 << "b" << value))),
- BSON("a.b" << hashValue));
- ASSERT_EQUALS(queryKey(pattern, //
- BSON("$and" << BSON_ARRAY(BSON("a.b" << BSON("$eq" << value))))),
- BSON("a.b" << hashValue));
-
- ASSERT_EQUALS(queryKey(pattern, BSON("a" << BSON("b" << BSON("$eq" << value)))), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, BSON("a.b" << BSON("$gt" << value))), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, BSON("a" << BSON("c" << value))), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, BSON("a" << BSON("b" << BSON_ARRAY(value)))), BSONObj());
- ASSERT_EQUALS(queryKey(pattern, BSON("a" << BSON_ARRAY(BSON("b" << value)))), BSONObj());
- }
-
- static bool indexComp(const ShardKeyPattern& pattern, const BSONObj& indexPattern) {
- return pattern.isUniqueIndexCompatible(indexPattern);
- }
-
- TEST(ShardKeyPattern, UniqueIndexCompatibleSingle) {
-
- //
- // Single field ShardKeyPatterns
- //
-
- ShardKeyPattern pattern(BSON("a" << 1));
- ASSERT(indexComp(pattern, BSON("a" << 1)));
- ASSERT(indexComp(pattern, BSON("a" << -1)));
- ASSERT(indexComp(pattern, BSON("a" << 1 << "b" << 1)));
- ASSERT(indexComp(pattern, BSON("a" << -1 << "b" << 1)));
-
- ASSERT(indexComp(pattern, BSON("_id" << 1)));
- ASSERT(indexComp(pattern, BSON("_id" << -1 << "b" << 1)));
-
- ASSERT(!indexComp(pattern, BSON("b" << 1)));
- ASSERT(!indexComp(pattern, BSON("b" << -1 << "a" << 1)));
- }
-
- TEST(ShardKeyPattern, UniqueIndexCompatibleCompound) {
-
- //
- // Compound ShardKeyPatterns
- //
-
- ShardKeyPattern pattern(BSON("a" << 1 << "b" << 1.0));
- ASSERT(indexComp(pattern, BSON("a" << 1 << "b" << 1)));
- ASSERT(indexComp(pattern, BSON("a" << 1 << "b" << -1.0)));
- ASSERT(indexComp(pattern, BSON("a" << 1 << "b" << -1.0 << "c" << 1)));
-
- ASSERT(indexComp(pattern, BSON("_id" << 1)));
- ASSERT(indexComp(pattern, BSON("_id" << -1 << "c" << 1)));
-
- ASSERT(!indexComp(pattern, BSON("a" << 1)));
- ASSERT(!indexComp(pattern, BSON("b" << 1)));
- ASSERT(!indexComp(pattern, BSON("a" << 1 << "c" << 1.0f)));
- ASSERT(!indexComp(pattern, BSON("b" << -1 << "a" << 1 << "c" << 1)));
- }
-
- TEST(ShardKeyPattern, UniqueIndexCompatibleNested) {
-
- //
- // Nested ShardKeyPatterns
- //
-
- ShardKeyPattern pattern(BSON("a.b" << 1 << "c" << 1.0));
- ASSERT(indexComp(pattern, BSON("a.b" << 1 << "c" << 1.0f)));
-
- ASSERT(!indexComp(pattern, BSON("a.b" << 1)));
- ASSERT(!indexComp(pattern, BSON("a" << 1 << "c" << -1.0)));
- ASSERT(!indexComp(pattern, BSON("c" << -1 << "a.b" << 1)));
- }
-
- TEST(ShardKeyPattern, UniqueIndexCompatibleHashed) {
-
- //
- // Hashed ShardKeyPatterns
- //
-
- ShardKeyPattern pattern(BSON("a.b" << "hashed"));
-
- ASSERT(indexComp(pattern, BSON("a.b" << 1)));
- ASSERT(indexComp(pattern, BSON("a.b" << -1)));
- ASSERT(indexComp(pattern, BSON("a.b" << 1 << "c" << 1)));
- ASSERT(indexComp(pattern, BSON("a.b" << -1 << "c" << 1)));
-
- ASSERT(indexComp(pattern, BSON("_id" << 1)));
- ASSERT(indexComp(pattern, BSON("_id" << -1 << "c" << 1)));
-
- ASSERT(!indexComp(pattern, BSON("c" << 1)));
- ASSERT(!indexComp(pattern, BSON("c" << -1 << "a.b" << 1)));
- }
+ ASSERT(!indexComp(pattern, BSON("a.b" << 1)));
+ ASSERT(!indexComp(pattern, BSON("a" << 1 << "c" << -1.0)));
+ ASSERT(!indexComp(pattern, BSON("c" << -1 << "a.b" << 1)));
}
+TEST(ShardKeyPattern, UniqueIndexCompatibleHashed) {
+ //
+ // Hashed ShardKeyPatterns
+ //
+
+ ShardKeyPattern pattern(BSON("a.b"
+ << "hashed"));
+
+ ASSERT(indexComp(pattern, BSON("a.b" << 1)));
+ ASSERT(indexComp(pattern, BSON("a.b" << -1)));
+ ASSERT(indexComp(pattern, BSON("a.b" << 1 << "c" << 1)));
+ ASSERT(indexComp(pattern, BSON("a.b" << -1 << "c" << 1)));
+
+ ASSERT(indexComp(pattern, BSON("_id" << 1)));
+ ASSERT(indexComp(pattern, BSON("_id" << -1 << "c" << 1)));
+
+ ASSERT(!indexComp(pattern, BSON("c" << 1)));
+ ASSERT(!indexComp(pattern, BSON("c" << -1 << "a.b" << 1)));
+}
+}
diff --git a/src/mongo/s/shard_resolver.h b/src/mongo/s/shard_resolver.h
index ac51c5fcd04..6c229290805 100644
--- a/src/mongo/s/shard_resolver.h
+++ b/src/mongo/s/shard_resolver.h
@@ -36,26 +36,23 @@
namespace mongo {
+/**
+ * Given a shard name, the ShardResolver resolves a particular host on that shard to contact.
+ *
+ * TODO: Internally, this is backed by a cache - do we need explicit refresh mechanisms built
+ * into this interface?
+ */
+class ShardResolver {
+public:
+ virtual ~ShardResolver() {}
+
/**
- * Given a shard name, the ShardResolver resolves a particular host on that shard to contact.
+ * Returns a host we can use for write ops to this shard.
*
- * TODO: Internally, this is backed by a cache - do we need explicit refresh mechanisms built
- * into this interface?
+ * Returns !OK with message if the shard host could not be found for other reasons.
*/
- class ShardResolver {
- public:
-
- virtual ~ShardResolver() {
- }
-
- /**
- * Returns a host we can use for write ops to this shard.
- *
- * Returns !OK with message if the shard host could not be found for other reasons.
- */
- virtual Status chooseWriteHost( const std::string& shardName,
- ConnectionString* shardHost ) const = 0;
-
- };
+ virtual Status chooseWriteHost(const std::string& shardName,
+ ConnectionString* shardHost) const = 0;
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/stale_exception.h b/src/mongo/s/stale_exception.h
index 9075e6ee93e..227fced8698 100644
--- a/src/mongo/s/stale_exception.h
+++ b/src/mongo/s/stale_exception.h
@@ -35,143 +35,125 @@
namespace mongo {
- using mongoutils::str::stream;
+using mongoutils::str::stream;
+
+/**
+ * Thrown whenever your config info for a given shard/chunk is out of date.
+ */
+class StaleConfigException : public AssertionException {
+public:
+ StaleConfigException(const std::string& ns,
+ const std::string& raw,
+ int code,
+ ChunkVersion received,
+ ChunkVersion wanted)
+ : AssertionException(stream() << raw << " ( ns : " << ns
+ << ", received : " << received.toString()
+ << ", wanted : " << wanted.toString() << ", "
+ << (code == SendStaleConfigCode ? "send" : "recv") << " )",
+ code),
+ _ns(ns),
+ _received(received),
+ _wanted(wanted) {}
+
+ /** Preferred if we're rebuilding this from a thrown exception */
+ StaleConfigException(const std::string& raw, int code, const BSONObj& error)
+ : AssertionException(
+ stream() << raw
+ << " ( ns : " << (error["ns"].type() == String ? error["ns"].String()
+ : std::string("<unknown>"))
+ << ", received : " << ChunkVersion::fromBSON(error, "vReceived").toString()
+ << ", wanted : " << ChunkVersion::fromBSON(error, "vWanted").toString()
+ << ", " << (code == SendStaleConfigCode ? "send" : "recv") << " )",
+ code),
+ // For legacy reasons, we may not always get a namespace here
+ _ns(error["ns"].type() == String ? error["ns"].String() : ""),
+ _received(ChunkVersion::fromBSON(error, "vReceived")),
+ _wanted(ChunkVersion::fromBSON(error, "vWanted")) {}
+
+ /**
+ * Needs message so when we trace all exceptions on construction we get a useful
+ * message
+ */
+ StaleConfigException()
+ : AssertionException("initializing empty stale config exception object", 0) {}
+
+ virtual ~StaleConfigException() throw() {}
+
+ virtual void appendPrefix(std::stringstream& ss) const {
+ ss << "stale sharding config exception: ";
+ }
+
+ std::string getns() const {
+ return _ns;
+ }
/**
- * Thrown whenever your config info for a given shard/chunk is out of date.
+ * true if this exception would require a full reload of config data to resolve
*/
- class StaleConfigException : public AssertionException {
- public:
- StaleConfigException(const std::string& ns,
+ bool requiresFullReload() const {
+ return !_received.hasEqualEpoch(_wanted) || _received.isSet() != _wanted.isSet();
+ }
+
+ static bool parse(const std::string& big, std::string& ns, std::string& raw) {
+ std::string::size_type start = big.find('[');
+ if (start == std::string::npos)
+ return false;
+ std::string::size_type end = big.find(']', start);
+ if (end == std::string::npos)
+ return false;
+
+ ns = big.substr(start + 1, (end - start) - 1);
+ raw = big.substr(end + 1);
+ return true;
+ }
+
+ ChunkVersion getVersionReceived() const {
+ return _received;
+ }
+
+ ChunkVersion getVersionWanted() const {
+ return _wanted;
+ }
+
+ StaleConfigException& operator=(const StaleConfigException& elem) {
+ this->_ei.msg = elem._ei.msg;
+ this->_ei.code = elem._ei.code;
+ this->_ns = elem._ns;
+ this->_received = elem._received;
+ this->_wanted = elem._wanted;
+
+ return *this;
+ }
+
+private:
+ std::string _ns;
+ ChunkVersion _received;
+ ChunkVersion _wanted;
+};
+
+class SendStaleConfigException : public StaleConfigException {
+public:
+ SendStaleConfigException(const std::string& ns,
const std::string& raw,
- int code,
ChunkVersion received,
ChunkVersion wanted)
- : AssertionException(stream() << raw << " ( ns : " << ns
- << ", received : " << received.toString()
- << ", wanted : " << wanted.toString()
- << ", " << ( code == SendStaleConfigCode ?
- "send" : "recv" ) << " )",
- code),
- _ns(ns),
- _received( received ),
- _wanted( wanted ) {
- }
-
- /** Preferred if we're rebuilding this from a thrown exception */
- StaleConfigException( const std::string& raw,
- int code,
- const BSONObj& error)
- : AssertionException( stream() << raw << " ( ns : "
- << ( error["ns"].type() == String ?
- error["ns"].String() : std::string("<unknown>") )
- << ", received : "
- << ChunkVersion::fromBSON( error, "vReceived" ).toString()
- << ", wanted : "
- << ChunkVersion::fromBSON( error, "vWanted" ).toString()
- << ", "
- << ( code == SendStaleConfigCode ?
- "send" : "recv" ) << " )",
- code ),
- // For legacy reasons, we may not always get a namespace here
- _ns( error["ns"].type() == String ? error["ns"].String() : "" ),
- _received( ChunkVersion::fromBSON( error, "vReceived" ) ),
- _wanted( ChunkVersion::fromBSON( error, "vWanted" ) ) {
- }
-
- /**
- * Needs message so when we trace all exceptions on construction we get a useful
- * message
- */
- StaleConfigException() :
- AssertionException( "initializing empty stale config exception object", 0 ) {
- }
-
- virtual ~StaleConfigException() throw() {}
-
- virtual void appendPrefix( std::stringstream& ss ) const {
- ss << "stale sharding config exception: ";
- }
-
- std::string getns() const { return _ns; }
-
- /**
- * true if this exception would require a full reload of config data to resolve
- */
- bool requiresFullReload() const {
- return ! _received.hasEqualEpoch( _wanted ) ||
- _received.isSet() != _wanted.isSet();
- }
-
- static bool parse( const std::string& big , std::string& ns , std::string& raw ) {
- std::string::size_type start = big.find( '[' );
- if ( start == std::string::npos )
- return false;
- std::string::size_type end = big.find( ']' ,start );
- if ( end == std::string::npos )
- return false;
-
- ns = big.substr( start + 1 , ( end - start ) - 1 );
- raw = big.substr( end + 1 );
- return true;
- }
-
- ChunkVersion getVersionReceived() const {
- return _received;
- }
-
- ChunkVersion getVersionWanted() const {
- return _wanted;
- }
-
- StaleConfigException& operator=(const StaleConfigException& elem) {
- this->_ei.msg = elem._ei.msg;
- this->_ei.code = elem._ei.code;
- this->_ns = elem._ns;
- this->_received = elem._received;
- this->_wanted = elem._wanted;
-
- return *this;
- }
-
- private:
- std::string _ns;
- ChunkVersion _received;
- ChunkVersion _wanted;
- };
-
- class SendStaleConfigException : public StaleConfigException {
- public:
- SendStaleConfigException(const std::string& ns,
- const std::string& raw,
- ChunkVersion received,
- ChunkVersion wanted)
- : StaleConfigException(ns, raw, SendStaleConfigCode, received, wanted) {
-
- }
-
- SendStaleConfigException( const std::string& raw,
- const BSONObj& error)
- : StaleConfigException(raw, SendStaleConfigCode, error) {
-
- }
- };
-
- class RecvStaleConfigException : public StaleConfigException {
- public:
- RecvStaleConfigException(const std::string& ns,
- const std::string& raw,
- ChunkVersion received,
- ChunkVersion wanted)
- : StaleConfigException(ns, raw, RecvStaleConfigCode, received, wanted) {
-
- }
-
- RecvStaleConfigException(const std::string& raw,
- const BSONObj& error)
- : StaleConfigException(raw, RecvStaleConfigCode, error) {
-
- }
- };
-
-} // namespace mongo
+ : StaleConfigException(ns, raw, SendStaleConfigCode, received, wanted) {}
+
+ SendStaleConfigException(const std::string& raw, const BSONObj& error)
+ : StaleConfigException(raw, SendStaleConfigCode, error) {}
+};
+
+class RecvStaleConfigException : public StaleConfigException {
+public:
+ RecvStaleConfigException(const std::string& ns,
+ const std::string& raw,
+ ChunkVersion received,
+ ChunkVersion wanted)
+ : StaleConfigException(ns, raw, RecvStaleConfigCode, received, wanted) {}
+
+ RecvStaleConfigException(const std::string& raw, const BSONObj& error)
+ : StaleConfigException(raw, RecvStaleConfigCode, error) {}
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/strategy.cpp b/src/mongo/s/strategy.cpp
index 061059c709d..f2d44b594c0 100644
--- a/src/mongo/s/strategy.cpp
+++ b/src/mongo/s/strategy.cpp
@@ -69,643 +69,620 @@
namespace mongo {
- using std::unique_ptr;
- using std::shared_ptr;
- using std::endl;
- using std::set;
- using std::string;
- using std::stringstream;
- using std::vector;
-
- static bool _isSystemIndexes( const char* ns ) {
- return nsToCollectionSubstring(ns) == "system.indexes";
+using std::unique_ptr;
+using std::shared_ptr;
+using std::endl;
+using std::set;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+static bool _isSystemIndexes(const char* ns) {
+ return nsToCollectionSubstring(ns) == "system.indexes";
+}
+
+/**
+ * Returns true if request is a query for sharded indexes.
+ */
+static bool doShardedIndexQuery(Request& r, const QuerySpec& qSpec) {
+ // Extract the ns field from the query, which may be embedded within the "query" or
+ // "$query" field.
+ auto nsField = qSpec.filter()["ns"];
+ if (nsField.eoo()) {
+ return false;
}
+ const NamespaceString indexNSSQuery(nsField.str());
- /**
- * Returns true if request is a query for sharded indexes.
- */
- static bool doShardedIndexQuery(Request& r, const QuerySpec& qSpec) {
- // Extract the ns field from the query, which may be embedded within the "query" or
- // "$query" field.
- auto nsField = qSpec.filter()["ns"];
- if (nsField.eoo()) {
- return false;
- }
- const NamespaceString indexNSSQuery(nsField.str());
+ auto status = grid.catalogCache()->getDatabase(indexNSSQuery.db().toString());
+ if (!status.isOK()) {
+ return false;
+ }
- auto status = grid.catalogCache()->getDatabase(indexNSSQuery.db().toString());
- if (!status.isOK()) {
- return false;
- }
+ shared_ptr<DBConfig> config = status.getValue();
+ if (!config->isSharded(indexNSSQuery.ns())) {
+ return false;
+ }
- shared_ptr<DBConfig> config = status.getValue();
- if (!config->isSharded(indexNSSQuery.ns())) {
- return false;
- }
+ // if you are querying on system.indexes, we need to make sure we go to a shard
+ // that actually has chunks. This is not a perfect solution (what if you just
+ // look at all indexes), but better than doing nothing.
- // if you are querying on system.indexes, we need to make sure we go to a shard
- // that actually has chunks. This is not a perfect solution (what if you just
- // look at all indexes), but better than doing nothing.
-
- ShardPtr shard;
- ChunkManagerPtr cm;
- config->getChunkManagerOrPrimary(indexNSSQuery.ns(), cm, shard);
- if ( cm ) {
- set<ShardId> shardIds;
- cm->getAllShardIds(&shardIds);
- verify(shardIds.size() > 0);
- shard = grid.shardRegistry()->getShard(*shardIds.begin());
- }
+ ShardPtr shard;
+ ChunkManagerPtr cm;
+ config->getChunkManagerOrPrimary(indexNSSQuery.ns(), cm, shard);
+ if (cm) {
+ set<ShardId> shardIds;
+ cm->getAllShardIds(&shardIds);
+ verify(shardIds.size() > 0);
+ shard = grid.shardRegistry()->getShard(*shardIds.begin());
+ }
- ShardConnection dbcon(shard->getConnString(), r.getns());
- DBClientBase &c = dbcon.conn();
+ ShardConnection dbcon(shard->getConnString(), r.getns());
+ DBClientBase& c = dbcon.conn();
- string actualServer;
+ string actualServer;
- Message response;
- bool ok = c.call( r.m(), response, true , &actualServer );
- uassert( 10200 , "mongos: error calling db", ok );
+ Message response;
+ bool ok = c.call(r.m(), response, true, &actualServer);
+ uassert(10200, "mongos: error calling db", ok);
- {
- QueryResult::View qr = response.singleData().view2ptr();
- if ( qr.getResultFlags() & ResultFlag_ShardConfigStale ) {
- dbcon.done();
- // Version is zero b/c this is deprecated codepath
- throw RecvStaleConfigException( r.getns(),
- "Strategy::doQuery",
- ChunkVersion( 0, 0, OID() ),
- ChunkVersion( 0, 0, OID() ));
- }
+ {
+ QueryResult::View qr = response.singleData().view2ptr();
+ if (qr.getResultFlags() & ResultFlag_ShardConfigStale) {
+ dbcon.done();
+ // Version is zero b/c this is deprecated codepath
+ throw RecvStaleConfigException(r.getns(),
+ "Strategy::doQuery",
+ ChunkVersion(0, 0, OID()),
+ ChunkVersion(0, 0, OID()));
}
-
- r.reply( response , actualServer.size() ? actualServer : c.getServerAddress() );
- dbcon.done();
-
- return true;
}
- void Strategy::queryOp( Request& r ) {
+ r.reply(response, actualServer.size() ? actualServer : c.getServerAddress());
+ dbcon.done();
- verify( !NamespaceString( r.getns() ).isCommand() );
+ return true;
+}
- Timer queryTimer;
+void Strategy::queryOp(Request& r) {
+ verify(!NamespaceString(r.getns()).isCommand());
- QueryMessage q( r.d() );
+ Timer queryTimer;
- NamespaceString ns(q.ns);
- ClientBasic* client = ClientBasic::getCurrent();
- AuthorizationSession* authSession = AuthorizationSession::get(client);
- Status status = authSession->checkAuthForQuery(ns, q.query);
- audit::logQueryAuthzCheck(client, ns, q.query, status.code());
- uassertStatusOK(status);
+ QueryMessage q(r.d());
- LOG(3) << "query: " << q.ns << " " << q.query << " ntoreturn: " << q.ntoreturn
- << " options: " << q.queryOptions << endl;
+ NamespaceString ns(q.ns);
+ ClientBasic* client = ClientBasic::getCurrent();
+ AuthorizationSession* authSession = AuthorizationSession::get(client);
+ Status status = authSession->checkAuthForQuery(ns, q.query);
+ audit::logQueryAuthzCheck(client, ns, q.query, status.code());
+ uassertStatusOK(status);
- if ( q.ntoreturn == 1 && strstr(q.ns, ".$cmd") )
- throw UserException( 8010 , "something is wrong, shouldn't see a command here" );
+ LOG(3) << "query: " << q.ns << " " << q.query << " ntoreturn: " << q.ntoreturn
+ << " options: " << q.queryOptions << endl;
- if (q.queryOptions & QueryOption_Exhaust) {
- uasserted(18526,
- string("the 'exhaust' query option is invalid for mongos queries: ") + q.ns
- + " " + q.query.toString());
- }
+ if (q.ntoreturn == 1 && strstr(q.ns, ".$cmd"))
+ throw UserException(8010, "something is wrong, shouldn't see a command here");
- QuerySpec qSpec( (string)q.ns, q.query, q.fields, q.ntoskip, q.ntoreturn, q.queryOptions );
+ if (q.queryOptions & QueryOption_Exhaust) {
+ uasserted(18526,
+ string("the 'exhaust' query option is invalid for mongos queries: ") + q.ns +
+ " " + q.query.toString());
+ }
- // Parse "$maxTimeMS".
- StatusWith<int> maxTimeMS = LiteParsedQuery::parseMaxTimeMSQuery( q.query );
- uassert( 17233,
- maxTimeMS.getStatus().reason(),
- maxTimeMS.isOK() );
+ QuerySpec qSpec((string)q.ns, q.query, q.fields, q.ntoskip, q.ntoreturn, q.queryOptions);
- if ( _isSystemIndexes( q.ns ) && doShardedIndexQuery( r, qSpec )) {
- return;
- }
+ // Parse "$maxTimeMS".
+ StatusWith<int> maxTimeMS = LiteParsedQuery::parseMaxTimeMSQuery(q.query);
+ uassert(17233, maxTimeMS.getStatus().reason(), maxTimeMS.isOK());
- ParallelSortClusteredCursor * cursor = new ParallelSortClusteredCursor( qSpec, CommandInfo() );
- verify( cursor );
+ if (_isSystemIndexes(q.ns) && doShardedIndexQuery(r, qSpec)) {
+ return;
+ }
- // TODO: Move out to Request itself, not strategy based
- try {
- cursor->init();
-
- if ( qSpec.isExplain() ) {
- BSONObjBuilder explain_builder;
- cursor->explain( explain_builder );
- explain_builder.appendNumber( "executionTimeMillis",
- static_cast<long long>(queryTimer.millis()) );
- BSONObj b = explain_builder.obj();
-
- replyToQuery( 0 , r.p() , r.m() , b );
- delete( cursor );
- return;
- }
- }
- catch(...) {
- delete cursor;
- throw;
- }
+ ParallelSortClusteredCursor* cursor = new ParallelSortClusteredCursor(qSpec, CommandInfo());
+ verify(cursor);
- // TODO: Revisit all of this when we revisit the sharded cursor cache
+ // TODO: Move out to Request itself, not strategy based
+ try {
+ cursor->init();
- if (cursor->getNumQueryShards() != 1) {
+ if (qSpec.isExplain()) {
+ BSONObjBuilder explain_builder;
+ cursor->explain(explain_builder);
+ explain_builder.appendNumber("executionTimeMillis",
+ static_cast<long long>(queryTimer.millis()));
+ BSONObj b = explain_builder.obj();
- // More than one shard (or zero), manage with a ShardedClientCursor
- // NOTE: We may also have *zero* shards here when the returnPartial flag is set.
- // Currently the code in ShardedClientCursor handles this.
+ replyToQuery(0, r.p(), r.m(), b);
+ delete (cursor);
+ return;
+ }
+ } catch (...) {
+ delete cursor;
+ throw;
+ }
- ShardedClientCursorPtr cc (new ShardedClientCursor( q , cursor ));
+ // TODO: Revisit all of this when we revisit the sharded cursor cache
- BufBuilder buffer( ShardedClientCursor::INIT_REPLY_BUFFER_SIZE );
- int docCount = 0;
- const int startFrom = cc->getTotalSent();
- bool hasMore = cc->sendNextBatch(q.ntoreturn, buffer, docCount);
+ if (cursor->getNumQueryShards() != 1) {
+ // More than one shard (or zero), manage with a ShardedClientCursor
+ // NOTE: We may also have *zero* shards here when the returnPartial flag is set.
+ // Currently the code in ShardedClientCursor handles this.
- if ( hasMore ) {
- LOG(5) << "storing cursor : " << cc->getId() << endl;
+ ShardedClientCursorPtr cc(new ShardedClientCursor(q, cursor));
- int cursorLeftoverMillis = maxTimeMS.getValue() - queryTimer.millis();
- if ( maxTimeMS.getValue() == 0 ) { // 0 represents "no limit".
- cursorLeftoverMillis = kMaxTimeCursorNoTimeLimit;
- }
- else if ( cursorLeftoverMillis <= 0 ) {
- cursorLeftoverMillis = kMaxTimeCursorTimeLimitExpired;
- }
+ BufBuilder buffer(ShardedClientCursor::INIT_REPLY_BUFFER_SIZE);
+ int docCount = 0;
+ const int startFrom = cc->getTotalSent();
+ bool hasMore = cc->sendNextBatch(q.ntoreturn, buffer, docCount);
+
+ if (hasMore) {
+ LOG(5) << "storing cursor : " << cc->getId() << endl;
- cursorCache.store( cc, cursorLeftoverMillis );
+ int cursorLeftoverMillis = maxTimeMS.getValue() - queryTimer.millis();
+ if (maxTimeMS.getValue() == 0) { // 0 represents "no limit".
+ cursorLeftoverMillis = kMaxTimeCursorNoTimeLimit;
+ } else if (cursorLeftoverMillis <= 0) {
+ cursorLeftoverMillis = kMaxTimeCursorTimeLimitExpired;
}
- replyToQuery( 0, r.p(), r.m(), buffer.buf(), buffer.len(), docCount,
- startFrom, hasMore ? cc->getId() : 0 );
+ cursorCache.store(cc, cursorLeftoverMillis);
}
- else{
- // Only one shard is used
+ replyToQuery(0,
+ r.p(),
+ r.m(),
+ buffer.buf(),
+ buffer.len(),
+ docCount,
+ startFrom,
+ hasMore ? cc->getId() : 0);
+ } else {
+ // Only one shard is used
- // Remote cursors are stored remotely, we shouldn't need this around.
- unique_ptr<ParallelSortClusteredCursor> cursorDeleter( cursor );
+ // Remote cursors are stored remotely, we shouldn't need this around.
+ unique_ptr<ParallelSortClusteredCursor> cursorDeleter(cursor);
- ShardPtr shard = cursor->getQueryShard();
- verify( shard.get() );
- DBClientCursorPtr shardCursor = cursor->getShardCursor(shard->getId());
+ ShardPtr shard = cursor->getQueryShard();
+ verify(shard.get());
+ DBClientCursorPtr shardCursor = cursor->getShardCursor(shard->getId());
- // Implicitly stores the cursor in the cache
- r.reply( *(shardCursor->getMessage()) , shardCursor->originalHost() );
+ // Implicitly stores the cursor in the cache
+ r.reply(*(shardCursor->getMessage()), shardCursor->originalHost());
- // We don't want to kill the cursor remotely if there's still data left
- shardCursor->decouple();
- }
+ // We don't want to kill the cursor remotely if there's still data left
+ shardCursor->decouple();
}
+}
- void Strategy::clientCommandOp( Request& r ) {
- QueryMessage q( r.d() );
+void Strategy::clientCommandOp(Request& r) {
+ QueryMessage q(r.d());
- LOG(3) << "command: " << q.ns << " " << q.query << " ntoreturn: " << q.ntoreturn
- << " options: " << q.queryOptions << endl;
+ LOG(3) << "command: " << q.ns << " " << q.query << " ntoreturn: " << q.ntoreturn
+ << " options: " << q.queryOptions << endl;
- if (q.queryOptions & QueryOption_Exhaust) {
- uasserted(18527,
- string("the 'exhaust' query option is invalid for mongos commands: ") + q.ns
- + " " + q.query.toString());
- }
+ if (q.queryOptions & QueryOption_Exhaust) {
+ uasserted(18527,
+ string("the 'exhaust' query option is invalid for mongos commands: ") + q.ns +
+ " " + q.query.toString());
+ }
- NamespaceString nss( r.getns() );
- // Regular queries are handled in strategy_shard.cpp
- verify( nss.isCommand() || nss.isSpecialCommand() );
+ NamespaceString nss(r.getns());
+ // Regular queries are handled in strategy_shard.cpp
+ verify(nss.isCommand() || nss.isSpecialCommand());
- if ( handleSpecialNamespaces( r , q ) )
- return;
+ if (handleSpecialNamespaces(r, q))
+ return;
- int loops = 5;
- while ( true ) {
- BSONObjBuilder builder;
- try {
- BSONObj cmdObj = q.query;
- {
- BSONElement e = cmdObj.firstElement();
- if (e.type() == Object && (e.fieldName()[0] == '$'
- ? str::equals("query", e.fieldName()+1)
- : str::equals("query", e.fieldName()))) {
- // Extract the embedded query object.
-
- if (cmdObj.hasField(Query::ReadPrefField.name())) {
- // The command has a read preference setting. We don't want
- // to lose this information so we copy this to a new field
- // called $queryOptions.$readPreference
- BSONObjBuilder finalCmdObjBuilder;
- finalCmdObjBuilder.appendElements(e.embeddedObject());
-
- BSONObjBuilder queryOptionsBuilder(
- finalCmdObjBuilder.subobjStart("$queryOptions"));
- queryOptionsBuilder.append(cmdObj[Query::ReadPrefField.name()]);
- queryOptionsBuilder.done();
-
- cmdObj = finalCmdObjBuilder.obj();
- }
- else {
- cmdObj = e.embeddedObject();
- }
+ int loops = 5;
+ while (true) {
+ BSONObjBuilder builder;
+ try {
+ BSONObj cmdObj = q.query;
+ {
+ BSONElement e = cmdObj.firstElement();
+ if (e.type() == Object &&
+ (e.fieldName()[0] == '$' ? str::equals("query", e.fieldName() + 1)
+ : str::equals("query", e.fieldName()))) {
+ // Extract the embedded query object.
+
+ if (cmdObj.hasField(Query::ReadPrefField.name())) {
+ // The command has a read preference setting. We don't want
+ // to lose this information so we copy this to a new field
+ // called $queryOptions.$readPreference
+ BSONObjBuilder finalCmdObjBuilder;
+ finalCmdObjBuilder.appendElements(e.embeddedObject());
+
+ BSONObjBuilder queryOptionsBuilder(
+ finalCmdObjBuilder.subobjStart("$queryOptions"));
+ queryOptionsBuilder.append(cmdObj[Query::ReadPrefField.name()]);
+ queryOptionsBuilder.done();
+
+ cmdObj = finalCmdObjBuilder.obj();
+ } else {
+ cmdObj = e.embeddedObject();
}
}
-
- Command::runAgainstRegistered(q.ns, cmdObj, builder, q.queryOptions);
- BSONObj x = builder.done();
- replyToQuery(0, r.p(), r.m(), x);
- return;
}
- catch ( StaleConfigException& e ) {
- if ( loops <= 0 )
- throw e;
-
- loops--;
- log() << "retrying command: " << q.query << endl;
- // For legacy reasons, ns may not actually be set in the exception :-(
- string staleNS = e.getns();
- if( staleNS.size() == 0 ) staleNS = q.ns;
-
- ShardConnection::checkMyConnectionVersions( staleNS );
- if( loops < 4 ) versionManager.forceRemoteCheckShardVersionCB( staleNS );
- }
- catch ( AssertionException& e ) {
- Command::appendCommandStatus(builder, e.toStatus());
- BSONObj x = builder.done();
- replyToQuery(0, r.p(), r.m(), x);
- return;
- }
+ Command::runAgainstRegistered(q.ns, cmdObj, builder, q.queryOptions);
+ BSONObj x = builder.done();
+ replyToQuery(0, r.p(), r.m(), x);
+ return;
+ } catch (StaleConfigException& e) {
+ if (loops <= 0)
+ throw e;
+
+ loops--;
+ log() << "retrying command: " << q.query << endl;
+
+ // For legacy reasons, ns may not actually be set in the exception :-(
+ string staleNS = e.getns();
+ if (staleNS.size() == 0)
+ staleNS = q.ns;
+
+ ShardConnection::checkMyConnectionVersions(staleNS);
+ if (loops < 4)
+ versionManager.forceRemoteCheckShardVersionCB(staleNS);
+ } catch (AssertionException& e) {
+ Command::appendCommandStatus(builder, e.toStatus());
+ BSONObj x = builder.done();
+ replyToQuery(0, r.p(), r.m(), x);
+ return;
}
}
+}
- // TODO: remove after MongoDB 3.2
- bool Strategy::handleSpecialNamespaces( Request& r , QueryMessage& q ) {
- const char * ns = strstr( r.getns() , ".$cmd.sys." );
- if ( ! ns )
- return false;
- ns += 10;
-
- BSONObjBuilder reply;
-
- const auto upgradeToRealCommand = [&r, &q, &reply](StringData commandName) {
- BSONObjBuilder cmdBob;
- cmdBob.append(commandName, 1);
- cmdBob.appendElements(q.query); // fields are validated by Commands
- auto interposedCmd = cmdBob.done();
- NamespaceString nss(r.getns());
- NamespaceString interposedNss(nss.db(), "$cmd");
- Command::runAgainstRegistered(interposedNss.ns().c_str(),
- interposedCmd,
- reply,
- q.queryOptions);
- };
-
- if ( strcmp( ns , "inprog" ) == 0 ) {
- upgradeToRealCommand("currentOp");
- }
- else if ( strcmp( ns , "killop" ) == 0 ) {
- upgradeToRealCommand("killOp");
- }
- else if ( strcmp( ns , "unlock" ) == 0 ) {
- reply.append( "err" , "can't do unlock through mongos" );
- }
- else {
- warning() << "unknown sys command [" << ns << "]" << endl;
- return false;
- }
-
- BSONObj x = reply.done();
- replyToQuery(0, r.p(), r.m(), x);
- return true;
+// TODO: remove after MongoDB 3.2
+bool Strategy::handleSpecialNamespaces(Request& r, QueryMessage& q) {
+ const char* ns = strstr(r.getns(), ".$cmd.sys.");
+ if (!ns)
+ return false;
+ ns += 10;
+
+ BSONObjBuilder reply;
+
+ const auto upgradeToRealCommand = [&r, &q, &reply](StringData commandName) {
+ BSONObjBuilder cmdBob;
+ cmdBob.append(commandName, 1);
+ cmdBob.appendElements(q.query); // fields are validated by Commands
+ auto interposedCmd = cmdBob.done();
+ NamespaceString nss(r.getns());
+ NamespaceString interposedNss(nss.db(), "$cmd");
+ Command::runAgainstRegistered(
+ interposedNss.ns().c_str(), interposedCmd, reply, q.queryOptions);
+ };
+
+ if (strcmp(ns, "inprog") == 0) {
+ upgradeToRealCommand("currentOp");
+ } else if (strcmp(ns, "killop") == 0) {
+ upgradeToRealCommand("killOp");
+ } else if (strcmp(ns, "unlock") == 0) {
+ reply.append("err", "can't do unlock through mongos");
+ } else {
+ warning() << "unknown sys command [" << ns << "]" << endl;
+ return false;
}
- void Strategy::commandOp( const string& db,
- const BSONObj& command,
- int options,
- const string& versionedNS,
- const BSONObj& targetingQuery,
- vector<CommandResult>* results )
- {
-
- QuerySpec qSpec(db + ".$cmd", command, BSONObj(), 0, 1, options);
+ BSONObj x = reply.done();
+ replyToQuery(0, r.p(), r.m(), x);
+ return true;
+}
- ParallelSortClusteredCursor cursor( qSpec, CommandInfo( versionedNS, targetingQuery ) );
+void Strategy::commandOp(const string& db,
+ const BSONObj& command,
+ int options,
+ const string& versionedNS,
+ const BSONObj& targetingQuery,
+ vector<CommandResult>* results) {
+ QuerySpec qSpec(db + ".$cmd", command, BSONObj(), 0, 1, options);
- // Initialize the cursor
- cursor.init();
+ ParallelSortClusteredCursor cursor(qSpec, CommandInfo(versionedNS, targetingQuery));
- set<ShardId> shardIds;
- cursor.getQueryShardIds(shardIds);
-
- for (const ShardId& shardId : shardIds) {
- CommandResult result;
- result.shardTargetId = shardId;
-
- string errMsg; // ignored, should never be invalid b/c an exception thrown earlier
- result.target =
- ConnectionString::parse( cursor.getShardCursor(shardId)->originalHost(),
- errMsg );
- result.result = cursor.getShardCursor(shardId)->peekFirst().getOwned();
- results->push_back( result );
- }
+ // Initialize the cursor
+ cursor.init();
- }
+ set<ShardId> shardIds;
+ cursor.getQueryShardIds(shardIds);
- Status Strategy::commandOpWrite(const std::string& dbName,
- const BSONObj& command,
- BatchItemRef targetingBatchItem,
- std::vector<CommandResult>* results) {
+ for (const ShardId& shardId : shardIds) {
+ CommandResult result;
+ result.shardTargetId = shardId;
- // Note that this implementation will not handle targeting retries and does not completely
- // emulate write behavior
+ string errMsg; // ignored, should never be invalid b/c an exception thrown earlier
+ result.target =
+ ConnectionString::parse(cursor.getShardCursor(shardId)->originalHost(), errMsg);
+ result.result = cursor.getShardCursor(shardId)->peekFirst().getOwned();
+ results->push_back(result);
+ }
+}
- ChunkManagerTargeter targeter(NamespaceString(
- targetingBatchItem.getRequest()->getTargetingNS()));
- Status status = targeter.init();
+Status Strategy::commandOpWrite(const std::string& dbName,
+ const BSONObj& command,
+ BatchItemRef targetingBatchItem,
+ std::vector<CommandResult>* results) {
+ // Note that this implementation will not handle targeting retries and does not completely
+ // emulate write behavior
+
+ ChunkManagerTargeter targeter(
+ NamespaceString(targetingBatchItem.getRequest()->getTargetingNS()));
+ Status status = targeter.init();
+ if (!status.isOK())
+ return status;
+
+ OwnedPointerVector<ShardEndpoint> endpointsOwned;
+ vector<ShardEndpoint*>& endpoints = endpointsOwned.mutableVector();
+
+ if (targetingBatchItem.getOpType() == BatchedCommandRequest::BatchType_Insert) {
+ ShardEndpoint* endpoint;
+ Status status = targeter.targetInsert(targetingBatchItem.getDocument(), &endpoint);
+ if (!status.isOK())
+ return status;
+ endpoints.push_back(endpoint);
+ } else if (targetingBatchItem.getOpType() == BatchedCommandRequest::BatchType_Update) {
+ Status status = targeter.targetUpdate(*targetingBatchItem.getUpdate(), &endpoints);
if (!status.isOK())
return status;
+ } else {
+ invariant(targetingBatchItem.getOpType() == BatchedCommandRequest::BatchType_Delete);
+ Status status = targeter.targetDelete(*targetingBatchItem.getDelete(), &endpoints);
+ if (!status.isOK())
+ return status;
+ }
- OwnedPointerVector<ShardEndpoint> endpointsOwned;
- vector<ShardEndpoint*>& endpoints = endpointsOwned.mutableVector();
+ DBClientShardResolver resolver;
+ DBClientMultiCommand dispatcher;
- if (targetingBatchItem.getOpType() == BatchedCommandRequest::BatchType_Insert) {
- ShardEndpoint* endpoint;
- Status status = targeter.targetInsert(targetingBatchItem.getDocument(), &endpoint);
- if (!status.isOK())
- return status;
- endpoints.push_back(endpoint);
- }
- else if (targetingBatchItem.getOpType() == BatchedCommandRequest::BatchType_Update) {
- Status status = targeter.targetUpdate(*targetingBatchItem.getUpdate(), &endpoints);
- if (!status.isOK())
- return status;
- }
- else {
- invariant(targetingBatchItem.getOpType() == BatchedCommandRequest::BatchType_Delete);
- Status status = targeter.targetDelete(*targetingBatchItem.getDelete(), &endpoints);
- if (!status.isOK())
- return status;
- }
+ // Assemble requests
+ for (vector<ShardEndpoint*>::const_iterator it = endpoints.begin(); it != endpoints.end();
+ ++it) {
+ const ShardEndpoint* endpoint = *it;
- DBClientShardResolver resolver;
- DBClientMultiCommand dispatcher;
+ ConnectionString host;
+ Status status = resolver.chooseWriteHost(endpoint->shardName, &host);
+ if (!status.isOK())
+ return status;
- // Assemble requests
- for (vector<ShardEndpoint*>::const_iterator it = endpoints.begin(); it != endpoints.end();
- ++it) {
+ RawBSONSerializable request(command);
+ dispatcher.addCommand(host, dbName, request);
+ }
- const ShardEndpoint* endpoint = *it;
+ // Errors reported when recv'ing responses
+ dispatcher.sendAll();
+ Status dispatchStatus = Status::OK();
- ConnectionString host;
- Status status = resolver.chooseWriteHost(endpoint->shardName, &host);
- if (!status.isOK())
- return status;
+ // Recv responses
+ while (dispatcher.numPending() > 0) {
+ ConnectionString host;
+ RawBSONSerializable response;
- RawBSONSerializable request(command);
- dispatcher.addCommand(host, dbName, request);
+ Status status = dispatcher.recvAny(&host, &response);
+ if (!status.isOK()) {
+ // We always need to recv() all the sent operations
+ dispatchStatus = status;
+ continue;
}
- // Errors reported when recv'ing responses
- dispatcher.sendAll();
- Status dispatchStatus = Status::OK();
-
- // Recv responses
- while (dispatcher.numPending() > 0) {
-
- ConnectionString host;
- RawBSONSerializable response;
-
- Status status = dispatcher.recvAny(&host, &response);
- if (!status.isOK()) {
- // We always need to recv() all the sent operations
- dispatchStatus = status;
- continue;
- }
-
- CommandResult result;
- result.target = host;
- {
- const auto shard = grid.shardRegistry()->getShard(host.toString());
- result.shardTargetId = shard->getId();
- }
- result.result = response.toBSON();
-
- results->push_back(result);
+ CommandResult result;
+ result.target = host;
+ {
+ const auto shard = grid.shardRegistry()->getShard(host.toString());
+ result.shardTargetId = shard->getId();
}
+ result.result = response.toBSON();
- return dispatchStatus;
+ results->push_back(result);
}
- Status Strategy::commandOpUnsharded(const std::string& db,
- const BSONObj& command,
- int options,
- const std::string& versionedNS,
- CommandResult* cmdResult) {
+ return dispatchStatus;
+}
- // Note that this implementation will not handle targeting retries and fails when the
- // sharding metadata is too stale
- auto status = grid.catalogCache()->getDatabase(db);
- if (!status.isOK()) {
- mongoutils::str::stream ss;
- ss << "Passthrough command failed: " << command.toString()
- << " on ns " << versionedNS << ". Caused by " << causedBy(status.getStatus());
- return Status(ErrorCodes::IllegalOperation, ss);
- }
+Status Strategy::commandOpUnsharded(const std::string& db,
+ const BSONObj& command,
+ int options,
+ const std::string& versionedNS,
+ CommandResult* cmdResult) {
+ // Note that this implementation will not handle targeting retries and fails when the
+ // sharding metadata is too stale
+ auto status = grid.catalogCache()->getDatabase(db);
+ if (!status.isOK()) {
+ mongoutils::str::stream ss;
+ ss << "Passthrough command failed: " << command.toString() << " on ns " << versionedNS
+ << ". Caused by " << causedBy(status.getStatus());
+ return Status(ErrorCodes::IllegalOperation, ss);
+ }
- shared_ptr<DBConfig> conf = status.getValue();
- if (conf->isSharded(versionedNS)) {
- mongoutils::str::stream ss;
- ss << "Passthrough command failed: " << command.toString()
- << " on ns " << versionedNS << ". Cannot run on sharded namespace.";
- return Status(ErrorCodes::IllegalOperation, ss);
- }
+ shared_ptr<DBConfig> conf = status.getValue();
+ if (conf->isSharded(versionedNS)) {
+ mongoutils::str::stream ss;
+ ss << "Passthrough command failed: " << command.toString() << " on ns " << versionedNS
+ << ". Cannot run on sharded namespace.";
+ return Status(ErrorCodes::IllegalOperation, ss);
+ }
- const auto primaryShard = grid.shardRegistry()->getShard(conf->getPrimaryId());
+ const auto primaryShard = grid.shardRegistry()->getShard(conf->getPrimaryId());
- BSONObj shardResult;
- try {
- ShardConnection conn(primaryShard->getConnString(), "");
-
- // TODO: this can throw a stale config when mongos is not up-to-date -- fix.
- if (!conn->runCommand(db, command, shardResult, options)) {
- conn.done();
- return Status(ErrorCodes::OperationFailed,
- str::stream() << "Passthrough command failed: " << command
- << " on ns " << versionedNS
- << "; result: " << shardResult);
- }
+ BSONObj shardResult;
+ try {
+ ShardConnection conn(primaryShard->getConnString(), "");
+
+ // TODO: this can throw a stale config when mongos is not up-to-date -- fix.
+ if (!conn->runCommand(db, command, shardResult, options)) {
conn.done();
+ return Status(ErrorCodes::OperationFailed,
+ str::stream() << "Passthrough command failed: " << command << " on ns "
+ << versionedNS << "; result: " << shardResult);
}
- catch (const DBException& ex) {
- return ex.toStatus();
- }
-
- // Fill out the command result.
- cmdResult->shardTargetId = conf->getPrimaryId();
- cmdResult->result = shardResult;
- cmdResult->target = primaryShard->getConnString();
-
- return Status::OK();
+ conn.done();
+ } catch (const DBException& ex) {
+ return ex.toStatus();
}
- void Strategy::getMore( Request& r ) {
- Timer getMoreTimer;
-
- const char* ns = r.getns();
- const int ntoreturn = r.d().pullInt();
- const long long id = r.d().pullInt64();
+ // Fill out the command result.
+ cmdResult->shardTargetId = conf->getPrimaryId();
+ cmdResult->result = shardResult;
+ cmdResult->target = primaryShard->getConnString();
- // TODO: Handle stale config exceptions here from coll being dropped or sharded during op
- // for now has same semantics as legacy request
- const NamespaceString nss(ns);
- auto statusGetDb = grid.catalogCache()->getDatabase(nss.db().toString());
- if (statusGetDb == ErrorCodes::DatabaseNotFound) {
- cursorCache.remove(id);
- replyToQuery(ResultFlag_CursorNotFound, r.p(), r.m(), 0, 0, 0);
- return;
- }
+ return Status::OK();
+}
- uassertStatusOK(statusGetDb);
+void Strategy::getMore(Request& r) {
+ Timer getMoreTimer;
+
+ const char* ns = r.getns();
+ const int ntoreturn = r.d().pullInt();
+ const long long id = r.d().pullInt64();
+
+ // TODO: Handle stale config exceptions here from coll being dropped or sharded during op
+ // for now has same semantics as legacy request
+ const NamespaceString nss(ns);
+ auto statusGetDb = grid.catalogCache()->getDatabase(nss.db().toString());
+ if (statusGetDb == ErrorCodes::DatabaseNotFound) {
+ cursorCache.remove(id);
+ replyToQuery(ResultFlag_CursorNotFound, r.p(), r.m(), 0, 0, 0);
+ return;
+ }
- shared_ptr<DBConfig> config = statusGetDb.getValue();
+ uassertStatusOK(statusGetDb);
- ShardPtr primary;
- ChunkManagerPtr info;
- config->getChunkManagerOrPrimary( ns, info, primary );
+ shared_ptr<DBConfig> config = statusGetDb.getValue();
- //
- // TODO: Cleanup cursor cache, consolidate into single codepath
- //
- const string host = cursorCache.getRef(id);
- ShardedClientCursorPtr cursor = cursorCache.get( id );
- int cursorMaxTimeMS = cursorCache.getMaxTimeMS( id );
+ ShardPtr primary;
+ ChunkManagerPtr info;
+ config->getChunkManagerOrPrimary(ns, info, primary);
- // Cursor ids should not overlap between sharded and unsharded cursors
- massert( 17012, str::stream() << "duplicate sharded and unsharded cursor id "
- << id << " detected for " << ns
- << ", duplicated on host " << host,
- NULL == cursorCache.get( id ).get() || host.empty() );
+ //
+ // TODO: Cleanup cursor cache, consolidate into single codepath
+ //
+ const string host = cursorCache.getRef(id);
+ ShardedClientCursorPtr cursor = cursorCache.get(id);
+ int cursorMaxTimeMS = cursorCache.getMaxTimeMS(id);
- ClientBasic* client = ClientBasic::getCurrent();
- NamespaceString nsString(ns);
- AuthorizationSession* authSession = AuthorizationSession::get(client);
- Status status = authSession->checkAuthForGetMore( nsString, id );
- audit::logGetMoreAuthzCheck( client, nsString, id, status.code() );
- uassertStatusOK(status);
+ // Cursor ids should not overlap between sharded and unsharded cursors
+ massert(17012,
+ str::stream() << "duplicate sharded and unsharded cursor id " << id << " detected for "
+ << ns << ", duplicated on host " << host,
+ NULL == cursorCache.get(id).get() || host.empty());
- if( !host.empty() ){
+ ClientBasic* client = ClientBasic::getCurrent();
+ NamespaceString nsString(ns);
+ AuthorizationSession* authSession = AuthorizationSession::get(client);
+ Status status = authSession->checkAuthForGetMore(nsString, id);
+ audit::logGetMoreAuthzCheck(client, nsString, id, status.code());
+ uassertStatusOK(status);
- LOG(3) << "single getmore: " << ns << endl;
+ if (!host.empty()) {
+ LOG(3) << "single getmore: " << ns << endl;
- // we used ScopedDbConnection because we don't get about config versions
- // not deleting data is handled elsewhere
- // and we don't want to call setShardVersion
- ScopedDbConnection conn(host);
+ // we used ScopedDbConnection because we don't get about config versions
+ // not deleting data is handled elsewhere
+ // and we don't want to call setShardVersion
+ ScopedDbConnection conn(host);
- Message response;
- bool ok = conn->callRead( r.m() , response);
- uassert( 10204 , "dbgrid: getmore: error calling db", ok);
+ Message response;
+ bool ok = conn->callRead(r.m(), response);
+ uassert(10204, "dbgrid: getmore: error calling db", ok);
- bool hasMore = (response.singleData().getCursor() != 0);
+ bool hasMore = (response.singleData().getCursor() != 0);
- if ( !hasMore ) {
- cursorCache.removeRef( id );
- }
+ if (!hasMore) {
+ cursorCache.removeRef(id);
+ }
- r.reply( response , "" /*conn->getServerAddress() */ );
- conn.done();
- return;
+ r.reply(response, "" /*conn->getServerAddress() */);
+ conn.done();
+ return;
+ } else if (cursor) {
+ if (cursorMaxTimeMS == kMaxTimeCursorTimeLimitExpired) {
+ cursorCache.remove(id);
+ uasserted(ErrorCodes::ExceededTimeLimit, "operation exceeded time limit");
}
- else if ( cursor ) {
- if ( cursorMaxTimeMS == kMaxTimeCursorTimeLimitExpired ) {
- cursorCache.remove( id );
- uasserted( ErrorCodes::ExceededTimeLimit, "operation exceeded time limit" );
- }
+ // TODO: Try to match logic of mongod, where on subsequent getMore() we pull lots more data?
+ BufBuilder buffer(ShardedClientCursor::INIT_REPLY_BUFFER_SIZE);
+ int docCount = 0;
+ const int startFrom = cursor->getTotalSent();
+ bool hasMore = cursor->sendNextBatch(ntoreturn, buffer, docCount);
- // TODO: Try to match logic of mongod, where on subsequent getMore() we pull lots more data?
- BufBuilder buffer( ShardedClientCursor::INIT_REPLY_BUFFER_SIZE );
- int docCount = 0;
- const int startFrom = cursor->getTotalSent();
- bool hasMore = cursor->sendNextBatch(ntoreturn, buffer, docCount);
-
- if ( hasMore ) {
- // still more data
- cursor->accessed();
-
- if ( cursorMaxTimeMS != kMaxTimeCursorNoTimeLimit ) {
- // Update remaining amount of time in cursor cache.
- int cursorLeftoverMillis = cursorMaxTimeMS - getMoreTimer.millis();
- if ( cursorLeftoverMillis <= 0 ) {
- cursorLeftoverMillis = kMaxTimeCursorTimeLimitExpired;
- }
- cursorCache.updateMaxTimeMS( id, cursorLeftoverMillis );
+ if (hasMore) {
+ // still more data
+ cursor->accessed();
+
+ if (cursorMaxTimeMS != kMaxTimeCursorNoTimeLimit) {
+ // Update remaining amount of time in cursor cache.
+ int cursorLeftoverMillis = cursorMaxTimeMS - getMoreTimer.millis();
+ if (cursorLeftoverMillis <= 0) {
+ cursorLeftoverMillis = kMaxTimeCursorTimeLimitExpired;
}
+ cursorCache.updateMaxTimeMS(id, cursorLeftoverMillis);
}
- else {
- // we've exhausted the cursor
- cursorCache.remove( id );
- }
-
- replyToQuery( 0, r.p(), r.m(), buffer.buf(), buffer.len(), docCount,
- startFrom, hasMore ? cursor->getId() : 0 );
- return;
+ } else {
+ // we've exhausted the cursor
+ cursorCache.remove(id);
}
- else {
-
- LOG( 3 ) << "could not find cursor " << id << " in cache for " << ns << endl;
- replyToQuery( ResultFlag_CursorNotFound , r.p() , r.m() , 0 , 0 , 0 );
- return;
- }
+ replyToQuery(0,
+ r.p(),
+ r.m(),
+ buffer.buf(),
+ buffer.len(),
+ docCount,
+ startFrom,
+ hasMore ? cursor->getId() : 0);
+ return;
+ } else {
+ LOG(3) << "could not find cursor " << id << " in cache for " << ns << endl;
+
+ replyToQuery(ResultFlag_CursorNotFound, r.p(), r.m(), 0, 0, 0);
+ return;
}
+}
- void Strategy::writeOp( int op , Request& r ) {
-
- // make sure we have a last error
- dassert(&LastError::get(cc()));
-
- OwnedPointerVector<BatchedCommandRequest> requestsOwned;
- vector<BatchedCommandRequest*>& requests = requestsOwned.mutableVector();
+void Strategy::writeOp(int op, Request& r) {
+ // make sure we have a last error
+ dassert(&LastError::get(cc()));
- msgToBatchRequests( r.m(), &requests );
+ OwnedPointerVector<BatchedCommandRequest> requestsOwned;
+ vector<BatchedCommandRequest*>& requests = requestsOwned.mutableVector();
- for ( vector<BatchedCommandRequest*>::iterator it = requests.begin();
- it != requests.end(); ++it ) {
+ msgToBatchRequests(r.m(), &requests);
- // Multiple commands registered to last error as multiple requests
- if ( it != requests.begin() )
- LastError::get(cc()).startRequest();
+ for (vector<BatchedCommandRequest*>::iterator it = requests.begin(); it != requests.end();
+ ++it) {
+ // Multiple commands registered to last error as multiple requests
+ if (it != requests.begin())
+ LastError::get(cc()).startRequest();
- BatchedCommandRequest* request = *it;
+ BatchedCommandRequest* request = *it;
- // Adjust namespaces for command
- NamespaceString fullNS( request->getNS() );
- string cmdNS = fullNS.getCommandNS();
- // We only pass in collection name to command
- request->setNS( fullNS.coll() );
+ // Adjust namespaces for command
+ NamespaceString fullNS(request->getNS());
+ string cmdNS = fullNS.getCommandNS();
+ // We only pass in collection name to command
+ request->setNS(fullNS.coll());
- BSONObjBuilder builder;
- BSONObj requestBSON = request->toBSON();
+ BSONObjBuilder builder;
+ BSONObj requestBSON = request->toBSON();
- {
- // Disable the last error object for the duration of the write cmd
- LastError::Disabled disableLastError(&LastError::get(cc()));
- Command::runAgainstRegistered( cmdNS.c_str(), requestBSON, builder, 0 );
- }
+ {
+ // Disable the last error object for the duration of the write cmd
+ LastError::Disabled disableLastError(&LastError::get(cc()));
+ Command::runAgainstRegistered(cmdNS.c_str(), requestBSON, builder, 0);
+ }
- BatchedCommandResponse response;
- bool parsed = response.parseBSON( builder.done(), NULL );
- (void) parsed; // for compile
- dassert( parsed && response.isValid( NULL ) );
+ BatchedCommandResponse response;
+ bool parsed = response.parseBSON(builder.done(), NULL);
+ (void)parsed; // for compile
+ dassert(parsed && response.isValid(NULL));
- // Populate the lastError object based on the write response
- LastError::get(cc()).reset();
- bool hadError = batchErrorToLastError(*request, response, &LastError::get(cc()));
+ // Populate the lastError object based on the write response
+ LastError::get(cc()).reset();
+ bool hadError = batchErrorToLastError(*request, response, &LastError::get(cc()));
- // Check if this is an ordered batch and we had an error which should stop processing
- if ( request->getOrdered() && hadError )
- break;
- }
+ // Check if this is an ordered batch and we had an error which should stop processing
+ if (request->getOrdered() && hadError)
+ break;
}
-
+}
}
diff --git a/src/mongo/s/strategy.h b/src/mongo/s/strategy.h
index 9d599a3f7a6..eb77b6afe42 100644
--- a/src/mongo/s/strategy.h
+++ b/src/mongo/s/strategy.h
@@ -35,83 +35,78 @@
namespace mongo {
- class BatchItemRef;
+class BatchItemRef;
- /**
- * Legacy interface for processing client read/write/cmd requests.
- */
- class Strategy {
- public:
+/**
+ * Legacy interface for processing client read/write/cmd requests.
+ */
+class Strategy {
+public:
+ static void queryOp(Request& r);
- static void queryOp(Request& r);
+ static void getMore(Request& r);
- static void getMore(Request& r);
+ static void writeOp(int op, Request& r);
- static void writeOp(int op , Request& r);
+ struct CommandResult {
+ ShardId shardTargetId;
+ ConnectionString target;
+ BSONObj result;
+ };
- struct CommandResult {
- ShardId shardTargetId;
- ConnectionString target;
- BSONObj result;
- };
+ /**
+ * Executes a command against a particular database, and targets the command based on a
+ * collection in that database.
+ *
+ * This version should be used by internal commands when possible.
+ *
+ * TODO: Replace these methods and all other methods of command dispatch with a more general
+ * command op framework.
+ */
+ static void commandOp(const std::string& db,
+ const BSONObj& command,
+ int options,
+ const std::string& versionedNS,
+ const BSONObj& targetingQuery,
+ std::vector<CommandResult>* results);
- /**
- * Executes a command against a particular database, and targets the command based on a
- * collection in that database.
- *
- * This version should be used by internal commands when possible.
- *
- * TODO: Replace these methods and all other methods of command dispatch with a more general
- * command op framework.
- */
- static void commandOp(const std::string& db,
- const BSONObj& command,
- int options,
- const std::string& versionedNS,
- const BSONObj& targetingQuery,
- std::vector<CommandResult>* results);
+ /**
+ * Executes a write command against a particular database, and targets the command based on
+ * a write operation.
+ *
+ * Does *not* retry or retarget if the metadata is stale.
+ *
+ * Similar to commandOp() above, but the targeting rules are different for writes than for
+ * reads.
+ */
+ static Status commandOpWrite(const std::string& db,
+ const BSONObj& command,
+ BatchItemRef targetingBatchItem,
+ std::vector<CommandResult>* results);
- /**
- * Executes a write command against a particular database, and targets the command based on
- * a write operation.
- *
- * Does *not* retry or retarget if the metadata is stale.
- *
- * Similar to commandOp() above, but the targeting rules are different for writes than for
- * reads.
- */
- static Status commandOpWrite(const std::string& db,
+ /**
+ * Some commands can only be run in a sharded configuration against a namespace that has
+ * not been sharded. Use this method to execute such commands.
+ *
+ * Does *not* retry or retarget if the metadata is stale.
+ *
+ * On success, fills in 'shardResult' with output from the namespace's primary shard. This
+ * output may itself indicate an error status on the shard.
+ */
+ static Status commandOpUnsharded(const std::string& db,
const BSONObj& command,
- BatchItemRef targetingBatchItem,
- std::vector<CommandResult>* results);
-
- /**
- * Some commands can only be run in a sharded configuration against a namespace that has
- * not been sharded. Use this method to execute such commands.
- *
- * Does *not* retry or retarget if the metadata is stale.
- *
- * On success, fills in 'shardResult' with output from the namespace's primary shard. This
- * output may itself indicate an error status on the shard.
- */
- static Status commandOpUnsharded(const std::string& db,
- const BSONObj& command,
- int options,
- const std::string& versionedNS,
- CommandResult* shardResult);
-
- /**
- * Executes a command represented in the Request on the sharded cluster.
- *
- * DEPRECATED: should not be used by new code.
- */
- static void clientCommandOp( Request& r );
-
- protected:
-
- static bool handleSpecialNamespaces( Request& r , QueryMessage& q );
+ int options,
+ const std::string& versionedNS,
+ CommandResult* shardResult);
- };
+ /**
+ * Executes a command represented in the Request on the sharded cluster.
+ *
+ * DEPRECATED: should not be used by new code.
+ */
+ static void clientCommandOp(Request& r);
+protected:
+ static bool handleSpecialNamespaces(Request& r, QueryMessage& q);
+};
}
-
diff --git a/src/mongo/s/type_config_version.cpp b/src/mongo/s/type_config_version.cpp
index 8f9c47f8d77..78a920904ba 100644
--- a/src/mongo/s/type_config_version.cpp
+++ b/src/mongo/s/type_config_version.cpp
@@ -33,159 +33,164 @@
namespace mongo {
- using std::string;
+using std::string;
- using mongoutils::str::stream;
+using mongoutils::str::stream;
- const std::string VersionType::ConfigNS = "config.version";
+const std::string VersionType::ConfigNS = "config.version";
- const BSONField<int> VersionType::minCompatibleVersion("minCompatibleVersion");
- const BSONField<int> VersionType::currentVersion("currentVersion");
- const BSONField<BSONArray> VersionType::excludingMongoVersions("excluding");
- const BSONField<OID> VersionType::clusterId("clusterId");
- const BSONField<OID> VersionType::upgradeId("upgradeId");
- const BSONField<BSONObj> VersionType::upgradeState("upgradeState");
+const BSONField<int> VersionType::minCompatibleVersion("minCompatibleVersion");
+const BSONField<int> VersionType::currentVersion("currentVersion");
+const BSONField<BSONArray> VersionType::excludingMongoVersions("excluding");
+const BSONField<OID> VersionType::clusterId("clusterId");
+const BSONField<OID> VersionType::upgradeId("upgradeId");
+const BSONField<BSONObj> VersionType::upgradeState("upgradeState");
- VersionType::VersionType() {
- clear();
- }
+VersionType::VersionType() {
+ clear();
+}
- VersionType::~VersionType() {
- }
+VersionType::~VersionType() {}
- bool VersionType::isValid(std::string* errMsg) const {
- std::string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
-
- // All the mandatory fields must be present.
- if (!_isMinCompatibleVersionSet) {
- *errMsg = stream() << "missing " << minCompatibleVersion.name() << " field";
- return false;
- }
- if (!_isCurrentVersionSet) {
- *errMsg = stream() << "missing " << currentVersion.name() << " field";
- return false;
- }
-
- // Hardcoded 3 here because it's the last version without a cluster id
- if (_currentVersion > 3 && !_isClusterIdSet) {
- *errMsg = stream() << "missing " << clusterId.name() << " field";
- return false;
- }
-
- return true;
+bool VersionType::isValid(std::string* errMsg) const {
+ std::string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
}
- BSONObj VersionType::toBSON() const {
- BSONObjBuilder builder;
-
- builder.append("_id", 1);
- if (_isMinCompatibleVersionSet) {
- builder.append(minCompatibleVersion(), _minCompatibleVersion);
- }
- if (_isCurrentVersionSet) builder.append(currentVersion(), _currentVersion);
- if (_isExcludingMongoVersionsSet) {
- builder.append(excludingMongoVersions(), _excludingMongoVersions);
- }
- if (_isClusterIdSet) builder.append(clusterId(), _clusterId);
- if (_isUpgradeIdSet) {
- builder.append(upgradeId(), _upgradeId);
- builder.append(upgradeState(), _upgradeState);
- }
-
- return builder.obj();
+ // All the mandatory fields must be present.
+ if (!_isMinCompatibleVersionSet) {
+ *errMsg = stream() << "missing " << minCompatibleVersion.name() << " field";
+ return false;
+ }
+ if (!_isCurrentVersionSet) {
+ *errMsg = stream() << "missing " << currentVersion.name() << " field";
+ return false;
}
- bool VersionType::parseBSON(const BSONObj& source, string* errMsg) {
- clear();
+ // Hardcoded 3 here because it's the last version without a cluster id
+ if (_currentVersion > 3 && !_isClusterIdSet) {
+ *errMsg = stream() << "missing " << clusterId.name() << " field";
+ return false;
+ }
- std::string dummy;
- if (!errMsg) errMsg = &dummy;
+ return true;
+}
- FieldParser::FieldState fieldState;
- fieldState = FieldParser::extractNumber(source, minCompatibleVersion,
- &_minCompatibleVersion, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isMinCompatibleVersionSet = fieldState == FieldParser::FIELD_SET;
+BSONObj VersionType::toBSON() const {
+ BSONObjBuilder builder;
- fieldState = FieldParser::extractNumber(source, currentVersion, &_currentVersion, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isCurrentVersionSet = fieldState == FieldParser::FIELD_SET;
+ builder.append("_id", 1);
+ if (_isMinCompatibleVersionSet) {
+ builder.append(minCompatibleVersion(), _minCompatibleVersion);
+ }
+ if (_isCurrentVersionSet)
+ builder.append(currentVersion(), _currentVersion);
+ if (_isExcludingMongoVersionsSet) {
+ builder.append(excludingMongoVersions(), _excludingMongoVersions);
+ }
+ if (_isClusterIdSet)
+ builder.append(clusterId(), _clusterId);
+ if (_isUpgradeIdSet) {
+ builder.append(upgradeId(), _upgradeId);
+ builder.append(upgradeState(), _upgradeState);
+ }
- if (!_isCurrentVersionSet && _isMinCompatibleVersionSet) {
- _currentVersion = _minCompatibleVersion;
- _isCurrentVersionSet = true;
- }
+ return builder.obj();
+}
- fieldState = FieldParser::extract(source, excludingMongoVersions,
- &_excludingMongoVersions, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isExcludingMongoVersionsSet = fieldState == FieldParser::FIELD_SET;
+bool VersionType::parseBSON(const BSONObj& source, string* errMsg) {
+ clear();
- fieldState = FieldParser::extract(source, clusterId, &_clusterId, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isClusterIdSet = fieldState == FieldParser::FIELD_SET;
+ std::string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
- fieldState = FieldParser::extract(source, upgradeId, &_upgradeId, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isUpgradeIdSet = fieldState == FieldParser::FIELD_SET;
+ FieldParser::FieldState fieldState;
+ fieldState =
+ FieldParser::extractNumber(source, minCompatibleVersion, &_minCompatibleVersion, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isMinCompatibleVersionSet = fieldState == FieldParser::FIELD_SET;
- fieldState = FieldParser::extract(source, upgradeState, &_upgradeState, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isUpgradeStateSet = fieldState == FieldParser::FIELD_SET;
+ fieldState = FieldParser::extractNumber(source, currentVersion, &_currentVersion, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isCurrentVersionSet = fieldState == FieldParser::FIELD_SET;
- return true;
+ if (!_isCurrentVersionSet && _isMinCompatibleVersionSet) {
+ _currentVersion = _minCompatibleVersion;
+ _isCurrentVersionSet = true;
}
- void VersionType::clear() {
+ fieldState =
+ FieldParser::extract(source, excludingMongoVersions, &_excludingMongoVersions, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isExcludingMongoVersionsSet = fieldState == FieldParser::FIELD_SET;
- _minCompatibleVersion = -1;
- _isMinCompatibleVersionSet = false;
+ fieldState = FieldParser::extract(source, clusterId, &_clusterId, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isClusterIdSet = fieldState == FieldParser::FIELD_SET;
- _currentVersion = -1;
- _isCurrentVersionSet = false;
+ fieldState = FieldParser::extract(source, upgradeId, &_upgradeId, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isUpgradeIdSet = fieldState == FieldParser::FIELD_SET;
- _excludingMongoVersions = BSONArray();
- _isExcludingMongoVersionsSet = false;
+ fieldState = FieldParser::extract(source, upgradeState, &_upgradeState, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isUpgradeStateSet = fieldState == FieldParser::FIELD_SET;
- _clusterId = OID();
- _isClusterIdSet = false;
+ return true;
+}
- _upgradeId = OID();
- _isUpgradeIdSet = false;
+void VersionType::clear() {
+ _minCompatibleVersion = -1;
+ _isMinCompatibleVersionSet = false;
- _upgradeState = BSONObj();
- _isUpgradeStateSet = false;
+ _currentVersion = -1;
+ _isCurrentVersionSet = false;
- }
+ _excludingMongoVersions = BSONArray();
+ _isExcludingMongoVersionsSet = false;
- void VersionType::cloneTo(VersionType* other) const {
- other->clear();
+ _clusterId = OID();
+ _isClusterIdSet = false;
- other->_minCompatibleVersion = _minCompatibleVersion;
- other->_isMinCompatibleVersionSet = _isMinCompatibleVersionSet;
+ _upgradeId = OID();
+ _isUpgradeIdSet = false;
- other->_currentVersion = _currentVersion;
- other->_isCurrentVersionSet = _isCurrentVersionSet;
+ _upgradeState = BSONObj();
+ _isUpgradeStateSet = false;
+}
- other->_excludingMongoVersions = _excludingMongoVersions;
- other->_isExcludingMongoVersionsSet = _isExcludingMongoVersionsSet;
+void VersionType::cloneTo(VersionType* other) const {
+ other->clear();
- other->_clusterId = _clusterId;
- other->_isClusterIdSet = _isClusterIdSet;
+ other->_minCompatibleVersion = _minCompatibleVersion;
+ other->_isMinCompatibleVersionSet = _isMinCompatibleVersionSet;
- other->_upgradeId = _upgradeId;
- other->_isUpgradeIdSet = _isUpgradeIdSet;
+ other->_currentVersion = _currentVersion;
+ other->_isCurrentVersionSet = _isCurrentVersionSet;
- other->_upgradeState = _upgradeState;
- other->_isUpgradeStateSet = _isUpgradeStateSet;
+ other->_excludingMongoVersions = _excludingMongoVersions;
+ other->_isExcludingMongoVersionsSet = _isExcludingMongoVersionsSet;
- }
+ other->_clusterId = _clusterId;
+ other->_isClusterIdSet = _isClusterIdSet;
- std::string VersionType::toString() const {
- return toBSON().toString();
- }
+ other->_upgradeId = _upgradeId;
+ other->_isUpgradeIdSet = _isUpgradeIdSet;
+
+ other->_upgradeState = _upgradeState;
+ other->_isUpgradeStateSet = _isUpgradeStateSet;
+}
+
+std::string VersionType::toString() const {
+ return toBSON().toString();
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/type_config_version.h b/src/mongo/s/type_config_version.h
index 778efab059f..c98bf531234 100644
--- a/src/mongo/s/type_config_version.h
+++ b/src/mongo/s/type_config_version.h
@@ -36,215 +36,233 @@
namespace mongo {
- /**
- * This class represents the layout and contents of documents contained in the
- * config.version collection. All manipulation of documents coming from that
- * collection should be done with this class.
- *
- * Usage Example:
- *
- * // Contact the config. 'conn' has been obtained before.
- * DBClientBase* conn;
- * BSONObj query = QUERY(VersionType::exampleField("exampleFieldName"));
- * exampleDoc = conn->findOne(VersionType::ConfigNS, query);
- *
- * // Process the response.
- * VersionType exampleType;
- * std::string errMsg;
- * if (!exampleType.parseBSON(exampleDoc, &errMsg) || !exampleType.isValid(&errMsg)) {
- * // Can't use 'exampleType'. Take action.
- * }
- * // use 'exampleType'
- *
- */
- class VersionType {
- MONGO_DISALLOW_COPYING(VersionType);
- public:
-
- //
- // schema declarations
- //
-
- // Name of the version collection in the config server.
- static const std::string ConfigNS;
-
- // Field names and types in the version collection type.
- static const BSONField<int> minCompatibleVersion;
- static const BSONField<int> currentVersion;
- static const BSONField<BSONArray> excludingMongoVersions;
- static const BSONField<OID> clusterId;
- static const BSONField<OID> upgradeId;
- static const BSONField<BSONObj> upgradeState;
-
- //
- // version type methods
- //
-
- VersionType();
- ~VersionType();
-
- /**
- * Returns true if all the mandatory fields are present and have valid
- * representations. Otherwise returns false and fills in the optional 'errMsg' string.
- */
- bool isValid(std::string* errMsg) const;
-
- /**
- * Returns the BSON representation of the entry.
- */
- BSONObj toBSON() const;
-
- /**
- * Clears and populates the internal state using the 'source' BSON object if the
- * latter contains valid values. Otherwise sets errMsg and returns false.
- */
- bool parseBSON(const BSONObj& source, std::string* errMsg);
-
- /**
- * Clears the internal state.
- */
- void clear();
-
- /**
- * Copies all the fields present in 'this' to 'other'.
- */
- void cloneTo(VersionType* other) const;
-
- /**
- * Returns a std::string representation of the current internal state.
- */
- std::string toString() const;
-
- //
- // individual field accessors
- //
-
- // Mandatory Fields
- void setMinCompatibleVersion(const int minCompatibleVersion) {
- _minCompatibleVersion = minCompatibleVersion;
- _isMinCompatibleVersionSet = true;
- }
-
- void unsetMinCompatibleVersion() { _isMinCompatibleVersionSet = false; }
-
- bool isMinCompatibleVersionSet() const { return _isMinCompatibleVersionSet; }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- int getMinCompatibleVersion() const {
- dassert(_isMinCompatibleVersionSet);
- return _minCompatibleVersion;
- }
-
- void setCurrentVersion(const int currentVersion) {
- _currentVersion = currentVersion;
- _isCurrentVersionSet = true;
- }
-
- void unsetCurrentVersion() { _isCurrentVersionSet = false; }
-
- bool isCurrentVersionSet() const { return _isCurrentVersionSet; }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- int getCurrentVersion() const {
- dassert(_isCurrentVersionSet);
- return _currentVersion;
- }
-
- void setExcludingMongoVersions(const BSONArray& excludingMongoVersions) {
- _excludingMongoVersions = excludingMongoVersions;
- _isExcludingMongoVersionsSet = true;
- }
+/**
+ * This class represents the layout and contents of documents contained in the
+ * config.version collection. All manipulation of documents coming from that
+ * collection should be done with this class.
+ *
+ * Usage Example:
+ *
+ * // Contact the config. 'conn' has been obtained before.
+ * DBClientBase* conn;
+ * BSONObj query = QUERY(VersionType::exampleField("exampleFieldName"));
+ * exampleDoc = conn->findOne(VersionType::ConfigNS, query);
+ *
+ * // Process the response.
+ * VersionType exampleType;
+ * std::string errMsg;
+ * if (!exampleType.parseBSON(exampleDoc, &errMsg) || !exampleType.isValid(&errMsg)) {
+ * // Can't use 'exampleType'. Take action.
+ * }
+ * // use 'exampleType'
+ *
+ */
+class VersionType {
+ MONGO_DISALLOW_COPYING(VersionType);
- void unsetExcludingMongoVersions() { _isExcludingMongoVersionsSet = false; }
+public:
+ //
+ // schema declarations
+ //
- bool isExcludingMongoVersionsSet() const {
- return _isExcludingMongoVersionsSet || excludingMongoVersions.hasDefault();
- }
+ // Name of the version collection in the config server.
+ static const std::string ConfigNS;
- // Calling get*() methods when the member is not set and has no default results in undefined
- // behavior
- const BSONArray getExcludingMongoVersions() const {
- if (_isExcludingMongoVersionsSet) {
- return _excludingMongoVersions;
- } else {
- dassert(excludingMongoVersions.hasDefault());
- return excludingMongoVersions.getDefault();
- }
- }
+ // Field names and types in the version collection type.
+ static const BSONField<int> minCompatibleVersion;
+ static const BSONField<int> currentVersion;
+ static const BSONField<BSONArray> excludingMongoVersions;
+ static const BSONField<OID> clusterId;
+ static const BSONField<OID> upgradeId;
+ static const BSONField<BSONObj> upgradeState;
- void setClusterId(const OID clusterId) {
- _clusterId = clusterId;
- _isClusterIdSet = true;
- }
+ //
+ // version type methods
+ //
- void unsetClusterId() { _isClusterIdSet = false; }
+ VersionType();
+ ~VersionType();
- bool isClusterIdSet() const { return _isClusterIdSet; }
+ /**
+ * Returns true if all the mandatory fields are present and have valid
+ * representations. Otherwise returns false and fills in the optional 'errMsg' string.
+ */
+ bool isValid(std::string* errMsg) const;
- // Calling get*() methods when the member is not set results in undefined behavior
- const OID getClusterId() const {
- dassert(_isClusterIdSet);
- return _clusterId;
- }
+ /**
+ * Returns the BSON representation of the entry.
+ */
+ BSONObj toBSON() const;
- // Optional Fields
- void setUpgradeId(OID upgradeId) {
- _upgradeId = upgradeId;
- _isUpgradeIdSet = true;
- }
+ /**
+ * Clears and populates the internal state using the 'source' BSON object if the
+ * latter contains valid values. Otherwise sets errMsg and returns false.
+ */
+ bool parseBSON(const BSONObj& source, std::string* errMsg);
- void unsetUpgradeId() { _isUpgradeIdSet = false; }
+ /**
+ * Clears the internal state.
+ */
+ void clear();
- bool isUpgradeIdSet() const {
- return _isUpgradeIdSet || upgradeId.hasDefault();
- }
+ /**
+ * Copies all the fields present in 'this' to 'other'.
+ */
+ void cloneTo(VersionType* other) const;
- // Calling get*() methods when the member is not set and has no default results in undefined
- // behavior
- OID getUpgradeId() const {
- if (_isUpgradeIdSet) {
- return _upgradeId;
- } else {
- dassert(upgradeId.hasDefault());
- return upgradeId.getDefault();
- }
- }
- void setUpgradeState(const BSONObj& upgradeState) {
- _upgradeState = upgradeState.getOwned();
- _isUpgradeStateSet = true;
+ /**
+ * Returns a std::string representation of the current internal state.
+ */
+ std::string toString() const;
+
+ //
+ // individual field accessors
+ //
+
+ // Mandatory Fields
+ void setMinCompatibleVersion(const int minCompatibleVersion) {
+ _minCompatibleVersion = minCompatibleVersion;
+ _isMinCompatibleVersionSet = true;
+ }
+
+ void unsetMinCompatibleVersion() {
+ _isMinCompatibleVersionSet = false;
+ }
+
+ bool isMinCompatibleVersionSet() const {
+ return _isMinCompatibleVersionSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ int getMinCompatibleVersion() const {
+ dassert(_isMinCompatibleVersionSet);
+ return _minCompatibleVersion;
+ }
+
+ void setCurrentVersion(const int currentVersion) {
+ _currentVersion = currentVersion;
+ _isCurrentVersionSet = true;
+ }
+
+ void unsetCurrentVersion() {
+ _isCurrentVersionSet = false;
+ }
+
+ bool isCurrentVersionSet() const {
+ return _isCurrentVersionSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ int getCurrentVersion() const {
+ dassert(_isCurrentVersionSet);
+ return _currentVersion;
+ }
+
+ void setExcludingMongoVersions(const BSONArray& excludingMongoVersions) {
+ _excludingMongoVersions = excludingMongoVersions;
+ _isExcludingMongoVersionsSet = true;
+ }
+
+ void unsetExcludingMongoVersions() {
+ _isExcludingMongoVersionsSet = false;
+ }
+
+ bool isExcludingMongoVersionsSet() const {
+ return _isExcludingMongoVersionsSet || excludingMongoVersions.hasDefault();
+ }
+
+ // Calling get*() methods when the member is not set and has no default results in undefined
+ // behavior
+ const BSONArray getExcludingMongoVersions() const {
+ if (_isExcludingMongoVersionsSet) {
+ return _excludingMongoVersions;
+ } else {
+ dassert(excludingMongoVersions.hasDefault());
+ return excludingMongoVersions.getDefault();
}
-
- void unsetUpgradeState() { _isUpgradeStateSet = false; }
-
- bool isUpgradeStateSet() const {
- return _isUpgradeStateSet || upgradeState.hasDefault();
+ }
+
+ void setClusterId(const OID clusterId) {
+ _clusterId = clusterId;
+ _isClusterIdSet = true;
+ }
+
+ void unsetClusterId() {
+ _isClusterIdSet = false;
+ }
+
+ bool isClusterIdSet() const {
+ return _isClusterIdSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const OID getClusterId() const {
+ dassert(_isClusterIdSet);
+ return _clusterId;
+ }
+
+ // Optional Fields
+ void setUpgradeId(OID upgradeId) {
+ _upgradeId = upgradeId;
+ _isUpgradeIdSet = true;
+ }
+
+ void unsetUpgradeId() {
+ _isUpgradeIdSet = false;
+ }
+
+ bool isUpgradeIdSet() const {
+ return _isUpgradeIdSet || upgradeId.hasDefault();
+ }
+
+ // Calling get*() methods when the member is not set and has no default results in undefined
+ // behavior
+ OID getUpgradeId() const {
+ if (_isUpgradeIdSet) {
+ return _upgradeId;
+ } else {
+ dassert(upgradeId.hasDefault());
+ return upgradeId.getDefault();
}
-
- // Calling get*() methods when the member is not set and has no default results in undefined
- // behavior
- BSONObj getUpgradeState() const {
- if (_isUpgradeStateSet) {
- return _upgradeState;
- } else {
- dassert(upgradeState.hasDefault());
- return upgradeState.getDefault();
- }
+ }
+ void setUpgradeState(const BSONObj& upgradeState) {
+ _upgradeState = upgradeState.getOwned();
+ _isUpgradeStateSet = true;
+ }
+
+ void unsetUpgradeState() {
+ _isUpgradeStateSet = false;
+ }
+
+ bool isUpgradeStateSet() const {
+ return _isUpgradeStateSet || upgradeState.hasDefault();
+ }
+
+ // Calling get*() methods when the member is not set and has no default results in undefined
+ // behavior
+ BSONObj getUpgradeState() const {
+ if (_isUpgradeStateSet) {
+ return _upgradeState;
+ } else {
+ dassert(upgradeState.hasDefault());
+ return upgradeState.getDefault();
}
-
- private:
- // Convention: (M)andatory, (O)ptional, (S)pecial rule.
- int _minCompatibleVersion; // (M) minimum compatible version
- bool _isMinCompatibleVersionSet;
- int _currentVersion; // (M) current version
- bool _isCurrentVersionSet;
- BSONArray _excludingMongoVersions; // (O) range of disallowed versions to upgrade to
- bool _isExcludingMongoVersionsSet;
- OID _clusterId; // (M) clusterId
- bool _isClusterIdSet;
- OID _upgradeId; // (O) upgrade id of current or last upgrade
- bool _isUpgradeIdSet;
- BSONObj _upgradeState; // (O) upgrade state of current or last upgrade
- bool _isUpgradeStateSet;
- };
-
-} // namespace mongo
+ }
+
+private:
+ // Convention: (M)andatory, (O)ptional, (S)pecial rule.
+ int _minCompatibleVersion; // (M) minimum compatible version
+ bool _isMinCompatibleVersionSet;
+ int _currentVersion; // (M) current version
+ bool _isCurrentVersionSet;
+ BSONArray _excludingMongoVersions; // (O) range of disallowed versions to upgrade to
+ bool _isExcludingMongoVersionsSet;
+ OID _clusterId; // (M) clusterId
+ bool _isClusterIdSet;
+ OID _upgradeId; // (O) upgrade id of current or last upgrade
+ bool _isUpgradeIdSet;
+ BSONObj _upgradeState; // (O) upgrade state of current or last upgrade
+ bool _isUpgradeStateSet;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/type_config_version_test.cpp b/src/mongo/s/type_config_version_test.cpp
index 6c4c8ea6a3a..618238402b9 100644
--- a/src/mongo/s/type_config_version_test.cpp
+++ b/src/mongo/s/type_config_version_test.cpp
@@ -37,131 +37,127 @@
namespace {
- using std::string;
- using mongo::VersionType;
- using mongo::BSONObj;
- using mongo::BSONArray;
- using mongo::BSONObjBuilder;
- using mongo::BSONArrayBuilder;
- using mongo::OID;
-
- TEST(Validity, Empty) {
-
- //
- // Tests parsing of empty document
- //
-
- VersionType versionInfo;
- BSONObj emptyObj = BSONObj();
-
- // parses ok
- string errMsg;
- bool result = versionInfo.parseBSON(emptyObj, &errMsg);
- ASSERT_EQUALS(errMsg, "");
- ASSERT(result);
-
- // not valid
- result = versionInfo.isValid(&errMsg);
- ASSERT_NOT_EQUALS(errMsg, "");
- ASSERT(!result);
- }
-
- TEST(Validity, NewVersion) {
-
- //
- // Tests parsing a new-style config version
- //
-
- VersionType versionInfo;
-
- OID clusterId = OID::gen();
-
- BSONObjBuilder bob;
- bob << VersionType::minCompatibleVersion(3);
- bob << VersionType::currentVersion(4);
- bob << VersionType::clusterId(clusterId);
-
- BSONObj versionDoc = bob.obj();
-
- string errMsg;
- bool result = versionInfo.parseBSON(versionDoc, &errMsg);
- ASSERT_EQUALS(errMsg, "");
- ASSERT(result);
- ASSERT_EQUALS(versionInfo.getMinCompatibleVersion(), 3);
- ASSERT_EQUALS(versionInfo.getCurrentVersion(), 4);
- ASSERT_EQUALS(versionInfo.getClusterId(), clusterId);
-
- // Valid
- result = versionInfo.isValid(&errMsg);
- ASSERT_EQUALS(errMsg, "");
- ASSERT(result);
- }
-
- TEST(Validity, NewVersionRoundTrip) {
-
- //
- // Round-trip
- //
-
- VersionType versionInfo;
-
- OID clusterId = OID::gen();
- OID upgradeId = OID::gen();
- BSONObj upgradeState = BSON("a" << 1);
-
- BSONObjBuilder bob;
- bob << VersionType::minCompatibleVersion(3);
- bob << VersionType::currentVersion(4);
- bob << VersionType::clusterId(clusterId);
- bob << VersionType::upgradeId(upgradeId);
- bob << VersionType::upgradeState(upgradeState);
-
- BSONObj versionDoc = bob.obj();
-
- string errMsg;
- bool result = versionInfo.parseBSON(versionDoc, &errMsg);
- ASSERT_EQUALS(errMsg, "");
- ASSERT(result);
-
- result = versionInfo.parseBSON(versionInfo.toBSON(), &errMsg);
- ASSERT_EQUALS(errMsg, "");
- ASSERT(result);
- ASSERT_EQUALS(versionInfo.getMinCompatibleVersion(), 3);
- ASSERT_EQUALS(versionInfo.getCurrentVersion(), 4);
- ASSERT_EQUALS(versionInfo.getClusterId(), clusterId);
- ASSERT_EQUALS(versionInfo.getUpgradeId(), upgradeId);
- ASSERT_EQUALS(versionInfo.getUpgradeState(), upgradeState);
-
- // Valid
- result = versionInfo.isValid(&errMsg);
- ASSERT_EQUALS(errMsg, "");
- ASSERT(result);
- }
-
- TEST(Validity, NewVersionNoClusterId) {
-
- //
- // Tests error on parsing new format with no clusterId
- //
-
- VersionType versionInfo;
-
- BSONObjBuilder bob;
- bob << VersionType::minCompatibleVersion(3);
- bob << VersionType::currentVersion(4);
-
- BSONObj versionDoc = bob.obj();
-
- // Parses ok
- string errMsg;
- bool result = versionInfo.parseBSON(versionDoc, &errMsg);
- ASSERT_EQUALS(errMsg, "");
- ASSERT(result);
-
- // Not valid
- result = versionInfo.isValid(&errMsg);
- ASSERT_NOT_EQUALS(errMsg, "");
- ASSERT(!result);
- }
-
-} // unnamed namespace
+using std::string;
+using mongo::VersionType;
+using mongo::BSONObj;
+using mongo::BSONArray;
+using mongo::BSONObjBuilder;
+using mongo::BSONArrayBuilder;
+using mongo::OID;
+
+TEST(Validity, Empty) {
+ //
+ // Tests parsing of empty document
+ //
+
+ VersionType versionInfo;
+ BSONObj emptyObj = BSONObj();
+
+ // parses ok
+ string errMsg;
+ bool result = versionInfo.parseBSON(emptyObj, &errMsg);
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(result);
+
+ // not valid
+ result = versionInfo.isValid(&errMsg);
+ ASSERT_NOT_EQUALS(errMsg, "");
+ ASSERT(!result);
+}
+
+TEST(Validity, NewVersion) {
+ //
+ // Tests parsing a new-style config version
+ //
+
+ VersionType versionInfo;
+
+ OID clusterId = OID::gen();
+
+ BSONObjBuilder bob;
+ bob << VersionType::minCompatibleVersion(3);
+ bob << VersionType::currentVersion(4);
+ bob << VersionType::clusterId(clusterId);
+
+ BSONObj versionDoc = bob.obj();
+
+ string errMsg;
+ bool result = versionInfo.parseBSON(versionDoc, &errMsg);
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(result);
+ ASSERT_EQUALS(versionInfo.getMinCompatibleVersion(), 3);
+ ASSERT_EQUALS(versionInfo.getCurrentVersion(), 4);
+ ASSERT_EQUALS(versionInfo.getClusterId(), clusterId);
+
+ // Valid
+ result = versionInfo.isValid(&errMsg);
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(result);
+}
+
+TEST(Validity, NewVersionRoundTrip) {
+ //
+ // Round-trip
+ //
+
+ VersionType versionInfo;
+
+ OID clusterId = OID::gen();
+ OID upgradeId = OID::gen();
+ BSONObj upgradeState = BSON("a" << 1);
+
+ BSONObjBuilder bob;
+ bob << VersionType::minCompatibleVersion(3);
+ bob << VersionType::currentVersion(4);
+ bob << VersionType::clusterId(clusterId);
+ bob << VersionType::upgradeId(upgradeId);
+ bob << VersionType::upgradeState(upgradeState);
+
+ BSONObj versionDoc = bob.obj();
+
+ string errMsg;
+ bool result = versionInfo.parseBSON(versionDoc, &errMsg);
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(result);
+
+ result = versionInfo.parseBSON(versionInfo.toBSON(), &errMsg);
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(result);
+ ASSERT_EQUALS(versionInfo.getMinCompatibleVersion(), 3);
+ ASSERT_EQUALS(versionInfo.getCurrentVersion(), 4);
+ ASSERT_EQUALS(versionInfo.getClusterId(), clusterId);
+ ASSERT_EQUALS(versionInfo.getUpgradeId(), upgradeId);
+ ASSERT_EQUALS(versionInfo.getUpgradeState(), upgradeState);
+
+ // Valid
+ result = versionInfo.isValid(&errMsg);
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(result);
+}
+
+TEST(Validity, NewVersionNoClusterId) {
+ //
+ // Tests error on parsing new format with no clusterId
+ //
+
+ VersionType versionInfo;
+
+ BSONObjBuilder bob;
+ bob << VersionType::minCompatibleVersion(3);
+ bob << VersionType::currentVersion(4);
+
+ BSONObj versionDoc = bob.obj();
+
+ // Parses ok
+ string errMsg;
+ bool result = versionInfo.parseBSON(versionDoc, &errMsg);
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(result);
+
+ // Not valid
+ result = versionInfo.isValid(&errMsg);
+ ASSERT_NOT_EQUALS(errMsg, "");
+ ASSERT(!result);
+}
+
+} // unnamed namespace
diff --git a/src/mongo/s/type_lockpings.cpp b/src/mongo/s/type_lockpings.cpp
index 47beaea1485..c24d09a3506 100644
--- a/src/mongo/s/type_lockpings.cpp
+++ b/src/mongo/s/type_lockpings.cpp
@@ -32,91 +32,92 @@
namespace mongo {
- using std::string;
+using std::string;
- using mongoutils::str::stream;
+using mongoutils::str::stream;
- const std::string LockpingsType::ConfigNS = "config.lockpings";
+const std::string LockpingsType::ConfigNS = "config.lockpings";
- const BSONField<std::string> LockpingsType::process("_id");
- const BSONField<Date_t> LockpingsType::ping("ping");
+const BSONField<std::string> LockpingsType::process("_id");
+const BSONField<Date_t> LockpingsType::ping("ping");
- LockpingsType::LockpingsType() {
- clear();
- }
+LockpingsType::LockpingsType() {
+ clear();
+}
- LockpingsType::~LockpingsType() {
- }
+LockpingsType::~LockpingsType() {}
- bool LockpingsType::isValid(std::string* errMsg) const {
- std::string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
-
- // All the mandatory fields must be present.
- if (!_isProcessSet) {
- *errMsg = stream() << "missing " << process.name() << " field";
- return false;
- }
- if (!_isPingSet) {
- *errMsg = stream() << "missing " << ping.name() << " field";
- return false;
- }
-
- return true;
+bool LockpingsType::isValid(std::string* errMsg) const {
+ std::string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
}
- BSONObj LockpingsType::toBSON() const {
- BSONObjBuilder builder;
-
- if (_isProcessSet) builder.append(process(), _process);
- if (_isPingSet) builder.append(ping(), _ping);
-
- return builder.obj();
+ // All the mandatory fields must be present.
+ if (!_isProcessSet) {
+ *errMsg = stream() << "missing " << process.name() << " field";
+ return false;
+ }
+ if (!_isPingSet) {
+ *errMsg = stream() << "missing " << ping.name() << " field";
+ return false;
}
- bool LockpingsType::parseBSON(const BSONObj& source, string* errMsg) {
- clear();
+ return true;
+}
- std::string dummy;
- if (!errMsg) errMsg = &dummy;
+BSONObj LockpingsType::toBSON() const {
+ BSONObjBuilder builder;
- FieldParser::FieldState fieldState;
- fieldState = FieldParser::extract(source, process, &_process, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isProcessSet = fieldState == FieldParser::FIELD_SET;
+ if (_isProcessSet)
+ builder.append(process(), _process);
+ if (_isPingSet)
+ builder.append(ping(), _ping);
- fieldState = FieldParser::extract(source, ping, &_ping, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isPingSet = fieldState == FieldParser::FIELD_SET;
+ return builder.obj();
+}
- return true;
- }
+bool LockpingsType::parseBSON(const BSONObj& source, string* errMsg) {
+ clear();
- void LockpingsType::clear() {
+ std::string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
- _process.clear();
- _isProcessSet = false;
+ FieldParser::FieldState fieldState;
+ fieldState = FieldParser::extract(source, process, &_process, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isProcessSet = fieldState == FieldParser::FIELD_SET;
- _ping = Date_t();
- _isPingSet = false;
+ fieldState = FieldParser::extract(source, ping, &_ping, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isPingSet = fieldState == FieldParser::FIELD_SET;
- }
+ return true;
+}
- void LockpingsType::cloneTo(LockpingsType* other) const {
- other->clear();
+void LockpingsType::clear() {
+ _process.clear();
+ _isProcessSet = false;
- other->_process = _process;
- other->_isProcessSet = _isProcessSet;
+ _ping = Date_t();
+ _isPingSet = false;
+}
- other->_ping = _ping;
- other->_isPingSet = _isPingSet;
+void LockpingsType::cloneTo(LockpingsType* other) const {
+ other->clear();
- }
+ other->_process = _process;
+ other->_isProcessSet = _isProcessSet;
- std::string LockpingsType::toString() const {
- return toBSON().toString();
- }
+ other->_ping = _ping;
+ other->_isPingSet = _isPingSet;
+}
+
+std::string LockpingsType::toString() const {
+ return toBSON().toString();
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/type_lockpings.h b/src/mongo/s/type_lockpings.h
index 8197da6b9ed..845b544d206 100644
--- a/src/mongo/s/type_lockpings.h
+++ b/src/mongo/s/type_lockpings.h
@@ -35,131 +35,130 @@
namespace mongo {
+/**
+ * This class represents the layout and contents of documents contained in the
+ * config.lockpings collection. All manipulation of documents coming from that
+ * collection should be done with this class.
+ *
+ * Usage Example:
+ *
+ * // Contact the config. 'conn' has been obtained before.
+ * DBClientBase* conn;
+ * BSONObj query = QUERY(LockpingsType::exampleField("exampleFieldName"));
+ * exampleDoc = conn->findOne(LockpingsType::ConfigNS, query);
+ *
+ * // Process the response.
+ * LockpingsType exampleType;
+ * std::string errMsg;
+ * if (!exampleType.parseBSON(exampleDoc, &errMsg) || !exampleType.isValid(&errMsg)) {
+ * // Can't use 'exampleType'. Take action.
+ * }
+ * // use 'exampleType'
+ *
+ */
+class LockpingsType {
+public:
+ //
+ // schema declarations
+ //
+
+ // Name of the lockpings collection in the config server.
+ static const std::string ConfigNS;
+
+ // Field names and types in the lockpings collection type.
+ static const BSONField<std::string> process;
+ static const BSONField<Date_t> ping;
+
+ //
+ // lockpings type methods
+ //
+
+ LockpingsType();
+ ~LockpingsType();
+
+ /**
+ * Returns true if all the mandatory fields are present and have valid
+ * representations. Otherwise returns false and fills in the optional 'errMsg' string.
+ */
+ bool isValid(std::string* errMsg) const;
+
+ /**
+ * Returns the BSON representation of the entry.
+ */
+ BSONObj toBSON() const;
+
+ /**
+ * Clears and populates the internal state using the 'source' BSON object if the
+ * latter contains valid values. Otherwise sets errMsg and returns false.
+ */
+ bool parseBSON(const BSONObj& source, std::string* errMsg);
+
+ /**
+ * Clears the internal state.
+ */
+ void clear();
+
+ /**
+ * Copies all the fields present in 'this' to 'other'.
+ */
+ void cloneTo(LockpingsType* other) const;
+
/**
- * This class represents the layout and contents of documents contained in the
- * config.lockpings collection. All manipulation of documents coming from that
- * collection should be done with this class.
- *
- * Usage Example:
- *
- * // Contact the config. 'conn' has been obtained before.
- * DBClientBase* conn;
- * BSONObj query = QUERY(LockpingsType::exampleField("exampleFieldName"));
- * exampleDoc = conn->findOne(LockpingsType::ConfigNS, query);
- *
- * // Process the response.
- * LockpingsType exampleType;
- * std::string errMsg;
- * if (!exampleType.parseBSON(exampleDoc, &errMsg) || !exampleType.isValid(&errMsg)) {
- * // Can't use 'exampleType'. Take action.
- * }
- * // use 'exampleType'
- *
+ * Returns a std::string representation of the current internal state.
*/
- class LockpingsType {
- public:
-
- //
- // schema declarations
- //
-
- // Name of the lockpings collection in the config server.
- static const std::string ConfigNS;
-
- // Field names and types in the lockpings collection type.
- static const BSONField<std::string> process;
- static const BSONField<Date_t> ping;
-
- //
- // lockpings type methods
- //
-
- LockpingsType();
- ~LockpingsType();
-
- /**
- * Returns true if all the mandatory fields are present and have valid
- * representations. Otherwise returns false and fills in the optional 'errMsg' string.
- */
- bool isValid(std::string* errMsg) const;
-
- /**
- * Returns the BSON representation of the entry.
- */
- BSONObj toBSON() const;
-
- /**
- * Clears and populates the internal state using the 'source' BSON object if the
- * latter contains valid values. Otherwise sets errMsg and returns false.
- */
- bool parseBSON(const BSONObj& source, std::string* errMsg);
-
- /**
- * Clears the internal state.
- */
- void clear();
-
- /**
- * Copies all the fields present in 'this' to 'other'.
- */
- void cloneTo(LockpingsType* other) const;
-
- /**
- * Returns a std::string representation of the current internal state.
- */
- std::string toString() const;
-
- //
- // individual field accessors
- //
-
- // Mandatory Fields
- void setProcess(StringData process) {
- _process = process.toString();
- _isProcessSet = true;
- }
-
- void unsetProcess() {
- _isProcessSet = false;
- }
-
- bool isProcessSet() const {
- return _isProcessSet;
- }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- const std::string& getProcess() const {
- dassert(_isProcessSet);
- return _process;
- }
-
- void setPing(const Date_t ping) {
- _ping = ping;
- _isPingSet = true;
- }
-
- void unsetPing() {
- _isPingSet = false;
- }
-
- bool isPingSet() const {
- return _isPingSet;
- }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- const Date_t getPing() const {
- dassert(_isPingSet);
- return _ping;
- }
-
- // Optional Fields
-
- private:
- // Convention: (M)andatory, (O)ptional, (S)pecial rule.
- std::string _process; // (M) std::string describing the process holding the lock
- bool _isProcessSet;
- Date_t _ping; // (M) last time the holding process updated this document
- bool _isPingSet;
- };
-
-} // namespace mongo
+ std::string toString() const;
+
+ //
+ // individual field accessors
+ //
+
+ // Mandatory Fields
+ void setProcess(StringData process) {
+ _process = process.toString();
+ _isProcessSet = true;
+ }
+
+ void unsetProcess() {
+ _isProcessSet = false;
+ }
+
+ bool isProcessSet() const {
+ return _isProcessSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const std::string& getProcess() const {
+ dassert(_isProcessSet);
+ return _process;
+ }
+
+ void setPing(const Date_t ping) {
+ _ping = ping;
+ _isPingSet = true;
+ }
+
+ void unsetPing() {
+ _isPingSet = false;
+ }
+
+ bool isPingSet() const {
+ return _isPingSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const Date_t getPing() const {
+ dassert(_isPingSet);
+ return _ping;
+ }
+
+ // Optional Fields
+
+private:
+ // Convention: (M)andatory, (O)ptional, (S)pecial rule.
+ std::string _process; // (M) std::string describing the process holding the lock
+ bool _isProcessSet;
+ Date_t _ping; // (M) last time the holding process updated this document
+ bool _isPingSet;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/type_lockpings_test.cpp b/src/mongo/s/type_lockpings_test.cpp
index 487a8714e2c..1bf6c29048a 100644
--- a/src/mongo/s/type_lockpings_test.cpp
+++ b/src/mongo/s/type_lockpings_test.cpp
@@ -32,42 +32,42 @@
namespace {
- using std::string;
- using mongo::BSONObj;
- using mongo::LockpingsType;
- using mongo::Date_t;
+using std::string;
+using mongo::BSONObj;
+using mongo::LockpingsType;
+using mongo::Date_t;
- TEST(Validity, MissingFields) {
- LockpingsType lockPing;
- BSONObj objNoProcess = BSON(LockpingsType::ping(Date_t::now()));
- string errMsg;
- ASSERT(lockPing.parseBSON(objNoProcess, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(lockPing.isValid(NULL));
+TEST(Validity, MissingFields) {
+ LockpingsType lockPing;
+ BSONObj objNoProcess = BSON(LockpingsType::ping(Date_t::now()));
+ string errMsg;
+ ASSERT(lockPing.parseBSON(objNoProcess, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(lockPing.isValid(NULL));
- BSONObj objNoPing = BSON(LockpingsType::process("host.local:27017:1352918870:16807"));
- ASSERT(lockPing.parseBSON(objNoPing, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(lockPing.isValid(NULL));
- }
+ BSONObj objNoPing = BSON(LockpingsType::process("host.local:27017:1352918870:16807"));
+ ASSERT(lockPing.parseBSON(objNoPing, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(lockPing.isValid(NULL));
+}
- TEST(Validity, Valid) {
- LockpingsType lockPing;
- BSONObj obj = BSON(LockpingsType::process("host.local:27017:1352918870:16807") <<
- LockpingsType::ping(Date_t::fromMillisSinceEpoch(1)));
- string errMsg;
- ASSERT(lockPing.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_TRUE(lockPing.isValid(NULL));
- ASSERT_EQUALS(lockPing.getProcess(), "host.local:27017:1352918870:16807");
- ASSERT_EQUALS(lockPing.getPing(), Date_t::fromMillisSinceEpoch(1));
- }
+TEST(Validity, Valid) {
+ LockpingsType lockPing;
+ BSONObj obj = BSON(LockpingsType::process("host.local:27017:1352918870:16807")
+ << LockpingsType::ping(Date_t::fromMillisSinceEpoch(1)));
+ string errMsg;
+ ASSERT(lockPing.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_TRUE(lockPing.isValid(NULL));
+ ASSERT_EQUALS(lockPing.getProcess(), "host.local:27017:1352918870:16807");
+ ASSERT_EQUALS(lockPing.getPing(), Date_t::fromMillisSinceEpoch(1));
+}
- TEST(Validity, BadType) {
- LockpingsType lockPing;
- BSONObj obj = BSON(LockpingsType::process() << 0);
- string errMsg;
- ASSERT((!lockPing.parseBSON(obj, &errMsg)) && (errMsg != ""));
- }
+TEST(Validity, BadType) {
+ LockpingsType lockPing;
+ BSONObj obj = BSON(LockpingsType::process() << 0);
+ string errMsg;
+ ASSERT((!lockPing.parseBSON(obj, &errMsg)) && (errMsg != ""));
+}
-} // unnamed namespace
+} // unnamed namespace
diff --git a/src/mongo/s/type_locks.cpp b/src/mongo/s/type_locks.cpp
index 4fd16b636fb..af4ccda0f83 100644
--- a/src/mongo/s/type_locks.cpp
+++ b/src/mongo/s/type_locks.cpp
@@ -32,163 +32,172 @@
namespace mongo {
- using std::string;
+using std::string;
- using mongoutils::str::stream;
+using mongoutils::str::stream;
- const std::string LocksType::ConfigNS = "config.locks";
+const std::string LocksType::ConfigNS = "config.locks";
- const BSONField<std::string> LocksType::name("_id");
- const BSONField<int> LocksType::state("state");
- const BSONField<std::string> LocksType::process("process");
- const BSONField<OID> LocksType::lockID("ts");
- const BSONField<std::string> LocksType::who("who");
- const BSONField<std::string> LocksType::why("why");
- const BSONField<Date_t> LocksType::when("when");
+const BSONField<std::string> LocksType::name("_id");
+const BSONField<int> LocksType::state("state");
+const BSONField<std::string> LocksType::process("process");
+const BSONField<OID> LocksType::lockID("ts");
+const BSONField<std::string> LocksType::who("who");
+const BSONField<std::string> LocksType::why("why");
+const BSONField<Date_t> LocksType::when("when");
- LocksType::LocksType() {
- clear();
+LocksType::LocksType() {
+ clear();
+}
+
+LocksType::~LocksType() {}
+
+bool LocksType::isValid(std::string* errMsg) const {
+ std::string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
}
- LocksType::~LocksType() {
+ // All the mandatory fields must be present.
+ if (!_isNameSet) {
+ *errMsg = stream() << "missing " << name.name() << " field";
+ return false;
+ }
+ if (!_isStateSet) {
+ *errMsg = stream() << "missing " << state.name() << " field";
+ return false;
}
- bool LocksType::isValid(std::string* errMsg) const {
- std::string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
+ // If the lock is locked check the remaining fields
+ if (_state != UNLOCKED) {
+ if (!_isProcessSet) {
+ *errMsg = stream() << "missing " << process.name() << " field";
+ return false;
}
- // All the mandatory fields must be present.
- if (!_isNameSet) {
- *errMsg = stream() << "missing " << name.name() << " field";
+ if (!_isLockIDSet) {
+ *errMsg = stream() << "missing " << lockID.name() << " field";
return false;
}
- if (!_isStateSet) {
- *errMsg = stream() << "missing " << state.name() << " field";
+
+ if (!_isWhoSet) {
+ *errMsg = stream() << "missing " << who.name() << " field";
return false;
}
- // If the lock is locked check the remaining fields
- if (_state != UNLOCKED) {
- if (!_isProcessSet) {
- *errMsg = stream() << "missing " << process.name() << " field";
- return false;
- }
-
- if (!_isLockIDSet) {
- *errMsg = stream() << "missing " << lockID.name() << " field";
- return false;
- }
-
- if (!_isWhoSet) {
- *errMsg = stream() << "missing " << who.name() << " field";
- return false;
- }
-
- if (!_isWhySet) {
- *errMsg = stream() << "missing " << why.name() << " field";
- return false;
- }
+ if (!_isWhySet) {
+ *errMsg = stream() << "missing " << why.name() << " field";
+ return false;
}
-
- return true;
}
- BSONObj LocksType::toBSON() const {
- BSONObjBuilder builder;
+ return true;
+}
- if (_isNameSet) builder.append(name(), _name);
- if (_isStateSet) builder.append(state(), _state);
- if (_isProcessSet) builder.append(process(), _process);
- if (_isLockIDSet) builder.append(lockID(), _lockID);
- if (_isWhoSet) builder.append(who(), _who);
- if (_isWhySet) builder.append(why(), _why);
+BSONObj LocksType::toBSON() const {
+ BSONObjBuilder builder;
- return builder.obj();
- }
-
- bool LocksType::parseBSON(const BSONObj& source, string* errMsg) {
- clear();
+ if (_isNameSet)
+ builder.append(name(), _name);
+ if (_isStateSet)
+ builder.append(state(), _state);
+ if (_isProcessSet)
+ builder.append(process(), _process);
+ if (_isLockIDSet)
+ builder.append(lockID(), _lockID);
+ if (_isWhoSet)
+ builder.append(who(), _who);
+ if (_isWhySet)
+ builder.append(why(), _why);
- std::string dummy;
- if (!errMsg) errMsg = &dummy;
+ return builder.obj();
+}
- FieldParser::FieldState fieldState;
- fieldState = FieldParser::extract(source, name, &_name, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isNameSet = fieldState == FieldParser::FIELD_SET;
+bool LocksType::parseBSON(const BSONObj& source, string* errMsg) {
+ clear();
- fieldState = FieldParser::extract(source, state, &_state, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isStateSet = fieldState == FieldParser::FIELD_SET;
+ std::string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
- fieldState = FieldParser::extract(source, process, &_process, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isProcessSet = fieldState == FieldParser::FIELD_SET;
+ FieldParser::FieldState fieldState;
+ fieldState = FieldParser::extract(source, name, &_name, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isNameSet = fieldState == FieldParser::FIELD_SET;
- fieldState = FieldParser::extract(source, lockID, &_lockID, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isLockIDSet = fieldState == FieldParser::FIELD_SET;
+ fieldState = FieldParser::extract(source, state, &_state, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isStateSet = fieldState == FieldParser::FIELD_SET;
- fieldState = FieldParser::extract(source, who, &_who, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isWhoSet = fieldState == FieldParser::FIELD_SET;
+ fieldState = FieldParser::extract(source, process, &_process, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isProcessSet = fieldState == FieldParser::FIELD_SET;
- fieldState = FieldParser::extract(source, why, &_why, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isWhySet = fieldState == FieldParser::FIELD_SET;
+ fieldState = FieldParser::extract(source, lockID, &_lockID, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isLockIDSet = fieldState == FieldParser::FIELD_SET;
- return true;
- }
+ fieldState = FieldParser::extract(source, who, &_who, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isWhoSet = fieldState == FieldParser::FIELD_SET;
- void LocksType::clear() {
+ fieldState = FieldParser::extract(source, why, &_why, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isWhySet = fieldState == FieldParser::FIELD_SET;
- _name.clear();
- _isNameSet = false;
+ return true;
+}
- _state = UNLOCKED;
- _isStateSet = false;
+void LocksType::clear() {
+ _name.clear();
+ _isNameSet = false;
- _process.clear();
- _isProcessSet = false;
+ _state = UNLOCKED;
+ _isStateSet = false;
- _lockID = OID();
- _isLockIDSet = false;
+ _process.clear();
+ _isProcessSet = false;
- _who.clear();
- _isWhoSet = false;
+ _lockID = OID();
+ _isLockIDSet = false;
- _why.clear();
- _isWhySet = false;
+ _who.clear();
+ _isWhoSet = false;
- }
+ _why.clear();
+ _isWhySet = false;
+}
- void LocksType::cloneTo(LocksType* other) const {
- other->clear();
+void LocksType::cloneTo(LocksType* other) const {
+ other->clear();
- other->_name = _name;
- other->_isNameSet = _isNameSet;
+ other->_name = _name;
+ other->_isNameSet = _isNameSet;
- other->_state = _state;
- other->_isStateSet = _isStateSet;
+ other->_state = _state;
+ other->_isStateSet = _isStateSet;
- other->_process = _process;
- other->_isProcessSet = _isProcessSet;
+ other->_process = _process;
+ other->_isProcessSet = _isProcessSet;
- other->_lockID = _lockID;
- other->_isLockIDSet = _isLockIDSet;
+ other->_lockID = _lockID;
+ other->_isLockIDSet = _isLockIDSet;
- other->_who = _who;
- other->_isWhoSet = _isWhoSet;
+ other->_who = _who;
+ other->_isWhoSet = _isWhoSet;
- other->_why = _why;
- other->_isWhySet = _isWhySet;
+ other->_why = _why;
+ other->_isWhySet = _isWhySet;
+}
- }
-
- std::string LocksType::toString() const {
- return toBSON().toString();
- }
+std::string LocksType::toString() const {
+ return toBSON().toString();
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/type_locks.h b/src/mongo/s/type_locks.h
index 3eb6c3f86dd..e77da7c188c 100644
--- a/src/mongo/s/type_locks.h
+++ b/src/mongo/s/type_locks.h
@@ -35,226 +35,241 @@
namespace mongo {
- /**
- * This class represents the layout and contents of documents contained in the
- * config.locks collection. All manipulation of documents coming from that
- * collection should be done with this class.
- *
- * Usage Example:
- *
- * // Contact the config. 'conn' has been obtained before.
- * DBClientBase* conn;
- * BSONObj query = QUERY(LocksType::exampleField("exampleFieldName"));
- * exampleDoc = conn->findOne(LocksType::ConfigNS, query);
- *
- * // Process the response.
- * LocksType exampleType;
- * std::string errMsg;
- * if (!exampleType.parseBSON(exampleDoc, &errMsg) || !exampleType.isValid(&errMsg)) {
- * // Can't use 'exampleType'. Take action.
- * }
- * // use 'exampleType'
- *
- */
- class LocksType {
- public:
-
- enum State {
- UNLOCKED = 0,
- LOCK_PREP, // Only for legacy 3 config servers.
- LOCKED,
- };
-
- //
- // schema declarations
- //
-
- // Name of the locks collection in the config server.
- static const std::string ConfigNS;
-
- // Field names and types in the locks collection type.
- static const BSONField<std::string> name;
- static const BSONField<int> state;
- static const BSONField<std::string> process;
- static const BSONField<OID> lockID;
- static const BSONField<std::string> who;
- static const BSONField<std::string> why;
- static const BSONField<Date_t> when;
-
- //
- // locks type methods
- //
-
- LocksType();
- ~LocksType();
-
- /**
- * Returns true if all the mandatory fields are present and have valid
- * representations. Otherwise returns false and fills in the optional 'errMsg' string.
- */
- bool isValid(std::string* errMsg) const;
-
- /**
- * Returns the BSON representation of the entry.
- */
- BSONObj toBSON() const;
-
- /**
- * Clears and populates the internal state using the 'source' BSON object if the
- * latter contains valid values. Otherwise sets errMsg and returns false.
- */
- bool parseBSON(const BSONObj& source, std::string* errMsg);
-
- /**
- * Clears the internal state.
- */
- void clear();
-
- /**
- * Copies all the fields present in 'this' to 'other'.
- */
- void cloneTo(LocksType* other) const;
-
- /**
- * Returns a std::string representation of the current internal state.
- */
- std::string toString() const;
-
- //
- // individual field accessors
- //
-
- // Mandatory Fields
- void setName(StringData name) {
- _name = name.toString();
- _isNameSet = true;
- }
-
- void unsetName() { _isNameSet = false; }
-
- bool isNameSet() const { return _isNameSet; }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- const std::string getName() const {
- dassert(_isNameSet);
- return _name;
- }
-
- void setState(const int state) {
- _state = state;
- _isStateSet = true;
- }
-
- void unsetState() { _isStateSet = false; }
-
- bool isStateSet() const { return _isStateSet; }
+/**
+ * This class represents the layout and contents of documents contained in the
+ * config.locks collection. All manipulation of documents coming from that
+ * collection should be done with this class.
+ *
+ * Usage Example:
+ *
+ * // Contact the config. 'conn' has been obtained before.
+ * DBClientBase* conn;
+ * BSONObj query = QUERY(LocksType::exampleField("exampleFieldName"));
+ * exampleDoc = conn->findOne(LocksType::ConfigNS, query);
+ *
+ * // Process the response.
+ * LocksType exampleType;
+ * std::string errMsg;
+ * if (!exampleType.parseBSON(exampleDoc, &errMsg) || !exampleType.isValid(&errMsg)) {
+ * // Can't use 'exampleType'. Take action.
+ * }
+ * // use 'exampleType'
+ *
+ */
+class LocksType {
+public:
+ enum State {
+ UNLOCKED = 0,
+ LOCK_PREP, // Only for legacy 3 config servers.
+ LOCKED,
+ };
- // Calling get*() methods when the member is not set results in undefined behavior
- int getState() const {
- dassert(_isStateSet);
- return _state;
- }
+ //
+ // schema declarations
+ //
- // Optional Fields
- void setProcess(StringData process) {
- _process = process.toString();
- _isProcessSet = true;
- }
+ // Name of the locks collection in the config server.
+ static const std::string ConfigNS;
- void unsetProcess() { _isProcessSet = false; }
+ // Field names and types in the locks collection type.
+ static const BSONField<std::string> name;
+ static const BSONField<int> state;
+ static const BSONField<std::string> process;
+ static const BSONField<OID> lockID;
+ static const BSONField<std::string> who;
+ static const BSONField<std::string> why;
+ static const BSONField<Date_t> when;
- bool isProcessSet() const {
- return _isProcessSet || process.hasDefault();
- }
+ //
+ // locks type methods
+ //
- // Calling get*() methods when the member is not set and has no default results in undefined
- // behavior
- std::string getProcess() const {
- if (_isProcessSet) {
- return _process;
- } else {
- dassert(process.hasDefault());
- return process.getDefault();
- }
- }
- void setLockID(OID lockID) {
- _lockID = lockID;
- _isLockIDSet = true;
- }
+ LocksType();
+ ~LocksType();
- void unsetLockID() { _isLockIDSet = false; }
+ /**
+ * Returns true if all the mandatory fields are present and have valid
+ * representations. Otherwise returns false and fills in the optional 'errMsg' string.
+ */
+ bool isValid(std::string* errMsg) const;
- bool isLockIDSet() const {
- return _isLockIDSet || lockID.hasDefault();
- }
+ /**
+ * Returns the BSON representation of the entry.
+ */
+ BSONObj toBSON() const;
- // Calling get*() methods when the member is not set and has no default results in undefined
- // behavior
- OID getLockID() const {
- if (_isLockIDSet) {
- return _lockID;
- } else {
- dassert(lockID.hasDefault());
- return lockID.getDefault();
- }
- }
- void setWho(StringData who) {
- _who = who.toString();
- _isWhoSet = true;
- }
+ /**
+ * Clears and populates the internal state using the 'source' BSON object if the
+ * latter contains valid values. Otherwise sets errMsg and returns false.
+ */
+ bool parseBSON(const BSONObj& source, std::string* errMsg);
- void unsetWho() { _isWhoSet = false; }
+ /**
+ * Clears the internal state.
+ */
+ void clear();
- bool isWhoSet() const {
- return _isWhoSet || who.hasDefault();
- }
+ /**
+ * Copies all the fields present in 'this' to 'other'.
+ */
+ void cloneTo(LocksType* other) const;
- // Calling get*() methods when the member is not set and has no default results in undefined
- // behavior
- std::string getWho() const {
- if (_isWhoSet) {
- return _who;
- } else {
- dassert(who.hasDefault());
- return who.getDefault();
- }
+ /**
+ * Returns a std::string representation of the current internal state.
+ */
+ std::string toString() const;
+
+ //
+ // individual field accessors
+ //
+
+ // Mandatory Fields
+ void setName(StringData name) {
+ _name = name.toString();
+ _isNameSet = true;
+ }
+
+ void unsetName() {
+ _isNameSet = false;
+ }
+
+ bool isNameSet() const {
+ return _isNameSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const std::string getName() const {
+ dassert(_isNameSet);
+ return _name;
+ }
+
+ void setState(const int state) {
+ _state = state;
+ _isStateSet = true;
+ }
+
+ void unsetState() {
+ _isStateSet = false;
+ }
+
+ bool isStateSet() const {
+ return _isStateSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ int getState() const {
+ dassert(_isStateSet);
+ return _state;
+ }
+
+ // Optional Fields
+ void setProcess(StringData process) {
+ _process = process.toString();
+ _isProcessSet = true;
+ }
+
+ void unsetProcess() {
+ _isProcessSet = false;
+ }
+
+ bool isProcessSet() const {
+ return _isProcessSet || process.hasDefault();
+ }
+
+ // Calling get*() methods when the member is not set and has no default results in undefined
+ // behavior
+ std::string getProcess() const {
+ if (_isProcessSet) {
+ return _process;
+ } else {
+ dassert(process.hasDefault());
+ return process.getDefault();
}
- void setWhy(StringData why) {
- _why = why.toString();
- _isWhySet = true;
+ }
+ void setLockID(OID lockID) {
+ _lockID = lockID;
+ _isLockIDSet = true;
+ }
+
+ void unsetLockID() {
+ _isLockIDSet = false;
+ }
+
+ bool isLockIDSet() const {
+ return _isLockIDSet || lockID.hasDefault();
+ }
+
+ // Calling get*() methods when the member is not set and has no default results in undefined
+ // behavior
+ OID getLockID() const {
+ if (_isLockIDSet) {
+ return _lockID;
+ } else {
+ dassert(lockID.hasDefault());
+ return lockID.getDefault();
}
-
- void unsetWhy() { _isWhySet = false; }
-
- bool isWhySet() const {
- return _isWhySet || why.hasDefault();
+ }
+ void setWho(StringData who) {
+ _who = who.toString();
+ _isWhoSet = true;
+ }
+
+ void unsetWho() {
+ _isWhoSet = false;
+ }
+
+ bool isWhoSet() const {
+ return _isWhoSet || who.hasDefault();
+ }
+
+ // Calling get*() methods when the member is not set and has no default results in undefined
+ // behavior
+ std::string getWho() const {
+ if (_isWhoSet) {
+ return _who;
+ } else {
+ dassert(who.hasDefault());
+ return who.getDefault();
}
-
- // Calling get*() methods when the member is not set and has no default results in undefined
- // behavior
- std::string getWhy() const {
- if (_isWhySet) {
- return _why;
- } else {
- dassert(why.hasDefault());
- return why.getDefault();
- }
+ }
+ void setWhy(StringData why) {
+ _why = why.toString();
+ _isWhySet = true;
+ }
+
+ void unsetWhy() {
+ _isWhySet = false;
+ }
+
+ bool isWhySet() const {
+ return _isWhySet || why.hasDefault();
+ }
+
+ // Calling get*() methods when the member is not set and has no default results in undefined
+ // behavior
+ std::string getWhy() const {
+ if (_isWhySet) {
+ return _why;
+ } else {
+ dassert(why.hasDefault());
+ return why.getDefault();
}
-
- private:
- // Convention: (M)andatory, (O)ptional, (S)pecial rule.
- std::string _name; // (M) name of the lock
- bool _isNameSet;
- int _state; // (M) 0: Unlocked | 1: Locks in contention | 2: Lock held
- bool _isStateSet;
- std::string _process; // (O) optional if unlocked. contains the (unique) identifier
- bool _isProcessSet;
- OID _lockID; // (O) optional if unlocked. a unique identifier for the instance
- bool _isLockIDSet;
- std::string _who; // (O) optional if unlocked. a note about why the lock is held,
- bool _isWhoSet;
- std::string _why; // (O) optional if unlocked. a human readable description of the
- bool _isWhySet;
- };
-
-} // namespace mongo
+ }
+
+private:
+ // Convention: (M)andatory, (O)ptional, (S)pecial rule.
+ std::string _name; // (M) name of the lock
+ bool _isNameSet;
+ int _state; // (M) 0: Unlocked | 1: Locks in contention | 2: Lock held
+ bool _isStateSet;
+ std::string _process; // (O) optional if unlocked. contains the (unique) identifier
+ bool _isProcessSet;
+ OID _lockID; // (O) optional if unlocked. a unique identifier for the instance
+ bool _isLockIDSet;
+ std::string _who; // (O) optional if unlocked. a note about why the lock is held,
+ bool _isWhoSet;
+ std::string _why; // (O) optional if unlocked. a human readable description of the
+ bool _isWhySet;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/type_locks_test.cpp b/src/mongo/s/type_locks_test.cpp
index 602a5f672e1..024be534dc8 100644
--- a/src/mongo/s/type_locks_test.cpp
+++ b/src/mongo/s/type_locks_test.cpp
@@ -32,197 +32,187 @@
namespace {
- using std::string;
- using mongo::BSONObj;
- using mongo::LocksType;
- using mongo::OID;
-
- TEST(Validity, Empty) {
- LocksType lock;
- BSONObj emptyObj = BSONObj();
- string errMsg;
- ASSERT(lock.parseBSON(emptyObj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(lock.isValid(NULL));
- }
-
- TEST(Validity, UnlockedWithOptional) {
- LocksType lock;
- OID testLockID = OID::gen();
- BSONObj obj = BSON(LocksType::name("balancer") <<
- LocksType::process("host.local:27017:1352918870:16807") <<
- LocksType::state(0) <<
- LocksType::lockID(testLockID) <<
- LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249") <<
- LocksType::why("doing balance round"));
- string errMsg;
- ASSERT(lock.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_TRUE(lock.isValid(NULL));
- ASSERT_EQUALS(lock.getName(), "balancer");
- ASSERT_EQUALS(lock.getProcess(), "host.local:27017:1352918870:16807");
- ASSERT_EQUALS(lock.getState(), 0);
- ASSERT_EQUALS(lock.getLockID(), testLockID);
- ASSERT_EQUALS(lock.getWho(), "host.local:27017:1352918870:16807:Balancer:282475249");
- ASSERT_EQUALS(lock.getWhy(), "doing balance round");
- }
-
- TEST(Validity, UnlockedWithoutOptional) {
- LocksType lock;
- BSONObj obj = BSON(LocksType::name("balancer") <<
- LocksType::state(0));
- string errMsg;
- ASSERT(lock.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_TRUE(lock.isValid(NULL));
- ASSERT_EQUALS(lock.getName(), "balancer");
- ASSERT_EQUALS(lock.getState(), 0);
- }
-
- TEST(Validity, LockedValid) {
- LocksType lock;
- OID testLockID = OID::gen();
- BSONObj obj = BSON(LocksType::name("balancer") <<
- LocksType::process("host.local:27017:1352918870:16807") <<
- LocksType::state(2) <<
- LocksType::lockID(testLockID) <<
- LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249") <<
- LocksType::why("doing balance round"));
- string errMsg;
- ASSERT(lock.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_TRUE(lock.isValid(NULL));
- ASSERT_EQUALS(lock.getName(), "balancer");
- ASSERT_EQUALS(lock.getProcess(), "host.local:27017:1352918870:16807");
- ASSERT_EQUALS(lock.getState(), 2);
- ASSERT_EQUALS(lock.getLockID(), testLockID);
- ASSERT_EQUALS(lock.getWho(), "host.local:27017:1352918870:16807:Balancer:282475249");
- ASSERT_EQUALS(lock.getWhy(), "doing balance round");
- }
-
- TEST(Validity, LockedMissingProcess) {
- LocksType lock;
- BSONObj obj = BSON(LocksType::name("balancer") <<
- LocksType::state(2) <<
- LocksType::lockID(OID::gen()) <<
- LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249") <<
- LocksType::why("doing balance round"));
- string errMsg;
- ASSERT(lock.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(lock.isValid(NULL));
- }
-
- TEST(Validity, LockedMissingLockID) {
- LocksType lock;
- BSONObj obj = BSON(LocksType::name("balancer") <<
- LocksType::process("host.local:27017:1352918870:16807") <<
- LocksType::state(2) <<
- LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249") <<
- LocksType::why("doing balance round"));
- string errMsg;
- ASSERT(lock.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(lock.isValid(NULL));
- }
-
- TEST(Validity, LockedMissingWho) {
- LocksType lock;
- BSONObj obj = BSON(LocksType::name("balancer") <<
- LocksType::process("host.local:27017:1352918870:16807") <<
- LocksType::state(2) <<
- LocksType::lockID(OID::gen()) <<
- LocksType::why("doing balance round"));
- string errMsg;
- ASSERT(lock.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(lock.isValid(NULL));
- }
-
- TEST(Validity, LockedMissingWhy) {
- LocksType lock;
- BSONObj obj = BSON(LocksType::name("balancer") <<
- LocksType::process("host.local:27017:1352918870:16807") <<
- LocksType::state(2) <<
- LocksType::lockID(OID::gen()) <<
- LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249"));
- string errMsg;
- ASSERT(lock.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(lock.isValid(NULL));
- }
-
- TEST(Validity, ContestedValid) {
- LocksType lock;
- BSONObj obj = BSON(LocksType::name("balancer") <<
- LocksType::process("host.local:27017:1352918870:16807") <<
- LocksType::state(1) <<
- LocksType::lockID(OID::gen()) <<
- LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249") <<
- LocksType::why("doing balance round"));
- string errMsg;
- ASSERT(lock.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_TRUE(lock.isValid(NULL));
- }
-
- TEST(Validity, ContestedMissingProcess) {
- LocksType lock;
- BSONObj obj = BSON(LocksType::name("balancer") <<
- LocksType::state(1) <<
- LocksType::lockID(OID::gen()) <<
- LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249") <<
- LocksType::why("doing balance round"));
- string errMsg;
- ASSERT(lock.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(lock.isValid(NULL));
- }
-
- TEST(Validity, ContestedMissingLockID) {
- LocksType lock;
- BSONObj obj = BSON(LocksType::name("balancer") <<
- LocksType::process("host.local:27017:1352918870:16807") <<
- LocksType::state(1) <<
- LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249") <<
- LocksType::why("doing balance round"));
- string errMsg;
- ASSERT(lock.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(lock.isValid(NULL));
- }
-
- TEST(Validity, ContestedMissingWho) {
- LocksType lock;
- BSONObj obj = BSON(LocksType::name("balancer") <<
- LocksType::process("host.local:27017:1352918870:16807") <<
- LocksType::state(1) <<
- LocksType::lockID(OID::gen()) <<
- LocksType::why("doing balance round"));
- string errMsg;
- ASSERT(lock.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(lock.isValid(NULL));
- }
-
- TEST(Validity, ContestedMissingWhy) {
- LocksType lock;
- BSONObj obj = BSON(LocksType::name("balancer") <<
- LocksType::process("host.local:27017:1352918870:16807") <<
- LocksType::state(1) <<
- LocksType::lockID(OID::gen()) <<
- LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249"));
- string errMsg;
- ASSERT(lock.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(lock.isValid(NULL));
- }
-
- TEST(Validity, BadType) {
- LocksType lock;
- BSONObj obj = BSON(LocksType::name() << 0);
- string errMsg;
- ASSERT((!lock.parseBSON(obj, &errMsg)) && (errMsg != ""));
- }
-
-} // unnamed namespace
+using std::string;
+using mongo::BSONObj;
+using mongo::LocksType;
+using mongo::OID;
+
+TEST(Validity, Empty) {
+ LocksType lock;
+ BSONObj emptyObj = BSONObj();
+ string errMsg;
+ ASSERT(lock.parseBSON(emptyObj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(lock.isValid(NULL));
+}
+
+TEST(Validity, UnlockedWithOptional) {
+ LocksType lock;
+ OID testLockID = OID::gen();
+ BSONObj obj = BSON(LocksType::name("balancer")
+ << LocksType::process("host.local:27017:1352918870:16807")
+ << LocksType::state(0) << LocksType::lockID(testLockID)
+ << LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249")
+ << LocksType::why("doing balance round"));
+ string errMsg;
+ ASSERT(lock.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_TRUE(lock.isValid(NULL));
+ ASSERT_EQUALS(lock.getName(), "balancer");
+ ASSERT_EQUALS(lock.getProcess(), "host.local:27017:1352918870:16807");
+ ASSERT_EQUALS(lock.getState(), 0);
+ ASSERT_EQUALS(lock.getLockID(), testLockID);
+ ASSERT_EQUALS(lock.getWho(), "host.local:27017:1352918870:16807:Balancer:282475249");
+ ASSERT_EQUALS(lock.getWhy(), "doing balance round");
+}
+
+TEST(Validity, UnlockedWithoutOptional) {
+ LocksType lock;
+ BSONObj obj = BSON(LocksType::name("balancer") << LocksType::state(0));
+ string errMsg;
+ ASSERT(lock.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_TRUE(lock.isValid(NULL));
+ ASSERT_EQUALS(lock.getName(), "balancer");
+ ASSERT_EQUALS(lock.getState(), 0);
+}
+
+TEST(Validity, LockedValid) {
+ LocksType lock;
+ OID testLockID = OID::gen();
+ BSONObj obj = BSON(LocksType::name("balancer")
+ << LocksType::process("host.local:27017:1352918870:16807")
+ << LocksType::state(2) << LocksType::lockID(testLockID)
+ << LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249")
+ << LocksType::why("doing balance round"));
+ string errMsg;
+ ASSERT(lock.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_TRUE(lock.isValid(NULL));
+ ASSERT_EQUALS(lock.getName(), "balancer");
+ ASSERT_EQUALS(lock.getProcess(), "host.local:27017:1352918870:16807");
+ ASSERT_EQUALS(lock.getState(), 2);
+ ASSERT_EQUALS(lock.getLockID(), testLockID);
+ ASSERT_EQUALS(lock.getWho(), "host.local:27017:1352918870:16807:Balancer:282475249");
+ ASSERT_EQUALS(lock.getWhy(), "doing balance round");
+}
+
+TEST(Validity, LockedMissingProcess) {
+ LocksType lock;
+ BSONObj obj = BSON(LocksType::name("balancer")
+ << LocksType::state(2) << LocksType::lockID(OID::gen())
+ << LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249")
+ << LocksType::why("doing balance round"));
+ string errMsg;
+ ASSERT(lock.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(lock.isValid(NULL));
+}
+
+TEST(Validity, LockedMissingLockID) {
+ LocksType lock;
+ BSONObj obj =
+ BSON(LocksType::name("balancer")
+ << LocksType::process("host.local:27017:1352918870:16807") << LocksType::state(2)
+ << LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249")
+ << LocksType::why("doing balance round"));
+ string errMsg;
+ ASSERT(lock.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(lock.isValid(NULL));
+}
+
+TEST(Validity, LockedMissingWho) {
+ LocksType lock;
+ BSONObj obj =
+ BSON(LocksType::name("balancer") << LocksType::process("host.local:27017:1352918870:16807")
+ << LocksType::state(2) << LocksType::lockID(OID::gen())
+ << LocksType::why("doing balance round"));
+ string errMsg;
+ ASSERT(lock.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(lock.isValid(NULL));
+}
+
+TEST(Validity, LockedMissingWhy) {
+ LocksType lock;
+ BSONObj obj = BSON(LocksType::name("balancer")
+ << LocksType::process("host.local:27017:1352918870:16807")
+ << LocksType::state(2) << LocksType::lockID(OID::gen())
+ << LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249"));
+ string errMsg;
+ ASSERT(lock.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(lock.isValid(NULL));
+}
+
+TEST(Validity, ContestedValid) {
+ LocksType lock;
+ BSONObj obj = BSON(LocksType::name("balancer")
+ << LocksType::process("host.local:27017:1352918870:16807")
+ << LocksType::state(1) << LocksType::lockID(OID::gen())
+ << LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249")
+ << LocksType::why("doing balance round"));
+ string errMsg;
+ ASSERT(lock.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_TRUE(lock.isValid(NULL));
+}
+
+TEST(Validity, ContestedMissingProcess) {
+ LocksType lock;
+ BSONObj obj = BSON(LocksType::name("balancer")
+ << LocksType::state(1) << LocksType::lockID(OID::gen())
+ << LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249")
+ << LocksType::why("doing balance round"));
+ string errMsg;
+ ASSERT(lock.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(lock.isValid(NULL));
+}
+
+TEST(Validity, ContestedMissingLockID) {
+ LocksType lock;
+ BSONObj obj =
+ BSON(LocksType::name("balancer")
+ << LocksType::process("host.local:27017:1352918870:16807") << LocksType::state(1)
+ << LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249")
+ << LocksType::why("doing balance round"));
+ string errMsg;
+ ASSERT(lock.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(lock.isValid(NULL));
+}
+
+TEST(Validity, ContestedMissingWho) {
+ LocksType lock;
+ BSONObj obj =
+ BSON(LocksType::name("balancer") << LocksType::process("host.local:27017:1352918870:16807")
+ << LocksType::state(1) << LocksType::lockID(OID::gen())
+ << LocksType::why("doing balance round"));
+ string errMsg;
+ ASSERT(lock.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(lock.isValid(NULL));
+}
+
+TEST(Validity, ContestedMissingWhy) {
+ LocksType lock;
+ BSONObj obj = BSON(LocksType::name("balancer")
+ << LocksType::process("host.local:27017:1352918870:16807")
+ << LocksType::state(1) << LocksType::lockID(OID::gen())
+ << LocksType::who("host.local:27017:1352918870:16807:Balancer:282475249"));
+ string errMsg;
+ ASSERT(lock.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(lock.isValid(NULL));
+}
+
+TEST(Validity, BadType) {
+ LocksType lock;
+ BSONObj obj = BSON(LocksType::name() << 0);
+ string errMsg;
+ ASSERT((!lock.parseBSON(obj, &errMsg)) && (errMsg != ""));
+}
+
+} // unnamed namespace
diff --git a/src/mongo/s/type_mongos.cpp b/src/mongo/s/type_mongos.cpp
index 07009c6a864..34e9f94efa5 100644
--- a/src/mongo/s/type_mongos.cpp
+++ b/src/mongo/s/type_mongos.cpp
@@ -32,147 +32,156 @@
namespace mongo {
- using std::string;
+using std::string;
- using mongoutils::str::stream;
+using mongoutils::str::stream;
- const std::string MongosType::ConfigNS = "config.mongos";
+const std::string MongosType::ConfigNS = "config.mongos";
- const BSONField<std::string> MongosType::name("_id");
- const BSONField<Date_t> MongosType::ping("ping");
- const BSONField<int> MongosType::up("up");
- const BSONField<bool> MongosType::waiting("waiting");
- const BSONField<std::string> MongosType::mongoVersion("mongoVersion");
- const BSONField<int> MongosType::configVersion("configVersion");
+const BSONField<std::string> MongosType::name("_id");
+const BSONField<Date_t> MongosType::ping("ping");
+const BSONField<int> MongosType::up("up");
+const BSONField<bool> MongosType::waiting("waiting");
+const BSONField<std::string> MongosType::mongoVersion("mongoVersion");
+const BSONField<int> MongosType::configVersion("configVersion");
- MongosType::MongosType() {
- clear();
- }
+MongosType::MongosType() {
+ clear();
+}
- MongosType::~MongosType() {
- }
+MongosType::~MongosType() {}
- bool MongosType::isValid(std::string* errMsg) const {
- std::string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
-
- // All the mandatory fields must be present.
- if (!_isNameSet) {
- *errMsg = stream() << "missing " << name.name() << " field";
- return false;
- }
- if (!_isPingSet) {
- *errMsg = stream() << "missing " << ping.name() << " field";
- return false;
- }
- if (!_isUpSet) {
- *errMsg = stream() << "missing " << up.name() << " field";
- return false;
- }
- if (!_isWaitingSet) {
- *errMsg = stream() << "missing " << waiting.name() << " field";
- return false;
- }
-
- return true;
+bool MongosType::isValid(std::string* errMsg) const {
+ std::string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
}
- BSONObj MongosType::toBSON() const {
- BSONObjBuilder builder;
-
- if (_isNameSet) builder.append(name(), _name);
- if (_isPingSet) builder.append(ping(), _ping);
- if (_isUpSet) builder.append(up(), _up);
- if (_isWaitingSet) builder.append(waiting(), _waiting);
- if (_isMongoVersionSet) builder.append(mongoVersion(), _mongoVersion);
- if (_isConfigVersionSet) builder.append(configVersion(), _configVersion);
-
- return builder.obj();
+ // All the mandatory fields must be present.
+ if (!_isNameSet) {
+ *errMsg = stream() << "missing " << name.name() << " field";
+ return false;
+ }
+ if (!_isPingSet) {
+ *errMsg = stream() << "missing " << ping.name() << " field";
+ return false;
+ }
+ if (!_isUpSet) {
+ *errMsg = stream() << "missing " << up.name() << " field";
+ return false;
+ }
+ if (!_isWaitingSet) {
+ *errMsg = stream() << "missing " << waiting.name() << " field";
+ return false;
}
- bool MongosType::parseBSON(const BSONObj& source, string* errMsg) {
- clear();
+ return true;
+}
- std::string dummy;
- if (!errMsg) errMsg = &dummy;
+BSONObj MongosType::toBSON() const {
+ BSONObjBuilder builder;
- FieldParser::FieldState fieldState;
- fieldState = FieldParser::extract(source, name, &_name, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isNameSet = fieldState == FieldParser::FIELD_SET;
+ if (_isNameSet)
+ builder.append(name(), _name);
+ if (_isPingSet)
+ builder.append(ping(), _ping);
+ if (_isUpSet)
+ builder.append(up(), _up);
+ if (_isWaitingSet)
+ builder.append(waiting(), _waiting);
+ if (_isMongoVersionSet)
+ builder.append(mongoVersion(), _mongoVersion);
+ if (_isConfigVersionSet)
+ builder.append(configVersion(), _configVersion);
- fieldState = FieldParser::extract(source, ping, &_ping, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isPingSet = fieldState == FieldParser::FIELD_SET;
+ return builder.obj();
+}
- fieldState = FieldParser::extract(source, up, &_up, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isUpSet = fieldState == FieldParser::FIELD_SET;
+bool MongosType::parseBSON(const BSONObj& source, string* errMsg) {
+ clear();
- fieldState = FieldParser::extract(source, waiting, &_waiting, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isWaitingSet = fieldState == FieldParser::FIELD_SET;
+ std::string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
- fieldState = FieldParser::extract(source, mongoVersion, &_mongoVersion, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isMongoVersionSet = fieldState == FieldParser::FIELD_SET;
+ FieldParser::FieldState fieldState;
+ fieldState = FieldParser::extract(source, name, &_name, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isNameSet = fieldState == FieldParser::FIELD_SET;
- fieldState = FieldParser::extract(source, configVersion, &_configVersion, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isConfigVersionSet = fieldState == FieldParser::FIELD_SET;
+ fieldState = FieldParser::extract(source, ping, &_ping, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isPingSet = fieldState == FieldParser::FIELD_SET;
- return true;
- }
+ fieldState = FieldParser::extract(source, up, &_up, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isUpSet = fieldState == FieldParser::FIELD_SET;
- void MongosType::clear() {
+ fieldState = FieldParser::extract(source, waiting, &_waiting, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isWaitingSet = fieldState == FieldParser::FIELD_SET;
- _name.clear();
- _isNameSet = false;
+ fieldState = FieldParser::extract(source, mongoVersion, &_mongoVersion, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isMongoVersionSet = fieldState == FieldParser::FIELD_SET;
- _ping = Date_t();
- _isPingSet = false;
+ fieldState = FieldParser::extract(source, configVersion, &_configVersion, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isConfigVersionSet = fieldState == FieldParser::FIELD_SET;
- _up = 0;
- _isUpSet = false;
+ return true;
+}
- _waiting = false;
- _isWaitingSet = false;
+void MongosType::clear() {
+ _name.clear();
+ _isNameSet = false;
- _mongoVersion.clear();
- _isMongoVersionSet = false;
+ _ping = Date_t();
+ _isPingSet = false;
- _configVersion = 0;
- _isConfigVersionSet = false;
+ _up = 0;
+ _isUpSet = false;
- }
+ _waiting = false;
+ _isWaitingSet = false;
- void MongosType::cloneTo(MongosType* other) const {
- other->clear();
+ _mongoVersion.clear();
+ _isMongoVersionSet = false;
- other->_name = _name;
- other->_isNameSet = _isNameSet;
+ _configVersion = 0;
+ _isConfigVersionSet = false;
+}
- other->_ping = _ping;
- other->_isPingSet = _isPingSet;
+void MongosType::cloneTo(MongosType* other) const {
+ other->clear();
- other->_up = _up;
- other->_isUpSet = _isUpSet;
+ other->_name = _name;
+ other->_isNameSet = _isNameSet;
- other->_waiting = _waiting;
- other->_isWaitingSet = _isWaitingSet;
+ other->_ping = _ping;
+ other->_isPingSet = _isPingSet;
- other->_mongoVersion = _mongoVersion;
- other->_isMongoVersionSet = _isMongoVersionSet;
+ other->_up = _up;
+ other->_isUpSet = _isUpSet;
- other->_configVersion = _configVersion;
- other->_isConfigVersionSet = _isConfigVersionSet;
+ other->_waiting = _waiting;
+ other->_isWaitingSet = _isWaitingSet;
- }
+ other->_mongoVersion = _mongoVersion;
+ other->_isMongoVersionSet = _isMongoVersionSet;
- std::string MongosType::toString() const {
- return toBSON().toString();
- }
+ other->_configVersion = _configVersion;
+ other->_isConfigVersionSet = _isConfigVersionSet;
+}
+
+std::string MongosType::toString() const {
+ return toBSON().toString();
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/type_mongos.h b/src/mongo/s/type_mongos.h
index 9969fca4815..2bf0a372359 100644
--- a/src/mongo/s/type_mongos.h
+++ b/src/mongo/s/type_mongos.h
@@ -36,208 +36,228 @@
namespace mongo {
- /**
- * This class represents the layout and contents of documents contained in the
- * config.mongos collection. All manipulation of documents coming from that
- * collection should be done with this class.
- *
- * Usage Example:
- *
- * // Contact the config. 'conn' has been obtained before.
- * DBClientBase* conn;
- * BSONObj query = QUERY(MongosType::exampleField("exampleFieldName"));
- * exampleDoc = conn->findOne(MongosType::ConfigNS, query);
- *
- * // Process the response.
- * MongosType exampleType;
- * std::string errMsg;
- * if (!exampleType.parseBSON(exampleDoc, &errMsg) || !exampleType.isValid(&errMsg)) {
- * // Can't use 'exampleType'. Take action.
- * }
- * // use 'exampleType'
- *
- */
- class MongosType {
- MONGO_DISALLOW_COPYING(MongosType);
- public:
-
- //
- // schema declarations
- //
-
- // Name of the mongos collection in the config server.
- static const std::string ConfigNS;
-
- // Field names and types in the mongos collection type.
- static const BSONField<std::string> name;
- static const BSONField<Date_t> ping;
- static const BSONField<int> up;
- static const BSONField<bool> waiting;
- static const BSONField<std::string> mongoVersion;
- static const BSONField<int> configVersion;
-
- //
- // mongos type methods
- //
-
- MongosType();
- ~MongosType();
-
- /**
- * Returns true if all the mandatory fields are present and have valid
- * representations. Otherwise returns false and fills in the optional 'errMsg' string.
- */
- bool isValid(std::string* errMsg) const;
-
- /**
- * Returns the BSON representation of the entry.
- */
- BSONObj toBSON() const;
-
- /**
- * Clears and populates the internal state using the 'source' BSON object if the
- * latter contains valid values. Otherwise sets errMsg and returns false.
- */
- bool parseBSON(const BSONObj& source, std::string* errMsg);
-
- /**
- * Clears the internal state.
- */
- void clear();
-
- /**
- * Copies all the fields present in 'this' to 'other'.
- */
- void cloneTo(MongosType* other) const;
-
- /**
- * Returns a std::string representation of the current internal state.
- */
- std::string toString() const;
-
- //
- // individual field accessors
- //
-
- // Mandatory Fields
- void setName(StringData name) {
- _name = name.toString();
- _isNameSet = true;
- }
-
- void unsetName() { _isNameSet = false; }
-
- bool isNameSet() const { return _isNameSet; }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- const std::string& getName() const {
- dassert(_isNameSet);
- return _name;
- }
-
- void setPing(const Date_t ping) {
- _ping = ping;
- _isPingSet = true;
- }
-
- void unsetPing() { _isPingSet = false; }
-
- bool isPingSet() const { return _isPingSet; }
-
- // Calling get*() methods when the member is not set results in undefined behavior
- const Date_t getPing() const {
- dassert(_isPingSet);
- return _ping;
- }
-
- void setUp(const int up) {
- _up = up;
- _isUpSet = true;
- }
-
- void unsetUp() { _isUpSet = false; }
-
- bool isUpSet() const { return _isUpSet; }
+/**
+ * This class represents the layout and contents of documents contained in the
+ * config.mongos collection. All manipulation of documents coming from that
+ * collection should be done with this class.
+ *
+ * Usage Example:
+ *
+ * // Contact the config. 'conn' has been obtained before.
+ * DBClientBase* conn;
+ * BSONObj query = QUERY(MongosType::exampleField("exampleFieldName"));
+ * exampleDoc = conn->findOne(MongosType::ConfigNS, query);
+ *
+ * // Process the response.
+ * MongosType exampleType;
+ * std::string errMsg;
+ * if (!exampleType.parseBSON(exampleDoc, &errMsg) || !exampleType.isValid(&errMsg)) {
+ * // Can't use 'exampleType'. Take action.
+ * }
+ * // use 'exampleType'
+ *
+ */
+class MongosType {
+ MONGO_DISALLOW_COPYING(MongosType);
- // Calling get*() methods when the member is not set results in undefined behavior
- int getUp() const {
- dassert(_isUpSet);
- return _up;
- }
+public:
+ //
+ // schema declarations
+ //
- void setWaiting(const bool waiting) {
- _waiting = waiting;
- _isWaitingSet = true;
- }
+ // Name of the mongos collection in the config server.
+ static const std::string ConfigNS;
- void unsetWaiting() { _isWaitingSet = false; }
+ // Field names and types in the mongos collection type.
+ static const BSONField<std::string> name;
+ static const BSONField<Date_t> ping;
+ static const BSONField<int> up;
+ static const BSONField<bool> waiting;
+ static const BSONField<std::string> mongoVersion;
+ static const BSONField<int> configVersion;
- bool isWaitingSet() const { return _isWaitingSet; }
+ //
+ // mongos type methods
+ //
- // Calling get*() methods when the member is not set results in undefined behavior
- bool getWaiting() const {
- dassert(_isWaitingSet);
- return _waiting;
- }
+ MongosType();
+ ~MongosType();
- // Optional Fields
- void setMongoVersion(StringData mongoVersion) {
- _mongoVersion = mongoVersion.toString();
- _isMongoVersionSet = true;
- }
+ /**
+ * Returns true if all the mandatory fields are present and have valid
+ * representations. Otherwise returns false and fills in the optional 'errMsg' string.
+ */
+ bool isValid(std::string* errMsg) const;
- void unsetMongoVersion() { _isMongoVersionSet = false; }
+ /**
+ * Returns the BSON representation of the entry.
+ */
+ BSONObj toBSON() const;
- bool isMongoVersionSet() const {
- return _isMongoVersionSet || mongoVersion.hasDefault();
- }
+ /**
+ * Clears and populates the internal state using the 'source' BSON object if the
+ * latter contains valid values. Otherwise sets errMsg and returns false.
+ */
+ bool parseBSON(const BSONObj& source, std::string* errMsg);
- // Calling get*() methods when the member is not set and has no default results in undefined
- // behavior
- std::string getMongoVersion() const {
- if (_isMongoVersionSet) {
- return _mongoVersion;
- } else {
- dassert(mongoVersion.hasDefault());
- return mongoVersion.getDefault();
- }
- }
- void setConfigVersion(const int configVersion) {
- _configVersion = configVersion;
- _isConfigVersionSet = true;
- }
+ /**
+ * Clears the internal state.
+ */
+ void clear();
- void unsetConfigVersion() { _isConfigVersionSet = false; }
+ /**
+ * Copies all the fields present in 'this' to 'other'.
+ */
+ void cloneTo(MongosType* other) const;
- bool isConfigVersionSet() const {
- return _isConfigVersionSet || configVersion.hasDefault();
+ /**
+ * Returns a std::string representation of the current internal state.
+ */
+ std::string toString() const;
+
+ //
+ // individual field accessors
+ //
+
+ // Mandatory Fields
+ void setName(StringData name) {
+ _name = name.toString();
+ _isNameSet = true;
+ }
+
+ void unsetName() {
+ _isNameSet = false;
+ }
+
+ bool isNameSet() const {
+ return _isNameSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const std::string& getName() const {
+ dassert(_isNameSet);
+ return _name;
+ }
+
+ void setPing(const Date_t ping) {
+ _ping = ping;
+ _isPingSet = true;
+ }
+
+ void unsetPing() {
+ _isPingSet = false;
+ }
+
+ bool isPingSet() const {
+ return _isPingSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ const Date_t getPing() const {
+ dassert(_isPingSet);
+ return _ping;
+ }
+
+ void setUp(const int up) {
+ _up = up;
+ _isUpSet = true;
+ }
+
+ void unsetUp() {
+ _isUpSet = false;
+ }
+
+ bool isUpSet() const {
+ return _isUpSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ int getUp() const {
+ dassert(_isUpSet);
+ return _up;
+ }
+
+ void setWaiting(const bool waiting) {
+ _waiting = waiting;
+ _isWaitingSet = true;
+ }
+
+ void unsetWaiting() {
+ _isWaitingSet = false;
+ }
+
+ bool isWaitingSet() const {
+ return _isWaitingSet;
+ }
+
+ // Calling get*() methods when the member is not set results in undefined behavior
+ bool getWaiting() const {
+ dassert(_isWaitingSet);
+ return _waiting;
+ }
+
+ // Optional Fields
+ void setMongoVersion(StringData mongoVersion) {
+ _mongoVersion = mongoVersion.toString();
+ _isMongoVersionSet = true;
+ }
+
+ void unsetMongoVersion() {
+ _isMongoVersionSet = false;
+ }
+
+ bool isMongoVersionSet() const {
+ return _isMongoVersionSet || mongoVersion.hasDefault();
+ }
+
+ // Calling get*() methods when the member is not set and has no default results in undefined
+ // behavior
+ std::string getMongoVersion() const {
+ if (_isMongoVersionSet) {
+ return _mongoVersion;
+ } else {
+ dassert(mongoVersion.hasDefault());
+ return mongoVersion.getDefault();
}
-
- // Calling get*() methods when the member is not set and has no default results in undefined
- // behavior
- int getConfigVersion() const {
- if (_isConfigVersionSet) {
- return _configVersion;
- } else {
- dassert(configVersion.hasDefault());
- return configVersion.getDefault();
- }
+ }
+ void setConfigVersion(const int configVersion) {
+ _configVersion = configVersion;
+ _isConfigVersionSet = true;
+ }
+
+ void unsetConfigVersion() {
+ _isConfigVersionSet = false;
+ }
+
+ bool isConfigVersionSet() const {
+ return _isConfigVersionSet || configVersion.hasDefault();
+ }
+
+ // Calling get*() methods when the member is not set and has no default results in undefined
+ // behavior
+ int getConfigVersion() const {
+ if (_isConfigVersionSet) {
+ return _configVersion;
+ } else {
+ dassert(configVersion.hasDefault());
+ return configVersion.getDefault();
}
-
- private:
- // Convention: (M)andatory, (O)ptional, (S)pecial rule.
- std::string _name; // (M) "host:port" for this mongos
- bool _isNameSet;
- Date_t _ping; // (M) last time it was seen alive
- bool _isPingSet;
- int _up; // (M) uptime at the last ping
- bool _isUpSet;
- bool _waiting; // (M) for testing purposes
- bool _isWaitingSet;
- std::string _mongoVersion; // (O) the mongodb version of the pinging mongos
- bool _isMongoVersionSet;
- int _configVersion; // (O) the config version of the pinging mongos
- bool _isConfigVersionSet;
- };
-
-} // namespace mongo
+ }
+
+private:
+ // Convention: (M)andatory, (O)ptional, (S)pecial rule.
+ std::string _name; // (M) "host:port" for this mongos
+ bool _isNameSet;
+ Date_t _ping; // (M) last time it was seen alive
+ bool _isPingSet;
+ int _up; // (M) uptime at the last ping
+ bool _isUpSet;
+ bool _waiting; // (M) for testing purposes
+ bool _isWaitingSet;
+ std::string _mongoVersion; // (O) the mongodb version of the pinging mongos
+ bool _isMongoVersionSet;
+ int _configVersion; // (O) the config version of the pinging mongos
+ bool _isConfigVersionSet;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/type_mongos_test.cpp b/src/mongo/s/type_mongos_test.cpp
index 1c0ab5be56d..210c1ae4169 100644
--- a/src/mongo/s/type_mongos_test.cpp
+++ b/src/mongo/s/type_mongos_test.cpp
@@ -32,154 +32,141 @@
namespace {
- using std::string;
- using mongo::BSONObj;
- using mongo::MongosType;
- using mongo::Date_t;
+using std::string;
+using mongo::BSONObj;
+using mongo::MongosType;
+using mongo::Date_t;
- TEST(Validity, MissingName) {
- MongosType mongos;
- string errMsg;
- BSONObj obj = BSON(MongosType::ping(Date_t::fromMillisSinceEpoch(1)) <<
- MongosType::up(100) <<
- MongosType::waiting(false) <<
- MongosType::mongoVersion("x.x.x") <<
- MongosType::configVersion(0));
- ASSERT(mongos.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_FALSE(mongos.isNameSet());
- ASSERT_TRUE(mongos.isPingSet());
- ASSERT_TRUE(mongos.isUpSet());
- ASSERT_TRUE(mongos.isWaitingSet());
- ASSERT_TRUE(mongos.isMongoVersionSet());
- ASSERT_TRUE(mongos.isConfigVersionSet());
- ASSERT_FALSE(mongos.isValid(NULL));
- }
+TEST(Validity, MissingName) {
+ MongosType mongos;
+ string errMsg;
+ BSONObj obj = BSON(MongosType::ping(Date_t::fromMillisSinceEpoch(1))
+ << MongosType::up(100) << MongosType::waiting(false)
+ << MongosType::mongoVersion("x.x.x") << MongosType::configVersion(0));
+ ASSERT(mongos.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_FALSE(mongos.isNameSet());
+ ASSERT_TRUE(mongos.isPingSet());
+ ASSERT_TRUE(mongos.isUpSet());
+ ASSERT_TRUE(mongos.isWaitingSet());
+ ASSERT_TRUE(mongos.isMongoVersionSet());
+ ASSERT_TRUE(mongos.isConfigVersionSet());
+ ASSERT_FALSE(mongos.isValid(NULL));
+}
- TEST(Validity, MissingPing) {
- MongosType mongos;
- string errMsg;
- BSONObj obj = BSON(MongosType::name("localhost:27017") <<
- MongosType::up(100) <<
- MongosType::waiting(false) <<
- MongosType::mongoVersion("x.x.x") <<
- MongosType::configVersion(0));
- ASSERT(mongos.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_TRUE(mongos.isNameSet());
- ASSERT_FALSE(mongos.isPingSet());
- ASSERT_TRUE(mongos.isUpSet());
- ASSERT_TRUE(mongos.isWaitingSet());
- ASSERT_TRUE(mongos.isMongoVersionSet());
- ASSERT_TRUE(mongos.isConfigVersionSet());
- ASSERT_FALSE(mongos.isValid(NULL));
- }
+TEST(Validity, MissingPing) {
+ MongosType mongos;
+ string errMsg;
+ BSONObj obj = BSON(MongosType::name("localhost:27017")
+ << MongosType::up(100) << MongosType::waiting(false)
+ << MongosType::mongoVersion("x.x.x") << MongosType::configVersion(0));
+ ASSERT(mongos.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_TRUE(mongos.isNameSet());
+ ASSERT_FALSE(mongos.isPingSet());
+ ASSERT_TRUE(mongos.isUpSet());
+ ASSERT_TRUE(mongos.isWaitingSet());
+ ASSERT_TRUE(mongos.isMongoVersionSet());
+ ASSERT_TRUE(mongos.isConfigVersionSet());
+ ASSERT_FALSE(mongos.isValid(NULL));
+}
- TEST(Validity, MissingUp) {
- MongosType mongos;
- string errMsg;
- BSONObj obj = BSON(MongosType::name("localhost:27017") <<
- MongosType::ping(Date_t::fromMillisSinceEpoch(1)) <<
- MongosType::waiting(false) <<
- MongosType::mongoVersion("x.x.x") <<
- MongosType::configVersion(0));
- ASSERT(mongos.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_TRUE(mongos.isNameSet());
- ASSERT_TRUE(mongos.isPingSet());
- ASSERT_FALSE(mongos.isUpSet());
- ASSERT_TRUE(mongos.isWaitingSet());
- ASSERT_TRUE(mongos.isMongoVersionSet());
- ASSERT_TRUE(mongos.isConfigVersionSet());
- ASSERT_FALSE(mongos.isValid(NULL));
- }
+TEST(Validity, MissingUp) {
+ MongosType mongos;
+ string errMsg;
+ BSONObj obj =
+ BSON(MongosType::name("localhost:27017")
+ << MongosType::ping(Date_t::fromMillisSinceEpoch(1)) << MongosType::waiting(false)
+ << MongosType::mongoVersion("x.x.x") << MongosType::configVersion(0));
+ ASSERT(mongos.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_TRUE(mongos.isNameSet());
+ ASSERT_TRUE(mongos.isPingSet());
+ ASSERT_FALSE(mongos.isUpSet());
+ ASSERT_TRUE(mongos.isWaitingSet());
+ ASSERT_TRUE(mongos.isMongoVersionSet());
+ ASSERT_TRUE(mongos.isConfigVersionSet());
+ ASSERT_FALSE(mongos.isValid(NULL));
+}
- TEST(Validity, MissingWaiting) {
- MongosType mongos;
- string errMsg;
- BSONObj obj = BSON(MongosType::name("localhost:27017") <<
- MongosType::ping(Date_t::fromMillisSinceEpoch(1)) <<
- MongosType::up(100) <<
- MongosType::mongoVersion("x.x.x") <<
- MongosType::configVersion(0));
- ASSERT(mongos.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_TRUE(mongos.isNameSet());
- ASSERT_TRUE(mongos.isPingSet());
- ASSERT_TRUE(mongos.isUpSet());
- ASSERT_FALSE(mongos.isWaitingSet());
- ASSERT_TRUE(mongos.isMongoVersionSet());
- ASSERT_TRUE(mongos.isConfigVersionSet());
- ASSERT_FALSE(mongos.isValid(NULL));
- }
+TEST(Validity, MissingWaiting) {
+ MongosType mongos;
+ string errMsg;
+ BSONObj obj = BSON(MongosType::name("localhost:27017")
+ << MongosType::ping(Date_t::fromMillisSinceEpoch(1)) << MongosType::up(100)
+ << MongosType::mongoVersion("x.x.x") << MongosType::configVersion(0));
+ ASSERT(mongos.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_TRUE(mongos.isNameSet());
+ ASSERT_TRUE(mongos.isPingSet());
+ ASSERT_TRUE(mongos.isUpSet());
+ ASSERT_FALSE(mongos.isWaitingSet());
+ ASSERT_TRUE(mongos.isMongoVersionSet());
+ ASSERT_TRUE(mongos.isConfigVersionSet());
+ ASSERT_FALSE(mongos.isValid(NULL));
+}
- TEST(Validity, MissingMongoVersion) {
- MongosType mongos;
- string errMsg;
- BSONObj obj = BSON(MongosType::name("localhost:27017") <<
- MongosType::ping(Date_t::fromMillisSinceEpoch(1)) <<
- MongosType::up(100) <<
- MongosType::waiting(false) <<
- MongosType::configVersion(0));
- ASSERT(mongos.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_TRUE(mongos.isNameSet());
- ASSERT_TRUE(mongos.isPingSet());
- ASSERT_TRUE(mongos.isUpSet());
- ASSERT_TRUE(mongos.isWaitingSet());
- ASSERT_FALSE(mongos.isMongoVersionSet());
- ASSERT_TRUE(mongos.isConfigVersionSet());
- /* NOTE: mongoVersion should eventually become mandatory, but is optional now for backward
- * compatibility reasons */
- ASSERT_TRUE(mongos.isValid(NULL));
- }
+TEST(Validity, MissingMongoVersion) {
+ MongosType mongos;
+ string errMsg;
+ BSONObj obj = BSON(MongosType::name("localhost:27017")
+ << MongosType::ping(Date_t::fromMillisSinceEpoch(1)) << MongosType::up(100)
+ << MongosType::waiting(false) << MongosType::configVersion(0));
+ ASSERT(mongos.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_TRUE(mongos.isNameSet());
+ ASSERT_TRUE(mongos.isPingSet());
+ ASSERT_TRUE(mongos.isUpSet());
+ ASSERT_TRUE(mongos.isWaitingSet());
+ ASSERT_FALSE(mongos.isMongoVersionSet());
+ ASSERT_TRUE(mongos.isConfigVersionSet());
+ /* NOTE: mongoVersion should eventually become mandatory, but is optional now for backward
+ * compatibility reasons */
+ ASSERT_TRUE(mongos.isValid(NULL));
+}
- TEST(Validity, MissingConfigVersion) {
- MongosType mongos;
- string errMsg;
- BSONObj obj = BSON(MongosType::name("localhost:27017") <<
- MongosType::ping(Date_t::fromMillisSinceEpoch(1)) <<
- MongosType::up(100) <<
- MongosType::waiting(false) <<
- MongosType::mongoVersion("x.x.x"));
- ASSERT(mongos.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_TRUE(mongos.isNameSet());
- ASSERT_TRUE(mongos.isPingSet());
- ASSERT_TRUE(mongos.isUpSet());
- ASSERT_TRUE(mongos.isWaitingSet());
- ASSERT_TRUE(mongos.isMongoVersionSet());
- ASSERT_FALSE(mongos.isConfigVersionSet());
- /* NOTE: configVersion should eventually become mandatory, but is optional now for backward
- * compatibility reasons */
- ASSERT_TRUE(mongos.isValid(NULL));
- }
+TEST(Validity, MissingConfigVersion) {
+ MongosType mongos;
+ string errMsg;
+ BSONObj obj = BSON(MongosType::name("localhost:27017")
+ << MongosType::ping(Date_t::fromMillisSinceEpoch(1)) << MongosType::up(100)
+ << MongosType::waiting(false) << MongosType::mongoVersion("x.x.x"));
+ ASSERT(mongos.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_TRUE(mongos.isNameSet());
+ ASSERT_TRUE(mongos.isPingSet());
+ ASSERT_TRUE(mongos.isUpSet());
+ ASSERT_TRUE(mongos.isWaitingSet());
+ ASSERT_TRUE(mongos.isMongoVersionSet());
+ ASSERT_FALSE(mongos.isConfigVersionSet());
+ /* NOTE: configVersion should eventually become mandatory, but is optional now for backward
+ * compatibility reasons */
+ ASSERT_TRUE(mongos.isValid(NULL));
+}
- TEST(Validity, Valid) {
- MongosType mongos;
- BSONObj obj = BSON(MongosType::name("localhost:27017") <<
- MongosType::ping(Date_t::fromMillisSinceEpoch(1)) <<
- MongosType::up(100) <<
- MongosType::waiting(false) <<
- MongosType::mongoVersion("x.x.x") <<
- MongosType::configVersion(0));
- string errMsg;
- ASSERT(mongos.parseBSON(obj, &errMsg));
- ASSERT_EQUALS(errMsg, "");
- ASSERT_TRUE(mongos.isValid(NULL));
- ASSERT_EQUALS(mongos.getName(), "localhost:27017");
- ASSERT_EQUALS(mongos.getPing(), Date_t::fromMillisSinceEpoch(1));
- ASSERT_EQUALS(mongos.getUp(), 100);
- ASSERT_EQUALS(mongos.getWaiting(), false);
- ASSERT_EQUALS(mongos.getMongoVersion(), "x.x.x");
- ASSERT_EQUALS(mongos.getConfigVersion(), 0);
- }
+TEST(Validity, Valid) {
+ MongosType mongos;
+ BSONObj obj = BSON(MongosType::name("localhost:27017")
+ << MongosType::ping(Date_t::fromMillisSinceEpoch(1)) << MongosType::up(100)
+ << MongosType::waiting(false) << MongosType::mongoVersion("x.x.x")
+ << MongosType::configVersion(0));
+ string errMsg;
+ ASSERT(mongos.parseBSON(obj, &errMsg));
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT_TRUE(mongos.isValid(NULL));
+ ASSERT_EQUALS(mongos.getName(), "localhost:27017");
+ ASSERT_EQUALS(mongos.getPing(), Date_t::fromMillisSinceEpoch(1));
+ ASSERT_EQUALS(mongos.getUp(), 100);
+ ASSERT_EQUALS(mongos.getWaiting(), false);
+ ASSERT_EQUALS(mongos.getMongoVersion(), "x.x.x");
+ ASSERT_EQUALS(mongos.getConfigVersion(), 0);
+}
- TEST(Validity, BadType) {
- MongosType mongos;
- BSONObj obj = BSON(MongosType::name() << 0);
- string errMsg;
- ASSERT((!mongos.parseBSON(obj, &errMsg)) && (errMsg != ""));
- }
+TEST(Validity, BadType) {
+ MongosType mongos;
+ BSONObj obj = BSON(MongosType::name() << 0);
+ string errMsg;
+ ASSERT((!mongos.parseBSON(obj, &errMsg)) && (errMsg != ""));
+}
-} // unnamed namespace
+} // unnamed namespace
diff --git a/src/mongo/s/version_manager.cpp b/src/mongo/s/version_manager.cpp
index ad96e57b3b7..17c624e541d 100644
--- a/src/mongo/s/version_manager.cpp
+++ b/src/mongo/s/version_manager.cpp
@@ -51,396 +51,386 @@
namespace mongo {
- using std::shared_ptr;
- using std::endl;
- using std::map;
- using std::string;
-
- // Global version manager
- VersionManager versionManager;
-
- /**
- * Tracking information, per-connection, of the latest chunk manager iteration or sequence
- * number that was used to send a shard version over this connection.
- * When the chunk manager is replaced, implying new versions were loaded, the chunk manager
- * sequence number is iterated by 1 and connections need to re-send shard versions.
- */
- struct ConnectionShardStatus {
-
- bool hasAnySequenceSet(DBClientBase* conn) {
- stdx::lock_guard<stdx::mutex> lk(_mutex);
-
- SequenceMap::const_iterator seenConnIt = _map.find(conn->getConnectionId());
- return seenConnIt != _map.end() && seenConnIt->second.size() > 0;
- }
+using std::shared_ptr;
+using std::endl;
+using std::map;
+using std::string;
- bool getSequence(DBClientBase * conn,
- const string& ns,
- unsigned long long* sequence) {
+// Global version manager
+VersionManager versionManager;
- stdx::lock_guard<stdx::mutex> lk(_mutex);
+/**
+ * Tracking information, per-connection, of the latest chunk manager iteration or sequence
+ * number that was used to send a shard version over this connection.
+ * When the chunk manager is replaced, implying new versions were loaded, the chunk manager
+ * sequence number is iterated by 1 and connections need to re-send shard versions.
+ */
+struct ConnectionShardStatus {
+ bool hasAnySequenceSet(DBClientBase* conn) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+
+ SequenceMap::const_iterator seenConnIt = _map.find(conn->getConnectionId());
+ return seenConnIt != _map.end() && seenConnIt->second.size() > 0;
+ }
- SequenceMap::const_iterator seenConnIt = _map.find(conn->getConnectionId());
- if (seenConnIt == _map.end())
- return false;
+ bool getSequence(DBClientBase* conn, const string& ns, unsigned long long* sequence) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
- map<string, unsigned long long>::const_iterator seenNSIt = seenConnIt->second.find(ns);
- if (seenNSIt == seenConnIt->second.end())
- return false;
+ SequenceMap::const_iterator seenConnIt = _map.find(conn->getConnectionId());
+ if (seenConnIt == _map.end())
+ return false;
- *sequence = seenNSIt->second;
- return true;
- }
+ map<string, unsigned long long>::const_iterator seenNSIt = seenConnIt->second.find(ns);
+ if (seenNSIt == seenConnIt->second.end())
+ return false;
- void setSequence( DBClientBase * conn , const string& ns , const unsigned long long& s ) {
- stdx::lock_guard<stdx::mutex> lk( _mutex );
- _map[conn->getConnectionId()][ns] = s;
- }
+ *sequence = seenNSIt->second;
+ return true;
+ }
- void reset( DBClientBase * conn ) {
- stdx::lock_guard<stdx::mutex> lk( _mutex );
- _map.erase( conn->getConnectionId() );
- }
+ void setSequence(DBClientBase* conn, const string& ns, const unsigned long long& s) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _map[conn->getConnectionId()][ns] = s;
+ }
- // protects _map
- stdx::mutex _mutex;
+ void reset(DBClientBase* conn) {
+ stdx::lock_guard<stdx::mutex> lk(_mutex);
+ _map.erase(conn->getConnectionId());
+ }
- // a map from a connection into ChunkManager's sequence number for each namespace
- typedef map<unsigned long long, map<string,unsigned long long> > SequenceMap;
- SequenceMap _map;
+ // protects _map
+ stdx::mutex _mutex;
- } connectionShardStatus;
+ // a map from a connection into ChunkManager's sequence number for each namespace
+ typedef map<unsigned long long, map<string, unsigned long long>> SequenceMap;
+ SequenceMap _map;
- void VersionManager::resetShardVersionCB( DBClientBase * conn ) {
- connectionShardStatus.reset( conn );
- }
+} connectionShardStatus;
- bool VersionManager::isVersionableCB(DBClientBase* conn) {
- // We do not version shard connections when issued from mongod
- if (!isMongos()) {
- return false;
- }
+void VersionManager::resetShardVersionCB(DBClientBase* conn) {
+ connectionShardStatus.reset(conn);
+}
- return conn->type() == ConnectionString::MASTER || conn->type() == ConnectionString::SET;
+bool VersionManager::isVersionableCB(DBClientBase* conn) {
+ // We do not version shard connections when issued from mongod
+ if (!isMongos()) {
+ return false;
}
- DBClientBase* getVersionable(DBClientBase* conn) {
- switch (conn->type()) {
+ return conn->type() == ConnectionString::MASTER || conn->type() == ConnectionString::SET;
+}
+
+DBClientBase* getVersionable(DBClientBase* conn) {
+ switch (conn->type()) {
case ConnectionString::INVALID:
- massert(15904, str::stream() << "cannot set version on invalid connection "
- << conn->toString(), false);
+ massert(15904,
+ str::stream() << "cannot set version on invalid connection "
+ << conn->toString(),
+ false);
return nullptr;
case ConnectionString::MASTER:
return conn;
case ConnectionString::SYNC:
- massert(15906, str::stream() << "cannot set version or shard on sync connection "
- << conn->toString(), false);
+ massert(15906,
+ str::stream() << "cannot set version or shard on sync connection "
+ << conn->toString(),
+ false);
return nullptr;
case ConnectionString::CUSTOM:
- massert(16334, str::stream() << "cannot set version or shard on custom connection "
- << conn->toString(), false);
+ massert(16334,
+ str::stream() << "cannot set version or shard on custom connection "
+ << conn->toString(),
+ false);
return nullptr;
case ConnectionString::SET:
DBClientReplicaSet* set = (DBClientReplicaSet*)conn;
return &(set->masterConn());
- }
-
- MONGO_UNREACHABLE;
}
- bool VersionManager::forceRemoteCheckShardVersionCB(const string& ns) {
- const NamespaceString nss(ns);
+ MONGO_UNREACHABLE;
+}
- // This will force the database catalog entry to be reloaded
- grid.catalogCache()->invalidate(nss.db().toString());
+bool VersionManager::forceRemoteCheckShardVersionCB(const string& ns) {
+ const NamespaceString nss(ns);
- auto status = grid.catalogCache()->getDatabase(nss.db().toString());
- if (!status.isOK()) {
- return false;
- }
+ // This will force the database catalog entry to be reloaded
+ grid.catalogCache()->invalidate(nss.db().toString());
- shared_ptr<DBConfig> conf = status.getValue();
-
- // If we don't have a collection, don't refresh the chunk manager
- if (nsGetCollection(ns).size() == 0) {
- return false;
- }
-
- ChunkManagerPtr manager = conf->getChunkManagerIfExists(ns, true, true);
- if (!manager) {
- return false;
- }
+ auto status = grid.catalogCache()->getDatabase(nss.db().toString());
+ if (!status.isOK()) {
+ return false;
+ }
- return true;
+ shared_ptr<DBConfig> conf = status.getValue();
+ // If we don't have a collection, don't refresh the chunk manager
+ if (nsGetCollection(ns).size() == 0) {
+ return false;
}
- /**
- * Special internal logic to run reduced version handshake for empty namespace operations to
- * shards.
- *
- * Eventually this should go completely away, but for now many commands rely on unversioned but
- * mongos-specific behavior on mongod (auditing and replication information in commands)
- */
- static bool initShardVersionEmptyNS(DBClientBase * conn_in) {
-
- bool ok;
- BSONObj result;
- DBClientBase* conn = NULL;
- try {
- // May throw if replica set primary is down
- conn = getVersionable( conn_in );
- dassert( conn ); // errors thrown above
-
- // Check to see if we've already initialized this connection
- if (connectionShardStatus.hasAnySequenceSet(conn))
- return false;
-
- // Check to see if this is actually a shard and not a single config server
- // NOTE: Config servers are registered only by the name "config" in the shard cache, not
- // by host, so lookup by host will fail unless the host is also a shard.
- const auto shard = grid.shardRegistry()->getShard(conn->getServerAddress());
- if (!shard) {
- return false;
- }
-
- LOG(1) << "initializing shard connection to " << shard->toString() << endl;
-
- ok = setShardVersion(*conn,
- "",
- grid.catalogManager()->connectionString().toString(),
- ChunkVersion(),
- NULL,
- true,
- result);
- }
- catch( const DBException& ) {
-
- // NOTE: Replica sets may fail to initShardVersion because future calls relying on
- // correct versioning must later call checkShardVersion on the primary.
- // Secondary queries and commands may not call checkShardVersion, but secondary ops
- // aren't versioned at all.
- if ( conn_in->type() != ConnectionString::SET ) {
- throw;
- }
+ ChunkManagerPtr manager = conf->getChunkManagerIfExists(ns, true, true);
+ if (!manager) {
+ return false;
+ }
- // NOTE: Only old-style cluster operations will talk via DBClientReplicaSets - using
- // checkShardVersion is required (which includes initShardVersion information) if these
- // connections are used.
+ return true;
+}
- OCCASIONALLY {
- warning() << "failed to initialize new replica set connection version, "
- << "will initialize on first use" << endl;
- }
+/**
+ * Special internal logic to run reduced version handshake for empty namespace operations to
+ * shards.
+ *
+ * Eventually this should go completely away, but for now many commands rely on unversioned but
+ * mongos-specific behavior on mongod (auditing and replication information in commands)
+ */
+static bool initShardVersionEmptyNS(DBClientBase* conn_in) {
+ bool ok;
+ BSONObj result;
+ DBClientBase* conn = NULL;
+ try {
+ // May throw if replica set primary is down
+ conn = getVersionable(conn_in);
+ dassert(conn); // errors thrown above
+
+ // Check to see if we've already initialized this connection
+ if (connectionShardStatus.hasAnySequenceSet(conn))
+ return false;
+ // Check to see if this is actually a shard and not a single config server
+ // NOTE: Config servers are registered only by the name "config" in the shard cache, not
+ // by host, so lookup by host will fail unless the host is also a shard.
+ const auto shard = grid.shardRegistry()->getShard(conn->getServerAddress());
+ if (!shard) {
return false;
}
- // Record the connection wire version if sent in the response, initShardVersion is a
- // handshake for mongos->mongod connections.
- if ( !result["minWireVersion"].eoo() ) {
+ LOG(1) << "initializing shard connection to " << shard->toString() << endl;
+
+ ok = setShardVersion(*conn,
+ "",
+ grid.catalogManager()->connectionString().toString(),
+ ChunkVersion(),
+ NULL,
+ true,
+ result);
+ } catch (const DBException&) {
+ // NOTE: Replica sets may fail to initShardVersion because future calls relying on
+ // correct versioning must later call checkShardVersion on the primary.
+ // Secondary queries and commands may not call checkShardVersion, but secondary ops
+ // aren't versioned at all.
+ if (conn_in->type() != ConnectionString::SET) {
+ throw;
+ }
+
+ // NOTE: Only old-style cluster operations will talk via DBClientReplicaSets - using
+ // checkShardVersion is required (which includes initShardVersion information) if these
+ // connections are used.
- int minWireVersion = result["minWireVersion"].numberInt();
- int maxWireVersion = result["maxWireVersion"].numberInt();
- conn->setWireVersions( minWireVersion, maxWireVersion );
+ OCCASIONALLY {
+ warning() << "failed to initialize new replica set connection version, "
+ << "will initialize on first use" << endl;
}
- LOG(3) << "initial sharding result : " << result << endl;
+ return false;
+ }
- connectionShardStatus.setSequence(conn, "", 0);
- return ok;
+ // Record the connection wire version if sent in the response, initShardVersion is a
+ // handshake for mongos->mongod connections.
+ if (!result["minWireVersion"].eoo()) {
+ int minWireVersion = result["minWireVersion"].numberInt();
+ int maxWireVersion = result["maxWireVersion"].numberInt();
+ conn->setWireVersions(minWireVersion, maxWireVersion);
}
- /**
- * Updates the remote cached version on the remote shard host (primary, in the case of replica
- * sets) if needed with a fully-qualified shard version for the given namespace:
- * config server(s) + shard name + shard version
- *
- * If no remote cached version has ever been set, an initial shard version is sent.
- *
- * If the namespace is empty and no version has ever been sent, the config server + shard name
- * is sent to the remote shard host to initialize the connection as coming from mongos.
- * NOTE: This initialization is *best-effort only*. Operations which wish to correctly version
- * must send the namespace.
- *
- * Config servers are special and are not (unless otherwise a shard) kept up to date with this
- * protocol. This is safe so long as config servers only contain unversioned collections.
- *
- * It is an error to call checkShardVersion with an unversionable connection (isVersionableCB).
- *
- * @return true if we contacted the remote host
- */
- bool checkShardVersion(DBClientBase* conn_in,
- const string& ns,
- ChunkManagerPtr refManager,
- bool authoritative,
- int tryNumber) {
-
- // TODO: cache, optimize, etc...
-
- // Empty namespaces are special - we require initialization but not versioning
- if (ns.size() == 0) {
- return initShardVersionEmptyNS(conn_in);
- }
+ LOG(3) << "initial sharding result : " << result << endl;
- auto status = grid.catalogCache()->getDatabase(nsToDatabase(ns));
- if (!status.isOK()) {
- return false;
- }
+ connectionShardStatus.setSequence(conn, "", 0);
+ return ok;
+}
- shared_ptr<DBConfig> conf = status.getValue();
+/**
+ * Updates the remote cached version on the remote shard host (primary, in the case of replica
+ * sets) if needed with a fully-qualified shard version for the given namespace:
+ * config server(s) + shard name + shard version
+ *
+ * If no remote cached version has ever been set, an initial shard version is sent.
+ *
+ * If the namespace is empty and no version has ever been sent, the config server + shard name
+ * is sent to the remote shard host to initialize the connection as coming from mongos.
+ * NOTE: This initialization is *best-effort only*. Operations which wish to correctly version
+ * must send the namespace.
+ *
+ * Config servers are special and are not (unless otherwise a shard) kept up to date with this
+ * protocol. This is safe so long as config servers only contain unversioned collections.
+ *
+ * It is an error to call checkShardVersion with an unversionable connection (isVersionableCB).
+ *
+ * @return true if we contacted the remote host
+ */
+bool checkShardVersion(DBClientBase* conn_in,
+ const string& ns,
+ ChunkManagerPtr refManager,
+ bool authoritative,
+ int tryNumber) {
+ // TODO: cache, optimize, etc...
+
+ // Empty namespaces are special - we require initialization but not versioning
+ if (ns.size() == 0) {
+ return initShardVersionEmptyNS(conn_in);
+ }
- DBClientBase* conn = getVersionable( conn_in );
- verify(conn); // errors thrown above
+ auto status = grid.catalogCache()->getDatabase(nsToDatabase(ns));
+ if (!status.isOK()) {
+ return false;
+ }
- unsigned long long officialSequenceNumber = 0;
+ shared_ptr<DBConfig> conf = status.getValue();
- ShardPtr primary;
- ChunkManagerPtr manager;
- if (authoritative)
- conf->getChunkManagerIfExists(ns, true);
+ DBClientBase* conn = getVersionable(conn_in);
+ verify(conn); // errors thrown above
- conf->getChunkManagerOrPrimary(ns, manager, primary);
+ unsigned long long officialSequenceNumber = 0;
- if (manager) {
- officialSequenceNumber = manager->getSequenceNumber();
- }
+ ShardPtr primary;
+ ChunkManagerPtr manager;
+ if (authoritative)
+ conf->getChunkManagerIfExists(ns, true);
- const auto shard = grid.shardRegistry()->getShard(conn->getServerAddress());
- uassert(ErrorCodes::ShardNotFound,
- str::stream() << conn->getServerAddress() << " is not recognized as a shard",
- shard);
-
- // Check this manager against the reference manager
- if (manager) {
-
- if (refManager && !refManager->compatibleWith(*manager, shard->getId())) {
- const ChunkVersion refVersion(refManager->getVersion(shard->getId()));
- const ChunkVersion currentVersion(manager->getVersion(shard->getId()));
-
- string msg(str::stream() << "manager ("
- << currentVersion.toString()
- << " : " << manager->getSequenceNumber() << ") "
- << "not compatible with reference manager ("
- << refVersion.toString()
- << " : " << refManager->getSequenceNumber() << ") "
- << "on shard " << shard->getId()
- << " (" << shard->getConnString().toString() << ")");
-
- throw SendStaleConfigException(ns,
- msg,
- refVersion,
- currentVersion);
- }
- }
- else if (refManager) {
-
- string msg( str::stream() << "not sharded ("
- << ( (manager.get() == 0) ? string( "<none>" ) :
- str::stream() << manager->getSequenceNumber() )
- << ") but has reference manager ("
- << refManager->getSequenceNumber() << ") "
- << "on conn " << conn->getServerAddress() << " ("
- << conn_in->getServerAddress() << ")" );
-
- throw SendStaleConfigException(ns,
- msg,
- refManager->getVersion(shard->getId()),
- ChunkVersion::UNSHARDED());
- }
+ conf->getChunkManagerOrPrimary(ns, manager, primary);
- // Do not send setShardVersion to collections on the config servers - this causes problems
- // when config servers are also shards and get SSV with conflicting names.
- // TODO: Make config servers regular shards
- if (primary && primary->getId() == "config") {
- return false;
- }
+ if (manager) {
+ officialSequenceNumber = manager->getSequenceNumber();
+ }
- // Has the ChunkManager been reloaded since the last time we updated the shard version over
- // this connection? If we've never updated the shard version, do so now.
- unsigned long long sequenceNumber = 0;
- if (connectionShardStatus.getSequence(conn, ns, &sequenceNumber)) {
- if (sequenceNumber == officialSequenceNumber) {
- return false;
- }
+ const auto shard = grid.shardRegistry()->getShard(conn->getServerAddress());
+ uassert(ErrorCodes::ShardNotFound,
+ str::stream() << conn->getServerAddress() << " is not recognized as a shard",
+ shard);
+
+ // Check this manager against the reference manager
+ if (manager) {
+ if (refManager && !refManager->compatibleWith(*manager, shard->getId())) {
+ const ChunkVersion refVersion(refManager->getVersion(shard->getId()));
+ const ChunkVersion currentVersion(manager->getVersion(shard->getId()));
+
+ string msg(str::stream()
+ << "manager (" << currentVersion.toString() << " : "
+ << manager->getSequenceNumber() << ") "
+ << "not compatible with reference manager (" << refVersion.toString()
+ << " : " << refManager->getSequenceNumber() << ") "
+ << "on shard " << shard->getId() << " (" << shard->getConnString().toString()
+ << ")");
+
+ throw SendStaleConfigException(ns, msg, refVersion, currentVersion);
}
+ } else if (refManager) {
+ string msg(str::stream() << "not sharded ("
+ << ((manager.get() == 0) ? string("<none>") : str::stream()
+ << manager->getSequenceNumber())
+ << ") but has reference manager ("
+ << refManager->getSequenceNumber() << ") "
+ << "on conn " << conn->getServerAddress() << " ("
+ << conn_in->getServerAddress() << ")");
+
+ throw SendStaleConfigException(
+ ns, msg, refManager->getVersion(shard->getId()), ChunkVersion::UNSHARDED());
+ }
- ChunkVersion version = ChunkVersion(0, 0, OID());
- if (manager) {
- version = manager->getVersion(shard->getId());
- }
+ // Do not send setShardVersion to collections on the config servers - this causes problems
+ // when config servers are also shards and get SSV with conflicting names.
+ // TODO: Make config servers regular shards
+ if (primary && primary->getId() == "config") {
+ return false;
+ }
- LOG(1) << "setting shard version of " << version << " for " << ns << " on shard "
- << shard->toString();
+ // Has the ChunkManager been reloaded since the last time we updated the shard version over
+ // this connection? If we've never updated the shard version, do so now.
+ unsigned long long sequenceNumber = 0;
+ if (connectionShardStatus.getSequence(conn, ns, &sequenceNumber)) {
+ if (sequenceNumber == officialSequenceNumber) {
+ return false;
+ }
+ }
- LOG(3) << "last version sent with chunk manager iteration " << sequenceNumber
- << ", current chunk manager iteration is " << officialSequenceNumber;
+ ChunkVersion version = ChunkVersion(0, 0, OID());
+ if (manager) {
+ version = manager->getVersion(shard->getId());
+ }
- BSONObj result;
- if (setShardVersion(*conn,
- ns,
- grid.catalogManager()->connectionString().toString(),
- version,
- manager.get(),
- authoritative,
- result)) {
+ LOG(1) << "setting shard version of " << version << " for " << ns << " on shard "
+ << shard->toString();
+
+ LOG(3) << "last version sent with chunk manager iteration " << sequenceNumber
+ << ", current chunk manager iteration is " << officialSequenceNumber;
+
+ BSONObj result;
+ if (setShardVersion(*conn,
+ ns,
+ grid.catalogManager()->connectionString().toString(),
+ version,
+ manager.get(),
+ authoritative,
+ result)) {
+ LOG(1) << " setShardVersion success: " << result;
+ connectionShardStatus.setSequence(conn, ns, officialSequenceNumber);
+ return true;
+ }
- LOG(1) << " setShardVersion success: " << result;
- connectionShardStatus.setSequence( conn , ns , officialSequenceNumber );
- return true;
- }
+ LOG(1) << " setShardVersion failed!\n" << result << endl;
- LOG(1) << " setShardVersion failed!\n" << result << endl;
+ if (result["need_authoritative"].trueValue())
+ massert(10428, "need_authoritative set but in authoritative mode already", !authoritative);
- if ( result["need_authoritative"].trueValue() )
- massert( 10428 , "need_authoritative set but in authoritative mode already" , ! authoritative );
+ if (!authoritative) {
+ // use the original connection and get a fresh versionable connection
+ // since conn can be invalidated (or worse, freed) after the failure
+ checkShardVersion(conn_in, ns, refManager, 1, tryNumber + 1);
+ return true;
+ }
- if ( ! authoritative ) {
- // use the original connection and get a fresh versionable connection
- // since conn can be invalidated (or worse, freed) after the failure
- checkShardVersion(conn_in, ns, refManager, 1, tryNumber + 1);
- return true;
- }
-
- if ( result["reloadConfig"].trueValue() ) {
- if( result["version"].timestampTime() == Date_t() ){
-
- warning() << "reloading full configuration for " << conf->name()
- << ", connection state indicates significant version changes";
-
- // reload db
- conf->reload();
- }
- else {
- // reload config
- conf->getChunkManager( ns , true );
- }
- }
+ if (result["reloadConfig"].trueValue()) {
+ if (result["version"].timestampTime() == Date_t()) {
+ warning() << "reloading full configuration for " << conf->name()
+ << ", connection state indicates significant version changes";
- const int maxNumTries = 7;
- if ( tryNumber < maxNumTries ) {
- LOG( tryNumber < ( maxNumTries / 2 ) ? 1 : 0 )
- << "going to retry checkShardVersion shard: " << shard->toString() << " " << result;
- sleepmillis( 10 * tryNumber );
- // use the original connection and get a fresh versionable connection
- // since conn can be invalidated (or worse, freed) after the failure
- checkShardVersion(conn_in, ns, refManager, true, tryNumber + 1);
- return true;
+ // reload db
+ conf->reload();
+ } else {
+ // reload config
+ conf->getChunkManager(ns, true);
}
-
- string errmsg = str::stream() << "setShardVersion failed shard: " << shard->toString()
- << " " << result;
- log() << " " << errmsg << endl;
- massert( 10429 , errmsg , 0 );
- return true;
}
- bool VersionManager::checkShardVersionCB( DBClientBase* conn_in , const string& ns , bool authoritative , int tryNumber ) {
- return checkShardVersion( conn_in, ns, ChunkManagerPtr(), authoritative, tryNumber );
+ const int maxNumTries = 7;
+ if (tryNumber < maxNumTries) {
+ LOG(tryNumber < (maxNumTries / 2) ? 1 : 0)
+ << "going to retry checkShardVersion shard: " << shard->toString() << " " << result;
+ sleepmillis(10 * tryNumber);
+ // use the original connection and get a fresh versionable connection
+ // since conn can be invalidated (or worse, freed) after the failure
+ checkShardVersion(conn_in, ns, refManager, true, tryNumber + 1);
+ return true;
}
- bool VersionManager::checkShardVersionCB( ShardConnection* conn_in , bool authoritative , int tryNumber ) {
- return checkShardVersion( conn_in->get(), conn_in->getNS(), conn_in->getManager(), authoritative, tryNumber );
- }
+ string errmsg = str::stream() << "setShardVersion failed shard: " << shard->toString() << " "
+ << result;
+ log() << " " << errmsg << endl;
+ massert(10429, errmsg, 0);
+ return true;
+}
+
+bool VersionManager::checkShardVersionCB(DBClientBase* conn_in,
+ const string& ns,
+ bool authoritative,
+ int tryNumber) {
+ return checkShardVersion(conn_in, ns, ChunkManagerPtr(), authoritative, tryNumber);
+}
+
+bool VersionManager::checkShardVersionCB(ShardConnection* conn_in,
+ bool authoritative,
+ int tryNumber) {
+ return checkShardVersion(
+ conn_in->get(), conn_in->getNS(), conn_in->getManager(), authoritative, tryNumber);
+}
} // namespace mongo
diff --git a/src/mongo/s/version_manager.h b/src/mongo/s/version_manager.h
index d1903b2d3eb..6dc6877d574 100644
--- a/src/mongo/s/version_manager.h
+++ b/src/mongo/s/version_manager.h
@@ -32,22 +32,21 @@
namespace mongo {
- class DBClientBase;
- class ShardConnection;
+class DBClientBase;
+class ShardConnection;
- class VersionManager {
- public:
- VersionManager() { }
+class VersionManager {
+public:
+ VersionManager() {}
- bool isVersionableCB(DBClientBase*);
- bool forceRemoteCheckShardVersionCB(const std::string&);
- bool checkShardVersionCB(DBClientBase*, const std::string&, bool, int);
- bool checkShardVersionCB(ShardConnection*, bool, int);
- void resetShardVersionCB(DBClientBase*);
+ bool isVersionableCB(DBClientBase*);
+ bool forceRemoteCheckShardVersionCB(const std::string&);
+ bool checkShardVersionCB(DBClientBase*, const std::string&, bool, int);
+ bool checkShardVersionCB(ShardConnection*, bool, int);
+ void resetShardVersionCB(DBClientBase*);
+};
- };
+extern VersionManager versionManager;
- extern VersionManager versionManager;
-
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/version_mongos.cpp b/src/mongo/s/version_mongos.cpp
index 1b974512643..9ec5d9be000 100644
--- a/src/mongo/s/version_mongos.cpp
+++ b/src/mongo/s/version_mongos.cpp
@@ -42,23 +42,21 @@
namespace mongo {
- void printShardingVersionInfo( bool out ) {
- if ( out ) {
- std::cout << "MongoS version " << versionString << " starting: pid=" <<
- ProcessId::getCurrent() << " port=" << serverGlobalParams.port <<
- ( sizeof(int*) == 4 ? " 32" : " 64" ) << "-bit host=" << getHostNameCached() <<
- " (--help for usage)" << std::endl;
- DEV std::cout << "DEBUG build" << std::endl;
- std::cout << "git version: " << gitVersion() << std::endl;
- std::cout << openSSLVersion("OpenSSL version: ") << std::endl;
- }
- else {
- log() << "MongoS version " << versionString << " starting: pid=" <<
- ProcessId::getCurrent() << " port=" << serverGlobalParams.port <<
- ( sizeof( int* ) == 4 ? " 32" : " 64" ) << "-bit host=" << getHostNameCached() <<
- " (--help for usage)" << std::endl;
- DEV log() << "DEBUG build" << std::endl;
- logProcessDetails();
- }
+void printShardingVersionInfo(bool out) {
+ if (out) {
+ std::cout << "MongoS version " << versionString
+ << " starting: pid=" << ProcessId::getCurrent()
+ << " port=" << serverGlobalParams.port << (sizeof(int*) == 4 ? " 32" : " 64")
+ << "-bit host=" << getHostNameCached() << " (--help for usage)" << std::endl;
+ DEV std::cout << "DEBUG build" << std::endl;
+ std::cout << "git version: " << gitVersion() << std::endl;
+ std::cout << openSSLVersion("OpenSSL version: ") << std::endl;
+ } else {
+ log() << "MongoS version " << versionString << " starting: pid=" << ProcessId::getCurrent()
+ << " port=" << serverGlobalParams.port << (sizeof(int*) == 4 ? " 32" : " 64")
+ << "-bit host=" << getHostNameCached() << " (--help for usage)" << std::endl;
+ DEV log() << "DEBUG build" << std::endl;
+ logProcessDetails();
}
-} // namespace mongo
+}
+} // namespace mongo
diff --git a/src/mongo/s/version_mongos.h b/src/mongo/s/version_mongos.h
index 491c36da5da..6367d9185d0 100644
--- a/src/mongo/s/version_mongos.h
+++ b/src/mongo/s/version_mongos.h
@@ -27,6 +27,6 @@
*/
namespace mongo {
- // print mongos version info
- void printShardingVersionInfo(bool out);
-} // namespace mongo
+// print mongos version info
+void printShardingVersionInfo(bool out);
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/batch_downconvert.cpp b/src/mongo/s/write_ops/batch_downconvert.cpp
index 3bcafcf31a1..f786606d9b6 100644
--- a/src/mongo/s/write_ops/batch_downconvert.cpp
+++ b/src/mongo/s/write_ops/batch_downconvert.cpp
@@ -40,274 +40,254 @@
namespace mongo {
- using std::endl;
- using std::string;
- using std::vector;
-
- Status extractGLEErrors( const BSONObj& gleResponse, GLEErrors* errors ) {
-
- // DRAGONS
- // Parsing GLE responses is incredibly finicky.
- // The order of testing here is extremely important.
-
- ///////////////////////////////////////////////////////////////////////
- // IMPORTANT!
- // Also update extractGLEErrors in batch_api.js for any changes made here.
-
- const bool isOK = gleResponse["ok"].trueValue();
- const string err = gleResponse["err"].str();
- const string errMsg = gleResponse["errmsg"].str();
- const string wNote = gleResponse["wnote"].str();
- const string jNote = gleResponse["jnote"].str();
- const int code = gleResponse["code"].numberInt();
- const bool timeout = gleResponse["wtimeout"].trueValue();
-
- if ( err == "norepl" || err == "noreplset" ) {
- // Know this is legacy gle and the repl not enforced - write concern error in 2.4
- errors->wcError.reset( new WCErrorDetail );
- errors->wcError->setErrCode( ErrorCodes::WriteConcernFailed );
- if ( !errMsg.empty() ) {
- errors->wcError->setErrMessage( errMsg );
- }
- else if ( !wNote.empty() ) {
- errors->wcError->setErrMessage( wNote );
- }
- else {
- errors->wcError->setErrMessage( err );
- }
- }
- else if ( timeout ) {
- // Know there was no write error
- errors->wcError.reset( new WCErrorDetail );
- errors->wcError->setErrCode( ErrorCodes::WriteConcernFailed );
- if ( !errMsg.empty() ) {
- errors->wcError->setErrMessage( errMsg );
- }
- else {
- errors->wcError->setErrMessage( err );
- }
- errors->wcError->setErrInfo( BSON( "wtimeout" << true ) );
- }
- else if ( code == 10990 /* no longer primary */
- || code == 16805 /* replicatedToNum no longer primary */
- || code == 14830 /* gle wmode changed / invalid */
- // 2.6 Error codes
- || code == ErrorCodes::NotMaster
- || code == ErrorCodes::UnknownReplWriteConcern
- || code == ErrorCodes::WriteConcernFailed ) {
- // Write concern errors that get returned as regular errors (result may not be ok: 1.0)
- errors->wcError.reset( new WCErrorDetail );
- errors->wcError->setErrCode( code );
- errors->wcError->setErrMessage( errMsg );
+using std::endl;
+using std::string;
+using std::vector;
+
+Status extractGLEErrors(const BSONObj& gleResponse, GLEErrors* errors) {
+ // DRAGONS
+ // Parsing GLE responses is incredibly finicky.
+ // The order of testing here is extremely important.
+
+ ///////////////////////////////////////////////////////////////////////
+ // IMPORTANT!
+ // Also update extractGLEErrors in batch_api.js for any changes made here.
+
+ const bool isOK = gleResponse["ok"].trueValue();
+ const string err = gleResponse["err"].str();
+ const string errMsg = gleResponse["errmsg"].str();
+ const string wNote = gleResponse["wnote"].str();
+ const string jNote = gleResponse["jnote"].str();
+ const int code = gleResponse["code"].numberInt();
+ const bool timeout = gleResponse["wtimeout"].trueValue();
+
+ if (err == "norepl" || err == "noreplset") {
+ // Know this is legacy gle and the repl not enforced - write concern error in 2.4
+ errors->wcError.reset(new WCErrorDetail);
+ errors->wcError->setErrCode(ErrorCodes::WriteConcernFailed);
+ if (!errMsg.empty()) {
+ errors->wcError->setErrMessage(errMsg);
+ } else if (!wNote.empty()) {
+ errors->wcError->setErrMessage(wNote);
+ } else {
+ errors->wcError->setErrMessage(err);
}
- else if ( !isOK ) {
-
- //
- // !!! SOME GLE ERROR OCCURRED, UNKNOWN WRITE RESULT !!!
- //
-
- return Status( DBException::convertExceptionCode(
- code ? code : ErrorCodes::UnknownError ),
- errMsg );
+ } else if (timeout) {
+ // Know there was no write error
+ errors->wcError.reset(new WCErrorDetail);
+ errors->wcError->setErrCode(ErrorCodes::WriteConcernFailed);
+ if (!errMsg.empty()) {
+ errors->wcError->setErrMessage(errMsg);
+ } else {
+ errors->wcError->setErrMessage(err);
}
- else if ( !err.empty() ) {
- // Write error
- errors->writeError.reset( new WriteErrorDetail );
- int writeErrorCode = code == 0 ? ErrorCodes::UnknownError : code;
-
- // COMPATIBILITY
- // Certain clients expect write commands to always report 11000 for duplicate key
- // errors, while legacy GLE can return additional codes.
- if ( writeErrorCode == 11001 /* dup key in update */
- || writeErrorCode == 12582 /* dup key capped */) {
- writeErrorCode = ErrorCodes::DuplicateKey;
- }
-
- errors->writeError->setErrCode( writeErrorCode );
- errors->writeError->setErrMessage( err );
- }
- else if ( !jNote.empty() ) {
- // Know this is legacy gle and the journaling not enforced - write concern error in 2.4
- errors->wcError.reset( new WCErrorDetail );
- errors->wcError->setErrCode( ErrorCodes::WriteConcernFailed );
- errors->wcError->setErrMessage( jNote );
+ errors->wcError->setErrInfo(BSON("wtimeout" << true));
+ } else if (code == 10990 /* no longer primary */
+ ||
+ code == 16805 /* replicatedToNum no longer primary */
+ ||
+ code == 14830 /* gle wmode changed / invalid */
+ // 2.6 Error codes
+ ||
+ code == ErrorCodes::NotMaster || code == ErrorCodes::UnknownReplWriteConcern ||
+ code == ErrorCodes::WriteConcernFailed) {
+ // Write concern errors that get returned as regular errors (result may not be ok: 1.0)
+ errors->wcError.reset(new WCErrorDetail);
+ errors->wcError->setErrCode(code);
+ errors->wcError->setErrMessage(errMsg);
+ } else if (!isOK) {
+ //
+ // !!! SOME GLE ERROR OCCURRED, UNKNOWN WRITE RESULT !!!
+ //
+
+ return Status(DBException::convertExceptionCode(code ? code : ErrorCodes::UnknownError),
+ errMsg);
+ } else if (!err.empty()) {
+ // Write error
+ errors->writeError.reset(new WriteErrorDetail);
+ int writeErrorCode = code == 0 ? ErrorCodes::UnknownError : code;
+
+ // COMPATIBILITY
+ // Certain clients expect write commands to always report 11000 for duplicate key
+ // errors, while legacy GLE can return additional codes.
+ if (writeErrorCode == 11001 /* dup key in update */
+ ||
+ writeErrorCode == 12582 /* dup key capped */) {
+ writeErrorCode = ErrorCodes::DuplicateKey;
}
- return Status::OK();
+ errors->writeError->setErrCode(writeErrorCode);
+ errors->writeError->setErrMessage(err);
+ } else if (!jNote.empty()) {
+ // Know this is legacy gle and the journaling not enforced - write concern error in 2.4
+ errors->wcError.reset(new WCErrorDetail);
+ errors->wcError->setErrCode(ErrorCodes::WriteConcernFailed);
+ errors->wcError->setErrMessage(jNote);
}
- /**
- * Suppress the "err" and "code" field if they are coming from a previous write error and
- * are not related to write concern. Also removes any write stats information (e.g. "n")
- *
- * Also, In some cases, 2.4 GLE w/ wOpTime can give us duplicate "err" and "code" fields b/c of
- * reporting a previous error. The later field is what we want - dedup and use later field.
- *
- * Returns the stripped GLE response.
- */
- BSONObj stripNonWCInfo( const BSONObj& gleResponse ) {
-
- BSONObjIterator it( gleResponse );
- BSONObjBuilder builder;
-
- BSONElement codeField; // eoo
- BSONElement errField; // eoo
-
- while ( it.more() ) {
- BSONElement el = it.next();
- StringData fieldName( el.fieldName() );
- if ( fieldName.compare( "err" ) == 0 ) {
- errField = el;
- }
- else if ( fieldName.compare( "code" ) == 0 ) {
- codeField = el;
- }
- else if ( fieldName.compare( "n" ) == 0 || fieldName.compare( "nModified" ) == 0
- || fieldName.compare( "upserted" ) == 0
- || fieldName.compare( "updatedExisting" ) == 0 ) {
- // Suppress field
- }
- else {
- builder.append( el );
- }
- }
+ return Status::OK();
+}
- if ( !codeField.eoo() ) {
- if ( !gleResponse["ok"].trueValue() ) {
- // The last code will be from the write concern
- builder.append( codeField );
- }
- else {
- // The code is from a non-wc error on this connection - suppress it
- }
+/**
+ * Suppress the "err" and "code" field if they are coming from a previous write error and
+ * are not related to write concern. Also removes any write stats information (e.g. "n")
+ *
+ * Also, In some cases, 2.4 GLE w/ wOpTime can give us duplicate "err" and "code" fields b/c of
+ * reporting a previous error. The later field is what we want - dedup and use later field.
+ *
+ * Returns the stripped GLE response.
+ */
+BSONObj stripNonWCInfo(const BSONObj& gleResponse) {
+ BSONObjIterator it(gleResponse);
+ BSONObjBuilder builder;
+
+ BSONElement codeField; // eoo
+ BSONElement errField; // eoo
+
+ while (it.more()) {
+ BSONElement el = it.next();
+ StringData fieldName(el.fieldName());
+ if (fieldName.compare("err") == 0) {
+ errField = el;
+ } else if (fieldName.compare("code") == 0) {
+ codeField = el;
+ } else if (fieldName.compare("n") == 0 || fieldName.compare("nModified") == 0 ||
+ fieldName.compare("upserted") == 0 ||
+ fieldName.compare("updatedExisting") == 0) {
+ // Suppress field
+ } else {
+ builder.append(el);
}
+ }
- if ( !errField.eoo() ) {
- string err = errField.str();
- if ( err == "norepl" || err == "noreplset" || err == "timeout" ) {
- // Append err if it's from a write concern issue
- builder.append( errField );
- }
- else {
- // Suppress non-write concern err as null, but we need to report null err if ok
- if ( gleResponse["ok"].trueValue() )
- builder.appendNull( errField.fieldName() );
- }
+ if (!codeField.eoo()) {
+ if (!gleResponse["ok"].trueValue()) {
+ // The last code will be from the write concern
+ builder.append(codeField);
+ } else {
+ // The code is from a non-wc error on this connection - suppress it
}
-
- return builder.obj();
}
- // Adds a wOpTime and a wElectionId field to a set of gle options
- static BSONObj buildGLECmdWithOpTime( const BSONObj& gleOptions,
- const Timestamp& opTime,
- const OID& electionId ) {
- BSONObjBuilder builder;
- BSONObjIterator it( gleOptions );
-
- for ( int i = 0; it.more(); ++i ) {
- BSONElement el = it.next();
-
- // Make sure first element is getLastError : 1
- if ( i == 0 ) {
- StringData elName( el.fieldName() );
- if ( !elName.equalCaseInsensitive( "getLastError" ) ) {
- builder.append( "getLastError", 1 );
- }
- }
-
- builder.append( el );
+ if (!errField.eoo()) {
+ string err = errField.str();
+ if (err == "norepl" || err == "noreplset" || err == "timeout") {
+ // Append err if it's from a write concern issue
+ builder.append(errField);
+ } else {
+ // Suppress non-write concern err as null, but we need to report null err if ok
+ if (gleResponse["ok"].trueValue())
+ builder.appendNull(errField.fieldName());
}
- builder.append( "wOpTime", opTime );
- builder.appendOID( "wElectionId", const_cast<OID*>(&electionId) );
- return builder.obj();
}
- Status enforceLegacyWriteConcern( MultiCommandDispatch* dispatcher,
- StringData dbName,
- const BSONObj& options,
- const HostOpTimeMap& hostOpTimes,
- vector<LegacyWCResponse>* legacyWCResponses ) {
+ return builder.obj();
+}
- if ( hostOpTimes.empty() ) {
- return Status::OK();
+// Adds a wOpTime and a wElectionId field to a set of gle options
+static BSONObj buildGLECmdWithOpTime(const BSONObj& gleOptions,
+ const Timestamp& opTime,
+ const OID& electionId) {
+ BSONObjBuilder builder;
+ BSONObjIterator it(gleOptions);
+
+ for (int i = 0; it.more(); ++i) {
+ BSONElement el = it.next();
+
+ // Make sure first element is getLastError : 1
+ if (i == 0) {
+ StringData elName(el.fieldName());
+ if (!elName.equalCaseInsensitive("getLastError")) {
+ builder.append("getLastError", 1);
+ }
}
- for ( HostOpTimeMap::const_iterator it = hostOpTimes.begin(); it != hostOpTimes.end();
- ++it ) {
-
- const ConnectionString& shardEndpoint = it->first;
- const HostOpTime hot = it->second;
- const Timestamp& opTime = hot.opTime;
- const OID& electionId = hot.electionId;
-
- LOG( 3 ) << "enforcing write concern " << options << " on " << shardEndpoint.toString()
- << " at opTime " << opTime.toStringPretty() << " with electionID "
- << electionId;
+ builder.append(el);
+ }
+ builder.append("wOpTime", opTime);
+ builder.appendOID("wElectionId", const_cast<OID*>(&electionId));
+ return builder.obj();
+}
- BSONObj gleCmd = buildGLECmdWithOpTime( options, opTime, electionId );
+Status enforceLegacyWriteConcern(MultiCommandDispatch* dispatcher,
+ StringData dbName,
+ const BSONObj& options,
+ const HostOpTimeMap& hostOpTimes,
+ vector<LegacyWCResponse>* legacyWCResponses) {
+ if (hostOpTimes.empty()) {
+ return Status::OK();
+ }
- RawBSONSerializable gleCmdSerial( gleCmd );
- dispatcher->addCommand( shardEndpoint, dbName, gleCmdSerial );
- }
+ for (HostOpTimeMap::const_iterator it = hostOpTimes.begin(); it != hostOpTimes.end(); ++it) {
+ const ConnectionString& shardEndpoint = it->first;
+ const HostOpTime hot = it->second;
+ const Timestamp& opTime = hot.opTime;
+ const OID& electionId = hot.electionId;
- dispatcher->sendAll();
+ LOG(3) << "enforcing write concern " << options << " on " << shardEndpoint.toString()
+ << " at opTime " << opTime.toStringPretty() << " with electionID " << electionId;
- vector<Status> failedStatuses;
+ BSONObj gleCmd = buildGLECmdWithOpTime(options, opTime, electionId);
- while ( dispatcher->numPending() > 0 ) {
+ RawBSONSerializable gleCmdSerial(gleCmd);
+ dispatcher->addCommand(shardEndpoint, dbName, gleCmdSerial);
+ }
- ConnectionString shardEndpoint;
- RawBSONSerializable gleResponseSerial;
+ dispatcher->sendAll();
- Status dispatchStatus = dispatcher->recvAny( &shardEndpoint, &gleResponseSerial );
- if ( !dispatchStatus.isOK() ) {
- // We need to get all responses before returning
- failedStatuses.push_back( dispatchStatus );
- continue;
- }
+ vector<Status> failedStatuses;
- BSONObj gleResponse = stripNonWCInfo( gleResponseSerial.toBSON() );
+ while (dispatcher->numPending() > 0) {
+ ConnectionString shardEndpoint;
+ RawBSONSerializable gleResponseSerial;
- // Use the downconversion tools to determine if this GLE response is ok, a
- // write concern error, or an unknown error we should immediately abort for.
- GLEErrors errors;
- Status extractStatus = extractGLEErrors( gleResponse, &errors );
- if ( !extractStatus.isOK() ) {
- failedStatuses.push_back( extractStatus );
- continue;
- }
+ Status dispatchStatus = dispatcher->recvAny(&shardEndpoint, &gleResponseSerial);
+ if (!dispatchStatus.isOK()) {
+ // We need to get all responses before returning
+ failedStatuses.push_back(dispatchStatus);
+ continue;
+ }
- LegacyWCResponse wcResponse;
- wcResponse.shardHost = shardEndpoint.toString();
- wcResponse.gleResponse = gleResponse;
- if ( errors.wcError.get() ) {
- wcResponse.errToReport = errors.wcError->getErrMessage();
- }
+ BSONObj gleResponse = stripNonWCInfo(gleResponseSerial.toBSON());
- legacyWCResponses->push_back( wcResponse );
+ // Use the downconversion tools to determine if this GLE response is ok, a
+ // write concern error, or an unknown error we should immediately abort for.
+ GLEErrors errors;
+ Status extractStatus = extractGLEErrors(gleResponse, &errors);
+ if (!extractStatus.isOK()) {
+ failedStatuses.push_back(extractStatus);
+ continue;
}
- if ( failedStatuses.empty() ) {
- return Status::OK();
+ LegacyWCResponse wcResponse;
+ wcResponse.shardHost = shardEndpoint.toString();
+ wcResponse.gleResponse = gleResponse;
+ if (errors.wcError.get()) {
+ wcResponse.errToReport = errors.wcError->getErrMessage();
}
- StringBuilder builder;
- builder << "could not enforce write concern";
+ legacyWCResponses->push_back(wcResponse);
+ }
- for ( vector<Status>::const_iterator it = failedStatuses.begin();
- it != failedStatuses.end(); ++it ) {
- const Status& failedStatus = *it;
- if ( it == failedStatuses.begin() ) {
- builder << causedBy( failedStatus.toString() );
- }
- else {
- builder << ":: and ::" << failedStatus.toString();
- }
- }
+ if (failedStatuses.empty()) {
+ return Status::OK();
+ }
+
+ StringBuilder builder;
+ builder << "could not enforce write concern";
- return Status( failedStatuses.size() == 1u ? failedStatuses.front().code() :
- ErrorCodes::MultipleErrorsOccurred,
- builder.str() );
+ for (vector<Status>::const_iterator it = failedStatuses.begin(); it != failedStatuses.end();
+ ++it) {
+ const Status& failedStatus = *it;
+ if (it == failedStatuses.begin()) {
+ builder << causedBy(failedStatus.toString());
+ } else {
+ builder << ":: and ::" << failedStatus.toString();
+ }
}
+
+ return Status(failedStatuses.size() == 1u ? failedStatuses.front().code()
+ : ErrorCodes::MultipleErrorsOccurred,
+ builder.str());
+}
}
diff --git a/src/mongo/s/write_ops/batch_downconvert.h b/src/mongo/s/write_ops/batch_downconvert.h
index e3c6cc56f6a..659e59d3a9a 100644
--- a/src/mongo/s/write_ops/batch_downconvert.h
+++ b/src/mongo/s/write_ops/batch_downconvert.h
@@ -41,50 +41,48 @@
namespace mongo {
- class MultiCommandDispatch;
+class MultiCommandDispatch;
- // Used for reporting legacy write concern responses
- struct LegacyWCResponse {
- std::string shardHost;
- BSONObj gleResponse;
- std::string errToReport;
- };
+// Used for reporting legacy write concern responses
+struct LegacyWCResponse {
+ std::string shardHost;
+ BSONObj gleResponse;
+ std::string errToReport;
+};
- /**
- * Uses GLE and the shard hosts and opTimes last written by write commands to enforce a
- * write concern across the previously used shards.
- *
- * Returns OK with the LegacyWCResponses containing only write concern error information
- * Returns !OK if there was an error getting a GLE response
- */
- Status enforceLegacyWriteConcern( MultiCommandDispatch* dispatcher,
- StringData dbName,
- const BSONObj& options,
- const HostOpTimeMap& hostOpTimes,
- std::vector<LegacyWCResponse>* wcResponses );
-
- //
- // Below exposed for testing only
- //
-
- // Helper that acts as an auto-ptr for write and wc errors
- struct GLEErrors {
- std::unique_ptr<WriteErrorDetail> writeError;
- std::unique_ptr<WCErrorDetail> wcError;
- };
+/**
+ * Uses GLE and the shard hosts and opTimes last written by write commands to enforce a
+ * write concern across the previously used shards.
+ *
+ * Returns OK with the LegacyWCResponses containing only write concern error information
+ * Returns !OK if there was an error getting a GLE response
+ */
+Status enforceLegacyWriteConcern(MultiCommandDispatch* dispatcher,
+ StringData dbName,
+ const BSONObj& options,
+ const HostOpTimeMap& hostOpTimes,
+ std::vector<LegacyWCResponse>* wcResponses);
- /**
- * Given a GLE response, extracts a write error and a write concern error for the previous
- * operation.
- *
- * Returns !OK if the GLE itself failed in an unknown way.
- */
- Status extractGLEErrors( const BSONObj& gleResponse, GLEErrors* errors );
+//
+// Below exposed for testing only
+//
- /**
- * Given a GLE response, strips out all non-write-concern related information
- */
- BSONObj stripNonWCInfo( const BSONObj& gleResponse );
+// Helper that acts as an auto-ptr for write and wc errors
+struct GLEErrors {
+ std::unique_ptr<WriteErrorDetail> writeError;
+ std::unique_ptr<WCErrorDetail> wcError;
+};
+/**
+ * Given a GLE response, extracts a write error and a write concern error for the previous
+ * operation.
+ *
+ * Returns !OK if the GLE itself failed in an unknown way.
+ */
+Status extractGLEErrors(const BSONObj& gleResponse, GLEErrors* errors);
+/**
+ * Given a GLE response, strips out all non-write-concern related information
+ */
+BSONObj stripNonWCInfo(const BSONObj& gleResponse);
}
diff --git a/src/mongo/s/write_ops/batch_downconvert_test.cpp b/src/mongo/s/write_ops/batch_downconvert_test.cpp
index 8dd4b440a39..548bfb6f732 100644
--- a/src/mongo/s/write_ops/batch_downconvert_test.cpp
+++ b/src/mongo/s/write_ops/batch_downconvert_test.cpp
@@ -38,194 +38,179 @@
namespace {
- using namespace mongo;
- using std::vector;
- using std::deque;
-
- //
- // Tests for parsing GLE responses into write errors and write concern errors for write
- // commands. These tests essentially document our expected 2.4 GLE behaviors.
- //
+using namespace mongo;
+using std::vector;
+using std::deque;
+
+//
+// Tests for parsing GLE responses into write errors and write concern errors for write
+// commands. These tests essentially document our expected 2.4 GLE behaviors.
+//
+
+TEST(GLEParsing, Empty) {
+ const BSONObj gleResponse = fromjson("{ok: 1.0, err: null}");
+
+ GLEErrors errors;
+ ASSERT_OK(extractGLEErrors(gleResponse, &errors));
+ ASSERT(!errors.writeError.get());
+ ASSERT(!errors.wcError.get());
+}
+
+TEST(GLEParsing, WriteErr) {
+ const BSONObj gleResponse = fromjson("{ok: 1.0, err: 'message', code: 1000}");
+
+ GLEErrors errors;
+ ASSERT_OK(extractGLEErrors(gleResponse, &errors));
+ ASSERT(errors.writeError.get());
+ ASSERT_EQUALS(errors.writeError->getErrMessage(), "message");
+ ASSERT_EQUALS(errors.writeError->getErrCode(), 1000);
+ ASSERT(!errors.wcError.get());
+}
+
+TEST(GLEParsing, JournalFail) {
+ const BSONObj gleResponse = fromjson("{ok: 1.0, err: null, jnote: 'message'}");
+
+ GLEErrors errors;
+ ASSERT_OK(extractGLEErrors(gleResponse, &errors));
+ ASSERT(!errors.writeError.get());
+ ASSERT(errors.wcError.get());
+ ASSERT_EQUALS(errors.wcError->getErrMessage(), "message");
+ ASSERT_EQUALS(errors.wcError->getErrCode(), ErrorCodes::WriteConcernFailed);
+}
+
+TEST(GLEParsing, ReplErr) {
+ const BSONObj gleResponse = fromjson("{ok: 1.0, err: 'norepl', wnote: 'message'}");
+
+ GLEErrors errors;
+ ASSERT_OK(extractGLEErrors(gleResponse, &errors));
+ ASSERT(!errors.writeError.get());
+ ASSERT(errors.wcError.get());
+ ASSERT_EQUALS(errors.wcError->getErrMessage(), "message");
+ ASSERT_EQUALS(errors.wcError->getErrCode(), ErrorCodes::WriteConcernFailed);
+}
+
+TEST(GLEParsing, ReplTimeoutErr) {
+ const BSONObj gleResponse =
+ fromjson("{ok: 1.0, err: 'timeout', errmsg: 'message', wtimeout: true}");
+
+ GLEErrors errors;
+ ASSERT_OK(extractGLEErrors(gleResponse, &errors));
+ ASSERT(!errors.writeError.get());
+ ASSERT(errors.wcError.get());
+ ASSERT_EQUALS(errors.wcError->getErrMessage(), "message");
+ ASSERT(errors.wcError->getErrInfo()["wtimeout"].trueValue());
+ ASSERT_EQUALS(errors.wcError->getErrCode(), ErrorCodes::WriteConcernFailed);
+}
+
+TEST(GLEParsing, GLEFail) {
+ const BSONObj gleResponse = fromjson("{ok: 0.0, err: null, errmsg: 'message', code: 1000}");
+
+ GLEErrors errors;
+ Status status = extractGLEErrors(gleResponse, &errors);
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(status.reason(), "message");
+ ASSERT_EQUALS(status.code(), 1000);
+}
+
+TEST(GLEParsing, GLEFailNoCode) {
+ const BSONObj gleResponse = fromjson("{ok: 0.0, err: null, errmsg: 'message'}");
+
+ GLEErrors errors;
+ Status status = extractGLEErrors(gleResponse, &errors);
+ ASSERT_NOT_OK(status);
+ ASSERT_EQUALS(status.reason(), "message");
+ ASSERT_EQUALS(status.code(), ErrorCodes::UnknownError);
+}
+
+TEST(GLEParsing, NotMasterGLEFail) {
+ // Not master code in response
+ const BSONObj gleResponse = fromjson("{ok: 0.0, err: null, errmsg: 'message', code: 10990}");
- TEST(GLEParsing, Empty) {
+ GLEErrors errors;
+ ASSERT_OK(extractGLEErrors(gleResponse, &errors));
+ ASSERT(!errors.writeError.get());
+ ASSERT(errors.wcError.get());
+ ASSERT_EQUALS(errors.wcError->getErrMessage(), "message");
+ ASSERT_EQUALS(errors.wcError->getErrCode(), 10990);
+}
+
+TEST(GLEParsing, WriteErrWithStats) {
+ const BSONObj gleResponse = fromjson("{ok: 1.0, n: 2, err: 'message', code: 1000}");
+
+ GLEErrors errors;
+ ASSERT_OK(extractGLEErrors(gleResponse, &errors));
+ ASSERT(errors.writeError.get());
+ ASSERT_EQUALS(errors.writeError->getErrMessage(), "message");
+ ASSERT_EQUALS(errors.writeError->getErrCode(), 1000);
+ ASSERT(!errors.wcError.get());
+}
+
+TEST(GLEParsing, ReplTimeoutErrWithStats) {
+ const BSONObj gleResponse = fromjson(
+ "{ok: 1.0, err: 'timeout', errmsg: 'message', wtimeout: true,"
+ " n: 1, upserted: 'abcde'}");
+
+ GLEErrors errors;
+ ASSERT_OK(extractGLEErrors(gleResponse, &errors));
+ ASSERT(!errors.writeError.get());
+ ASSERT(errors.wcError.get());
+ ASSERT_EQUALS(errors.wcError->getErrMessage(), "message");
+ ASSERT(errors.wcError->getErrInfo()["wtimeout"].trueValue());
+ ASSERT_EQUALS(errors.wcError->getErrCode(), ErrorCodes::WriteConcernFailed);
+}
+
+//
+// Tests of processing and suppressing non-WC related fields from legacy GLE responses
+//
+
+TEST(LegacyGLESuppress, Basic) {
+ const BSONObj gleResponse = fromjson("{ok: 1.0, err: null}");
+
+ BSONObj stripped = stripNonWCInfo(gleResponse);
+ ASSERT_EQUALS(stripped.nFields(), 2); // with err, ok : true
+ ASSERT(stripped["ok"].trueValue());
+}
- const BSONObj gleResponse = fromjson( "{ok: 1.0, err: null}" );
-
- GLEErrors errors;
- ASSERT_OK( extractGLEErrors( gleResponse, &errors ) );
- ASSERT( !errors.writeError.get() );
- ASSERT( !errors.wcError.get() );
- }
-
- TEST(GLEParsing, WriteErr) {
-
- const BSONObj gleResponse = fromjson( "{ok: 1.0, err: 'message', code: 1000}" );
-
- GLEErrors errors;
- ASSERT_OK( extractGLEErrors( gleResponse, &errors ) );
- ASSERT( errors.writeError.get() );
- ASSERT_EQUALS( errors.writeError->getErrMessage(), "message" );
- ASSERT_EQUALS( errors.writeError->getErrCode(), 1000 );
- ASSERT( !errors.wcError.get() );
- }
-
- TEST(GLEParsing, JournalFail) {
-
- const BSONObj gleResponse = fromjson( "{ok: 1.0, err: null, jnote: 'message'}" );
-
- GLEErrors errors;
- ASSERT_OK( extractGLEErrors( gleResponse, &errors ) );
- ASSERT( !errors.writeError.get() );
- ASSERT( errors.wcError.get() );
- ASSERT_EQUALS( errors.wcError->getErrMessage(), "message" );
- ASSERT_EQUALS( errors.wcError->getErrCode(), ErrorCodes::WriteConcernFailed );
- }
-
- TEST(GLEParsing, ReplErr) {
-
- const BSONObj gleResponse = fromjson( "{ok: 1.0, err: 'norepl', wnote: 'message'}" );
-
- GLEErrors errors;
- ASSERT_OK( extractGLEErrors( gleResponse, &errors ) );
- ASSERT( !errors.writeError.get() );
- ASSERT( errors.wcError.get() );
- ASSERT_EQUALS( errors.wcError->getErrMessage(), "message" );
- ASSERT_EQUALS( errors.wcError->getErrCode(), ErrorCodes::WriteConcernFailed );
- }
-
- TEST(GLEParsing, ReplTimeoutErr) {
-
- const BSONObj gleResponse =
- fromjson( "{ok: 1.0, err: 'timeout', errmsg: 'message', wtimeout: true}" );
-
- GLEErrors errors;
- ASSERT_OK( extractGLEErrors( gleResponse, &errors ) );
- ASSERT( !errors.writeError.get() );
- ASSERT( errors.wcError.get() );
- ASSERT_EQUALS( errors.wcError->getErrMessage(), "message" );
- ASSERT( errors.wcError->getErrInfo()["wtimeout"].trueValue() );
- ASSERT_EQUALS( errors.wcError->getErrCode(), ErrorCodes::WriteConcernFailed );
- }
-
- TEST(GLEParsing, GLEFail) {
-
- const BSONObj gleResponse =
- fromjson( "{ok: 0.0, err: null, errmsg: 'message', code: 1000}" );
-
- GLEErrors errors;
- Status status = extractGLEErrors( gleResponse, &errors );
- ASSERT_NOT_OK( status );
- ASSERT_EQUALS( status.reason(), "message" );
- ASSERT_EQUALS( status.code(), 1000 );
- }
-
- TEST(GLEParsing, GLEFailNoCode) {
+TEST(LegacyGLESuppress, BasicStats) {
+ const BSONObj gleResponse = fromjson(
+ "{ok: 0.0, err: 'message',"
+ " n: 1, nModified: 1, upserted: 'abc', updatedExisting: true}");
- const BSONObj gleResponse = fromjson( "{ok: 0.0, err: null, errmsg: 'message'}" );
-
- GLEErrors errors;
- Status status = extractGLEErrors( gleResponse, &errors );
- ASSERT_NOT_OK( status );
- ASSERT_EQUALS( status.reason(), "message" );
- ASSERT_EQUALS( status.code(), ErrorCodes::UnknownError );
- }
-
- TEST(GLEParsing, NotMasterGLEFail) {
-
- // Not master code in response
- const BSONObj gleResponse =
- fromjson( "{ok: 0.0, err: null, errmsg: 'message', code: 10990}" );
-
- GLEErrors errors;
- ASSERT_OK( extractGLEErrors( gleResponse, &errors ) );
- ASSERT( !errors.writeError.get() );
- ASSERT( errors.wcError.get() );
- ASSERT_EQUALS( errors.wcError->getErrMessage(), "message" );
- ASSERT_EQUALS( errors.wcError->getErrCode(), 10990 );
- }
-
- TEST(GLEParsing, WriteErrWithStats) {
- const BSONObj gleResponse = fromjson( "{ok: 1.0, n: 2, err: 'message', code: 1000}" );
-
- GLEErrors errors;
- ASSERT_OK( extractGLEErrors( gleResponse, &errors ) );
- ASSERT( errors.writeError.get() );
- ASSERT_EQUALS( errors.writeError->getErrMessage(), "message" );
- ASSERT_EQUALS( errors.writeError->getErrCode(), 1000 );
- ASSERT( !errors.wcError.get() );
- }
-
- TEST(GLEParsing, ReplTimeoutErrWithStats) {
- const BSONObj gleResponse =
- fromjson( "{ok: 1.0, err: 'timeout', errmsg: 'message', wtimeout: true,"
- " n: 1, upserted: 'abcde'}" );
-
- GLEErrors errors;
- ASSERT_OK( extractGLEErrors( gleResponse, &errors ) );
- ASSERT( !errors.writeError.get() );
- ASSERT( errors.wcError.get() );
- ASSERT_EQUALS( errors.wcError->getErrMessage(), "message" );
- ASSERT( errors.wcError->getErrInfo()["wtimeout"].trueValue() );
- ASSERT_EQUALS( errors.wcError->getErrCode(), ErrorCodes::WriteConcernFailed );
- }
-
- //
- // Tests of processing and suppressing non-WC related fields from legacy GLE responses
- //
-
- TEST(LegacyGLESuppress, Basic) {
-
- const BSONObj gleResponse = fromjson( "{ok: 1.0, err: null}" );
-
- BSONObj stripped = stripNonWCInfo( gleResponse );
- ASSERT_EQUALS( stripped.nFields(), 2 ); // with err, ok : true
- ASSERT( stripped["ok"].trueValue() );
- }
-
- TEST(LegacyGLESuppress, BasicStats) {
-
- const BSONObj gleResponse =
- fromjson( "{ok: 0.0, err: 'message',"
- " n: 1, nModified: 1, upserted: 'abc', updatedExisting: true}" );
-
- BSONObj stripped = stripNonWCInfo( gleResponse );
- ASSERT_EQUALS( stripped.nFields(), 1 );
- ASSERT( !stripped["ok"].trueValue() );
- }
-
- TEST(LegacyGLESuppress, ReplError) {
-
- const BSONObj gleResponse =
- fromjson( "{ok: 0.0, err: 'norepl', n: 1, wcField: true}" );
-
- BSONObj stripped = stripNonWCInfo( gleResponse );
- ASSERT_EQUALS( stripped.nFields(), 3 );
- ASSERT( !stripped["ok"].trueValue() );
- ASSERT_EQUALS( stripped["err"].str(), "norepl" );
- ASSERT( stripped["wcField"].trueValue() );
- }
-
- TEST(LegacyGLESuppress, StripCode) {
-
- const BSONObj gleResponse =
- fromjson( "{ok: 1.0, err: 'message', code: 12345}" );
-
- BSONObj stripped = stripNonWCInfo( gleResponse );
- ASSERT_EQUALS( stripped.nFields(), 2 ); // with err, ok : true
- ASSERT( stripped["ok"].trueValue() );
- }
-
- TEST(LegacyGLESuppress, TimeoutDupError24) {
-
- const BSONObj gleResponse =
- BSON( "ok" << 0.0 << "err" << "message" << "code" << 12345
- << "err" << "timeout" << "code" << 56789 << "wtimeout" << true );
-
- BSONObj stripped = stripNonWCInfo( gleResponse );
- ASSERT_EQUALS( stripped.nFields(), 4 );
- ASSERT( !stripped["ok"].trueValue() );
- ASSERT_EQUALS( stripped["err"].str(), "timeout" );
- ASSERT_EQUALS( stripped["code"].numberInt(), 56789 );
- ASSERT( stripped["wtimeout"].trueValue() );
- }
+ BSONObj stripped = stripNonWCInfo(gleResponse);
+ ASSERT_EQUALS(stripped.nFields(), 1);
+ ASSERT(!stripped["ok"].trueValue());
+}
+
+TEST(LegacyGLESuppress, ReplError) {
+ const BSONObj gleResponse = fromjson("{ok: 0.0, err: 'norepl', n: 1, wcField: true}");
+
+ BSONObj stripped = stripNonWCInfo(gleResponse);
+ ASSERT_EQUALS(stripped.nFields(), 3);
+ ASSERT(!stripped["ok"].trueValue());
+ ASSERT_EQUALS(stripped["err"].str(), "norepl");
+ ASSERT(stripped["wcField"].trueValue());
+}
+
+TEST(LegacyGLESuppress, StripCode) {
+ const BSONObj gleResponse = fromjson("{ok: 1.0, err: 'message', code: 12345}");
+
+ BSONObj stripped = stripNonWCInfo(gleResponse);
+ ASSERT_EQUALS(stripped.nFields(), 2); // with err, ok : true
+ ASSERT(stripped["ok"].trueValue());
+}
+
+TEST(LegacyGLESuppress, TimeoutDupError24) {
+ const BSONObj gleResponse = BSON("ok" << 0.0 << "err"
+ << "message"
+ << "code" << 12345 << "err"
+ << "timeout"
+ << "code" << 56789 << "wtimeout" << true);
+
+ BSONObj stripped = stripNonWCInfo(gleResponse);
+ ASSERT_EQUALS(stripped.nFields(), 4);
+ ASSERT(!stripped["ok"].trueValue());
+ ASSERT_EQUALS(stripped["err"].str(), "timeout");
+ ASSERT_EQUALS(stripped["code"].numberInt(), 56789);
+ ASSERT(stripped["wtimeout"].trueValue());
+}
}
diff --git a/src/mongo/s/write_ops/batch_upconvert.cpp b/src/mongo/s/write_ops/batch_upconvert.cpp
index ee02c17744a..6c7fcacd361 100644
--- a/src/mongo/s/write_ops/batch_upconvert.cpp
+++ b/src/mongo/s/write_ops/batch_upconvert.cpp
@@ -43,201 +43,184 @@
namespace mongo {
- using mongoutils::str::stream;
- using std::string;
- using std::unique_ptr;
- using std::vector;
-
- void msgToBatchRequests( const Message& msg, vector<BatchedCommandRequest*>* requests ) {
-
- int opType = msg.operation();
-
- unique_ptr<BatchedCommandRequest> request;
- if ( opType == dbInsert ) {
- msgToBatchInserts( msg, requests );
- }
- else if ( opType == dbUpdate ) {
- requests->push_back( msgToBatchUpdate( msg ) );
- }
- else {
- dassert( opType == dbDelete );
- requests->push_back( msgToBatchDelete( msg ) );
- }
+using mongoutils::str::stream;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+void msgToBatchRequests(const Message& msg, vector<BatchedCommandRequest*>* requests) {
+ int opType = msg.operation();
+
+ unique_ptr<BatchedCommandRequest> request;
+ if (opType == dbInsert) {
+ msgToBatchInserts(msg, requests);
+ } else if (opType == dbUpdate) {
+ requests->push_back(msgToBatchUpdate(msg));
+ } else {
+ dassert(opType == dbDelete);
+ requests->push_back(msgToBatchDelete(msg));
}
+}
- void msgToBatchInserts( const Message& insertMsg,
- vector<BatchedCommandRequest*>* insertRequests ) {
-
- // Parsing DbMessage throws
- DbMessage dbMsg( insertMsg );
- NamespaceString nss( dbMsg.getns() );
-
- // Continue-on-error == unordered
- bool coe = dbMsg.reservedField() & Reserved_InsertOption_ContinueOnError;
- bool ordered = !coe;
-
- while ( insertRequests->empty() || dbMsg.moreJSObjs() ) {
-
- // Collect docs for next batch, but don't exceed maximum size
- int totalInsertSize = 0;
- vector<BSONObj> docs;
- do {
- const char* prevObjMark = dbMsg.markGet();
- BSONObj nextObj = dbMsg.nextJsObj();
- if ( totalInsertSize + nextObj.objsize() <= BSONObjMaxUserSize ) {
- docs.push_back( nextObj );
- totalInsertSize += docs.back().objsize();
- }
- else {
- // Size limit exceeded, rollback to previous insert position
- dbMsg.markReset( prevObjMark );
- break;
- }
+void msgToBatchInserts(const Message& insertMsg, vector<BatchedCommandRequest*>* insertRequests) {
+ // Parsing DbMessage throws
+ DbMessage dbMsg(insertMsg);
+ NamespaceString nss(dbMsg.getns());
+
+ // Continue-on-error == unordered
+ bool coe = dbMsg.reservedField() & Reserved_InsertOption_ContinueOnError;
+ bool ordered = !coe;
+
+ while (insertRequests->empty() || dbMsg.moreJSObjs()) {
+ // Collect docs for next batch, but don't exceed maximum size
+ int totalInsertSize = 0;
+ vector<BSONObj> docs;
+ do {
+ const char* prevObjMark = dbMsg.markGet();
+ BSONObj nextObj = dbMsg.nextJsObj();
+ if (totalInsertSize + nextObj.objsize() <= BSONObjMaxUserSize) {
+ docs.push_back(nextObj);
+ totalInsertSize += docs.back().objsize();
+ } else {
+ // Size limit exceeded, rollback to previous insert position
+ dbMsg.markReset(prevObjMark);
+ break;
}
- while ( docs.size() < BatchedCommandRequest::kMaxWriteBatchSize
- && dbMsg.moreJSObjs() );
+ } while (docs.size() < BatchedCommandRequest::kMaxWriteBatchSize && dbMsg.moreJSObjs());
- dassert( !docs.empty() );
-
- // No exceptions from here on
- BatchedCommandRequest* request =
- new BatchedCommandRequest( BatchedCommandRequest::BatchType_Insert );
- request->setNSS( nss );
- for ( vector<BSONObj>::const_iterator it = docs.begin(); it != docs.end(); ++it ) {
- request->getInsertRequest()->addToDocuments( *it );
- }
- request->setOrdered( ordered );
- request->setWriteConcern( WriteConcernOptions::Acknowledged );
-
- insertRequests->push_back( request );
- }
- }
-
- BatchedCommandRequest* msgToBatchUpdate( const Message& updateMsg ) {
-
- // Parsing DbMessage throws
- DbMessage dbMsg( updateMsg );
- NamespaceString nss( dbMsg.getns() );
- int flags = dbMsg.pullInt();
- bool upsert = flags & UpdateOption_Upsert;
- bool multi = flags & UpdateOption_Multi;
- const BSONObj query = dbMsg.nextJsObj();
- const BSONObj updateExpr = dbMsg.nextJsObj();
+ dassert(!docs.empty());
// No exceptions from here on
- BatchedUpdateDocument* updateDoc = new BatchedUpdateDocument;
- updateDoc->setQuery( query );
- updateDoc->setUpdateExpr( updateExpr );
- updateDoc->setUpsert( upsert );
- updateDoc->setMulti( multi );
-
BatchedCommandRequest* request =
- new BatchedCommandRequest( BatchedCommandRequest::BatchType_Update );
- request->setNSS( nss );
- request->getUpdateRequest()->addToUpdates( updateDoc );
- request->setWriteConcern( WriteConcernOptions::Acknowledged );
+ new BatchedCommandRequest(BatchedCommandRequest::BatchType_Insert);
+ request->setNSS(nss);
+ for (vector<BSONObj>::const_iterator it = docs.begin(); it != docs.end(); ++it) {
+ request->getInsertRequest()->addToDocuments(*it);
+ }
+ request->setOrdered(ordered);
+ request->setWriteConcern(WriteConcernOptions::Acknowledged);
- return request;
+ insertRequests->push_back(request);
}
+}
- BatchedCommandRequest* msgToBatchDelete( const Message& deleteMsg ) {
-
- // Parsing DbMessage throws
- DbMessage dbMsg( deleteMsg );
- NamespaceString nss( dbMsg.getns() );
- int flags = dbMsg.pullInt();
- const BSONObj query = dbMsg.nextJsObj();
- int limit = ( flags & RemoveOption_JustOne ) ? 1 : 0;
+BatchedCommandRequest* msgToBatchUpdate(const Message& updateMsg) {
+ // Parsing DbMessage throws
+ DbMessage dbMsg(updateMsg);
+ NamespaceString nss(dbMsg.getns());
+ int flags = dbMsg.pullInt();
+ bool upsert = flags & UpdateOption_Upsert;
+ bool multi = flags & UpdateOption_Multi;
+ const BSONObj query = dbMsg.nextJsObj();
+ const BSONObj updateExpr = dbMsg.nextJsObj();
+
+ // No exceptions from here on
+ BatchedUpdateDocument* updateDoc = new BatchedUpdateDocument;
+ updateDoc->setQuery(query);
+ updateDoc->setUpdateExpr(updateExpr);
+ updateDoc->setUpsert(upsert);
+ updateDoc->setMulti(multi);
+
+ BatchedCommandRequest* request =
+ new BatchedCommandRequest(BatchedCommandRequest::BatchType_Update);
+ request->setNSS(nss);
+ request->getUpdateRequest()->addToUpdates(updateDoc);
+ request->setWriteConcern(WriteConcernOptions::Acknowledged);
+
+ return request;
+}
- // No exceptions from here on
- BatchedDeleteDocument* deleteDoc = new BatchedDeleteDocument;
- deleteDoc->setLimit( limit );
- deleteDoc->setQuery( query );
+BatchedCommandRequest* msgToBatchDelete(const Message& deleteMsg) {
+ // Parsing DbMessage throws
+ DbMessage dbMsg(deleteMsg);
+ NamespaceString nss(dbMsg.getns());
+ int flags = dbMsg.pullInt();
+ const BSONObj query = dbMsg.nextJsObj();
+ int limit = (flags & RemoveOption_JustOne) ? 1 : 0;
+
+ // No exceptions from here on
+ BatchedDeleteDocument* deleteDoc = new BatchedDeleteDocument;
+ deleteDoc->setLimit(limit);
+ deleteDoc->setQuery(query);
+
+ BatchedCommandRequest* request =
+ new BatchedCommandRequest(BatchedCommandRequest::BatchType_Delete);
+ request->setNSS(nss);
+ request->getDeleteRequest()->addToDeletes(deleteDoc);
+ request->setWriteConcern(WriteConcernOptions::Acknowledged);
+
+ return request;
+}
- BatchedCommandRequest* request =
- new BatchedCommandRequest( BatchedCommandRequest::BatchType_Delete );
- request->setNSS( nss );
- request->getDeleteRequest()->addToDeletes( deleteDoc );
- request->setWriteConcern( WriteConcernOptions::Acknowledged );
+void buildErrorFromResponse(const BatchedCommandResponse& response, WriteErrorDetail* error) {
+ error->setErrCode(response.getErrCode());
+ error->setErrMessage(response.getErrMessage());
+}
- return request;
+bool batchErrorToLastError(const BatchedCommandRequest& request,
+ const BatchedCommandResponse& response,
+ LastError* error) {
+ unique_ptr<WriteErrorDetail> commandError;
+ WriteErrorDetail* lastBatchError = NULL;
+
+ if (!response.getOk()) {
+ // Command-level error, all writes failed
+
+ commandError.reset(new WriteErrorDetail);
+ buildErrorFromResponse(response, commandError.get());
+ lastBatchError = commandError.get();
+ } else if (response.isErrDetailsSet()) {
+ // The last error in the batch is always reported - this matches expected COE
+ // semantics for insert batches. For updates and deletes, error is only reported
+ // if the error was on the last item.
+
+ const bool lastOpErrored = response.getErrDetails().back()->getIndex() ==
+ static_cast<int>(request.sizeWriteOps() - 1);
+ if (request.getBatchType() == BatchedCommandRequest::BatchType_Insert || lastOpErrored) {
+ lastBatchError = response.getErrDetails().back();
+ }
+ } else {
+ // We don't care about write concern errors, these happen in legacy mode in GLE.
}
- void buildErrorFromResponse( const BatchedCommandResponse& response, WriteErrorDetail* error ) {
- error->setErrCode( response.getErrCode() );
- error->setErrMessage( response.getErrMessage() );
+ // Record an error if one exists
+ if (lastBatchError) {
+ string errMsg = lastBatchError->getErrMessage();
+ error->setLastError(lastBatchError->getErrCode(),
+ errMsg.empty() ? "see code for details" : errMsg.c_str());
+ return true;
}
- bool batchErrorToLastError( const BatchedCommandRequest& request,
- const BatchedCommandResponse& response,
- LastError* error ) {
-
- unique_ptr<WriteErrorDetail> commandError;
- WriteErrorDetail* lastBatchError = NULL;
-
- if ( !response.getOk() ) {
- // Command-level error, all writes failed
-
- commandError.reset( new WriteErrorDetail );
- buildErrorFromResponse( response, commandError.get() );
- lastBatchError = commandError.get();
- }
- else if ( response.isErrDetailsSet() ) {
- // The last error in the batch is always reported - this matches expected COE
- // semantics for insert batches. For updates and deletes, error is only reported
- // if the error was on the last item.
-
- const bool lastOpErrored = response.getErrDetails().back()->getIndex() ==
- static_cast<int>(request.sizeWriteOps() - 1);
- if ( request.getBatchType() == BatchedCommandRequest::BatchType_Insert ||
- lastOpErrored ) {
- lastBatchError = response.getErrDetails().back();
+ // Record write stats otherwise
+ // NOTE: For multi-write batches, our semantics change a little because we don't have
+ // un-aggregated "n" stats.
+ if (request.getBatchType() == BatchedCommandRequest::BatchType_Update) {
+ BSONObj upsertedId;
+ if (response.isUpsertDetailsSet()) {
+ // Only report the very last item's upserted id if applicable
+ if (response.getUpsertDetails().back()->getIndex() + 1 ==
+ static_cast<int>(request.sizeWriteOps())) {
+ upsertedId = response.getUpsertDetails().back()->getUpsertedID();
}
}
- else {
- // We don't care about write concern errors, these happen in legacy mode in GLE.
- }
- // Record an error if one exists
- if ( lastBatchError ) {
- string errMsg = lastBatchError->getErrMessage();
- error->setLastError(lastBatchError->getErrCode(),
- errMsg.empty() ? "see code for details" : errMsg.c_str());
- return true;
- }
-
- // Record write stats otherwise
- // NOTE: For multi-write batches, our semantics change a little because we don't have
- // un-aggregated "n" stats.
- if ( request.getBatchType() == BatchedCommandRequest::BatchType_Update ) {
-
- BSONObj upsertedId;
- if( response.isUpsertDetailsSet() ) {
- // Only report the very last item's upserted id if applicable
- if ( response.getUpsertDetails().back()->getIndex() + 1
- == static_cast<int>( request.sizeWriteOps() ) ) {
- upsertedId = response.getUpsertDetails().back()->getUpsertedID();
- }
- }
+ int numUpserted = 0;
+ if (response.isUpsertDetailsSet())
+ numUpserted = response.sizeUpsertDetails();
- int numUpserted = 0;
- if ( response.isUpsertDetailsSet() )
- numUpserted = response.sizeUpsertDetails();
+ int numMatched = response.getN() - numUpserted;
+ dassert(numMatched >= 0);
- int numMatched = response.getN() - numUpserted;
- dassert( numMatched >= 0 );
+ // Wrap upserted id in "upserted" field
+ BSONObj leUpsertedId;
+ if (!upsertedId.isEmpty())
+ leUpsertedId = upsertedId.firstElement().wrap(kUpsertedFieldName);
- // Wrap upserted id in "upserted" field
- BSONObj leUpsertedId;
- if ( !upsertedId.isEmpty() )
- leUpsertedId = upsertedId.firstElement().wrap( kUpsertedFieldName );
-
- error->recordUpdate( numMatched > 0, response.getN(), leUpsertedId );
- }
- else if ( request.getBatchType() == BatchedCommandRequest::BatchType_Delete ) {
- error->recordDelete( response.getN() );
- }
-
- return false;
+ error->recordUpdate(numMatched > 0, response.getN(), leUpsertedId);
+ } else if (request.getBatchType() == BatchedCommandRequest::BatchType_Delete) {
+ error->recordDelete(response.getN());
}
+
+ return false;
+}
}
diff --git a/src/mongo/s/write_ops/batch_upconvert.h b/src/mongo/s/write_ops/batch_upconvert.h
index 46b6d4552b6..a0b4712cf96 100644
--- a/src/mongo/s/write_ops/batch_upconvert.h
+++ b/src/mongo/s/write_ops/batch_upconvert.h
@@ -37,29 +37,28 @@
namespace mongo {
- //
- // Utility functions for up-converting incoming write messages into batch write requests.
- // NOTE: These functions throw on invalid message format.
- //
+//
+// Utility functions for up-converting incoming write messages into batch write requests.
+// NOTE: These functions throw on invalid message format.
+//
- void msgToBatchRequests( const Message& msg, std::vector<BatchedCommandRequest*>* requests );
+void msgToBatchRequests(const Message& msg, std::vector<BatchedCommandRequest*>* requests);
- // Batch inserts may get mapped to multiple batch requests, to avoid spilling MaxBSONObjSize
- void msgToBatchInserts( const Message& insertMsg,
- std::vector<BatchedCommandRequest*>* insertRequests );
+// Batch inserts may get mapped to multiple batch requests, to avoid spilling MaxBSONObjSize
+void msgToBatchInserts(const Message& insertMsg,
+ std::vector<BatchedCommandRequest*>* insertRequests);
- BatchedCommandRequest* msgToBatchUpdate( const Message& updateMsg );
+BatchedCommandRequest* msgToBatchUpdate(const Message& updateMsg);
- BatchedCommandRequest* msgToBatchDelete( const Message& deleteMsg );
-
- /**
- * Utility function for recording completed batch writes into the LastError object.
- * (Interpreting the response requires the request object as well.)
- *
- * Returns true if an error occurred in the batch.
- */
- bool batchErrorToLastError( const BatchedCommandRequest& request,
- const BatchedCommandResponse& response,
- LastError* error );
+BatchedCommandRequest* msgToBatchDelete(const Message& deleteMsg);
+/**
+ * Utility function for recording completed batch writes into the LastError object.
+ * (Interpreting the response requires the request object as well.)
+ *
+ * Returns true if an error occurred in the batch.
+ */
+bool batchErrorToLastError(const BatchedCommandRequest& request,
+ const BatchedCommandResponse& response,
+ LastError* error);
}
diff --git a/src/mongo/s/write_ops/batch_upconvert_test.cpp b/src/mongo/s/write_ops/batch_upconvert_test.cpp
index c802411c9e5..00d485818fe 100644
--- a/src/mongo/s/write_ops/batch_upconvert_test.cpp
+++ b/src/mongo/s/write_ops/batch_upconvert_test.cpp
@@ -33,7 +33,7 @@
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/bson/util/builder.h"
-#include "mongo/client/dbclientinterface.h" // for write constants
+#include "mongo/client/dbclientinterface.h" // for write constants
#include "mongo/db/write_concern_options.h"
#include "mongo/s/write_ops/batched_command_request.h"
#include "mongo/unittest/unittest.h"
@@ -41,112 +41,109 @@
namespace {
- using std::string;
- using std::vector;
-
- using namespace mongo;
-
- TEST(WriteBatchUpconvert, BasicInsert) {
-
- // Tests that an insert message is correctly upconverted to a batch insert
-
- const string ns = "foo.bar";
- const BSONObj doc = BSON( "hello" << "world" );
-
- Message insertMsg;
- BufBuilder insertMsgB;
-
- int reservedFlags = InsertOption_ContinueOnError;
- insertMsgB.appendNum( reservedFlags );
- insertMsgB.appendStr( ns );
- doc.appendSelfToBufBuilder( insertMsgB );
- insertMsg.setData( dbInsert, insertMsgB.buf(), insertMsgB.len() );
-
- OwnedPointerVector<BatchedCommandRequest> requestsOwned;
- vector<BatchedCommandRequest*>& requests = requestsOwned.mutableVector();
- msgToBatchRequests( insertMsg, &requests );
-
- BatchedCommandRequest* request = requests.back();
- ASSERT_EQUALS( request->getBatchType(), BatchedCommandRequest::BatchType_Insert );
- string errMsg;
- ASSERT( request->isValid( &errMsg ) );
- ASSERT_EQUALS( request->getNS(), ns );
- ASSERT( !request->getOrdered() );
- ASSERT_EQUALS( request->sizeWriteOps(), 1u );
- bool isSameDoc = doc.woCompare( request->getInsertRequest()->getDocumentsAt( 0 ) ) == 0;
- ASSERT( isSameDoc );
- ASSERT( request->getWriteConcern().woCompare( WriteConcernOptions::Acknowledged ) == 0 );
- }
-
- TEST(WriteBatchUpconvert, BasicUpdate) {
-
- // Tests that an update message is correctly upconverted to a batch update
-
- const string ns = "foo.bar";
- const BSONObj query = BSON( "hello" << "world" );
- const BSONObj update = BSON( "$set" << BSON( "hello" << "world" ) );
-
- Message updateMsg;
- BufBuilder updateMsgB;
-
- int reservedFlags = 0;
- updateMsgB.appendNum( reservedFlags );
- updateMsgB.appendStr( ns );
- updateMsgB.appendNum( UpdateOption_Upsert );
- query.appendSelfToBufBuilder( updateMsgB );
- update.appendSelfToBufBuilder( updateMsgB );
- updateMsg.setData( dbUpdate, updateMsgB.buf(), updateMsgB.len() );
-
- OwnedPointerVector<BatchedCommandRequest> requestsOwned;
- vector<BatchedCommandRequest*>& requests = requestsOwned.mutableVector();
- msgToBatchRequests( updateMsg, &requests );
-
- BatchedCommandRequest* request = requests.back();
- ASSERT_EQUALS( request->getBatchType(), BatchedCommandRequest::BatchType_Update );
- string errMsg;
- ASSERT( request->isValid( &errMsg ) );
- ASSERT_EQUALS( request->getNS(), ns );
- ASSERT_EQUALS( request->sizeWriteOps(), 1u );
- ASSERT( query.woCompare(
- request->getUpdateRequest()->getUpdatesAt( 0 )->getQuery() ) == 0 );
- ASSERT( update.woCompare(
- request->getUpdateRequest()->getUpdatesAt( 0 )->getUpdateExpr() ) == 0 );
- ASSERT( request->getUpdateRequest()->getUpdatesAt( 0 )->getUpsert() );
- ASSERT( !request->getUpdateRequest()->getUpdatesAt( 0 )->getMulti() );
- ASSERT( request->getWriteConcern().woCompare( WriteConcernOptions::Acknowledged ) == 0 );
- }
-
- TEST(WriteBatchUpconvert, BasicDelete) {
-
- // Tests that an remove message is correctly upconverted to a batch delete
-
- const string ns = "foo.bar";
- const BSONObj query = BSON( "hello" << "world" );
-
- Message deleteMsg;
- BufBuilder deleteMsgB;
-
- int reservedFlags = 0;
- deleteMsgB.appendNum( reservedFlags );
- deleteMsgB.appendStr( ns );
- deleteMsgB.appendNum( RemoveOption_JustOne );
- query.appendSelfToBufBuilder( deleteMsgB );
- deleteMsg.setData( dbDelete, deleteMsgB.buf(), deleteMsgB.len() );
-
- OwnedPointerVector<BatchedCommandRequest> requestsOwned;
- vector<BatchedCommandRequest*>& requests = requestsOwned.mutableVector();
- msgToBatchRequests( deleteMsg, &requests );
-
- BatchedCommandRequest* request = requests.back();
- ASSERT_EQUALS( request->getBatchType(), BatchedCommandRequest::BatchType_Delete );
- string errMsg;
- ASSERT( request->isValid( &errMsg ) );
- ASSERT_EQUALS( request->getNS(), ns );
- ASSERT_EQUALS( request->sizeWriteOps(), 1u );
- ASSERT( query.woCompare(
- request->getDeleteRequest()->getDeletesAt( 0 )->getQuery() ) == 0 );
- ASSERT( request->getDeleteRequest()->getDeletesAt( 0 )->getLimit() == 1 );
- ASSERT( request->getWriteConcern().woCompare( WriteConcernOptions::Acknowledged ) == 0 );
- }
+using std::string;
+using std::vector;
+
+using namespace mongo;
+
+TEST(WriteBatchUpconvert, BasicInsert) {
+ // Tests that an insert message is correctly upconverted to a batch insert
+
+ const string ns = "foo.bar";
+ const BSONObj doc = BSON("hello"
+ << "world");
+
+ Message insertMsg;
+ BufBuilder insertMsgB;
+
+ int reservedFlags = InsertOption_ContinueOnError;
+ insertMsgB.appendNum(reservedFlags);
+ insertMsgB.appendStr(ns);
+ doc.appendSelfToBufBuilder(insertMsgB);
+ insertMsg.setData(dbInsert, insertMsgB.buf(), insertMsgB.len());
+
+ OwnedPointerVector<BatchedCommandRequest> requestsOwned;
+ vector<BatchedCommandRequest*>& requests = requestsOwned.mutableVector();
+ msgToBatchRequests(insertMsg, &requests);
+
+ BatchedCommandRequest* request = requests.back();
+ ASSERT_EQUALS(request->getBatchType(), BatchedCommandRequest::BatchType_Insert);
+ string errMsg;
+ ASSERT(request->isValid(&errMsg));
+ ASSERT_EQUALS(request->getNS(), ns);
+ ASSERT(!request->getOrdered());
+ ASSERT_EQUALS(request->sizeWriteOps(), 1u);
+ bool isSameDoc = doc.woCompare(request->getInsertRequest()->getDocumentsAt(0)) == 0;
+ ASSERT(isSameDoc);
+ ASSERT(request->getWriteConcern().woCompare(WriteConcernOptions::Acknowledged) == 0);
+}
+
+TEST(WriteBatchUpconvert, BasicUpdate) {
+ // Tests that an update message is correctly upconverted to a batch update
+
+ const string ns = "foo.bar";
+ const BSONObj query = BSON("hello"
+ << "world");
+ const BSONObj update = BSON("$set" << BSON("hello"
+ << "world"));
+
+ Message updateMsg;
+ BufBuilder updateMsgB;
+
+ int reservedFlags = 0;
+ updateMsgB.appendNum(reservedFlags);
+ updateMsgB.appendStr(ns);
+ updateMsgB.appendNum(UpdateOption_Upsert);
+ query.appendSelfToBufBuilder(updateMsgB);
+ update.appendSelfToBufBuilder(updateMsgB);
+ updateMsg.setData(dbUpdate, updateMsgB.buf(), updateMsgB.len());
+
+ OwnedPointerVector<BatchedCommandRequest> requestsOwned;
+ vector<BatchedCommandRequest*>& requests = requestsOwned.mutableVector();
+ msgToBatchRequests(updateMsg, &requests);
+
+ BatchedCommandRequest* request = requests.back();
+ ASSERT_EQUALS(request->getBatchType(), BatchedCommandRequest::BatchType_Update);
+ string errMsg;
+ ASSERT(request->isValid(&errMsg));
+ ASSERT_EQUALS(request->getNS(), ns);
+ ASSERT_EQUALS(request->sizeWriteOps(), 1u);
+ ASSERT(query.woCompare(request->getUpdateRequest()->getUpdatesAt(0)->getQuery()) == 0);
+ ASSERT(update.woCompare(request->getUpdateRequest()->getUpdatesAt(0)->getUpdateExpr()) == 0);
+ ASSERT(request->getUpdateRequest()->getUpdatesAt(0)->getUpsert());
+ ASSERT(!request->getUpdateRequest()->getUpdatesAt(0)->getMulti());
+ ASSERT(request->getWriteConcern().woCompare(WriteConcernOptions::Acknowledged) == 0);
+}
+TEST(WriteBatchUpconvert, BasicDelete) {
+ // Tests that an remove message is correctly upconverted to a batch delete
+
+ const string ns = "foo.bar";
+ const BSONObj query = BSON("hello"
+ << "world");
+
+ Message deleteMsg;
+ BufBuilder deleteMsgB;
+
+ int reservedFlags = 0;
+ deleteMsgB.appendNum(reservedFlags);
+ deleteMsgB.appendStr(ns);
+ deleteMsgB.appendNum(RemoveOption_JustOne);
+ query.appendSelfToBufBuilder(deleteMsgB);
+ deleteMsg.setData(dbDelete, deleteMsgB.buf(), deleteMsgB.len());
+
+ OwnedPointerVector<BatchedCommandRequest> requestsOwned;
+ vector<BatchedCommandRequest*>& requests = requestsOwned.mutableVector();
+ msgToBatchRequests(deleteMsg, &requests);
+
+ BatchedCommandRequest* request = requests.back();
+ ASSERT_EQUALS(request->getBatchType(), BatchedCommandRequest::BatchType_Delete);
+ string errMsg;
+ ASSERT(request->isValid(&errMsg));
+ ASSERT_EQUALS(request->getNS(), ns);
+ ASSERT_EQUALS(request->sizeWriteOps(), 1u);
+ ASSERT(query.woCompare(request->getDeleteRequest()->getDeletesAt(0)->getQuery()) == 0);
+ ASSERT(request->getDeleteRequest()->getDeletesAt(0)->getLimit() == 1);
+ ASSERT(request->getWriteConcern().woCompare(WriteConcernOptions::Acknowledged) == 0);
+}
}
diff --git a/src/mongo/s/write_ops/batch_write_exec.cpp b/src/mongo/s/write_ops/batch_write_exec.cpp
index 307c39321ee..1fa89c034bc 100644
--- a/src/mongo/s/write_ops/batch_write_exec.cpp
+++ b/src/mongo/s/write_ops/batch_write_exec.cpp
@@ -36,7 +36,7 @@
#include "mongo/base/owned_pointer_map.h"
#include "mongo/base/status.h"
#include "mongo/bson/util/builder.h"
-#include "mongo/client/dbclientinterface.h" // ConnectionString (header-only)
+#include "mongo/client/dbclientinterface.h" // ConnectionString (header-only)
#include "mongo/s/client/multi_command_dispatch.h"
#include "mongo/s/write_ops/batch_write_op.h"
#include "mongo/s/write_ops/write_error_detail.h"
@@ -44,354 +44,337 @@
namespace mongo {
- using std::endl;
- using std::make_pair;
- using std::stringstream;
- using std::vector;
-
- BatchWriteExec::BatchWriteExec( NSTargeter* targeter,
- ShardResolver* resolver,
- MultiCommandDispatch* dispatcher ) :
- _targeter( targeter ),
- _resolver( resolver ),
- _dispatcher( dispatcher ),
- _stats( new BatchWriteExecStats ) {
- }
+using std::endl;
+using std::make_pair;
+using std::stringstream;
+using std::vector;
- namespace {
+BatchWriteExec::BatchWriteExec(NSTargeter* targeter,
+ ShardResolver* resolver,
+ MultiCommandDispatch* dispatcher)
+ : _targeter(targeter),
+ _resolver(resolver),
+ _dispatcher(dispatcher),
+ _stats(new BatchWriteExecStats) {}
- //
- // Map which allows associating ConnectionString hosts with TargetedWriteBatches
- // This is needed since the dispatcher only returns hosts with responses.
- //
+namespace {
- // TODO: Unordered map?
- typedef OwnedPointerMap<ConnectionString, TargetedWriteBatch> OwnedHostBatchMap;
- }
+//
+// Map which allows associating ConnectionString hosts with TargetedWriteBatches
+// This is needed since the dispatcher only returns hosts with responses.
+//
- static void buildErrorFrom( const Status& status, WriteErrorDetail* error ) {
- error->setErrCode( status.code() );
- error->setErrMessage( status.reason() );
- }
+// TODO: Unordered map?
+typedef OwnedPointerMap<ConnectionString, TargetedWriteBatch> OwnedHostBatchMap;
+}
- // Helper to note several stale errors from a response
- static void noteStaleResponses( const vector<ShardError*>& staleErrors, NSTargeter* targeter ) {
- for ( vector<ShardError*>::const_iterator it = staleErrors.begin(); it != staleErrors.end();
- ++it ) {
- const ShardError* error = *it;
- targeter->noteStaleResponse( error->endpoint,
- error->error.isErrInfoSet() ? error->error.getErrInfo() :
- BSONObj() );
- }
- }
+static void buildErrorFrom(const Status& status, WriteErrorDetail* error) {
+ error->setErrCode(status.code());
+ error->setErrMessage(status.reason());
+}
- static bool isShardMetadataChanging( const vector<ShardError*>& staleErrors ) {
- if ( !staleErrors.empty() && staleErrors.back()->error.isErrInfoSet() )
- return staleErrors.back()->error.getErrInfo()["inCriticalSection"].trueValue();
- return false;
+// Helper to note several stale errors from a response
+static void noteStaleResponses(const vector<ShardError*>& staleErrors, NSTargeter* targeter) {
+ for (vector<ShardError*>::const_iterator it = staleErrors.begin(); it != staleErrors.end();
+ ++it) {
+ const ShardError* error = *it;
+ targeter->noteStaleResponse(
+ error->endpoint, error->error.isErrInfoSet() ? error->error.getErrInfo() : BSONObj());
}
+}
- // The number of times we'll try to continue a batch op if no progress is being made
- // This only applies when no writes are occurring and metadata is not changing on reload
- static const int kMaxRoundsWithoutProgress( 5 );
+static bool isShardMetadataChanging(const vector<ShardError*>& staleErrors) {
+ if (!staleErrors.empty() && staleErrors.back()->error.isErrInfoSet())
+ return staleErrors.back()->error.getErrInfo()["inCriticalSection"].trueValue();
+ return false;
+}
- void BatchWriteExec::executeBatch( const BatchedCommandRequest& clientRequest,
- BatchedCommandResponse* clientResponse ) {
+// The number of times we'll try to continue a batch op if no progress is being made
+// This only applies when no writes are occurring and metadata is not changing on reload
+static const int kMaxRoundsWithoutProgress(5);
- LOG( 4 ) << "starting execution of write batch of size "
- << static_cast<int>( clientRequest.sizeWriteOps() )
- << " for " << clientRequest.getNS() << endl;
+void BatchWriteExec::executeBatch(const BatchedCommandRequest& clientRequest,
+ BatchedCommandResponse* clientResponse) {
+ LOG(4) << "starting execution of write batch of size "
+ << static_cast<int>(clientRequest.sizeWriteOps()) << " for " << clientRequest.getNS()
+ << endl;
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &clientRequest );
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&clientRequest);
- // Current batch status
- bool refreshedTargeter = false;
- int rounds = 0;
- int numCompletedOps = 0;
- int numRoundsWithoutProgress = 0;
+ // Current batch status
+ bool refreshedTargeter = false;
+ int rounds = 0;
+ int numCompletedOps = 0;
+ int numRoundsWithoutProgress = 0;
- while ( !batchOp.isFinished() ) {
+ while (!batchOp.isFinished()) {
+ //
+ // Get child batches to send using the targeter
+ //
+ // Targeting errors can be caused by remote metadata changing (the collection could have
+ // been dropped and recreated, for example with a new shard key). If a remote metadata
+ // change occurs *before* a client sends us a batch, we need to make sure that we don't
+ // error out just because we're staler than the client - otherwise mongos will be have
+ // unpredictable behavior.
+ //
+ // (If a metadata change happens *during* or *after* a client sends us a batch, however,
+ // we make no guarantees about delivery.)
+ //
+ // For this reason, we don't record targeting errors until we've refreshed our targeting
+ // metadata at least once *after* receiving the client batch - at that point, we know:
+ //
+ // 1) our new metadata is the same as the metadata when the client sent a batch, and so
+ // targeting errors are real.
+ // OR
+ // 2) our new metadata is a newer version than when the client sent a batch, and so
+ // the metadata must have changed after the client batch was sent. We don't need to
+ // deliver in this case, since for all the client knows we may have gotten the batch
+ // exactly when the metadata changed.
+ //
- //
- // Get child batches to send using the targeter
- //
- // Targeting errors can be caused by remote metadata changing (the collection could have
- // been dropped and recreated, for example with a new shard key). If a remote metadata
- // change occurs *before* a client sends us a batch, we need to make sure that we don't
- // error out just because we're staler than the client - otherwise mongos will be have
- // unpredictable behavior.
- //
- // (If a metadata change happens *during* or *after* a client sends us a batch, however,
- // we make no guarantees about delivery.)
- //
- // For this reason, we don't record targeting errors until we've refreshed our targeting
- // metadata at least once *after* receiving the client batch - at that point, we know:
- //
- // 1) our new metadata is the same as the metadata when the client sent a batch, and so
- // targeting errors are real.
- // OR
- // 2) our new metadata is a newer version than when the client sent a batch, and so
- // the metadata must have changed after the client batch was sent. We don't need to
- // deliver in this case, since for all the client knows we may have gotten the batch
- // exactly when the metadata changed.
- //
+ OwnedPointerVector<TargetedWriteBatch> childBatchesOwned;
+ vector<TargetedWriteBatch*>& childBatches = childBatchesOwned.mutableVector();
+
+ // If we've already had a targeting error, we've refreshed the metadata once and can
+ // record target errors definitively.
+ bool recordTargetErrors = refreshedTargeter;
+ Status targetStatus = batchOp.targetBatch(*_targeter, recordTargetErrors, &childBatches);
+ if (!targetStatus.isOK()) {
+ // Don't do anything until a targeter refresh
+ _targeter->noteCouldNotTarget();
+ refreshedTargeter = true;
+ ++_stats->numTargetErrors;
+ dassert(childBatches.size() == 0u);
+ }
- OwnedPointerVector<TargetedWriteBatch> childBatchesOwned;
- vector<TargetedWriteBatch*>& childBatches = childBatchesOwned.mutableVector();
-
- // If we've already had a targeting error, we've refreshed the metadata once and can
- // record target errors definitively.
- bool recordTargetErrors = refreshedTargeter;
- Status targetStatus = batchOp.targetBatch( *_targeter,
- recordTargetErrors,
- &childBatches );
- if ( !targetStatus.isOK() ) {
- // Don't do anything until a targeter refresh
- _targeter->noteCouldNotTarget();
- refreshedTargeter = true;
- ++_stats->numTargetErrors;
- dassert( childBatches.size() == 0u );
- }
+ //
+ // Send all child batches
+ //
+
+ size_t numSent = 0;
+ size_t numToSend = childBatches.size();
+ bool remoteMetadataChanging = false;
+ while (numSent != numToSend) {
+ // Collect batches out on the network, mapped by endpoint
+ OwnedHostBatchMap ownedPendingBatches;
+ OwnedHostBatchMap::MapType& pendingBatches = ownedPendingBatches.mutableMap();
//
- // Send all child batches
+ // Send side
//
- size_t numSent = 0;
- size_t numToSend = childBatches.size();
- bool remoteMetadataChanging = false;
- while ( numSent != numToSend ) {
-
- // Collect batches out on the network, mapped by endpoint
- OwnedHostBatchMap ownedPendingBatches;
- OwnedHostBatchMap::MapType& pendingBatches = ownedPendingBatches.mutableMap();
-
+ // Get as many batches as we can at once
+ for (vector<TargetedWriteBatch*>::iterator it = childBatches.begin();
+ it != childBatches.end();
+ ++it) {
//
- // Send side
+ // Collect the info needed to dispatch our targeted batch
//
- // Get as many batches as we can at once
- for ( vector<TargetedWriteBatch*>::iterator it = childBatches.begin();
- it != childBatches.end(); ++it ) {
-
- //
- // Collect the info needed to dispatch our targeted batch
- //
-
- TargetedWriteBatch* nextBatch = *it;
- // If the batch is NULL, we sent it previously, so skip
- if ( nextBatch == NULL ) continue;
-
- // Figure out what host we need to dispatch our targeted batch
- ConnectionString shardHost;
- Status resolveStatus = _resolver->chooseWriteHost( nextBatch->getEndpoint()
- .shardName,
- &shardHost );
- if ( !resolveStatus.isOK() ) {
-
- ++_stats->numResolveErrors;
-
- // Record a resolve failure
- // TODO: It may be necessary to refresh the cache if stale, or maybe just
- // cancel and retarget the batch
- WriteErrorDetail error;
- buildErrorFrom( resolveStatus, &error );
-
- LOG( 4 ) << "unable to send write batch to " << shardHost.toString()
- << causedBy( resolveStatus.toString() ) << endl;
-
- batchOp.noteBatchError( *nextBatch, error );
-
- // We're done with this batch
- // Clean up when we can't resolve a host
- delete *it;
- *it = NULL;
- --numToSend;
- continue;
- }
-
- // If we already have a batch for this host, wait until the next time
- OwnedHostBatchMap::MapType::iterator pendingIt = pendingBatches.find( shardHost );
- if ( pendingIt != pendingBatches.end() ) continue;
-
- //
- // We now have all the info needed to dispatch the batch
- //
+ TargetedWriteBatch* nextBatch = *it;
+ // If the batch is NULL, we sent it previously, so skip
+ if (nextBatch == NULL)
+ continue;
- BatchedCommandRequest request( clientRequest.getBatchType() );
- batchOp.buildBatchRequest( *nextBatch, &request );
+ // Figure out what host we need to dispatch our targeted batch
+ ConnectionString shardHost;
+ Status resolveStatus =
+ _resolver->chooseWriteHost(nextBatch->getEndpoint().shardName, &shardHost);
+ if (!resolveStatus.isOK()) {
+ ++_stats->numResolveErrors;
- // Internally we use full namespaces for request/response, but we send the
- // command to a database with the collection name in the request.
- NamespaceString nss( request.getNS() );
- request.setNS( nss.coll() );
+ // Record a resolve failure
+ // TODO: It may be necessary to refresh the cache if stale, or maybe just
+ // cancel and retarget the batch
+ WriteErrorDetail error;
+ buildErrorFrom(resolveStatus, &error);
- LOG( 4 ) << "sending write batch to " << shardHost.toString() << ": "
- << request.toString() << endl;
+ LOG(4) << "unable to send write batch to " << shardHost.toString()
+ << causedBy(resolveStatus.toString()) << endl;
- _dispatcher->addCommand( shardHost, nss.db(), request );
+ batchOp.noteBatchError(*nextBatch, error);
- // Indicate we're done by setting the batch to NULL
- // We'll only get duplicate hostEndpoints if we have broadcast and non-broadcast
- // endpoints for the same host, so this should be pretty efficient without
- // moving stuff around.
+ // We're done with this batch
+ // Clean up when we can't resolve a host
+ delete *it;
*it = NULL;
-
- // Recv-side is responsible for cleaning up the nextBatch when used
- pendingBatches.insert( make_pair( shardHost, nextBatch ) );
+ --numToSend;
+ continue;
}
- // Send them all out
- _dispatcher->sendAll();
- numSent += pendingBatches.size();
+ // If we already have a batch for this host, wait until the next time
+ OwnedHostBatchMap::MapType::iterator pendingIt = pendingBatches.find(shardHost);
+ if (pendingIt != pendingBatches.end())
+ continue;
//
- // Recv side
+ // We now have all the info needed to dispatch the batch
//
- while ( _dispatcher->numPending() > 0 ) {
+ BatchedCommandRequest request(clientRequest.getBatchType());
+ batchOp.buildBatchRequest(*nextBatch, &request);
- // Get the response
- ConnectionString shardHost;
- BatchedCommandResponse response;
- Status dispatchStatus = _dispatcher->recvAny( &shardHost, &response );
+ // Internally we use full namespaces for request/response, but we send the
+ // command to a database with the collection name in the request.
+ NamespaceString nss(request.getNS());
+ request.setNS(nss.coll());
- // Get the TargetedWriteBatch to find where to put the response
- dassert( pendingBatches.find( shardHost ) != pendingBatches.end() );
- TargetedWriteBatch* batch = pendingBatches.find( shardHost )->second;
+ LOG(4) << "sending write batch to " << shardHost.toString() << ": "
+ << request.toString() << endl;
- if ( dispatchStatus.isOK() ) {
+ _dispatcher->addCommand(shardHost, nss.db(), request);
- TrackedErrors trackedErrors;
- trackedErrors.startTracking( ErrorCodes::StaleShardVersion );
+ // Indicate we're done by setting the batch to NULL
+ // We'll only get duplicate hostEndpoints if we have broadcast and non-broadcast
+ // endpoints for the same host, so this should be pretty efficient without
+ // moving stuff around.
+ *it = NULL;
- LOG( 4 ) << "write results received from " << shardHost.toString() << ": "
- << response.toString() << endl;
-
- // Dispatch was ok, note response
- batchOp.noteBatchResponse( *batch, response, &trackedErrors );
+ // Recv-side is responsible for cleaning up the nextBatch when used
+ pendingBatches.insert(make_pair(shardHost, nextBatch));
+ }
- // Note if anything was stale
- const vector<ShardError*>& staleErrors =
- trackedErrors.getErrors( ErrorCodes::StaleShardVersion );
+ // Send them all out
+ _dispatcher->sendAll();
+ numSent += pendingBatches.size();
- if ( staleErrors.size() > 0 ) {
- noteStaleResponses( staleErrors, _targeter );
- ++_stats->numStaleBatches;
- }
+ //
+ // Recv side
+ //
- // Remember if the shard is actively changing metadata right now
- if ( isShardMetadataChanging( staleErrors ) ) {
- remoteMetadataChanging = true;
- }
+ while (_dispatcher->numPending() > 0) {
+ // Get the response
+ ConnectionString shardHost;
+ BatchedCommandResponse response;
+ Status dispatchStatus = _dispatcher->recvAny(&shardHost, &response);
- // Remember that we successfully wrote to this shard
- // NOTE: This will record lastOps for shards where we actually didn't update
- // or delete any documents, which preserves old behavior but is conservative
- _stats->noteWriteAt( shardHost,
- response.isLastOpSet() ?
- response.getLastOp() : Timestamp(),
- response.isElectionIdSet() ?
- response.getElectionId() : OID());
- }
- else {
+ // Get the TargetedWriteBatch to find where to put the response
+ dassert(pendingBatches.find(shardHost) != pendingBatches.end());
+ TargetedWriteBatch* batch = pendingBatches.find(shardHost)->second;
- // Error occurred dispatching, note it
+ if (dispatchStatus.isOK()) {
+ TrackedErrors trackedErrors;
+ trackedErrors.startTracking(ErrorCodes::StaleShardVersion);
- stringstream msg;
- msg << "write results unavailable from " << shardHost.toString()
- << causedBy( dispatchStatus.toString() );
+ LOG(4) << "write results received from " << shardHost.toString() << ": "
+ << response.toString() << endl;
- WriteErrorDetail error;
- buildErrorFrom( Status( ErrorCodes::RemoteResultsUnavailable, msg.str() ),
- &error );
+ // Dispatch was ok, note response
+ batchOp.noteBatchResponse(*batch, response, &trackedErrors);
- LOG( 4 ) << "unable to receive write results from " << shardHost.toString()
- << causedBy( dispatchStatus.toString() ) << endl;
+ // Note if anything was stale
+ const vector<ShardError*>& staleErrors =
+ trackedErrors.getErrors(ErrorCodes::StaleShardVersion);
- batchOp.noteBatchError( *batch, error );
+ if (staleErrors.size() > 0) {
+ noteStaleResponses(staleErrors, _targeter);
+ ++_stats->numStaleBatches;
}
- }
- }
- ++rounds;
- ++_stats->numRounds;
+ // Remember if the shard is actively changing metadata right now
+ if (isShardMetadataChanging(staleErrors)) {
+ remoteMetadataChanging = true;
+ }
- // If we're done, get out
- if ( batchOp.isFinished() )
- break;
+ // Remember that we successfully wrote to this shard
+ // NOTE: This will record lastOps for shards where we actually didn't update
+ // or delete any documents, which preserves old behavior but is conservative
+ _stats->noteWriteAt(shardHost,
+ response.isLastOpSet() ? response.getLastOp() : Timestamp(),
+ response.isElectionIdSet() ? response.getElectionId()
+ : OID());
+ } else {
+ // Error occurred dispatching, note it
- // MORE WORK TO DO
+ stringstream msg;
+ msg << "write results unavailable from " << shardHost.toString()
+ << causedBy(dispatchStatus.toString());
- //
- // Refresh the targeter if we need to (no-op if nothing stale)
- //
+ WriteErrorDetail error;
+ buildErrorFrom(Status(ErrorCodes::RemoteResultsUnavailable, msg.str()), &error);
- bool targeterChanged = false;
- Status refreshStatus = _targeter->refreshIfNeeded( &targeterChanged );
+ LOG(4) << "unable to receive write results from " << shardHost.toString()
+ << causedBy(dispatchStatus.toString()) << endl;
- if ( !refreshStatus.isOK() ) {
-
- // It's okay if we can't refresh, we'll just record errors for the ops if
- // needed.
- warning() << "could not refresh targeter" << causedBy( refreshStatus.reason() )
- << endl;
+ batchOp.noteBatchError(*batch, error);
+ }
}
+ }
- //
- // Ensure progress is being made toward completing the batch op
- //
+ ++rounds;
+ ++_stats->numRounds;
- int currCompletedOps = batchOp.numWriteOpsIn( WriteOpState_Completed );
- if ( currCompletedOps == numCompletedOps && !targeterChanged
- && !remoteMetadataChanging ) {
- ++numRoundsWithoutProgress;
- }
- else {
- numRoundsWithoutProgress = 0;
- }
- numCompletedOps = currCompletedOps;
+ // If we're done, get out
+ if (batchOp.isFinished())
+ break;
- if ( numRoundsWithoutProgress > kMaxRoundsWithoutProgress ) {
+ // MORE WORK TO DO
- stringstream msg;
- msg << "no progress was made executing batch write op in " << clientRequest.getNS()
- << " after " << kMaxRoundsWithoutProgress << " rounds (" << numCompletedOps
- << " ops completed in " << rounds << " rounds total)";
+ //
+ // Refresh the targeter if we need to (no-op if nothing stale)
+ //
- WriteErrorDetail error;
- buildErrorFrom( Status( ErrorCodes::NoProgressMade, msg.str() ), &error );
- batchOp.abortBatch( error );
- break;
- }
+ bool targeterChanged = false;
+ Status refreshStatus = _targeter->refreshIfNeeded(&targeterChanged);
+
+ if (!refreshStatus.isOK()) {
+ // It's okay if we can't refresh, we'll just record errors for the ops if
+ // needed.
+ warning() << "could not refresh targeter" << causedBy(refreshStatus.reason()) << endl;
}
- batchOp.buildClientResponse( clientResponse );
+ //
+ // Ensure progress is being made toward completing the batch op
+ //
- LOG( 4 ) << "finished execution of write batch"
- << ( clientResponse->isErrDetailsSet() ? " with write errors" : "")
- << ( clientResponse->isErrDetailsSet() &&
- clientResponse->isWriteConcernErrorSet() ? " and" : "" )
- << ( clientResponse->isWriteConcernErrorSet() ? " with write concern error" : "" )
- << " for " << clientRequest.getNS() << endl;
+ int currCompletedOps = batchOp.numWriteOpsIn(WriteOpState_Completed);
+ if (currCompletedOps == numCompletedOps && !targeterChanged && !remoteMetadataChanging) {
+ ++numRoundsWithoutProgress;
+ } else {
+ numRoundsWithoutProgress = 0;
+ }
+ numCompletedOps = currCompletedOps;
+
+ if (numRoundsWithoutProgress > kMaxRoundsWithoutProgress) {
+ stringstream msg;
+ msg << "no progress was made executing batch write op in " << clientRequest.getNS()
+ << " after " << kMaxRoundsWithoutProgress << " rounds (" << numCompletedOps
+ << " ops completed in " << rounds << " rounds total)";
+
+ WriteErrorDetail error;
+ buildErrorFrom(Status(ErrorCodes::NoProgressMade, msg.str()), &error);
+ batchOp.abortBatch(error);
+ break;
+ }
}
- const BatchWriteExecStats& BatchWriteExec::getStats() {
- return *_stats;
- }
+ batchOp.buildClientResponse(clientResponse);
- BatchWriteExecStats* BatchWriteExec::releaseStats() {
- return _stats.release();
- }
+ LOG(4) << "finished execution of write batch"
+ << (clientResponse->isErrDetailsSet() ? " with write errors" : "")
+ << (clientResponse->isErrDetailsSet() && clientResponse->isWriteConcernErrorSet()
+ ? " and"
+ : "")
+ << (clientResponse->isWriteConcernErrorSet() ? " with write concern error" : "")
+ << " for " << clientRequest.getNS() << endl;
+}
- void BatchWriteExecStats::noteWriteAt(const ConnectionString& host,
- Timestamp opTime,
- const OID& electionId) {
- _writeOpTimes[host] = HostOpTime(opTime, electionId);
- }
+const BatchWriteExecStats& BatchWriteExec::getStats() {
+ return *_stats;
+}
- const HostOpTimeMap& BatchWriteExecStats::getWriteOpTimes() const {
- return _writeOpTimes;
- }
+BatchWriteExecStats* BatchWriteExec::releaseStats() {
+ return _stats.release();
+}
+
+void BatchWriteExecStats::noteWriteAt(const ConnectionString& host,
+ Timestamp opTime,
+ const OID& electionId) {
+ _writeOpTimes[host] = HostOpTime(opTime, electionId);
+}
+
+const HostOpTimeMap& BatchWriteExecStats::getWriteOpTimes() const {
+ return _writeOpTimes;
+}
}
diff --git a/src/mongo/s/write_ops/batch_write_exec.h b/src/mongo/s/write_ops/batch_write_exec.h
index b7fd6c51e43..708b9481aef 100644
--- a/src/mongo/s/write_ops/batch_write_exec.h
+++ b/src/mongo/s/write_ops/batch_write_exec.h
@@ -41,93 +41,87 @@
namespace mongo {
- class BatchWriteExecStats;
- class MultiCommandDispatch;
+class BatchWriteExecStats;
+class MultiCommandDispatch;
+
+/**
+ * The BatchWriteExec is able to execute client batch write requests, resulting in a batch
+ * response to send back to the client.
+ *
+ * There are two main interfaces the exec uses to "run" the batch:
+ *
+ * - the "targeter" used to generate child batch operations to send to particular shards
+ *
+ * - the "dispatcher" used to send child batches to several shards at once, and retrieve the
+ * results
+ *
+ * Both the targeter and dispatcher are assumed to be dedicated to this particular
+ * BatchWriteExec instance.
+ *
+ */
+class BatchWriteExec {
+ MONGO_DISALLOW_COPYING(BatchWriteExec);
+
+public:
+ BatchWriteExec(NSTargeter* targeter, ShardResolver* resolver, MultiCommandDispatch* dispatcher);
/**
- * The BatchWriteExec is able to execute client batch write requests, resulting in a batch
- * response to send back to the client.
- *
- * There are two main interfaces the exec uses to "run" the batch:
- *
- * - the "targeter" used to generate child batch operations to send to particular shards
- *
- * - the "dispatcher" used to send child batches to several shards at once, and retrieve the
- * results
- *
- * Both the targeter and dispatcher are assumed to be dedicated to this particular
- * BatchWriteExec instance.
+ * Executes a client batch write request by sending child batches to several shard
+ * endpoints, and returns a client batch write response.
*
+ * This function does not throw, any errors are reported via the clientResponse.
*/
- class BatchWriteExec {
- MONGO_DISALLOW_COPYING (BatchWriteExec);
- public:
-
- BatchWriteExec( NSTargeter* targeter,
- ShardResolver* resolver,
- MultiCommandDispatch* dispatcher );
-
- /**
- * Executes a client batch write request by sending child batches to several shard
- * endpoints, and returns a client batch write response.
- *
- * This function does not throw, any errors are reported via the clientResponse.
- */
- void executeBatch( const BatchedCommandRequest& clientRequest,
- BatchedCommandResponse* clientResponse );
-
- const BatchWriteExecStats& getStats();
-
- BatchWriteExecStats* releaseStats();
-
- private:
+ void executeBatch(const BatchedCommandRequest& clientRequest,
+ BatchedCommandResponse* clientResponse);
- // Not owned here
- NSTargeter* _targeter;
+ const BatchWriteExecStats& getStats();
- // Not owned here
- ShardResolver* _resolver;
+ BatchWriteExecStats* releaseStats();
- // Not owned here
- MultiCommandDispatch* _dispatcher;
+private:
+ // Not owned here
+ NSTargeter* _targeter;
- // Stats
- std::unique_ptr<BatchWriteExecStats> _stats;
- };
+ // Not owned here
+ ShardResolver* _resolver;
- struct HostOpTime {
- HostOpTime(Timestamp ot, OID e) : opTime(ot), electionId(e) {};
- HostOpTime() {};
- Timestamp opTime;
- OID electionId;
- };
+ // Not owned here
+ MultiCommandDispatch* _dispatcher;
- typedef std::map<ConnectionString, HostOpTime> HostOpTimeMap;
+ // Stats
+ std::unique_ptr<BatchWriteExecStats> _stats;
+};
- class BatchWriteExecStats {
- public:
+struct HostOpTime {
+ HostOpTime(Timestamp ot, OID e) : opTime(ot), electionId(e){};
+ HostOpTime(){};
+ Timestamp opTime;
+ OID electionId;
+};
- BatchWriteExecStats() :
- numRounds( 0 ), numTargetErrors( 0 ), numResolveErrors( 0 ), numStaleBatches( 0 ) {
- }
+typedef std::map<ConnectionString, HostOpTime> HostOpTimeMap;
- void noteWriteAt(const ConnectionString& host, Timestamp opTime, const OID& electionId);
+class BatchWriteExecStats {
+public:
+ BatchWriteExecStats()
+ : numRounds(0), numTargetErrors(0), numResolveErrors(0), numStaleBatches(0) {}
- const HostOpTimeMap& getWriteOpTimes() const;
+ void noteWriteAt(const ConnectionString& host, Timestamp opTime, const OID& electionId);
- // Expose via helpers if this gets more complex
+ const HostOpTimeMap& getWriteOpTimes() const;
- // Number of round trips required for the batch
- int numRounds;
- // Number of times targeting failed
- int numTargetErrors;
- // Number of times host resolution failed
- int numResolveErrors;
- // Number of stale batches
- int numStaleBatches;
+ // Expose via helpers if this gets more complex
- private:
+ // Number of round trips required for the batch
+ int numRounds;
+ // Number of times targeting failed
+ int numTargetErrors;
+ // Number of times host resolution failed
+ int numResolveErrors;
+ // Number of stale batches
+ int numStaleBatches;
- HostOpTimeMap _writeOpTimes;
- };
+private:
+ HostOpTimeMap _writeOpTimes;
+};
}
diff --git a/src/mongo/s/write_ops/batch_write_exec_test.cpp b/src/mongo/s/write_ops/batch_write_exec_test.cpp
index 3e0b2cfb9f3..21060c79983 100644
--- a/src/mongo/s/write_ops/batch_write_exec_test.cpp
+++ b/src/mongo/s/write_ops/batch_write_exec_test.cpp
@@ -39,277 +39,266 @@
namespace {
- using std::unique_ptr;
- using std::string;
- using std::vector;
+using std::unique_ptr;
+using std::string;
+using std::vector;
- using namespace mongo;
+using namespace mongo;
- /**
- * Mimics a single shard backend for a particular collection which can be initialized with a
- * set of write command results to return.
- */
- class MockSingleShardBackend {
- public:
-
- MockSingleShardBackend( const NamespaceString& nss ) {
-
- // Initialize targeting to a mock shard
- ShardEndpoint endpoint( "shard", ChunkVersion::IGNORED() );
- vector<MockRange*> mockRanges;
- mockRanges.push_back( new MockRange( endpoint,
- nss,
- BSON( "x" << MINKEY ),
- BSON( "x" << MAXKEY ) ) );
- targeter.init( mockRanges );
-
- // Get the connection string for the mock shard
- resolver.chooseWriteHost( mockRanges.front()->endpoint.shardName, &shardHost );
+/**
+ * Mimics a single shard backend for a particular collection which can be initialized with a
+ * set of write command results to return.
+ */
+class MockSingleShardBackend {
+public:
+ MockSingleShardBackend(const NamespaceString& nss) {
+ // Initialize targeting to a mock shard
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
+ vector<MockRange*> mockRanges;
+ mockRanges.push_back(
+ new MockRange(endpoint, nss, BSON("x" << MINKEY), BSON("x" << MAXKEY)));
+ targeter.init(mockRanges);
+
+ // Get the connection string for the mock shard
+ resolver.chooseWriteHost(mockRanges.front()->endpoint.shardName, &shardHost);
+
+ // Executor using the mock backend
+ exec.reset(new BatchWriteExec(&targeter, &resolver, &dispatcher));
+ }
- // Executor using the mock backend
- exec.reset( new BatchWriteExec( &targeter, &resolver, &dispatcher ) );
- }
+ void setMockResults(const vector<MockWriteResult*>& results) {
+ dispatcher.init(results);
+ }
- void setMockResults( const vector<MockWriteResult*>& results ) {
- dispatcher.init( results );
- }
+ ConnectionString shardHost;
- ConnectionString shardHost;
+ MockNSTargeter targeter;
+ MockShardResolver resolver;
+ MockMultiWriteCommand dispatcher;
- MockNSTargeter targeter;
- MockShardResolver resolver;
- MockMultiWriteCommand dispatcher;
+ unique_ptr<BatchWriteExec> exec;
+};
- unique_ptr<BatchWriteExec> exec;
- };
+//
+// Tests for the BatchWriteExec
+//
+TEST(BatchWriteExecTests, SingleOp) {
//
- // Tests for the BatchWriteExec
+ // Basic execution test
//
- TEST(BatchWriteExecTests, SingleOp) {
-
- //
- // Basic execution test
- //
+ NamespaceString nss("foo.bar");
- NamespaceString nss( "foo.bar" );
+ MockSingleShardBackend backend(nss);
- MockSingleShardBackend backend( nss );
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ request.setWriteConcern(BSONObj());
+ // Do single-target, single doc batch write op
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- request.setWriteConcern( BSONObj() );
- // Do single-target, single doc batch write op
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
+ BatchedCommandResponse response;
+ backend.exec->executeBatch(request, &response);
+ ASSERT(response.getOk());
- BatchedCommandResponse response;
- backend.exec->executeBatch( request, &response );
- ASSERT( response.getOk() );
+ const BatchWriteExecStats& stats = backend.exec->getStats();
+ ASSERT_EQUALS(stats.numRounds, 1);
+}
- const BatchWriteExecStats& stats = backend.exec->getStats();
- ASSERT_EQUALS( stats.numRounds, 1 );
- }
+TEST(BatchWriteExecTests, SingleOpError) {
+ //
+ // Basic error test
+ //
- TEST(BatchWriteExecTests, SingleOpError) {
+ NamespaceString nss("foo.bar");
- //
- // Basic error test
- //
+ MockSingleShardBackend backend(nss);
- NamespaceString nss( "foo.bar" );
+ vector<MockWriteResult*> mockResults;
+ BatchedCommandResponse errResponse;
+ errResponse.setOk(false);
+ errResponse.setErrCode(ErrorCodes::UnknownError);
+ errResponse.setErrMessage("mock error");
+ mockResults.push_back(new MockWriteResult(backend.shardHost, errResponse));
- MockSingleShardBackend backend( nss );
+ backend.setMockResults(mockResults);
- vector<MockWriteResult*> mockResults;
- BatchedCommandResponse errResponse;
- errResponse.setOk( false );
- errResponse.setErrCode( ErrorCodes::UnknownError );
- errResponse.setErrMessage( "mock error" );
- mockResults.push_back( new MockWriteResult( backend.shardHost, errResponse ) );
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ request.setWriteConcern(BSONObj());
+ // Do single-target, single doc batch write op
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
- backend.setMockResults( mockResults );
+ BatchedCommandResponse response;
+ backend.exec->executeBatch(request, &response);
+ ASSERT(response.getOk());
+ ASSERT_EQUALS(response.getN(), 0);
+ ASSERT(response.isErrDetailsSet());
+ ASSERT_EQUALS(response.getErrDetailsAt(0)->getErrCode(), errResponse.getErrCode());
+ ASSERT(response.getErrDetailsAt(0)->getErrMessage().find(errResponse.getErrMessage()) !=
+ string::npos);
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- request.setWriteConcern( BSONObj() );
- // Do single-target, single doc batch write op
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
+ const BatchWriteExecStats& stats = backend.exec->getStats();
+ ASSERT_EQUALS(stats.numRounds, 1);
+}
- BatchedCommandResponse response;
- backend.exec->executeBatch( request, &response );
- ASSERT( response.getOk() );
- ASSERT_EQUALS( response.getN(), 0 );
- ASSERT( response.isErrDetailsSet() );
- ASSERT_EQUALS( response.getErrDetailsAt( 0 )->getErrCode(), errResponse.getErrCode() );
- ASSERT( response.getErrDetailsAt( 0 )->getErrMessage().find( errResponse.getErrMessage() )
- != string::npos );
-
- const BatchWriteExecStats& stats = backend.exec->getStats();
- ASSERT_EQUALS( stats.numRounds, 1 );
- }
+//
+// Test retryable errors
+//
+TEST(BatchWriteExecTests, StaleOp) {
//
- // Test retryable errors
+ // Retry op in exec b/c of stale config
//
- TEST(BatchWriteExecTests, StaleOp) {
-
- //
- // Retry op in exec b/c of stale config
- //
+ NamespaceString nss("foo.bar");
- NamespaceString nss( "foo.bar" );
+ // Insert request
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ request.setWriteConcern(BSONObj());
+ // Do single-target, single doc batch write op
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
- // Insert request
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- request.setWriteConcern( BSONObj() );
- // Do single-target, single doc batch write op
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
+ MockSingleShardBackend backend(nss);
- MockSingleShardBackend backend( nss );
+ vector<MockWriteResult*> mockResults;
+ WriteErrorDetail error;
+ error.setErrCode(ErrorCodes::StaleShardVersion);
+ error.setErrMessage("mock stale error");
+ mockResults.push_back(new MockWriteResult(backend.shardHost, error));
- vector<MockWriteResult*> mockResults;
- WriteErrorDetail error;
- error.setErrCode( ErrorCodes::StaleShardVersion );
- error.setErrMessage( "mock stale error" );
- mockResults.push_back( new MockWriteResult( backend.shardHost, error ) );
+ backend.setMockResults(mockResults);
- backend.setMockResults( mockResults );
+ // Execute request
+ BatchedCommandResponse response;
+ backend.exec->executeBatch(request, &response);
+ ASSERT(response.getOk());
- // Execute request
- BatchedCommandResponse response;
- backend.exec->executeBatch( request, &response );
- ASSERT( response.getOk() );
+ const BatchWriteExecStats& stats = backend.exec->getStats();
+ ASSERT_EQUALS(stats.numStaleBatches, 1);
+}
- const BatchWriteExecStats& stats = backend.exec->getStats();
- ASSERT_EQUALS( stats.numStaleBatches, 1 );
- }
-
- TEST(BatchWriteExecTests, MultiStaleOp) {
+TEST(BatchWriteExecTests, MultiStaleOp) {
+ //
+ // Retry op in exec multiple times b/c of stale config
+ //
- //
- // Retry op in exec multiple times b/c of stale config
- //
+ NamespaceString nss("foo.bar");
- NamespaceString nss( "foo.bar" );
+ // Insert request
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ request.setWriteConcern(BSONObj());
+ // Do single-target, single doc batch write op
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
- // Insert request
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- request.setWriteConcern( BSONObj() );
- // Do single-target, single doc batch write op
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
+ MockSingleShardBackend backend(nss);
- MockSingleShardBackend backend( nss );
+ vector<MockWriteResult*> mockResults;
+ WriteErrorDetail error;
+ error.setErrCode(ErrorCodes::StaleShardVersion);
+ error.setErrMessage("mock stale error");
+ for (int i = 0; i < 3; i++) {
+ mockResults.push_back(new MockWriteResult(backend.shardHost, error));
+ }
- vector<MockWriteResult*> mockResults;
- WriteErrorDetail error;
- error.setErrCode( ErrorCodes::StaleShardVersion );
- error.setErrMessage( "mock stale error" );
- for ( int i = 0; i < 3; i++ ) {
- mockResults.push_back( new MockWriteResult( backend.shardHost, error ) );
- }
+ backend.setMockResults(mockResults);
- backend.setMockResults( mockResults );
+ // Execute request
+ BatchedCommandResponse response;
+ backend.exec->executeBatch(request, &response);
+ ASSERT(response.getOk());
- // Execute request
- BatchedCommandResponse response;
- backend.exec->executeBatch( request, &response );
- ASSERT( response.getOk() );
+ const BatchWriteExecStats& stats = backend.exec->getStats();
+ ASSERT_EQUALS(stats.numStaleBatches, 3);
+}
- const BatchWriteExecStats& stats = backend.exec->getStats();
- ASSERT_EQUALS( stats.numStaleBatches, 3 );
- }
+TEST(BatchWriteExecTests, TooManyStaleOp) {
+ //
+ // Retry op in exec too many times (without refresh) b/c of stale config
+ // (The mock targeter doesn't report progress on refresh)
+ // We should report a no progress error for everything in the batch
+ //
- TEST(BatchWriteExecTests, TooManyStaleOp) {
-
- //
- // Retry op in exec too many times (without refresh) b/c of stale config
- // (The mock targeter doesn't report progress on refresh)
- // We should report a no progress error for everything in the batch
- //
-
- NamespaceString nss( "foo.bar" );
-
- // Insert request
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- request.setWriteConcern( BSONObj() );
- // Do single-target, single doc batch write ops
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 2 ) );
-
- MockSingleShardBackend backend( nss );
-
- vector<MockWriteResult*> mockResults;
- WriteErrorDetail error;
- error.setErrCode( ErrorCodes::StaleShardVersion );
- error.setErrMessage( "mock stale error" );
- for ( int i = 0; i < 10; i++ ) {
- mockResults.push_back( new MockWriteResult( backend.shardHost,
- error,
- request.sizeWriteOps() ) );
- }
-
- backend.setMockResults( mockResults );
-
- // Execute request
- BatchedCommandResponse response;
- backend.exec->executeBatch( request, &response );
- ASSERT( response.getOk() );
- ASSERT_EQUALS( response.getN(), 0 );
- ASSERT( response.isErrDetailsSet() );
- ASSERT_EQUALS( response.getErrDetailsAt( 0 )->getErrCode(), ErrorCodes::NoProgressMade );
- ASSERT_EQUALS( response.getErrDetailsAt( 1 )->getErrCode(), ErrorCodes::NoProgressMade );
+ NamespaceString nss("foo.bar");
+
+ // Insert request
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ request.setWriteConcern(BSONObj());
+ // Do single-target, single doc batch write ops
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
+ request.getInsertRequest()->addToDocuments(BSON("x" << 2));
+
+ MockSingleShardBackend backend(nss);
+
+ vector<MockWriteResult*> mockResults;
+ WriteErrorDetail error;
+ error.setErrCode(ErrorCodes::StaleShardVersion);
+ error.setErrMessage("mock stale error");
+ for (int i = 0; i < 10; i++) {
+ mockResults.push_back(
+ new MockWriteResult(backend.shardHost, error, request.sizeWriteOps()));
}
- TEST(BatchWriteExecTests, ManyStaleOpWithMigration) {
+ backend.setMockResults(mockResults);
- //
- // Retry op in exec many times b/c of stale config, but simulate remote migrations occurring
- //
+ // Execute request
+ BatchedCommandResponse response;
+ backend.exec->executeBatch(request, &response);
+ ASSERT(response.getOk());
+ ASSERT_EQUALS(response.getN(), 0);
+ ASSERT(response.isErrDetailsSet());
+ ASSERT_EQUALS(response.getErrDetailsAt(0)->getErrCode(), ErrorCodes::NoProgressMade);
+ ASSERT_EQUALS(response.getErrDetailsAt(1)->getErrCode(), ErrorCodes::NoProgressMade);
+}
- NamespaceString nss( "foo.bar" );
-
- // Insert request
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- request.setWriteConcern( BSONObj() );
- // Do single-target, single doc batch write op
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
-
- MockSingleShardBackend backend( nss );
-
- vector<MockWriteResult*> mockResults;
- WriteErrorDetail error;
- error.setErrCode( ErrorCodes::StaleShardVersion );
- error.setErrMessage( "mock stale error" );
- for ( int i = 0; i < 10; i++ ) {
- if ( i % 2 == 0 )
- error.setErrInfo( BSONObj() );
- else
- error.setErrInfo( BSON( "inCriticalSection" << true ) );
+TEST(BatchWriteExecTests, ManyStaleOpWithMigration) {
+ //
+ // Retry op in exec many times b/c of stale config, but simulate remote migrations occurring
+ //
- mockResults.push_back( new MockWriteResult( backend.shardHost, error ) );
- }
+ NamespaceString nss("foo.bar");
+
+ // Insert request
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ request.setWriteConcern(BSONObj());
+ // Do single-target, single doc batch write op
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
+
+ MockSingleShardBackend backend(nss);
+
+ vector<MockWriteResult*> mockResults;
+ WriteErrorDetail error;
+ error.setErrCode(ErrorCodes::StaleShardVersion);
+ error.setErrMessage("mock stale error");
+ for (int i = 0; i < 10; i++) {
+ if (i % 2 == 0)
+ error.setErrInfo(BSONObj());
+ else
+ error.setErrInfo(BSON("inCriticalSection" << true));
+
+ mockResults.push_back(new MockWriteResult(backend.shardHost, error));
+ }
- backend.setMockResults( mockResults );
+ backend.setMockResults(mockResults);
- // Execute request
- BatchedCommandResponse response;
- backend.exec->executeBatch( request, &response );
- ASSERT( response.getOk() );
+ // Execute request
+ BatchedCommandResponse response;
+ backend.exec->executeBatch(request, &response);
+ ASSERT(response.getOk());
- const BatchWriteExecStats& stats = backend.exec->getStats();
- ASSERT_EQUALS( stats.numStaleBatches, 10 );
- }
+ const BatchWriteExecStats& stats = backend.exec->getStats();
+ ASSERT_EQUALS(stats.numStaleBatches, 10);
+}
-} // unnamed namespace
+} // unnamed namespace
diff --git a/src/mongo/s/write_ops/batch_write_op.cpp b/src/mongo/s/write_ops/batch_write_op.cpp
index b1032410c7a..847b96ff9ee 100644
--- a/src/mongo/s/write_ops/batch_write_op.cpp
+++ b/src/mongo/s/write_ops/batch_write_op.cpp
@@ -32,889 +32,829 @@
namespace mongo {
- using std::unique_ptr;
- using std::make_pair;
- using std::set;
- using std::stringstream;
- using std::vector;
-
- /**
- * Returns a new write concern that has the copy of every field from the original
- * document but with a w set to 1. This is intended for upgrading { w: 0 } write
- * concern to { w: 1 }.
- */
- static BSONObj upgradeWriteConcern ( const BSONObj& origWriteConcern ) {
- BSONObjIterator iter( origWriteConcern );
- BSONObjBuilder newWriteConcern;
-
- while ( iter.more() ) {
- BSONElement elem( iter.next() );
-
- if ( strncmp( elem.fieldName(), "w", 2 ) == 0 ) {
- newWriteConcern.append( "w", 1 );
- }
- else {
- newWriteConcern.append( elem );
- }
- }
-
- return newWriteConcern.obj();
- }
-
- BatchWriteStats::BatchWriteStats() :
- numInserted( 0 ), numUpserted( 0 ), numMatched( 0 ), numModified( 0 ), numDeleted( 0 ) {
- }
+using std::unique_ptr;
+using std::make_pair;
+using std::set;
+using std::stringstream;
+using std::vector;
- BatchWriteOp::BatchWriteOp() :
- _clientRequest( NULL ), _writeOps( NULL ), _stats( new BatchWriteStats ) {
- }
-
- void BatchWriteOp::initClientRequest( const BatchedCommandRequest* clientRequest ) {
- dassert( clientRequest->isValid( NULL ) );
+/**
+ * Returns a new write concern that has the copy of every field from the original
+ * document but with a w set to 1. This is intended for upgrading { w: 0 } write
+ * concern to { w: 1 }.
+ */
+static BSONObj upgradeWriteConcern(const BSONObj& origWriteConcern) {
+ BSONObjIterator iter(origWriteConcern);
+ BSONObjBuilder newWriteConcern;
- size_t numWriteOps = clientRequest->sizeWriteOps();
- _writeOps = static_cast<WriteOp*>( ::operator new[]( numWriteOps * sizeof(WriteOp) ) );
+ while (iter.more()) {
+ BSONElement elem(iter.next());
- for ( size_t i = 0; i < numWriteOps; ++i ) {
- // Don't want to have to define what an empty WriteOp means, so construct in-place
- new ( &_writeOps[i] ) WriteOp( BatchItemRef( clientRequest, i ) );
+ if (strncmp(elem.fieldName(), "w", 2) == 0) {
+ newWriteConcern.append("w", 1);
+ } else {
+ newWriteConcern.append(elem);
}
-
- _clientRequest = clientRequest;
}
- // Arbitrary endpoint ordering, needed for grouping by endpoint
- static int compareEndpoints( const ShardEndpoint* endpointA, const ShardEndpoint* endpointB ) {
-
- int shardNameDiff = endpointA->shardName.compare( endpointB->shardName );
- if ( shardNameDiff != 0 ) return shardNameDiff;
-
- long shardVersionDiff = endpointA->shardVersion.toLong() - endpointB->shardVersion.toLong();
- if ( shardVersionDiff != 0 ) return shardVersionDiff;
-
- int shardEpochDiff =
- endpointA->shardVersion.epoch().compare( endpointB->shardVersion.epoch() );
- return shardEpochDiff;
- }
-
- namespace {
-
- //
- // Types for comparing shard endpoints in a map
- //
-
- struct EndpointComp {
- bool operator()( const ShardEndpoint* endpointA,
- const ShardEndpoint* endpointB ) const {
- return compareEndpoints( endpointA, endpointB ) < 0;
- }
- };
-
- typedef std::map<const ShardEndpoint*, TargetedWriteBatch*, EndpointComp> TargetedBatchMap;
-
- //
- // Types for tracking batch sizes
- //
+ return newWriteConcern.obj();
+}
- struct BatchSize {
+BatchWriteStats::BatchWriteStats()
+ : numInserted(0), numUpserted(0), numMatched(0), numModified(0), numDeleted(0) {}
- BatchSize() :
- numOps(0), sizeBytes(0) {
- }
+BatchWriteOp::BatchWriteOp() : _clientRequest(NULL), _writeOps(NULL), _stats(new BatchWriteStats) {}
- int numOps;
- int sizeBytes;
- };
+void BatchWriteOp::initClientRequest(const BatchedCommandRequest* clientRequest) {
+ dassert(clientRequest->isValid(NULL));
- typedef std::map<const ShardEndpoint*, BatchSize, EndpointComp> TargetedBatchSizeMap;
- }
+ size_t numWriteOps = clientRequest->sizeWriteOps();
+ _writeOps = static_cast<WriteOp*>(::operator new[](numWriteOps * sizeof(WriteOp)));
- static void buildTargetError( const Status& errStatus, WriteErrorDetail* details ) {
- details->setErrCode( errStatus.code() );
- details->setErrMessage( errStatus.reason() );
+ for (size_t i = 0; i < numWriteOps; ++i) {
+ // Don't want to have to define what an empty WriteOp means, so construct in-place
+ new (&_writeOps[i]) WriteOp(BatchItemRef(clientRequest, i));
}
- // Helper to determine whether a number of targeted writes require a new targeted batch
- static bool isNewBatchRequired( const vector<TargetedWrite*>& writes,
- const TargetedBatchMap& batchMap ) {
-
- for ( vector<TargetedWrite*>::const_iterator it = writes.begin(); it != writes.end();
- ++it ) {
+ _clientRequest = clientRequest;
+}
- TargetedWrite* write = *it;
- if ( batchMap.find( &write->endpoint ) == batchMap.end() ) {
- return true;
- }
- }
+// Arbitrary endpoint ordering, needed for grouping by endpoint
+static int compareEndpoints(const ShardEndpoint* endpointA, const ShardEndpoint* endpointB) {
+ int shardNameDiff = endpointA->shardName.compare(endpointB->shardName);
+ if (shardNameDiff != 0)
+ return shardNameDiff;
- return false;
- }
+ long shardVersionDiff = endpointA->shardVersion.toLong() - endpointB->shardVersion.toLong();
+ if (shardVersionDiff != 0)
+ return shardVersionDiff;
- // MAGIC NUMBERS
- // Before serializing updates/deletes, we don't know how big their fields would be, but we break
- // batches before serializing.
- // TODO: Revisit when we revisit command limits in general
- static const int kEstUpdateOverheadBytes = (BSONObjMaxInternalSize - BSONObjMaxUserSize) / 100;
- static const int kEstDeleteOverheadBytes = (BSONObjMaxInternalSize - BSONObjMaxUserSize) / 100;
+ int shardEpochDiff = endpointA->shardVersion.epoch().compare(endpointB->shardVersion.epoch());
+ return shardEpochDiff;
+}
- static int getWriteSizeBytes(const WriteOp& writeOp) {
+namespace {
- const BatchItemRef& item = writeOp.getWriteItem();
- BatchedCommandRequest::BatchType batchType = item.getOpType();
+//
+// Types for comparing shard endpoints in a map
+//
- if (batchType == BatchedCommandRequest::BatchType_Insert) {
- return item.getDocument().objsize();
- }
- else if (batchType == BatchedCommandRequest::BatchType_Update) {
- // Note: Be conservative here - it's okay if we send slightly too many batches
- int estSize = item.getUpdate()->getQuery().objsize()
- + item.getUpdate()->getUpdateExpr().objsize() + kEstUpdateOverheadBytes;
- dassert(estSize >= item.getUpdate()->toBSON().objsize());
- return estSize;
- }
- else {
- dassert( batchType == BatchedCommandRequest::BatchType_Delete );
- // Note: Be conservative here - it's okay if we send slightly too many batches
- int estSize = item.getDelete()->getQuery().objsize() + kEstDeleteOverheadBytes;
- dassert(estSize >= item.getDelete()->toBSON().objsize());
- return estSize;
- }
+struct EndpointComp {
+ bool operator()(const ShardEndpoint* endpointA, const ShardEndpoint* endpointB) const {
+ return compareEndpoints(endpointA, endpointB) < 0;
}
+};
- // Helper to determine whether a number of targeted writes require a new targeted batch
- static bool wouldMakeBatchesTooBig(const vector<TargetedWrite*>& writes,
- int writeSizeBytes,
- const TargetedBatchSizeMap& batchSizes) {
+typedef std::map<const ShardEndpoint*, TargetedWriteBatch*, EndpointComp> TargetedBatchMap;
- for (vector<TargetedWrite*>::const_iterator it = writes.begin(); it != writes.end(); ++it) {
+//
+// Types for tracking batch sizes
+//
- const TargetedWrite* write = *it;
- TargetedBatchSizeMap::const_iterator seenIt = batchSizes.find(&write->endpoint);
+struct BatchSize {
+ BatchSize() : numOps(0), sizeBytes(0) {}
- if (seenIt == batchSizes.end()) {
- // If this is the first item in the batch, it can't be too big
- continue;
- }
+ int numOps;
+ int sizeBytes;
+};
- const BatchSize& batchSize = seenIt->second;
+typedef std::map<const ShardEndpoint*, BatchSize, EndpointComp> TargetedBatchSizeMap;
+}
- if (batchSize.numOps >= static_cast<int>(BatchedCommandRequest::kMaxWriteBatchSize)) {
- // Too many items in batch
- return true;
- }
+static void buildTargetError(const Status& errStatus, WriteErrorDetail* details) {
+ details->setErrCode(errStatus.code());
+ details->setErrMessage(errStatus.reason());
+}
- if (batchSize.sizeBytes + writeSizeBytes > BSONObjMaxUserSize) {
- // Batch would be too big
- return true;
- }
+// Helper to determine whether a number of targeted writes require a new targeted batch
+static bool isNewBatchRequired(const vector<TargetedWrite*>& writes,
+ const TargetedBatchMap& batchMap) {
+ for (vector<TargetedWrite*>::const_iterator it = writes.begin(); it != writes.end(); ++it) {
+ TargetedWrite* write = *it;
+ if (batchMap.find(&write->endpoint) == batchMap.end()) {
+ return true;
}
-
- return false;
}
- // Helper function to cancel all the write ops of targeted batches in a map
- static void cancelBatches( const WriteErrorDetail& why,
- WriteOp* writeOps,
- TargetedBatchMap* batchMap ) {
-
- set<WriteOp*> targetedWriteOps;
+ return false;
+}
- // Collect all the writeOps that are currently targeted
- for ( TargetedBatchMap::iterator it = batchMap->begin(); it != batchMap->end(); ) {
+// MAGIC NUMBERS
+// Before serializing updates/deletes, we don't know how big their fields would be, but we break
+// batches before serializing.
+// TODO: Revisit when we revisit command limits in general
+static const int kEstUpdateOverheadBytes = (BSONObjMaxInternalSize - BSONObjMaxUserSize) / 100;
+static const int kEstDeleteOverheadBytes = (BSONObjMaxInternalSize - BSONObjMaxUserSize) / 100;
+
+static int getWriteSizeBytes(const WriteOp& writeOp) {
+ const BatchItemRef& item = writeOp.getWriteItem();
+ BatchedCommandRequest::BatchType batchType = item.getOpType();
+
+ if (batchType == BatchedCommandRequest::BatchType_Insert) {
+ return item.getDocument().objsize();
+ } else if (batchType == BatchedCommandRequest::BatchType_Update) {
+ // Note: Be conservative here - it's okay if we send slightly too many batches
+ int estSize = item.getUpdate()->getQuery().objsize() +
+ item.getUpdate()->getUpdateExpr().objsize() + kEstUpdateOverheadBytes;
+ dassert(estSize >= item.getUpdate()->toBSON().objsize());
+ return estSize;
+ } else {
+ dassert(batchType == BatchedCommandRequest::BatchType_Delete);
+ // Note: Be conservative here - it's okay if we send slightly too many batches
+ int estSize = item.getDelete()->getQuery().objsize() + kEstDeleteOverheadBytes;
+ dassert(estSize >= item.getDelete()->toBSON().objsize());
+ return estSize;
+ }
+}
- TargetedWriteBatch* batch = it->second;
- const vector<TargetedWrite*>& writes = batch->getWrites();
+// Helper to determine whether a number of targeted writes require a new targeted batch
+static bool wouldMakeBatchesTooBig(const vector<TargetedWrite*>& writes,
+ int writeSizeBytes,
+ const TargetedBatchSizeMap& batchSizes) {
+ for (vector<TargetedWrite*>::const_iterator it = writes.begin(); it != writes.end(); ++it) {
+ const TargetedWrite* write = *it;
+ TargetedBatchSizeMap::const_iterator seenIt = batchSizes.find(&write->endpoint);
- for ( vector<TargetedWrite*>::const_iterator writeIt = writes.begin();
- writeIt != writes.end(); ++writeIt ) {
+ if (seenIt == batchSizes.end()) {
+ // If this is the first item in the batch, it can't be too big
+ continue;
+ }
- TargetedWrite* write = *writeIt;
+ const BatchSize& batchSize = seenIt->second;
- // NOTE: We may repeatedly cancel a write op here, but that's fast and we want to
- // cancel before erasing the TargetedWrite* (which owns the cancelled targeting
- // info) for reporting reasons.
- writeOps[write->writeOpRef.first].cancelWrites( &why );
- }
+ if (batchSize.numOps >= static_cast<int>(BatchedCommandRequest::kMaxWriteBatchSize)) {
+ // Too many items in batch
+ return true;
+ }
- // Note that we need to *erase* first, *then* delete, since the map keys are ptrs from
- // the values
- batchMap->erase( it++ );
- delete batch;
+ if (batchSize.sizeBytes + writeSizeBytes > BSONObjMaxUserSize) {
+ // Batch would be too big
+ return true;
}
- batchMap->clear();
}
- Status BatchWriteOp::targetBatch( const NSTargeter& targeter,
- bool recordTargetErrors,
- vector<TargetedWriteBatch*>* targetedBatches ) {
-
- //
- // Targeting of unordered batches is fairly simple - each remaining write op is targeted,
- // and each of those targeted writes are grouped into a batch for a particular shard
- // endpoint.
- //
- // Targeting of ordered batches is a bit more complex - to respect the ordering of the
- // batch, we can only send:
- // A) a single targeted batch to one shard endpoint
- // B) multiple targeted batches, but only containing targeted writes for a single write op
- //
- // This means that any multi-shard write operation must be targeted and sent one-by-one.
- // Subsequent single-shard write operations can be batched together if they go to the same
- // place.
- //
- // Ex: ShardA : { skey : a->k }, ShardB : { skey : k->z }
- //
- // Ordered insert batch of: [{ skey : a }, { skey : b }, { skey : x }]
- // broken into:
- // [{ skey : a }, { skey : b }],
- // [{ skey : x }]
- //
- // Ordered update Batch of :
- // [{ skey : a }{ $push },
- // { skey : b }{ $push },
- // { skey : [c, x] }{ $push },
- // { skey : y }{ $push },
- // { skey : z }{ $push }]
- // broken into:
- // [{ skey : a }, { skey : b }],
- // [{ skey : [c,x] }],
- // [{ skey : y }, { skey : z }]
- //
-
- const bool ordered = _clientRequest->getOrdered();
-
- TargetedBatchMap batchMap;
- TargetedBatchSizeMap batchSizes;
+ return false;
+}
- int numTargetErrors = 0;
+// Helper function to cancel all the write ops of targeted batches in a map
+static void cancelBatches(const WriteErrorDetail& why,
+ WriteOp* writeOps,
+ TargetedBatchMap* batchMap) {
+ set<WriteOp*> targetedWriteOps;
- size_t numWriteOps = _clientRequest->sizeWriteOps();
- for ( size_t i = 0; i < numWriteOps; ++i ) {
+ // Collect all the writeOps that are currently targeted
+ for (TargetedBatchMap::iterator it = batchMap->begin(); it != batchMap->end();) {
+ TargetedWriteBatch* batch = it->second;
+ const vector<TargetedWrite*>& writes = batch->getWrites();
- WriteOp& writeOp = _writeOps[i];
+ for (vector<TargetedWrite*>::const_iterator writeIt = writes.begin();
+ writeIt != writes.end();
+ ++writeIt) {
+ TargetedWrite* write = *writeIt;
- // Only target _Ready ops
- if ( writeOp.getWriteState() != WriteOpState_Ready ) continue;
+ // NOTE: We may repeatedly cancel a write op here, but that's fast and we want to
+ // cancel before erasing the TargetedWrite* (which owns the cancelled targeting
+ // info) for reporting reasons.
+ writeOps[write->writeOpRef.first].cancelWrites(&why);
+ }
- //
- // Get TargetedWrites from the targeter for the write operation
- //
+ // Note that we need to *erase* first, *then* delete, since the map keys are ptrs from
+ // the values
+ batchMap->erase(it++);
+ delete batch;
+ }
+ batchMap->clear();
+}
- // TargetedWrites need to be owned once returned
- OwnedPointerVector<TargetedWrite> writesOwned;
- vector<TargetedWrite*>& writes = writesOwned.mutableVector();
+Status BatchWriteOp::targetBatch(const NSTargeter& targeter,
+ bool recordTargetErrors,
+ vector<TargetedWriteBatch*>* targetedBatches) {
+ //
+ // Targeting of unordered batches is fairly simple - each remaining write op is targeted,
+ // and each of those targeted writes are grouped into a batch for a particular shard
+ // endpoint.
+ //
+ // Targeting of ordered batches is a bit more complex - to respect the ordering of the
+ // batch, we can only send:
+ // A) a single targeted batch to one shard endpoint
+ // B) multiple targeted batches, but only containing targeted writes for a single write op
+ //
+ // This means that any multi-shard write operation must be targeted and sent one-by-one.
+ // Subsequent single-shard write operations can be batched together if they go to the same
+ // place.
+ //
+ // Ex: ShardA : { skey : a->k }, ShardB : { skey : k->z }
+ //
+ // Ordered insert batch of: [{ skey : a }, { skey : b }, { skey : x }]
+ // broken into:
+ // [{ skey : a }, { skey : b }],
+ // [{ skey : x }]
+ //
+ // Ordered update Batch of :
+ // [{ skey : a }{ $push },
+ // { skey : b }{ $push },
+ // { skey : [c, x] }{ $push },
+ // { skey : y }{ $push },
+ // { skey : z }{ $push }]
+ // broken into:
+ // [{ skey : a }, { skey : b }],
+ // [{ skey : [c,x] }],
+ // [{ skey : y }, { skey : z }]
+ //
- Status targetStatus = writeOp.targetWrites( targeter, &writes );
+ const bool ordered = _clientRequest->getOrdered();
- if ( !targetStatus.isOK() ) {
+ TargetedBatchMap batchMap;
+ TargetedBatchSizeMap batchSizes;
- WriteErrorDetail targetError;
- buildTargetError( targetStatus, &targetError );
+ int numTargetErrors = 0;
- if ( !recordTargetErrors ) {
+ size_t numWriteOps = _clientRequest->sizeWriteOps();
+ for (size_t i = 0; i < numWriteOps; ++i) {
+ WriteOp& writeOp = _writeOps[i];
- // Cancel current batch state with an error
+ // Only target _Ready ops
+ if (writeOp.getWriteState() != WriteOpState_Ready)
+ continue;
- cancelBatches( targetError, _writeOps, &batchMap );
- dassert( batchMap.empty() );
- return targetStatus;
- }
- else if ( !ordered || batchMap.empty() ) {
+ //
+ // Get TargetedWrites from the targeter for the write operation
+ //
- // Record an error for this batch
+ // TargetedWrites need to be owned once returned
+ OwnedPointerVector<TargetedWrite> writesOwned;
+ vector<TargetedWrite*>& writes = writesOwned.mutableVector();
- writeOp.setOpError( targetError );
- ++numTargetErrors;
+ Status targetStatus = writeOp.targetWrites(targeter, &writes);
- if ( ordered )
- return Status::OK();
+ if (!targetStatus.isOK()) {
+ WriteErrorDetail targetError;
+ buildTargetError(targetStatus, &targetError);
- continue;
- }
- else {
- dassert( ordered && !batchMap.empty() );
+ if (!recordTargetErrors) {
+ // Cancel current batch state with an error
- // Send out what we have, but don't record an error yet, since there may be an
- // error in the writes before this point.
+ cancelBatches(targetError, _writeOps, &batchMap);
+ dassert(batchMap.empty());
+ return targetStatus;
+ } else if (!ordered || batchMap.empty()) {
+ // Record an error for this batch
- writeOp.cancelWrites( &targetError );
- break;
- }
- }
+ writeOp.setOpError(targetError);
+ ++numTargetErrors;
- //
- // If ordered and we have a previous endpoint, make sure we don't need to send these
- // targeted writes to any other endpoints.
- //
+ if (ordered)
+ return Status::OK();
- if ( ordered && !batchMap.empty() ) {
+ continue;
+ } else {
+ dassert(ordered && !batchMap.empty());
- dassert( batchMap.size() == 1u );
- if ( isNewBatchRequired( writes, batchMap ) ) {
+ // Send out what we have, but don't record an error yet, since there may be an
+ // error in the writes before this point.
- writeOp.cancelWrites( NULL );
- break;
- }
+ writeOp.cancelWrites(&targetError);
+ break;
}
+ }
- //
- // If this write will push us over some sort of size limit, stop targeting
- //
+ //
+ // If ordered and we have a previous endpoint, make sure we don't need to send these
+ // targeted writes to any other endpoints.
+ //
- int writeSizeBytes = getWriteSizeBytes(writeOp);
- if (wouldMakeBatchesTooBig(writes, writeSizeBytes, batchSizes)) {
- invariant(!batchMap.empty());
+ if (ordered && !batchMap.empty()) {
+ dassert(batchMap.size() == 1u);
+ if (isNewBatchRequired(writes, batchMap)) {
writeOp.cancelWrites(NULL);
break;
}
-
- //
- // Targeting went ok, add to appropriate TargetedBatch
- //
-
- for ( vector<TargetedWrite*>::iterator it = writes.begin(); it != writes.end(); ++it ) {
-
- TargetedWrite* write = *it;
-
- TargetedBatchMap::iterator batchIt = batchMap.find( &write->endpoint );
- TargetedBatchSizeMap::iterator batchSizeIt = batchSizes.find( &write->endpoint );
-
- if ( batchIt == batchMap.end() ) {
- TargetedWriteBatch* newBatch = new TargetedWriteBatch( write->endpoint );
- batchIt = batchMap.insert( make_pair( &newBatch->getEndpoint(),
- newBatch ) ).first;
- batchSizeIt = batchSizes.insert(make_pair(&newBatch->getEndpoint(),
- BatchSize())).first;
- }
-
- TargetedWriteBatch* batch = batchIt->second;
- BatchSize& batchSize = batchSizeIt->second;
-
- ++batchSize.numOps;
- batchSize.sizeBytes += writeSizeBytes;
- batch->addWrite( write );
- }
-
- // Relinquish ownership of TargetedWrites, now the TargetedBatches own them
- writesOwned.mutableVector().clear();
-
- //
- // Break if we're ordered and we have more than one endpoint - later writes cannot be
- // enforced as ordered across multiple shard endpoints.
- //
-
- if ( ordered && batchMap.size() > 1u )
- break;
}
//
- // Send back our targeted batches
+ // If this write will push us over some sort of size limit, stop targeting
//
- for ( TargetedBatchMap::iterator it = batchMap.begin(); it != batchMap.end(); ++it ) {
-
- TargetedWriteBatch* batch = it->second;
-
- if ( batch->getWrites().empty() )
- continue;
-
- // Remember targeted batch for reporting
- _targeted.insert( batch );
- // Send the handle back to caller
- targetedBatches->push_back( batch );
+ int writeSizeBytes = getWriteSizeBytes(writeOp);
+ if (wouldMakeBatchesTooBig(writes, writeSizeBytes, batchSizes)) {
+ invariant(!batchMap.empty());
+ writeOp.cancelWrites(NULL);
+ break;
}
- return Status::OK();
- }
-
- void BatchWriteOp::buildBatchRequest( const TargetedWriteBatch& targetedBatch,
- BatchedCommandRequest* request ) const {
-
- request->setNS( _clientRequest->getNS() );
- request->setShouldBypassValidation(_clientRequest->shouldBypassValidation());
-
- const vector<TargetedWrite*>& targetedWrites = targetedBatch.getWrites();
+ //
+ // Targeting went ok, add to appropriate TargetedBatch
+ //
- for ( vector<TargetedWrite*>::const_iterator it = targetedWrites.begin();
- it != targetedWrites.end(); ++it ) {
+ for (vector<TargetedWrite*>::iterator it = writes.begin(); it != writes.end(); ++it) {
+ TargetedWrite* write = *it;
- const WriteOpRef& writeOpRef = ( *it )->writeOpRef;
- BatchedCommandRequest::BatchType batchType = _clientRequest->getBatchType();
+ TargetedBatchMap::iterator batchIt = batchMap.find(&write->endpoint);
+ TargetedBatchSizeMap::iterator batchSizeIt = batchSizes.find(&write->endpoint);
- // NOTE: We copy the batch items themselves here from the client request
- // TODO: This could be inefficient, maybe we want to just reference in the future
- if ( batchType == BatchedCommandRequest::BatchType_Insert ) {
- BatchedInsertRequest* clientInsertRequest = _clientRequest->getInsertRequest();
- BSONObj insertDoc = clientInsertRequest->getDocumentsAt( writeOpRef.first );
- request->getInsertRequest()->addToDocuments( insertDoc );
- }
- else if ( batchType == BatchedCommandRequest::BatchType_Update ) {
- BatchedUpdateRequest* clientUpdateRequest = _clientRequest->getUpdateRequest();
- BatchedUpdateDocument* updateDoc = new BatchedUpdateDocument;
- clientUpdateRequest->getUpdatesAt( writeOpRef.first )->cloneTo( updateDoc );
- request->getUpdateRequest()->addToUpdates( updateDoc );
- }
- else {
- dassert( batchType == BatchedCommandRequest::BatchType_Delete );
- BatchedDeleteRequest* clientDeleteRequest = _clientRequest->getDeleteRequest();
- BatchedDeleteDocument* deleteDoc = new BatchedDeleteDocument;
- clientDeleteRequest->getDeletesAt( writeOpRef.first )->cloneTo( deleteDoc );
- request->getDeleteRequest()->addToDeletes( deleteDoc );
+ if (batchIt == batchMap.end()) {
+ TargetedWriteBatch* newBatch = new TargetedWriteBatch(write->endpoint);
+ batchIt = batchMap.insert(make_pair(&newBatch->getEndpoint(), newBatch)).first;
+ batchSizeIt =
+ batchSizes.insert(make_pair(&newBatch->getEndpoint(), BatchSize())).first;
}
- // TODO: We can add logic here to allow aborting individual ops
- //if ( NULL == response ) {
- // ->responses.erase( it++ );
- // continue;
- //}
- }
+ TargetedWriteBatch* batch = batchIt->second;
+ BatchSize& batchSize = batchSizeIt->second;
- if ( _clientRequest->isWriteConcernSet() ) {
- if ( _clientRequest->isVerboseWC() ) {
- request->setWriteConcern( _clientRequest->getWriteConcern() );
- }
- else {
- // Mongos needs to send to the shard with w > 0 so it will be able to
- // see the writeErrors.
- request->setWriteConcern( upgradeWriteConcern(
- _clientRequest->getWriteConcern() ));
- }
+ ++batchSize.numOps;
+ batchSize.sizeBytes += writeSizeBytes;
+ batch->addWrite(write);
}
- if ( !request->isOrderedSet() ) {
- request->setOrdered( _clientRequest->getOrdered() );
- }
+ // Relinquish ownership of TargetedWrites, now the TargetedBatches own them
+ writesOwned.mutableVector().clear();
+
+ //
+ // Break if we're ordered and we have more than one endpoint - later writes cannot be
+ // enforced as ordered across multiple shard endpoints.
+ //
- unique_ptr<BatchedRequestMetadata> requestMetadata( new BatchedRequestMetadata() );
- requestMetadata->setShardName( targetedBatch.getEndpoint().shardName );
- requestMetadata->setShardVersion( targetedBatch.getEndpoint().shardVersion );
- requestMetadata->setSession( 0 );
- request->setMetadata( requestMetadata.release() );
+ if (ordered && batchMap.size() > 1u)
+ break;
}
//
- // Helpers for manipulating batch responses
+ // Send back our targeted batches
//
- namespace {
- struct WriteErrorDetailComp {
- bool operator()( const WriteErrorDetail* errorA,
- const WriteErrorDetail* errorB ) const {
- return errorA->getIndex() < errorB->getIndex();
- }
- };
- }
+ for (TargetedBatchMap::iterator it = batchMap.begin(); it != batchMap.end(); ++it) {
+ TargetedWriteBatch* batch = it->second;
+
+ if (batch->getWrites().empty())
+ continue;
- static void cloneCommandErrorTo( const BatchedCommandResponse& batchResp,
- WriteErrorDetail* details ) {
- details->setErrCode( batchResp.getErrCode() );
- details->setErrMessage( batchResp.getErrMessage() );
+ // Remember targeted batch for reporting
+ _targeted.insert(batch);
+ // Send the handle back to caller
+ targetedBatches->push_back(batch);
}
- // Given *either* a batch error or an array of per-item errors, copies errors we're interested
- // in into a TrackedErrorMap
- static void trackErrors( const ShardEndpoint& endpoint,
- const vector<WriteErrorDetail*> itemErrors,
- TrackedErrors* trackedErrors ) {
- for ( vector<WriteErrorDetail*>::const_iterator it = itemErrors.begin();
- it != itemErrors.end(); ++it ) {
+ return Status::OK();
+}
- const WriteErrorDetail* error = *it;
+void BatchWriteOp::buildBatchRequest(const TargetedWriteBatch& targetedBatch,
+ BatchedCommandRequest* request) const {
+ request->setNS(_clientRequest->getNS());
+ request->setShouldBypassValidation(_clientRequest->shouldBypassValidation());
- if ( trackedErrors->isTracking( error->getErrCode() ) ) {
- trackedErrors->addError( new ShardError( endpoint, *error ) );
- }
- }
- }
+ const vector<TargetedWrite*>& targetedWrites = targetedBatch.getWrites();
- static void incBatchStats( BatchedCommandRequest::BatchType batchType,
- const BatchedCommandResponse& response,
- BatchWriteStats* stats ) {
+ for (vector<TargetedWrite*>::const_iterator it = targetedWrites.begin();
+ it != targetedWrites.end();
+ ++it) {
+ const WriteOpRef& writeOpRef = (*it)->writeOpRef;
+ BatchedCommandRequest::BatchType batchType = _clientRequest->getBatchType();
- if ( batchType == BatchedCommandRequest::BatchType_Insert) {
- stats->numInserted += response.getN();
- }
- else if ( batchType == BatchedCommandRequest::BatchType_Update ) {
- int numUpserted = 0;
- if( response.isUpsertDetailsSet() ) {
- numUpserted = response.sizeUpsertDetails();
- }
- stats->numMatched += ( response.getN() - numUpserted );
- long long numModified = response.getNModified();
+ // NOTE: We copy the batch items themselves here from the client request
+ // TODO: This could be inefficient, maybe we want to just reference in the future
+ if (batchType == BatchedCommandRequest::BatchType_Insert) {
+ BatchedInsertRequest* clientInsertRequest = _clientRequest->getInsertRequest();
+ BSONObj insertDoc = clientInsertRequest->getDocumentsAt(writeOpRef.first);
+ request->getInsertRequest()->addToDocuments(insertDoc);
+ } else if (batchType == BatchedCommandRequest::BatchType_Update) {
+ BatchedUpdateRequest* clientUpdateRequest = _clientRequest->getUpdateRequest();
+ BatchedUpdateDocument* updateDoc = new BatchedUpdateDocument;
+ clientUpdateRequest->getUpdatesAt(writeOpRef.first)->cloneTo(updateDoc);
+ request->getUpdateRequest()->addToUpdates(updateDoc);
+ } else {
+ dassert(batchType == BatchedCommandRequest::BatchType_Delete);
+ BatchedDeleteRequest* clientDeleteRequest = _clientRequest->getDeleteRequest();
+ BatchedDeleteDocument* deleteDoc = new BatchedDeleteDocument;
+ clientDeleteRequest->getDeletesAt(writeOpRef.first)->cloneTo(deleteDoc);
+ request->getDeleteRequest()->addToDeletes(deleteDoc);
+ }
+
+ // TODO: We can add logic here to allow aborting individual ops
+ // if ( NULL == response ) {
+ // ->responses.erase( it++ );
+ // continue;
+ //}
+ }
+
+ if (_clientRequest->isWriteConcernSet()) {
+ if (_clientRequest->isVerboseWC()) {
+ request->setWriteConcern(_clientRequest->getWriteConcern());
+ } else {
+ // Mongos needs to send to the shard with w > 0 so it will be able to
+ // see the writeErrors.
+ request->setWriteConcern(upgradeWriteConcern(_clientRequest->getWriteConcern()));
+ }
+ }
+
+ if (!request->isOrderedSet()) {
+ request->setOrdered(_clientRequest->getOrdered());
+ }
+
+ unique_ptr<BatchedRequestMetadata> requestMetadata(new BatchedRequestMetadata());
+ requestMetadata->setShardName(targetedBatch.getEndpoint().shardName);
+ requestMetadata->setShardVersion(targetedBatch.getEndpoint().shardVersion);
+ requestMetadata->setSession(0);
+ request->setMetadata(requestMetadata.release());
+}
- if (numModified >= 0)
- stats->numModified += numModified;
- else
- stats->numModified = -1; // sentinel used to indicate we omit the field downstream
+//
+// Helpers for manipulating batch responses
+//
- stats->numUpserted += numUpserted;
- }
- else {
- dassert( batchType == BatchedCommandRequest::BatchType_Delete );
- stats->numDeleted += response.getN();
- }
+namespace {
+struct WriteErrorDetailComp {
+ bool operator()(const WriteErrorDetail* errorA, const WriteErrorDetail* errorB) const {
+ return errorA->getIndex() < errorB->getIndex();
}
+};
+}
- void BatchWriteOp::noteBatchResponse( const TargetedWriteBatch& targetedBatch,
- const BatchedCommandResponse& response,
- TrackedErrors* trackedErrors ) {
-
- if ( !response.getOk() ) {
+static void cloneCommandErrorTo(const BatchedCommandResponse& batchResp,
+ WriteErrorDetail* details) {
+ details->setErrCode(batchResp.getErrCode());
+ details->setErrMessage(batchResp.getErrMessage());
+}
- WriteErrorDetail error;
- cloneCommandErrorTo( response, &error );
+// Given *either* a batch error or an array of per-item errors, copies errors we're interested
+// in into a TrackedErrorMap
+static void trackErrors(const ShardEndpoint& endpoint,
+ const vector<WriteErrorDetail*> itemErrors,
+ TrackedErrors* trackedErrors) {
+ for (vector<WriteErrorDetail*>::const_iterator it = itemErrors.begin(); it != itemErrors.end();
+ ++it) {
+ const WriteErrorDetail* error = *it;
- // Treat command errors exactly like other failures of the batch
- // Note that no errors will be tracked from these failures - as-designed
- noteBatchError( targetedBatch, error );
- return;
+ if (trackedErrors->isTracking(error->getErrCode())) {
+ trackedErrors->addError(new ShardError(endpoint, *error));
}
+ }
+}
- dassert( response.getOk() );
-
- // Stop tracking targeted batch
- _targeted.erase( &targetedBatch );
+static void incBatchStats(BatchedCommandRequest::BatchType batchType,
+ const BatchedCommandResponse& response,
+ BatchWriteStats* stats) {
+ if (batchType == BatchedCommandRequest::BatchType_Insert) {
+ stats->numInserted += response.getN();
+ } else if (batchType == BatchedCommandRequest::BatchType_Update) {
+ int numUpserted = 0;
+ if (response.isUpsertDetailsSet()) {
+ numUpserted = response.sizeUpsertDetails();
+ }
+ stats->numMatched += (response.getN() - numUpserted);
+ long long numModified = response.getNModified();
+
+ if (numModified >= 0)
+ stats->numModified += numModified;
+ else
+ stats->numModified = -1; // sentinel used to indicate we omit the field downstream
+
+ stats->numUpserted += numUpserted;
+ } else {
+ dassert(batchType == BatchedCommandRequest::BatchType_Delete);
+ stats->numDeleted += response.getN();
+ }
+}
- // Increment stats for this batch
- incBatchStats( _clientRequest->getBatchType(), response, _stats.get() );
+void BatchWriteOp::noteBatchResponse(const TargetedWriteBatch& targetedBatch,
+ const BatchedCommandResponse& response,
+ TrackedErrors* trackedErrors) {
+ if (!response.getOk()) {
+ WriteErrorDetail error;
+ cloneCommandErrorTo(response, &error);
- //
- // Assign errors to particular items.
- // Write Concern errors are stored and handled later.
- //
+ // Treat command errors exactly like other failures of the batch
+ // Note that no errors will be tracked from these failures - as-designed
+ noteBatchError(targetedBatch, error);
+ return;
+ }
- // Special handling for write concern errors, save for later
- if ( response.isWriteConcernErrorSet() ) {
- unique_ptr<ShardWCError> wcError( new ShardWCError( targetedBatch.getEndpoint(),
- *response.getWriteConcernError() ));
- _wcErrors.mutableVector().push_back( wcError.release() );
- }
+ dassert(response.getOk());
- vector<WriteErrorDetail*> itemErrors;
+ // Stop tracking targeted batch
+ _targeted.erase(&targetedBatch);
- // Handle batch and per-item errors
- if ( response.isErrDetailsSet() ) {
+ // Increment stats for this batch
+ incBatchStats(_clientRequest->getBatchType(), response, _stats.get());
- // Per-item errors were set
- itemErrors.insert( itemErrors.begin(),
- response.getErrDetails().begin(),
- response.getErrDetails().end() );
+ //
+ // Assign errors to particular items.
+ // Write Concern errors are stored and handled later.
+ //
- // Sort per-item errors by index
- std::sort( itemErrors.begin(), itemErrors.end(), WriteErrorDetailComp() );
- }
+ // Special handling for write concern errors, save for later
+ if (response.isWriteConcernErrorSet()) {
+ unique_ptr<ShardWCError> wcError(
+ new ShardWCError(targetedBatch.getEndpoint(), *response.getWriteConcernError()));
+ _wcErrors.mutableVector().push_back(wcError.release());
+ }
- //
- // Go through all pending responses of the op and sorted remote reponses, populate errors
- // This will either set all errors to the batch error or apply per-item errors as-needed
- //
- // If the batch is ordered, cancel all writes after the first error for retargeting.
- //
+ vector<WriteErrorDetail*> itemErrors;
- bool ordered = _clientRequest->getOrdered();
+ // Handle batch and per-item errors
+ if (response.isErrDetailsSet()) {
+ // Per-item errors were set
+ itemErrors.insert(
+ itemErrors.begin(), response.getErrDetails().begin(), response.getErrDetails().end());
- vector<WriteErrorDetail*>::iterator itemErrorIt = itemErrors.begin();
- int index = 0;
- WriteErrorDetail* lastError = NULL;
- for ( vector<TargetedWrite*>::const_iterator it = targetedBatch.getWrites().begin();
- it != targetedBatch.getWrites().end(); ++it, ++index ) {
+ // Sort per-item errors by index
+ std::sort(itemErrors.begin(), itemErrors.end(), WriteErrorDetailComp());
+ }
- const TargetedWrite* write = *it;
- WriteOp& writeOp = _writeOps[write->writeOpRef.first];
+ //
+ // Go through all pending responses of the op and sorted remote reponses, populate errors
+ // This will either set all errors to the batch error or apply per-item errors as-needed
+ //
+ // If the batch is ordered, cancel all writes after the first error for retargeting.
+ //
- dassert( writeOp.getWriteState() == WriteOpState_Pending );
+ bool ordered = _clientRequest->getOrdered();
- // See if we have an error for the write
- WriteErrorDetail* writeError = NULL;
+ vector<WriteErrorDetail*>::iterator itemErrorIt = itemErrors.begin();
+ int index = 0;
+ WriteErrorDetail* lastError = NULL;
+ for (vector<TargetedWrite*>::const_iterator it = targetedBatch.getWrites().begin();
+ it != targetedBatch.getWrites().end();
+ ++it, ++index) {
+ const TargetedWrite* write = *it;
+ WriteOp& writeOp = _writeOps[write->writeOpRef.first];
- if ( itemErrorIt != itemErrors.end() && ( *itemErrorIt )->getIndex() == index ) {
- // We have an per-item error for this write op's index
- writeError = *itemErrorIt;
- ++itemErrorIt;
- }
+ dassert(writeOp.getWriteState() == WriteOpState_Pending);
- // Finish the response (with error, if needed)
- if ( NULL == writeError ) {
- if ( !ordered || !lastError ){
- writeOp.noteWriteComplete( *write );
- }
- else {
- // We didn't actually apply this write - cancel so we can retarget
- dassert( writeOp.getNumTargeted() == 1u );
- writeOp.cancelWrites( lastError );
- }
- }
- else {
- writeOp.noteWriteError( *write, *writeError );
- lastError = writeError;
- }
- }
+ // See if we have an error for the write
+ WriteErrorDetail* writeError = NULL;
- // Track errors we care about, whether batch or individual errors
- if ( NULL != trackedErrors ) {
- trackErrors( targetedBatch.getEndpoint(), itemErrors, trackedErrors );
+ if (itemErrorIt != itemErrors.end() && (*itemErrorIt)->getIndex() == index) {
+ // We have an per-item error for this write op's index
+ writeError = *itemErrorIt;
+ ++itemErrorIt;
}
- // Track upserted ids if we need to
- if ( response.isUpsertDetailsSet() ) {
-
- const vector<BatchedUpsertDetail*>& upsertedIds = response.getUpsertDetails();
- for ( vector<BatchedUpsertDetail*>::const_iterator it = upsertedIds.begin();
- it != upsertedIds.end(); ++it ) {
-
- // The child upserted details don't have the correct index for the full batch
- const BatchedUpsertDetail* childUpsertedId = *it;
-
- // Work backward from the child batch item index to the batch item index
- int childBatchIndex = childUpsertedId->getIndex();
- int batchIndex = targetedBatch.getWrites()[childBatchIndex]->writeOpRef.first;
-
- // Push the upserted id with the correct index into the batch upserted ids
- BatchedUpsertDetail* upsertedId = new BatchedUpsertDetail;
- upsertedId->setIndex( batchIndex );
- upsertedId->setUpsertedID( childUpsertedId->getUpsertedID() );
- _upsertedIds.mutableVector().push_back( upsertedId );
+ // Finish the response (with error, if needed)
+ if (NULL == writeError) {
+ if (!ordered || !lastError) {
+ writeOp.noteWriteComplete(*write);
+ } else {
+ // We didn't actually apply this write - cancel so we can retarget
+ dassert(writeOp.getNumTargeted() == 1u);
+ writeOp.cancelWrites(lastError);
}
+ } else {
+ writeOp.noteWriteError(*write, *writeError);
+ lastError = writeError;
}
}
- static void toWriteErrorResponse( const WriteErrorDetail& error,
- bool ordered,
- int numWrites,
- BatchedCommandResponse* writeErrResponse ) {
+ // Track errors we care about, whether batch or individual errors
+ if (NULL != trackedErrors) {
+ trackErrors(targetedBatch.getEndpoint(), itemErrors, trackedErrors);
+ }
+
+ // Track upserted ids if we need to
+ if (response.isUpsertDetailsSet()) {
+ const vector<BatchedUpsertDetail*>& upsertedIds = response.getUpsertDetails();
+ for (vector<BatchedUpsertDetail*>::const_iterator it = upsertedIds.begin();
+ it != upsertedIds.end();
+ ++it) {
+ // The child upserted details don't have the correct index for the full batch
+ const BatchedUpsertDetail* childUpsertedId = *it;
- writeErrResponse->setOk( true );
- writeErrResponse->setN( 0 );
+ // Work backward from the child batch item index to the batch item index
+ int childBatchIndex = childUpsertedId->getIndex();
+ int batchIndex = targetedBatch.getWrites()[childBatchIndex]->writeOpRef.first;
- int numErrors = ordered ? 1 : numWrites;
- for ( int i = 0; i < numErrors; i++ ) {
- unique_ptr<WriteErrorDetail> errorClone( new WriteErrorDetail );
- error.cloneTo( errorClone.get() );
- errorClone->setIndex( i );
- writeErrResponse->addToErrDetails( errorClone.release() );
+ // Push the upserted id with the correct index into the batch upserted ids
+ BatchedUpsertDetail* upsertedId = new BatchedUpsertDetail;
+ upsertedId->setIndex(batchIndex);
+ upsertedId->setUpsertedID(childUpsertedId->getUpsertedID());
+ _upsertedIds.mutableVector().push_back(upsertedId);
}
-
- dassert( writeErrResponse->isValid( NULL ) );
}
+}
- void BatchWriteOp::noteBatchError( const TargetedWriteBatch& targetedBatch,
- const WriteErrorDetail& error ) {
-
- // Treat errors to get a batch response as failures of the contained writes
- BatchedCommandResponse emulatedResponse;
- toWriteErrorResponse( error,
- _clientRequest->getOrdered(),
- targetedBatch.getWrites().size(),
- &emulatedResponse );
+static void toWriteErrorResponse(const WriteErrorDetail& error,
+ bool ordered,
+ int numWrites,
+ BatchedCommandResponse* writeErrResponse) {
+ writeErrResponse->setOk(true);
+ writeErrResponse->setN(0);
- noteBatchResponse( targetedBatch, emulatedResponse, NULL );
+ int numErrors = ordered ? 1 : numWrites;
+ for (int i = 0; i < numErrors; i++) {
+ unique_ptr<WriteErrorDetail> errorClone(new WriteErrorDetail);
+ error.cloneTo(errorClone.get());
+ errorClone->setIndex(i);
+ writeErrResponse->addToErrDetails(errorClone.release());
}
- void BatchWriteOp::abortBatch( const WriteErrorDetail& error ) {
+ dassert(writeErrResponse->isValid(NULL));
+}
- dassert( !isFinished() );
- dassert( numWriteOpsIn( WriteOpState_Pending ) == 0 );
+void BatchWriteOp::noteBatchError(const TargetedWriteBatch& targetedBatch,
+ const WriteErrorDetail& error) {
+ // Treat errors to get a batch response as failures of the contained writes
+ BatchedCommandResponse emulatedResponse;
+ toWriteErrorResponse(
+ error, _clientRequest->getOrdered(), targetedBatch.getWrites().size(), &emulatedResponse);
- size_t numWriteOps = _clientRequest->sizeWriteOps();
- bool orderedOps = _clientRequest->getOrdered();
- for ( size_t i = 0; i < numWriteOps; ++i ) {
+ noteBatchResponse(targetedBatch, emulatedResponse, NULL);
+}
- WriteOp& writeOp = _writeOps[i];
- // Can only be called with no outstanding batches
- dassert( writeOp.getWriteState() != WriteOpState_Pending );
+void BatchWriteOp::abortBatch(const WriteErrorDetail& error) {
+ dassert(!isFinished());
+ dassert(numWriteOpsIn(WriteOpState_Pending) == 0);
- if ( writeOp.getWriteState() < WriteOpState_Completed ) {
+ size_t numWriteOps = _clientRequest->sizeWriteOps();
+ bool orderedOps = _clientRequest->getOrdered();
+ for (size_t i = 0; i < numWriteOps; ++i) {
+ WriteOp& writeOp = _writeOps[i];
+ // Can only be called with no outstanding batches
+ dassert(writeOp.getWriteState() != WriteOpState_Pending);
- writeOp.setOpError( error );
+ if (writeOp.getWriteState() < WriteOpState_Completed) {
+ writeOp.setOpError(error);
- // Only one error if we're ordered
- if ( orderedOps ) break;
- }
+ // Only one error if we're ordered
+ if (orderedOps)
+ break;
}
-
- dassert( isFinished() );
}
- bool BatchWriteOp::isFinished() {
-
- size_t numWriteOps = _clientRequest->sizeWriteOps();
- bool orderedOps = _clientRequest->getOrdered();
- for ( size_t i = 0; i < numWriteOps; ++i ) {
- WriteOp& writeOp = _writeOps[i];
- if ( writeOp.getWriteState() < WriteOpState_Completed ) return false;
- else if ( orderedOps && writeOp.getWriteState() == WriteOpState_Error ) return true;
- }
+ dassert(isFinished());
+}
- return true;
+bool BatchWriteOp::isFinished() {
+ size_t numWriteOps = _clientRequest->sizeWriteOps();
+ bool orderedOps = _clientRequest->getOrdered();
+ for (size_t i = 0; i < numWriteOps; ++i) {
+ WriteOp& writeOp = _writeOps[i];
+ if (writeOp.getWriteState() < WriteOpState_Completed)
+ return false;
+ else if (orderedOps && writeOp.getWriteState() == WriteOpState_Error)
+ return true;
}
- //
- // Aggregation functions for building the final response errors
- //
-
- void BatchWriteOp::buildClientResponse( BatchedCommandResponse* batchResp ) {
+ return true;
+}
- dassert( isFinished() );
+//
+// Aggregation functions for building the final response errors
+//
- // Result is OK
- batchResp->setOk( true );
+void BatchWriteOp::buildClientResponse(BatchedCommandResponse* batchResp) {
+ dassert(isFinished());
- // For non-verbose, it's all we need.
- if ( !_clientRequest->isVerboseWC() ) {
- dassert( batchResp->isValid( NULL ) );
- return;
- }
+ // Result is OK
+ batchResp->setOk(true);
- //
- // Find all the errors in the batch
- //
+ // For non-verbose, it's all we need.
+ if (!_clientRequest->isVerboseWC()) {
+ dassert(batchResp->isValid(NULL));
+ return;
+ }
- vector<WriteOp*> errOps;
+ //
+ // Find all the errors in the batch
+ //
- size_t numWriteOps = _clientRequest->sizeWriteOps();
- for ( size_t i = 0; i < numWriteOps; ++i ) {
+ vector<WriteOp*> errOps;
- WriteOp& writeOp = _writeOps[i];
+ size_t numWriteOps = _clientRequest->sizeWriteOps();
+ for (size_t i = 0; i < numWriteOps; ++i) {
+ WriteOp& writeOp = _writeOps[i];
- if ( writeOp.getWriteState() == WriteOpState_Error ) {
- errOps.push_back( &writeOp );
- }
+ if (writeOp.getWriteState() == WriteOpState_Error) {
+ errOps.push_back(&writeOp);
}
+ }
- //
- // Build the per-item errors.
- //
-
- if ( !errOps.empty() ) {
- for ( vector<WriteOp*>::iterator it = errOps.begin(); it != errOps.end(); ++it ) {
- WriteOp& writeOp = **it;
- WriteErrorDetail* error = new WriteErrorDetail();
- writeOp.getOpError().cloneTo( error );
- batchResp->addToErrDetails( error );
- }
- }
-
- // Only return a write concern error if everything succeeded (unordered or ordered)
- // OR if something succeeded and we're unordered
- bool reportWCError = errOps.empty()
- || ( !_clientRequest->getOrdered()
- && errOps.size() < _clientRequest->sizeWriteOps() );
- if ( !_wcErrors.empty() && reportWCError ) {
-
- WCErrorDetail* error = new WCErrorDetail;
-
- // Generate the multi-error message below
- stringstream msg;
- if ( _wcErrors.size() > 1 ) {
- msg << "multiple errors reported : ";
- error->setErrCode( ErrorCodes::WriteConcernFailed );
- }
- else {
- error->setErrCode( ( *_wcErrors.begin() )->error.getErrCode() );
- }
-
- for ( vector<ShardWCError*>::const_iterator it = _wcErrors.begin();
- it != _wcErrors.end(); ++it ) {
- const ShardWCError* wcError = *it;
- if ( it != _wcErrors.begin() )
- msg << " :: and :: ";
- msg << wcError->error.getErrMessage() << " at " << wcError->endpoint.shardName;
- }
+ //
+ // Build the per-item errors.
+ //
- error->setErrMessage( msg.str() );
- batchResp->setWriteConcernError( error );
+ if (!errOps.empty()) {
+ for (vector<WriteOp*>::iterator it = errOps.begin(); it != errOps.end(); ++it) {
+ WriteOp& writeOp = **it;
+ WriteErrorDetail* error = new WriteErrorDetail();
+ writeOp.getOpError().cloneTo(error);
+ batchResp->addToErrDetails(error);
}
+ }
- //
- // Append the upserted ids, if required
- //
+ // Only return a write concern error if everything succeeded (unordered or ordered)
+ // OR if something succeeded and we're unordered
+ bool reportWCError = errOps.empty() ||
+ (!_clientRequest->getOrdered() && errOps.size() < _clientRequest->sizeWriteOps());
+ if (!_wcErrors.empty() && reportWCError) {
+ WCErrorDetail* error = new WCErrorDetail;
- if ( _upsertedIds.size() != 0 ) {
- batchResp->setUpsertDetails( _upsertedIds.vector() );
+ // Generate the multi-error message below
+ stringstream msg;
+ if (_wcErrors.size() > 1) {
+ msg << "multiple errors reported : ";
+ error->setErrCode(ErrorCodes::WriteConcernFailed);
+ } else {
+ error->setErrCode((*_wcErrors.begin())->error.getErrCode());
}
- // Stats
- int nValue = _stats->numInserted + _stats->numUpserted + _stats->numMatched
- + _stats->numDeleted;
- batchResp->setN( nValue );
- if ( _clientRequest->getBatchType() == BatchedCommandRequest::BatchType_Update &&
- _stats->numModified >= 0) {
- batchResp->setNModified( _stats->numModified );
+ for (vector<ShardWCError*>::const_iterator it = _wcErrors.begin(); it != _wcErrors.end();
+ ++it) {
+ const ShardWCError* wcError = *it;
+ if (it != _wcErrors.begin())
+ msg << " :: and :: ";
+ msg << wcError->error.getErrMessage() << " at " << wcError->endpoint.shardName;
}
- dassert( batchResp->isValid( NULL ) );
+ error->setErrMessage(msg.str());
+ batchResp->setWriteConcernError(error);
}
- BatchWriteOp::~BatchWriteOp() {
- // Caller's responsibility to dispose of TargetedBatches
- dassert( _targeted.empty() );
-
- if ( NULL != _writeOps ) {
-
- size_t numWriteOps = _clientRequest->sizeWriteOps();
- for ( size_t i = 0; i < numWriteOps; ++i ) {
- // Placement new so manual destruct
- _writeOps[i].~WriteOp();
- }
+ //
+ // Append the upserted ids, if required
+ //
- ::operator delete[]( _writeOps );
- _writeOps = NULL;
- }
+ if (_upsertedIds.size() != 0) {
+ batchResp->setUpsertDetails(_upsertedIds.vector());
}
- int BatchWriteOp::numWriteOps() const {
- return static_cast<int>( _clientRequest->sizeWriteOps() );
+ // Stats
+ int nValue =
+ _stats->numInserted + _stats->numUpserted + _stats->numMatched + _stats->numDeleted;
+ batchResp->setN(nValue);
+ if (_clientRequest->getBatchType() == BatchedCommandRequest::BatchType_Update &&
+ _stats->numModified >= 0) {
+ batchResp->setNModified(_stats->numModified);
}
- int BatchWriteOp::numWriteOpsIn( WriteOpState opState ) const {
+ dassert(batchResp->isValid(NULL));
+}
+
+BatchWriteOp::~BatchWriteOp() {
+ // Caller's responsibility to dispose of TargetedBatches
+ dassert(_targeted.empty());
- // TODO: This could be faster, if we tracked this info explicitly
+ if (NULL != _writeOps) {
size_t numWriteOps = _clientRequest->sizeWriteOps();
- int count = 0;
- for ( size_t i = 0; i < numWriteOps; ++i ) {
- WriteOp& writeOp = _writeOps[i];
- if ( writeOp.getWriteState() == opState )
- ++count;
+ for (size_t i = 0; i < numWriteOps; ++i) {
+ // Placement new so manual destruct
+ _writeOps[i].~WriteOp();
}
- return count;
+ ::operator delete[](_writeOps);
+ _writeOps = NULL;
}
+}
- void TrackedErrors::startTracking( int errCode ) {
- dassert( !isTracking( errCode ) );
- _errorMap.insert( make_pair( errCode, vector<ShardError*>() ) );
- }
+int BatchWriteOp::numWriteOps() const {
+ return static_cast<int>(_clientRequest->sizeWriteOps());
+}
- bool TrackedErrors::isTracking( int errCode ) const {
- return _errorMap.find( errCode ) != _errorMap.end();
+int BatchWriteOp::numWriteOpsIn(WriteOpState opState) const {
+ // TODO: This could be faster, if we tracked this info explicitly
+ size_t numWriteOps = _clientRequest->sizeWriteOps();
+ int count = 0;
+ for (size_t i = 0; i < numWriteOps; ++i) {
+ WriteOp& writeOp = _writeOps[i];
+ if (writeOp.getWriteState() == opState)
+ ++count;
}
- void TrackedErrors::addError( ShardError* error ) {
- TrackedErrorMap::iterator seenIt = _errorMap.find( error->error.getErrCode() );
- if ( seenIt == _errorMap.end() ) return;
- seenIt->second.push_back( error );
- }
+ return count;
+}
- const vector<ShardError*>& TrackedErrors::getErrors( int errCode ) const {
- dassert( isTracking( errCode ) );
- return _errorMap.find( errCode )->second;
- }
+void TrackedErrors::startTracking(int errCode) {
+ dassert(!isTracking(errCode));
+ _errorMap.insert(make_pair(errCode, vector<ShardError*>()));
+}
- void TrackedErrors::clear() {
- for ( TrackedErrorMap::iterator it = _errorMap.begin(); it != _errorMap.end(); ++it ) {
+bool TrackedErrors::isTracking(int errCode) const {
+ return _errorMap.find(errCode) != _errorMap.end();
+}
- vector<ShardError*>& errors = it->second;
+void TrackedErrors::addError(ShardError* error) {
+ TrackedErrorMap::iterator seenIt = _errorMap.find(error->error.getErrCode());
+ if (seenIt == _errorMap.end())
+ return;
+ seenIt->second.push_back(error);
+}
- for ( vector<ShardError*>::iterator errIt = errors.begin(); errIt != errors.end();
- ++errIt ) {
- delete *errIt;
- }
- errors.clear();
- }
- }
+const vector<ShardError*>& TrackedErrors::getErrors(int errCode) const {
+ dassert(isTracking(errCode));
+ return _errorMap.find(errCode)->second;
+}
+
+void TrackedErrors::clear() {
+ for (TrackedErrorMap::iterator it = _errorMap.begin(); it != _errorMap.end(); ++it) {
+ vector<ShardError*>& errors = it->second;
- TrackedErrors::~TrackedErrors() {
- clear();
+ for (vector<ShardError*>::iterator errIt = errors.begin(); errIt != errors.end(); ++errIt) {
+ delete *errIt;
+ }
+ errors.clear();
}
+}
+TrackedErrors::~TrackedErrors() {
+ clear();
+}
}
diff --git a/src/mongo/s/write_ops/batch_write_op.h b/src/mongo/s/write_ops/batch_write_op.h
index 224d9985ef3..0add9339268 100644
--- a/src/mongo/s/write_ops/batch_write_op.h
+++ b/src/mongo/s/write_ops/batch_write_op.h
@@ -44,260 +44,244 @@
namespace mongo {
- class TargetedWriteBatch;
- struct ShardError;
- struct ShardWCError;
- class TrackedErrors;
- struct BatchWriteStats;
+class TargetedWriteBatch;
+struct ShardError;
+struct ShardWCError;
+class TrackedErrors;
+struct BatchWriteStats;
+
+/**
+ * The BatchWriteOp class manages the lifecycle of a batched write received by mongos. Each
+ * item in a batch is tracked via a WriteOp, and the function of the BatchWriteOp is to
+ * aggregate the dispatched requests and responses for the underlying WriteOps.
+ *
+ * Overall, the BatchWriteOp lifecycle is similar to the WriteOp lifecycle, with the following
+ * stages:
+ *
+ * 0) Client request comes in, batch write op is initialized
+ *
+ * 1a) One or more ops in the batch are targeted using targetBatch, resulting in
+ * TargetedWriteBatches for these ops.
+ * 1b) There are targeting errors, and the batch must be retargeted after refreshing the
+ * NSTargeter.
+ *
+ * 2) (Child BatchCommandRequests are be built for each TargetedWriteBatch before sending)
+ *
+ * 3) Responses for sent TargetedWriteBatches are noted, errors are stored and aggregated per-
+ * write-op. Errors the caller is interested in are returned.
+ *
+ * 4) If the batch write is not finished, goto 0
+ *
+ * 5) When all responses come back for all write ops, errors are aggregated and returned in
+ * a client response
+ *
+ */
+class BatchWriteOp {
+ MONGO_DISALLOW_COPYING(BatchWriteOp);
+
+public:
+ BatchWriteOp();
+
+ ~BatchWriteOp();
/**
- * The BatchWriteOp class manages the lifecycle of a batched write received by mongos. Each
- * item in a batch is tracked via a WriteOp, and the function of the BatchWriteOp is to
- * aggregate the dispatched requests and responses for the underlying WriteOps.
- *
- * Overall, the BatchWriteOp lifecycle is similar to the WriteOp lifecycle, with the following
- * stages:
- *
- * 0) Client request comes in, batch write op is initialized
- *
- * 1a) One or more ops in the batch are targeted using targetBatch, resulting in
- * TargetedWriteBatches for these ops.
- * 1b) There are targeting errors, and the batch must be retargeted after refreshing the
- * NSTargeter.
+ * Initializes the BatchWriteOp from a client batch request.
+ */
+ void initClientRequest(const BatchedCommandRequest* clientRequest);
+
+ /**
+ * Targets one or more of the next write ops in this batch op using a NSTargeter. The
+ * resulting TargetedWrites are aggregated together in the returned TargetedWriteBatches.
*
- * 2) (Child BatchCommandRequests are be built for each TargetedWriteBatch before sending)
+ * If 'recordTargetErrors' is false, any targeting error will abort all current batches and
+ * the method will return the targeting error. No targetedBatches will be returned on
+ * error.
*
- * 3) Responses for sent TargetedWriteBatches are noted, errors are stored and aggregated per-
- * write-op. Errors the caller is interested in are returned.
+ * Otherwise, if 'recordTargetErrors' is true, targeting errors will be recorded for each
+ * write op that fails to target, and the method will return OK.
*
- * 4) If the batch write is not finished, goto 0
+ * (The idea here is that if we are sure our NSTargeter is up-to-date we should record
+ * targeting errors, but if not we should refresh once first.)
*
- * 5) When all responses come back for all write ops, errors are aggregated and returned in
- * a client response
+ * Returned TargetedWriteBatches are owned by the caller.
+ */
+ Status targetBatch(const NSTargeter& targeter,
+ bool recordTargetErrors,
+ std::vector<TargetedWriteBatch*>* targetedBatches);
+
+ /**
+ * Fills a BatchCommandRequest from a TargetedWriteBatch for this BatchWriteOp.
+ */
+ void buildBatchRequest(const TargetedWriteBatch& targetedBatch,
+ BatchedCommandRequest* request) const;
+
+ /**
+ * Stores a response from one of the outstanding TargetedWriteBatches for this BatchWriteOp.
+ * The response may be in any form, error or not.
*
+ * There is an additional optional 'trackedErrors' parameter, which can be used to return
+ * copies of any write errors in the response that the caller is interested in (specified by
+ * errCode). (This avoids external callers needing to know much about the response format.)
*/
- class BatchWriteOp {
- MONGO_DISALLOW_COPYING(BatchWriteOp);
- public:
-
- BatchWriteOp();
-
- ~BatchWriteOp();
-
- /**
- * Initializes the BatchWriteOp from a client batch request.
- */
- void initClientRequest( const BatchedCommandRequest* clientRequest );
-
- /**
- * Targets one or more of the next write ops in this batch op using a NSTargeter. The
- * resulting TargetedWrites are aggregated together in the returned TargetedWriteBatches.
- *
- * If 'recordTargetErrors' is false, any targeting error will abort all current batches and
- * the method will return the targeting error. No targetedBatches will be returned on
- * error.
- *
- * Otherwise, if 'recordTargetErrors' is true, targeting errors will be recorded for each
- * write op that fails to target, and the method will return OK.
- *
- * (The idea here is that if we are sure our NSTargeter is up-to-date we should record
- * targeting errors, but if not we should refresh once first.)
- *
- * Returned TargetedWriteBatches are owned by the caller.
- */
- Status targetBatch( const NSTargeter& targeter,
- bool recordTargetErrors,
- std::vector<TargetedWriteBatch*>* targetedBatches );
-
- /**
- * Fills a BatchCommandRequest from a TargetedWriteBatch for this BatchWriteOp.
- */
- void buildBatchRequest( const TargetedWriteBatch& targetedBatch,
- BatchedCommandRequest* request ) const;
-
- /**
- * Stores a response from one of the outstanding TargetedWriteBatches for this BatchWriteOp.
- * The response may be in any form, error or not.
- *
- * There is an additional optional 'trackedErrors' parameter, which can be used to return
- * copies of any write errors in the response that the caller is interested in (specified by
- * errCode). (This avoids external callers needing to know much about the response format.)
- */
- void noteBatchResponse( const TargetedWriteBatch& targetedBatch,
- const BatchedCommandResponse& response,
- TrackedErrors* trackedErrors );
-
- /**
- * Stores an error that occurred trying to send/recv a TargetedWriteBatch for this
- * BatchWriteOp.
- */
- void noteBatchError( const TargetedWriteBatch& targetedBatch,
- const WriteErrorDetail& error );
-
- /**
- * Aborts any further writes in the batch with the provided error. There must be no pending
- * ops awaiting results when a batch is aborted.
- *
- * Batch is finished immediately after aborting.
- */
- void abortBatch( const WriteErrorDetail& error );
-
- /**
- * Returns false if the batch write op needs more processing.
- */
- bool isFinished();
-
- /**
- * Fills a batch response to send back to the client.
- */
- void buildClientResponse( BatchedCommandResponse* batchResp );
-
- //
- // Accessors
- //
-
- int numWriteOps() const;
-
- int numWriteOpsIn( WriteOpState state ) const;
-
- private:
-
- // Incoming client request, not owned here
- const BatchedCommandRequest* _clientRequest;
-
- // Array of ops being processed from the client request
- WriteOp* _writeOps;
-
- // Current outstanding batch op write requests
- // Not owned here but tracked for reporting
- std::set<const TargetedWriteBatch*> _targeted;
-
- // Write concern responses from all write batches so far
- OwnedPointerVector<ShardWCError> _wcErrors;
-
- // Upserted ids for the whole write batch
- OwnedPointerVector<BatchedUpsertDetail> _upsertedIds;
-
- // Stats for the entire batch op
- std::unique_ptr<BatchWriteStats> _stats;
- };
-
- struct BatchWriteStats {
-
- BatchWriteStats();
-
- int numInserted;
- int numUpserted;
- int numMatched;
- int numModified;
- int numDeleted;
-
- std::string toString() const {
- StringBuilder str;
- str << "numInserted: " << numInserted
- << " numUpserted: " << numUpserted
- << " numMatched: " << numMatched
- << " numModified: " << numModified
- << " numDeleted: " << numDeleted;
- return str.str();
- }
-
- };
+ void noteBatchResponse(const TargetedWriteBatch& targetedBatch,
+ const BatchedCommandResponse& response,
+ TrackedErrors* trackedErrors);
/**
- * Data structure representing the information needed to make a batch request, along with
- * pointers to where the resulting responses should be placed.
+ * Stores an error that occurred trying to send/recv a TargetedWriteBatch for this
+ * BatchWriteOp.
+ */
+ void noteBatchError(const TargetedWriteBatch& targetedBatch, const WriteErrorDetail& error);
+
+ /**
+ * Aborts any further writes in the batch with the provided error. There must be no pending
+ * ops awaiting results when a batch is aborted.
*
- * Internal support for storage as a doubly-linked list, to allow the TargetedWriteBatch to
- * efficiently be registered for reporting.
+ * Batch is finished immediately after aborting.
*/
- class TargetedWriteBatch {
- MONGO_DISALLOW_COPYING(TargetedWriteBatch);
- public:
+ void abortBatch(const WriteErrorDetail& error);
- TargetedWriteBatch( const ShardEndpoint& endpoint ) :
- _endpoint( endpoint ) {
- }
+ /**
+ * Returns false if the batch write op needs more processing.
+ */
+ bool isFinished();
- const ShardEndpoint& getEndpoint() const {
- return _endpoint;
- }
+ /**
+ * Fills a batch response to send back to the client.
+ */
+ void buildClientResponse(BatchedCommandResponse* batchResp);
- /**
- * TargetedWrite is owned here once given to the TargetedWriteBatch
- */
- void addWrite( TargetedWrite* targetedWrite ) {
- _writes.mutableVector().push_back( targetedWrite );
- }
+ //
+ // Accessors
+ //
- const std::vector<TargetedWrite*>& getWrites() const {
- return _writes.vector();
- }
+ int numWriteOps() const;
- private:
+ int numWriteOpsIn(WriteOpState state) const;
- // Where to send the batch
- const ShardEndpoint _endpoint;
+private:
+ // Incoming client request, not owned here
+ const BatchedCommandRequest* _clientRequest;
- // Where the responses go
- // TargetedWrite*s are owned by the TargetedWriteBatch
- OwnedPointerVector<TargetedWrite> _writes;
- };
+ // Array of ops being processed from the client request
+ WriteOp* _writeOps;
- /**
- * Simple struct for storing an error with an endpoint.
- *
- * Certain types of errors are not stored in WriteOps or must be returned to a caller.
- */
- struct ShardError {
+ // Current outstanding batch op write requests
+ // Not owned here but tracked for reporting
+ std::set<const TargetedWriteBatch*> _targeted;
- ShardError( const ShardEndpoint& endpoint, const WriteErrorDetail& error ) :
- endpoint( endpoint ) {
- error.cloneTo( &this->error );
- }
+ // Write concern responses from all write batches so far
+ OwnedPointerVector<ShardWCError> _wcErrors;
- const ShardEndpoint endpoint;
- WriteErrorDetail error;
- };
+ // Upserted ids for the whole write batch
+ OwnedPointerVector<BatchedUpsertDetail> _upsertedIds;
- /**
- * Simple struct for storing a write concern error with an endpoint.
- *
- * Certain types of errors are not stored in WriteOps or must be returned to a caller.
- */
- struct ShardWCError {
+ // Stats for the entire batch op
+ std::unique_ptr<BatchWriteStats> _stats;
+};
+
+struct BatchWriteStats {
+ BatchWriteStats();
+
+ int numInserted;
+ int numUpserted;
+ int numMatched;
+ int numModified;
+ int numDeleted;
- ShardWCError( const ShardEndpoint& endpoint, const WCErrorDetail& error ) :
- endpoint( endpoint ) {
- error.cloneTo( &this->error );
- }
+ std::string toString() const {
+ StringBuilder str;
+ str << "numInserted: " << numInserted << " numUpserted: " << numUpserted
+ << " numMatched: " << numMatched << " numModified: " << numModified
+ << " numDeleted: " << numDeleted;
+ return str.str();
+ }
+};
- const ShardEndpoint endpoint;
- WCErrorDetail error;
- };
+/**
+ * Data structure representing the information needed to make a batch request, along with
+ * pointers to where the resulting responses should be placed.
+ *
+ * Internal support for storage as a doubly-linked list, to allow the TargetedWriteBatch to
+ * efficiently be registered for reporting.
+ */
+class TargetedWriteBatch {
+ MONGO_DISALLOW_COPYING(TargetedWriteBatch);
+
+public:
+ TargetedWriteBatch(const ShardEndpoint& endpoint) : _endpoint(endpoint) {}
+
+ const ShardEndpoint& getEndpoint() const {
+ return _endpoint;
+ }
/**
- * Helper class for tracking certain errors from batch operations
+ * TargetedWrite is owned here once given to the TargetedWriteBatch
*/
- class TrackedErrors {
- public:
+ void addWrite(TargetedWrite* targetedWrite) {
+ _writes.mutableVector().push_back(targetedWrite);
+ }
+
+ const std::vector<TargetedWrite*>& getWrites() const {
+ return _writes.vector();
+ }
+
+private:
+ // Where to send the batch
+ const ShardEndpoint _endpoint;
- ~TrackedErrors();
+ // Where the responses go
+ // TargetedWrite*s are owned by the TargetedWriteBatch
+ OwnedPointerVector<TargetedWrite> _writes;
+};
+
+/**
+ * Simple struct for storing an error with an endpoint.
+ *
+ * Certain types of errors are not stored in WriteOps or must be returned to a caller.
+ */
+struct ShardError {
+ ShardError(const ShardEndpoint& endpoint, const WriteErrorDetail& error) : endpoint(endpoint) {
+ error.cloneTo(&this->error);
+ }
- void startTracking( int errCode );
+ const ShardEndpoint endpoint;
+ WriteErrorDetail error;
+};
- bool isTracking( int errCode ) const;
+/**
+ * Simple struct for storing a write concern error with an endpoint.
+ *
+ * Certain types of errors are not stored in WriteOps or must be returned to a caller.
+ */
+struct ShardWCError {
+ ShardWCError(const ShardEndpoint& endpoint, const WCErrorDetail& error) : endpoint(endpoint) {
+ error.cloneTo(&this->error);
+ }
+
+ const ShardEndpoint endpoint;
+ WCErrorDetail error;
+};
+
+/**
+ * Helper class for tracking certain errors from batch operations
+ */
+class TrackedErrors {
+public:
+ ~TrackedErrors();
- void addError( ShardError* error );
+ void startTracking(int errCode);
- const std::vector<ShardError*>& getErrors( int errCode ) const;
+ bool isTracking(int errCode) const;
- void clear();
+ void addError(ShardError* error);
- private:
+ const std::vector<ShardError*>& getErrors(int errCode) const;
- typedef unordered_map<int, std::vector<ShardError*> > TrackedErrorMap;
- TrackedErrorMap _errorMap;
- };
+ void clear();
+private:
+ typedef unordered_map<int, std::vector<ShardError*>> TrackedErrorMap;
+ TrackedErrorMap _errorMap;
+};
}
diff --git a/src/mongo/s/write_ops/batch_write_op_test.cpp b/src/mongo/s/write_ops/batch_write_op_test.cpp
index 6a00115531b..bb0e923cfb1 100644
--- a/src/mongo/s/write_ops/batch_write_op_test.cpp
+++ b/src/mongo/s/write_ops/batch_write_op_test.cpp
@@ -37,1801 +37,1747 @@
namespace {
- using std::unique_ptr;
- using std::string;
- using std::vector;
-
- using namespace mongo;
-
- static void initTargeterFullRange( const NamespaceString& nss,
- const ShardEndpoint& endpoint,
- MockNSTargeter* targeter ) {
-
- vector<MockRange*> mockRanges;
- mockRanges.push_back( new MockRange( endpoint,
- nss,
- BSON( "x" << MINKEY ),
- BSON( "x" << MAXKEY ) ) );
- targeter->init( mockRanges );
- }
-
- static void initTargeterSplitRange( const NamespaceString& nss,
- const ShardEndpoint& endpointA,
- const ShardEndpoint& endpointB,
- MockNSTargeter* targeter ) {
-
- vector<MockRange*> mockRanges;
- mockRanges.push_back( new MockRange( endpointA,
- nss,
- BSON( "x" << MINKEY ),
- BSON( "x" << 0 ) ) );
- mockRanges.push_back( new MockRange( endpointB,
- nss,
- BSON( "x" << 0 ),
- BSON( "x" << MAXKEY ) ) );
- targeter->init( mockRanges );
- }
-
- static void initTargeterHalfRange( const NamespaceString& nss,
- const ShardEndpoint& endpoint,
- MockNSTargeter* targeter ) {
-
- vector<MockRange*> mockRanges;
- mockRanges.push_back( new MockRange( endpoint,
- nss,
- BSON( "x" << MINKEY ),
- BSON( "x" << 0 ) ) );
-
- // x >= 0 values untargetable
-
- targeter->init( mockRanges );
- }
-
- static BatchedDeleteDocument* buildDelete( const BSONObj& query, int limit ) {
- BatchedDeleteDocument* deleteDoc = new BatchedDeleteDocument;
- deleteDoc->setQuery( query );
- deleteDoc->setLimit( limit );
- return deleteDoc;
- }
+using std::unique_ptr;
+using std::string;
+using std::vector;
+
+using namespace mongo;
+
+static void initTargeterFullRange(const NamespaceString& nss,
+ const ShardEndpoint& endpoint,
+ MockNSTargeter* targeter) {
+ vector<MockRange*> mockRanges;
+ mockRanges.push_back(new MockRange(endpoint, nss, BSON("x" << MINKEY), BSON("x" << MAXKEY)));
+ targeter->init(mockRanges);
+}
+
+static void initTargeterSplitRange(const NamespaceString& nss,
+ const ShardEndpoint& endpointA,
+ const ShardEndpoint& endpointB,
+ MockNSTargeter* targeter) {
+ vector<MockRange*> mockRanges;
+ mockRanges.push_back(new MockRange(endpointA, nss, BSON("x" << MINKEY), BSON("x" << 0)));
+ mockRanges.push_back(new MockRange(endpointB, nss, BSON("x" << 0), BSON("x" << MAXKEY)));
+ targeter->init(mockRanges);
+}
+
+static void initTargeterHalfRange(const NamespaceString& nss,
+ const ShardEndpoint& endpoint,
+ MockNSTargeter* targeter) {
+ vector<MockRange*> mockRanges;
+ mockRanges.push_back(new MockRange(endpoint, nss, BSON("x" << MINKEY), BSON("x" << 0)));
+
+ // x >= 0 values untargetable
+
+ targeter->init(mockRanges);
+}
+
+static BatchedDeleteDocument* buildDelete(const BSONObj& query, int limit) {
+ BatchedDeleteDocument* deleteDoc = new BatchedDeleteDocument;
+ deleteDoc->setQuery(query);
+ deleteDoc->setLimit(limit);
+ return deleteDoc;
+}
+
+static BatchedUpdateDocument* buildUpdate(const BSONObj& query, bool multi) {
+ BatchedUpdateDocument* updateDoc = new BatchedUpdateDocument;
+ updateDoc->setUpdateExpr(BSONObj());
+ updateDoc->setQuery(query);
+ updateDoc->setMulti(multi);
+ return updateDoc;
+}
+
+static BatchedUpdateDocument* buildUpdate(const BSONObj& query,
+ const BSONObj& updateExpr,
+ bool multi) {
+ BatchedUpdateDocument* updateDoc = new BatchedUpdateDocument;
+ updateDoc->setQuery(query);
+ updateDoc->setUpdateExpr(updateExpr);
+ updateDoc->setMulti(multi);
+ return updateDoc;
+}
+
+static void buildResponse(int n, BatchedCommandResponse* response) {
+ response->clear();
+ response->setOk(true);
+ response->setN(n);
+ ASSERT(response->isValid(NULL));
+}
+
+static void buildErrResponse(int code, const string& message, BatchedCommandResponse* response) {
+ response->clear();
+ response->setOk(false);
+ response->setN(0);
+ response->setErrCode(code);
+ response->setErrMessage(message);
+ ASSERT(response->isValid(NULL));
+}
+
+static void addError(int code, const string& message, int index, BatchedCommandResponse* response) {
+ unique_ptr<WriteErrorDetail> error(new WriteErrorDetail);
+ error->setErrCode(code);
+ error->setErrMessage(message);
+ error->setIndex(index);
+
+ response->addToErrDetails(error.release());
+}
+
+static void addWCError(BatchedCommandResponse* response) {
+ unique_ptr<WCErrorDetail> error(new WCErrorDetail);
+ error->setErrCode(ErrorCodes::WriteConcernFailed);
+ error->setErrMessage("mock wc error");
+
+ response->setWriteConcernError(error.release());
+}
+
+TEST(WriteOpTests, SingleOp) {
+ //
+ // Single-op targeting test
+ //
- static BatchedUpdateDocument* buildUpdate( const BSONObj& query, bool multi ) {
- BatchedUpdateDocument* updateDoc = new BatchedUpdateDocument;
- updateDoc->setUpdateExpr( BSONObj() );
- updateDoc->setQuery( query );
- updateDoc->setMulti( multi );
- return updateDoc;
- }
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterFullRange(nss, endpoint, &targeter);
- static BatchedUpdateDocument* buildUpdate(const BSONObj& query,
- const BSONObj& updateExpr,
- bool multi) {
- BatchedUpdateDocument* updateDoc = new BatchedUpdateDocument;
- updateDoc->setQuery( query );
- updateDoc->setUpdateExpr( updateExpr );
- updateDoc->setMulti( multi );
- return updateDoc;
- }
+ // Do single-target, single doc batch write op
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
- static void buildResponse( int n, BatchedCommandResponse* response ) {
- response->clear();
- response->setOk( true );
- response->setN( n );
- ASSERT( response->isValid( NULL ) );
- }
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+ ASSERT(!batchOp.isFinished());
- static void buildErrResponse( int code,
- const string& message,
- BatchedCommandResponse* response ) {
- response->clear();
- response->setOk( false );
- response->setN( 0 );
- response->setErrCode( code );
- response->setErrMessage( message );
- ASSERT( response->isValid( NULL ) );
- }
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
- static void addError( int code,
- const string& message,
- int index,
- BatchedCommandResponse* response ) {
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpoint);
- unique_ptr<WriteErrorDetail> error( new WriteErrorDetail );
- error->setErrCode( code );
- error->setErrMessage( message );
- error->setIndex( index );
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
- response->addToErrDetails( error.release() );
- }
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(batchOp.isFinished());
- static void addWCError( BatchedCommandResponse* response ) {
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+}
- unique_ptr<WCErrorDetail> error( new WCErrorDetail );
- error->setErrCode( ErrorCodes::WriteConcernFailed );
- error->setErrMessage( "mock wc error" );
+TEST(WriteOpTests, SingleError) {
+ //
+ // Single-op error test
+ //
- response->setWriteConcernError( error.release() );
- }
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterFullRange(nss, endpoint, &targeter);
- TEST(WriteOpTests, SingleOp) {
+ // Do single-target, single doc batch write op
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Delete);
+ request.setNS(nss.ns());
+ request.getDeleteRequest()->addToDeletes(buildDelete(BSON("x" << 1), 1));
- //
- // Single-op targeting test
- //
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+ ASSERT(!batchOp.isFinished());
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpoint( "shard", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterFullRange( nss, endpoint, &targeter );
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
- // Do single-target, single doc batch write op
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpoint);
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
- ASSERT( !batchOp.isFinished() );
+ BatchedCommandResponse response;
+ buildErrResponse(ErrorCodes::UnknownError, "message", &response);
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(batchOp.isFinished());
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 1u );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpoint );
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
- BatchedCommandResponse response;
- buildResponse( 1, &response );
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.sizeErrDetails(), 1u);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getErrCode(), response.getErrCode());
+ ASSERT(clientResponse.getErrDetailsAt(0)->getErrMessage().find(response.getErrMessage()) !=
+ string::npos);
+ ASSERT_EQUALS(clientResponse.getN(), 0);
+}
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( batchOp.isFinished() );
+TEST(WriteOpTests, SingleTargetError) {
+ //
+ // Single-op targeting error test
+ //
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterHalfRange(nss, endpoint, &targeter);
- }
+ // Do untargetable delete op
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Delete);
+ request.setNS(nss.ns());
+ request.getDeleteRequest()->addToDeletes(buildDelete(BSON("x" << 1), 1));
- TEST(WriteOpTests, SingleError) {
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+ ASSERT(!batchOp.isFinished());
- //
- // Single-op error test
- //
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpoint( "shard", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterFullRange( nss, endpoint, &targeter );
+ ASSERT(!status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 0u);
- // Do single-target, single doc batch write op
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Delete );
- request.setNS( nss.ns() );
- request.getDeleteRequest()->addToDeletes( buildDelete( BSON( "x" << 1 ), 1 ) );
+ // Record targeting failures
+ status = batchOp.targetBatch(targeter, true, &targeted);
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
- ASSERT( !batchOp.isFinished() );
+ ASSERT(status.isOK());
+ ASSERT(batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 0u);
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 0);
+ ASSERT_EQUALS(clientResponse.sizeErrDetails(), 1u);
+}
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 1u );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpoint );
+TEST(WriteOpTests, SingleWriteConcernErrorOrdered) {
+ //
+ // Write concern error test - we should pass write concern to sub-batches, and pass up the
+ // write concern error if one occurs
+ //
- BatchedCommandResponse response;
- buildErrResponse( ErrorCodes::UnknownError, "message", &response );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterFullRange(nss, endpoint, &targeter);
+
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
+ request.setWriteConcern(BSON("w" << 3));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+ ASSERT(!batchOp.isFinished());
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpoint);
+
+ BatchedCommandRequest targetBatch(BatchedCommandRequest::BatchType_Insert);
+ batchOp.buildBatchRequest(*targeted.front(), &targetBatch);
+ ASSERT(targetBatch.getWriteConcern().woCompare(request.getWriteConcern()) == 0);
+
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
+ addWCError(&response);
+
+ // First stale response comes back, we should retry
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 1);
+ ASSERT(!clientResponse.isErrDetailsSet());
+ ASSERT(clientResponse.isWriteConcernErrorSet());
+}
+
+TEST(WriteOpTests, SingleStaleError) {
+ //
+ // Single-op stale version test
+ // We should retry the same batch until we're not stale
+ //
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( batchOp.isFinished() );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterFullRange(nss, endpoint, &targeter);
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.sizeErrDetails(), 1u );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt( 0 )->getErrCode(), response.getErrCode() );
- ASSERT( clientResponse.getErrDetailsAt( 0 )->getErrMessage()
- .find( response.getErrMessage()) != string::npos );
- ASSERT_EQUALS( clientResponse.getN(), 0 );
- }
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
- TEST(WriteOpTests, SingleTargetError) {
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
- //
- // Single-op targeting error test
- //
+ BatchedCommandResponse response;
+ buildResponse(0, &response);
+ addError(ErrorCodes::StaleShardVersion, "mock stale error", 0, &response);
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpoint( "shard", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterHalfRange( nss, endpoint, &targeter );
+ // First stale response comes back, we should retry
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
- // Do untargetable delete op
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Delete );
- request.setNS( nss.ns() );
- request.getDeleteRequest()->addToDeletes( buildDelete( BSON( "x" << 1 ), 1 ) );
+ targetedOwned.clear();
+ status = batchOp.targetBatch(targeter, false, &targeted);
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
- ASSERT( !batchOp.isFinished() );
+ // Respond again with a stale response
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
+ targetedOwned.clear();
+ status = batchOp.targetBatch(targeter, false, &targeted);
- ASSERT( !status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 0u );
+ buildResponse(1, &response);
- // Record targeting failures
- status = batchOp.targetBatch( targeter, true, &targeted );
+ // Respond with an 'ok' response
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(batchOp.isFinished());
- ASSERT( status.isOK() );
- ASSERT( batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 0u );
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 1);
+ ASSERT(!clientResponse.isErrDetailsSet());
+}
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 0 );
- ASSERT_EQUALS( clientResponse.sizeErrDetails(), 1u );
- }
+//
+// Multi-operation batches
+//
- TEST(WriteOpTests, SingleWriteConcernErrorOrdered) {
-
- //
- // Write concern error test - we should pass write concern to sub-batches, and pass up the
- // write concern error if one occurs
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpoint( "shard", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterFullRange( nss, endpoint, &targeter );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
- request.setWriteConcern( BSON( "w" << 3 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
- ASSERT( !batchOp.isFinished() );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 1u );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpoint );
-
- BatchedCommandRequest targetBatch( BatchedCommandRequest::BatchType_Insert );
- batchOp.buildBatchRequest( *targeted.front(), &targetBatch );
- ASSERT( targetBatch.getWriteConcern().woCompare( request.getWriteConcern() ) == 0 );
-
- BatchedCommandResponse response;
- buildResponse( 1, &response );
- addWCError( &response );
-
- // First stale response comes back, we should retry
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 1 );
- ASSERT( !clientResponse.isErrDetailsSet() );
- ASSERT( clientResponse.isWriteConcernErrorSet() );
+struct EndpointComp {
+ bool operator()(const TargetedWriteBatch* writeA, const TargetedWriteBatch* writeB) const {
+ return writeA->getEndpoint().shardName.compare(writeB->getEndpoint().shardName) < 0;
}
+};
- TEST(WriteOpTests, SingleStaleError) {
-
- //
- // Single-op stale version test
- // We should retry the same batch until we're not stale
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpoint( "shard", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterFullRange( nss, endpoint, &targeter );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- BatchedCommandResponse response;
- buildResponse( 0, &response );
- addError( ErrorCodes::StaleShardVersion, "mock stale error", 0, &response );
-
- // First stale response comes back, we should retry
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
-
- targetedOwned.clear();
- status = batchOp.targetBatch( targeter, false, &targeted );
-
- // Respond again with a stale response
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
-
- targetedOwned.clear();
- status = batchOp.targetBatch( targeter, false, &targeted );
-
- buildResponse( 1, &response );
-
- // Respond with an 'ok' response
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 1 );
- ASSERT( !clientResponse.isErrDetailsSet() );
- }
+inline void sortByEndpoint(vector<TargetedWriteBatch*>* writes) {
+ std::sort(writes->begin(), writes->end(), EndpointComp());
+}
+TEST(WriteOpTests, MultiOpSameShardOrdered) {
//
- // Multi-operation batches
+ // Multi-op targeting test (ordered)
//
- struct EndpointComp {
- bool operator()( const TargetedWriteBatch* writeA,
- const TargetedWriteBatch* writeB ) const {
- return writeA->getEndpoint().shardName.compare( writeB->getEndpoint().shardName ) < 0;
- }
- };
-
- inline void sortByEndpoint( vector<TargetedWriteBatch*>* writes ) {
- std::sort( writes->begin(), writes->end(), EndpointComp() );
- }
-
- TEST(WriteOpTests, MultiOpSameShardOrdered) {
-
- //
- // Multi-op targeting test (ordered)
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpoint( "shard", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterFullRange( nss, endpoint, &targeter );
-
- // Do single-target, multi-doc batch write op
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Update );
- request.setNS( nss.ns() );
- request.setOrdered( true );
- request.getUpdateRequest()->addToUpdates( buildUpdate( BSON( "x" << 1 ), false ) );
- request.getUpdateRequest()->addToUpdates( buildUpdate( BSON( "x" << 2 ), false ) );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterFullRange(nss, endpoint, &targeter);
+
+ // Do single-target, multi-doc batch write op
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Update);
+ request.setNS(nss.ns());
+ request.setOrdered(true);
+ request.getUpdateRequest()->addToUpdates(buildUpdate(BSON("x" << 1), false));
+ request.getUpdateRequest()->addToUpdates(buildUpdate(BSON("x" << 2), false));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+ ASSERT(!batchOp.isFinished());
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 2u);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpoint);
+
+ BatchedCommandResponse response;
+ buildResponse(2, &response);
+
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 2);
+}
+
+TEST(WriteOpTests, MultiOpSameShardUnordered) {
+ //
+ // Multi-op targeting test (unordered)
+ //
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
- ASSERT( !batchOp.isFinished() );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterFullRange(nss, endpoint, &targeter);
+
+ // Do single-target, multi-doc batch write op
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Update);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ request.getUpdateRequest()->addToUpdates(buildUpdate(BSON("x" << 1), false));
+ request.getUpdateRequest()->addToUpdates(buildUpdate(BSON("x" << 2), false));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+ ASSERT(!batchOp.isFinished());
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 2u);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpoint);
+
+ BatchedCommandResponse response;
+ buildResponse(2, &response);
+
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 2);
+}
+
+TEST(WriteOpTests, MultiOpTwoShardsOrdered) {
+ //
+ // Multi-op, multi-endpoing targeting test (ordered)
+ // There should be two sets of single batches (one to each shard, one-by-one)
+ //
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpointA("shardA", ChunkVersion::IGNORED());
+ ShardEndpoint endpointB("shardB", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterSplitRange(nss, endpointA, endpointB, &targeter);
+
+ // Do multi-target, multi-doc batch write op
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.setOrdered(true);
+ request.getInsertRequest()->addToDocuments(BSON("x" << -1));
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+ ASSERT(!batchOp.isFinished());
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 1u);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpointA);
+
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
+
+ // Respond to first targeted batch
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+
+ targetedOwned.clear();
+ status = batchOp.targetBatch(targeter, false, &targeted);
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 1u);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpointB);
+
+ // Respond to second targeted batch
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 2);
+}
+
+TEST(WriteOpTests, MultiOpTwoShardsUnordered) {
+ //
+ // Multi-op, multi-endpoint targeting test (unordered)
+ // There should be one set of two batches (one to each shard)
+ //
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 1u );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 2u );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpoint );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpointA("shardA", ChunkVersion::IGNORED());
+ ShardEndpoint endpointB("shardB", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterSplitRange(nss, endpointA, endpointB, &targeter);
+
+ // Do multi-target, multi-doc batch write op
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ request.getInsertRequest()->addToDocuments(BSON("x" << -1));
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+ ASSERT(!batchOp.isFinished());
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 2u);
+ sortByEndpoint(&targeted);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 1u);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpointA);
+ ASSERT_EQUALS(targeted.back()->getWrites().size(), 1u);
+ assertEndpointsEqual(targeted.back()->getEndpoint(), endpointB);
+
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
+
+ // Respond to both targeted batches
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+ batchOp.noteBatchResponse(*targeted.back(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 2);
+}
+
+TEST(WriteOpTests, MultiOpTwoShardsEachOrdered) {
+ //
+ // Multi-op (ordered) targeting test where each op goes to both shards
+ // There should be two sets of two batches to each shard (two for each delete op)
+ //
- BatchedCommandResponse response;
- buildResponse( 2, &response );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpointA("shardA", ChunkVersion::IGNORED());
+ ShardEndpoint endpointB("shardB", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterSplitRange(nss, endpointA, endpointB, &targeter);
+
+ // Do multi-target, multi-doc batch write op
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Delete);
+ request.setNS(nss.ns());
+ request.setOrdered(true);
+ BSONObj queryA = BSON("x" << GTE << -1 << LT << 2);
+ request.getDeleteRequest()->addToDeletes(buildDelete(queryA, 0));
+ BSONObj queryB = BSON("x" << GTE << -2 << LT << 1);
+ request.getDeleteRequest()->addToDeletes(buildDelete(queryB, 0));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+ ASSERT(!batchOp.isFinished());
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 2u);
+ sortByEndpoint(&targeted);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 1u);
+ ASSERT_EQUALS(targeted.back()->getWrites().size(), 1u);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpointA);
+ assertEndpointsEqual(targeted.back()->getEndpoint(), endpointB);
+
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
+
+ // Respond to both targeted batches for first multi-delete
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+ batchOp.noteBatchResponse(*targeted.back(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+
+ targetedOwned.clear();
+ status = batchOp.targetBatch(targeter, false, &targeted);
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 2u);
+ sortByEndpoint(&targeted);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 1u);
+ ASSERT_EQUALS(targeted.back()->getWrites().size(), 1u);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpointA);
+ assertEndpointsEqual(targeted.back()->getEndpoint(), endpointB);
+
+ // Respond to second targeted batches for second multi-delete
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+ batchOp.noteBatchResponse(*targeted.back(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 4);
+}
+
+TEST(WriteOpTests, MultiOpTwoShardsEachUnordered) {
+ //
+ // Multi-op (unaordered) targeting test where each op goes to both shards
+ // There should be one set of two batches to each shard (containing writes for both ops)
+ //
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( batchOp.isFinished() );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpointA("shardA", ChunkVersion::IGNORED());
+ ShardEndpoint endpointB("shardB", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterSplitRange(nss, endpointA, endpointB, &targeter);
+
+ // Do multi-target, multi-doc batch write op
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Update);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ BSONObj queryA = BSON("x" << GTE << -1 << LT << 2);
+ request.getUpdateRequest()->addToUpdates(buildUpdate(queryA, true));
+ BSONObj queryB = BSON("x" << GTE << -2 << LT << 1);
+ request.getUpdateRequest()->addToUpdates(buildUpdate(queryB, true));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+ ASSERT(!batchOp.isFinished());
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 2u);
+ sortByEndpoint(&targeted);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 2u);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpointA);
+ ASSERT_EQUALS(targeted.back()->getWrites().size(), 2u);
+ assertEndpointsEqual(targeted.back()->getEndpoint(), endpointB);
+
+ BatchedCommandResponse response;
+ buildResponse(2, &response);
+
+ // Respond to both targeted batches, each containing two ops
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+ batchOp.noteBatchResponse(*targeted.back(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 4);
+}
+
+TEST(WriteOpTests, MultiOpOneOrTwoShardsOrdered) {
+ //
+ // Multi-op (ordered) targeting test where first two ops go to one shard, second two ops
+ // go to two shards.
+ // Should batch the first two ops, then second ops should be batched separately, then
+ // last ops should be batched together
+ //
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 2 );
- }
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpointA("shardA", ChunkVersion::IGNORED());
+ ShardEndpoint endpointB("shardB", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterSplitRange(nss, endpointA, endpointB, &targeter);
+
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Delete);
+ request.setNS(nss.ns());
+ request.setOrdered(true);
+ // These go to the same shard
+ request.getDeleteRequest()->addToDeletes(buildDelete(BSON("x" << -1), 1));
+ request.getDeleteRequest()->addToDeletes(buildDelete(BSON("x" << -2), 1));
+ // These go to both shards
+ BSONObj queryA = BSON("x" << GTE << -1 << LT << 2);
+ request.getDeleteRequest()->addToDeletes(buildDelete(queryA, 0));
+ BSONObj queryB = BSON("x" << GTE << -2 << LT << 1);
+ request.getDeleteRequest()->addToDeletes(buildDelete(queryB, 0));
+ // These go to the same shard
+ request.getDeleteRequest()->addToDeletes(buildDelete(BSON("x" << 1), 1));
+ request.getDeleteRequest()->addToDeletes(buildDelete(BSON("x" << 2), 1));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+ ASSERT(!batchOp.isFinished());
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 2u);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpointA);
+
+ BatchedCommandResponse response;
+ // Emulate one-write-per-delete-per-host
+ buildResponse(2, &response);
+
+ // Respond to first targeted batch containing the two single-host deletes
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+
+ targetedOwned.clear();
+ status = batchOp.targetBatch(targeter, false, &targeted);
+
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 2u);
+ sortByEndpoint(&targeted);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 1u);
+ ASSERT_EQUALS(targeted.back()->getWrites().size(), 1u);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpointA);
+ assertEndpointsEqual(targeted.back()->getEndpoint(), endpointB);
+
+ // Emulate one-write-per-delete-per-host
+ buildResponse(1, &response);
+
+ // Respond to two targeted batches for first multi-delete
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+ batchOp.noteBatchResponse(*targeted.back(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+
+ targetedOwned.clear();
+ status = batchOp.targetBatch(targeter, false, &targeted);
+
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 2u);
+ sortByEndpoint(&targeted);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 1u);
+ ASSERT_EQUALS(targeted.back()->getWrites().size(), 1u);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpointA);
+ assertEndpointsEqual(targeted.back()->getEndpoint(), endpointB);
+
+ // Respond to two targeted batches for second multi-delete
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+ batchOp.noteBatchResponse(*targeted.back(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+
+ targetedOwned.clear();
+ status = batchOp.targetBatch(targeter, false, &targeted);
+
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 2u);
+ assertEndpointsEqual(targeted.back()->getEndpoint(), endpointB);
+
+ // Emulate one-write-per-delete-per-host
+ buildResponse(2, &response);
+
+ // Respond to final targeted batch containing the last two single-host deletes
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 8);
+}
+
+TEST(WriteOpTests, MultiOpOneOrTwoShardsUnordered) {
+ //
+ // Multi-op (unordered) targeting test where first two ops go to one shard, second two ops
+ // go to two shards.
+ // Should batch all the ops together into two batches of four ops for each shard
+ //
- TEST(WriteOpTests, MultiOpSameShardUnordered) {
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpointA("shardA", ChunkVersion::IGNORED());
+ ShardEndpoint endpointB("shardB", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterSplitRange(nss, endpointA, endpointB, &targeter);
+
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Update);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ // These go to the same shard
+ request.getUpdateRequest()->addToUpdates(buildUpdate(BSON("x" << -1), false));
+ request.getUpdateRequest()->addToUpdates(buildUpdate(BSON("x" << -2), false));
+ // These go to both shards
+ BSONObj queryA = BSON("x" << GTE << -1 << LT << 2);
+ request.getUpdateRequest()->addToUpdates(buildUpdate(queryA, true));
+ BSONObj queryB = BSON("x" << GTE << -2 << LT << 1);
+ request.getUpdateRequest()->addToUpdates(buildUpdate(queryB, true));
+ // These go to the same shard
+ request.getUpdateRequest()->addToUpdates(buildUpdate(BSON("x" << 1), false));
+ request.getUpdateRequest()->addToUpdates(buildUpdate(BSON("x" << 2), false));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+ ASSERT(!batchOp.isFinished());
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 2u);
+ sortByEndpoint(&targeted);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 4u);
+ ASSERT_EQUALS(targeted.back()->getWrites().size(), 4u);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpointA);
+ assertEndpointsEqual(targeted.back()->getEndpoint(), endpointB);
+
+ BatchedCommandResponse response;
+ // Emulate one-write-per-delete-per-host
+ buildResponse(4, &response);
+
+ // Respond to first targeted batch containing the two single-host deletes
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+ batchOp.noteBatchResponse(*targeted.back(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 8);
+}
+
+TEST(WriteOpTests, MultiOpSingleShardErrorUnordered) {
+ //
+ // Multi-op targeting test where two ops go to two separate shards and there's an error on
+ // one op on one shard
+ // There should be one set of two batches to each shard and an error reported
+ //
- //
- // Multi-op targeting test (unordered)
- //
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpointA("shardA", ChunkVersion::IGNORED());
+ ShardEndpoint endpointB("shardB", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterSplitRange(nss, endpointA, endpointB, &targeter);
+
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ request.getInsertRequest()->addToDocuments(BSON("x" << -1));
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+ ASSERT(!batchOp.isFinished());
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 2u);
+ sortByEndpoint(&targeted);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpointA);
+ assertEndpointsEqual(targeted.back()->getEndpoint(), endpointB);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 1u);
+ ASSERT_EQUALS(targeted.back()->getWrites().size(), 1u);
+
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
+
+ // No error on first shard
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+
+ buildResponse(0, &response);
+ addError(ErrorCodes::UnknownError, "mock error", 0, &response);
+
+ // Error on second write on second shard
+ batchOp.noteBatchResponse(*targeted.back(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 1);
+ ASSERT(clientResponse.isErrDetailsSet());
+ ASSERT_EQUALS(clientResponse.sizeErrDetails(), 1u);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getErrCode(),
+ response.getErrDetailsAt(0)->getErrCode());
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getErrMessage(),
+ response.getErrDetailsAt(0)->getErrMessage());
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getIndex(), 1);
+}
+
+TEST(WriteOpTests, MultiOpTwoShardErrorsUnordered) {
+ //
+ // Multi-op targeting test where two ops go to two separate shards and there's an error on
+ // each op on each shard
+ // There should be one set of two batches to each shard and and two errors reported
+ //
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpoint( "shard", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterFullRange( nss, endpoint, &targeter );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpointA("shardA", ChunkVersion::IGNORED());
+ ShardEndpoint endpointB("shardB", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterSplitRange(nss, endpointA, endpointB, &targeter);
+
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ request.getInsertRequest()->addToDocuments(BSON("x" << -1));
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+ ASSERT(!batchOp.isFinished());
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 2u);
+ sortByEndpoint(&targeted);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpointA);
+ assertEndpointsEqual(targeted.back()->getEndpoint(), endpointB);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 1u);
+ ASSERT_EQUALS(targeted.back()->getWrites().size(), 1u);
+
+ BatchedCommandResponse response;
+ buildResponse(0, &response);
+ addError(ErrorCodes::UnknownError, "mock error", 0, &response);
+
+ // Error on first write on first shard
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+
+ // Error on second write on second shard
+ batchOp.noteBatchResponse(*targeted.back(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 0);
+ ASSERT(clientResponse.isErrDetailsSet());
+ ASSERT_EQUALS(clientResponse.sizeErrDetails(), 2u);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getErrCode(),
+ response.getErrDetailsAt(0)->getErrCode());
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getErrMessage(),
+ response.getErrDetailsAt(0)->getErrMessage());
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getIndex(), 0);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(1)->getErrCode(),
+ response.getErrDetailsAt(0)->getErrCode());
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(1)->getErrMessage(),
+ response.getErrDetailsAt(0)->getErrMessage());
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(1)->getIndex(), 1);
+}
+
+TEST(WriteOpTests, MultiOpPartialSingleShardErrorUnordered) {
+ //
+ // Multi-op targeting test where each op goes to both shards and there's an error on
+ // one op on one shard
+ // There should be one set of two batches to each shard and an error reported
+ //
- // Do single-target, multi-doc batch write op
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Update );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- request.getUpdateRequest()->addToUpdates( buildUpdate( BSON( "x" << 1 ), false ) );
- request.getUpdateRequest()->addToUpdates( buildUpdate( BSON( "x" << 2 ), false ) );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpointA("shardA", ChunkVersion::IGNORED());
+ ShardEndpoint endpointB("shardB", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterSplitRange(nss, endpointA, endpointB, &targeter);
+
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Delete);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ BSONObj queryA = BSON("x" << GTE << -1 << LT << 2);
+ request.getDeleteRequest()->addToDeletes(buildDelete(queryA, 0));
+ BSONObj queryB = BSON("x" << GTE << -2 << LT << 1);
+ request.getDeleteRequest()->addToDeletes(buildDelete(queryB, 0));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+ ASSERT(!batchOp.isFinished());
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 2u);
+ sortByEndpoint(&targeted);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpointA);
+ assertEndpointsEqual(targeted.back()->getEndpoint(), endpointB);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 2u);
+ ASSERT_EQUALS(targeted.back()->getWrites().size(), 2u);
+
+ BatchedCommandResponse response;
+ buildResponse(2, &response);
+
+ // No errors on first shard
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+
+ buildResponse(1, &response);
+ addError(ErrorCodes::UnknownError, "mock error", 1, &response);
+
+ // Error on second write on second shard
+ batchOp.noteBatchResponse(*targeted.back(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 3);
+ ASSERT(clientResponse.isErrDetailsSet());
+ ASSERT_EQUALS(clientResponse.sizeErrDetails(), 1u);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getErrCode(),
+ response.getErrDetailsAt(0)->getErrCode());
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getErrMessage(),
+ response.getErrDetailsAt(0)->getErrMessage());
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getIndex(), 1);
+}
+
+TEST(WriteOpTests, MultiOpPartialSingleShardErrorOrdered) {
+ //
+ // Multi-op targeting test where each op goes to both shards and there's an error on
+ // one op on one shard
+ // There should be one set of two batches to each shard and an error reported, the second
+ // op should not get run
+ //
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
- ASSERT( !batchOp.isFinished() );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpointA("shardA", ChunkVersion::IGNORED());
+ ShardEndpoint endpointB("shardB", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterSplitRange(nss, endpointA, endpointB, &targeter);
+
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Delete);
+ request.setNS(nss.ns());
+ request.setOrdered(true);
+ BSONObj queryA = BSON("x" << GTE << -1 << LT << 2);
+ request.getDeleteRequest()->addToDeletes(buildDelete(queryA, 0));
+ BSONObj queryB = BSON("x" << GTE << -2 << LT << 1);
+ request.getDeleteRequest()->addToDeletes(buildDelete(queryB, 0));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+ ASSERT(!batchOp.isFinished());
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 2u);
+ sortByEndpoint(&targeted);
+ assertEndpointsEqual(targeted.front()->getEndpoint(), endpointA);
+ assertEndpointsEqual(targeted.back()->getEndpoint(), endpointB);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 1u);
+ ASSERT_EQUALS(targeted.back()->getWrites().size(), 1u);
+
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
+
+ // No errors on first shard
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+
+ buildResponse(0, &response);
+ addError(ErrorCodes::UnknownError, "mock error", 0, &response);
+
+ // Error on second write on second shard
+ batchOp.noteBatchResponse(*targeted.back(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 1);
+ ASSERT(clientResponse.isErrDetailsSet());
+ ASSERT_EQUALS(clientResponse.sizeErrDetails(), 1u);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getErrCode(),
+ response.getErrDetailsAt(0)->getErrCode());
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getErrMessage(),
+ response.getErrDetailsAt(0)->getErrMessage());
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getIndex(), 0);
+}
+
+//
+// Tests of edge-case functionality, lifecycle is assumed to be behaving normally
+//
+
+TEST(WriteOpTests, MultiOpErrorAndWriteConcernErrorUnordered) {
+ //
+ // Multi-op (unordered) error and write concern error test
+ // We never report the write concern error for single-doc batches, since the error means
+ // there's no write concern applied.
+ // Don't suppress the error if ordered : false
+ //
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterFullRange(nss, endpoint, &targeter);
+
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
+ request.setWriteConcern(BSON("w" << 3));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
+ addError(ErrorCodes::UnknownError, "mock error", 1, &response);
+ addWCError(&response);
+
+ // First stale response comes back, we should retry
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ // Unordered reports write concern error
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 1);
+ ASSERT_EQUALS(clientResponse.sizeErrDetails(), 1u);
+ ASSERT(clientResponse.isWriteConcernErrorSet());
+}
+
+
+TEST(WriteOpTests, SingleOpErrorAndWriteConcernErrorOrdered) {
+ //
+ // Single-op (ordered) error and write concern error test
+ // Suppress the write concern error if ordered and we also have an error
+ //
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 1u );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 2u );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpoint );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpointA("shardA", ChunkVersion::IGNORED());
+ ShardEndpoint endpointB("shardB", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterSplitRange(nss, endpointA, endpointB, &targeter);
+
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Update);
+ request.setNS(nss.ns());
+ request.setOrdered(true);
+ BSONObj query = BSON("x" << GTE << -1 << LT << 2);
+ request.getUpdateRequest()->addToUpdates(buildUpdate(query, true));
+ request.setWriteConcern(BSON("w" << 3));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
+ addWCError(&response);
+
+ // First response comes back with write concern error
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+
+ buildResponse(0, &response);
+ addError(ErrorCodes::UnknownError, "mock error", 0, &response);
+
+ // Second response comes back with write error
+ batchOp.noteBatchResponse(*targeted.back(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ // Ordered doesn't report write concern error
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 1);
+ ASSERT(clientResponse.isErrDetailsSet());
+ ASSERT_EQUALS(clientResponse.sizeErrDetails(), 1u);
+ ASSERT(!clientResponse.isWriteConcernErrorSet());
+}
+
+TEST(WriteOpTests, MultiOpFailedTargetOrdered) {
+ //
+ // Targeting failure on second op in batch op (ordered)
+ //
- BatchedCommandResponse response;
- buildResponse( 2, &response );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterHalfRange(nss, endpoint, &targeter);
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( batchOp.isFinished() );
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.getInsertRequest()->addToDocuments(BSON("x" << -1));
+ request.getInsertRequest()->addToDocuments(BSON("x" << 2));
+ request.getInsertRequest()->addToDocuments(BSON("x" << -2));
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 2 );
- }
+ // Do single-target, multi-doc batch write op
- TEST(WriteOpTests, MultiOpTwoShardsOrdered) {
-
- //
- // Multi-op, multi-endpoing targeting test (ordered)
- // There should be two sets of single batches (one to each shard, one-by-one)
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpointA( "shardA", ChunkVersion::IGNORED() );
- ShardEndpoint endpointB( "shardB", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterSplitRange( nss, endpointA, endpointB, &targeter );
-
- // Do multi-target, multi-doc batch write op
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.setOrdered( true );
- request.getInsertRequest()->addToDocuments( BSON( "x" << -1 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
- ASSERT( !batchOp.isFinished() );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 1u );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 1u );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpointA );
-
- BatchedCommandResponse response;
- buildResponse( 1, &response );
-
- // Respond to first targeted batch
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
-
- targetedOwned.clear();
- status = batchOp.targetBatch( targeter, false, &targeted );
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 1u );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 1u );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpointB );
-
- // Respond to second targeted batch
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 2 );
- }
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
- TEST(WriteOpTests, MultiOpTwoShardsUnordered) {
-
- //
- // Multi-op, multi-endpoint targeting test (unordered)
- // There should be one set of two batches (one to each shard)
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpointA( "shardA", ChunkVersion::IGNORED() );
- ShardEndpoint endpointB( "shardB", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterSplitRange( nss, endpointA, endpointB, &targeter );
-
- // Do multi-target, multi-doc batch write op
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- request.getInsertRequest()->addToDocuments( BSON( "x" << -1 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
- ASSERT( !batchOp.isFinished() );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 2u );
- sortByEndpoint( &targeted );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 1u );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpointA );
- ASSERT_EQUALS( targeted.back()->getWrites().size(), 1u );
- assertEndpointsEqual( targeted.back()->getEndpoint(), endpointB );
-
- BatchedCommandResponse response;
- buildResponse( 1, &response );
-
- // Respond to both targeted batches
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
- batchOp.noteBatchResponse( *targeted.back(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 2 );
- }
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
- TEST(WriteOpTests, MultiOpTwoShardsEachOrdered) {
-
- //
- // Multi-op (ordered) targeting test where each op goes to both shards
- // There should be two sets of two batches to each shard (two for each delete op)
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpointA( "shardA", ChunkVersion::IGNORED() );
- ShardEndpoint endpointB( "shardB", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterSplitRange( nss, endpointA, endpointB, &targeter );
-
- // Do multi-target, multi-doc batch write op
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Delete );
- request.setNS( nss.ns() );
- request.setOrdered( true );
- BSONObj queryA = BSON( "x" << GTE << -1 << LT << 2 );
- request.getDeleteRequest()->addToDeletes( buildDelete( queryA, 0 ) );
- BSONObj queryB = BSON( "x" << GTE << -2 << LT << 1 );
- request.getDeleteRequest()->addToDeletes( buildDelete( queryB, 0 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
- ASSERT( !batchOp.isFinished() );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 2u );
- sortByEndpoint( &targeted );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 1u );
- ASSERT_EQUALS( targeted.back()->getWrites().size(), 1u );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpointA );
- assertEndpointsEqual( targeted.back()->getEndpoint(), endpointB );
-
- BatchedCommandResponse response;
- buildResponse( 1, &response );
-
- // Respond to both targeted batches for first multi-delete
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
- batchOp.noteBatchResponse( *targeted.back(), response, NULL );
- ASSERT( !batchOp.isFinished() );
-
- targetedOwned.clear();
- status = batchOp.targetBatch( targeter, false, &targeted );
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 2u );
- sortByEndpoint( &targeted );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 1u );
- ASSERT_EQUALS( targeted.back()->getWrites().size(), 1u );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpointA );
- assertEndpointsEqual( targeted.back()->getEndpoint(), endpointB );
-
- // Respond to second targeted batches for second multi-delete
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
- batchOp.noteBatchResponse( *targeted.back(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 4 );
- }
+ // First targeting round fails since we may be stale
+ ASSERT(!status.isOK());
+ ASSERT(!batchOp.isFinished());
- TEST(WriteOpTests, MultiOpTwoShardsEachUnordered) {
-
- //
- // Multi-op (unaordered) targeting test where each op goes to both shards
- // There should be one set of two batches to each shard (containing writes for both ops)
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpointA( "shardA", ChunkVersion::IGNORED() );
- ShardEndpoint endpointB( "shardB", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterSplitRange( nss, endpointA, endpointB, &targeter );
-
- // Do multi-target, multi-doc batch write op
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Update );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- BSONObj queryA = BSON( "x" << GTE << -1 << LT << 2 );
- request.getUpdateRequest()->addToUpdates( buildUpdate( queryA, true ) );
- BSONObj queryB = BSON( "x" << GTE << -2 << LT << 1 );
- request.getUpdateRequest()->addToUpdates( buildUpdate( queryB, true ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
- ASSERT( !batchOp.isFinished() );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 2u );
- sortByEndpoint( &targeted );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 2u );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpointA );
- ASSERT_EQUALS( targeted.back()->getWrites().size(), 2u );
- assertEndpointsEqual( targeted.back()->getEndpoint(), endpointB );
-
- BatchedCommandResponse response;
- buildResponse( 2, &response );
-
- // Respond to both targeted batches, each containing two ops
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
- batchOp.noteBatchResponse( *targeted.back(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 4 );
- }
+ targetedOwned.clear();
+ status = batchOp.targetBatch(targeter, true, &targeted);
- TEST(WriteOpTests, MultiOpOneOrTwoShardsOrdered) {
-
- //
- // Multi-op (ordered) targeting test where first two ops go to one shard, second two ops
- // go to two shards.
- // Should batch the first two ops, then second ops should be batched separately, then
- // last ops should be batched together
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpointA( "shardA", ChunkVersion::IGNORED() );
- ShardEndpoint endpointB( "shardB", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterSplitRange( nss, endpointA, endpointB, &targeter );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Delete );
- request.setNS( nss.ns() );
- request.setOrdered( true );
- // These go to the same shard
- request.getDeleteRequest()->addToDeletes( buildDelete( BSON( "x" << -1 ), 1 ) );
- request.getDeleteRequest()->addToDeletes( buildDelete( BSON( "x" << -2 ), 1 ) );
- // These go to both shards
- BSONObj queryA = BSON( "x" << GTE << -1 << LT << 2 );
- request.getDeleteRequest()->addToDeletes( buildDelete( queryA, 0 ) );
- BSONObj queryB = BSON( "x" << GTE << -2 << LT << 1 );
- request.getDeleteRequest()->addToDeletes( buildDelete( queryB, 0 ) );
- // These go to the same shard
- request.getDeleteRequest()->addToDeletes( buildDelete( BSON( "x" << 1 ), 1 ) );
- request.getDeleteRequest()->addToDeletes( buildDelete( BSON( "x" << 2 ), 1 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
- ASSERT( !batchOp.isFinished() );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 1u );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 2u );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpointA );
-
- BatchedCommandResponse response;
- // Emulate one-write-per-delete-per-host
- buildResponse( 2, &response );
-
- // Respond to first targeted batch containing the two single-host deletes
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
-
- targetedOwned.clear();
- status = batchOp.targetBatch( targeter, false, &targeted );
-
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 2u );
- sortByEndpoint( &targeted );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 1u );
- ASSERT_EQUALS( targeted.back()->getWrites().size(), 1u );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpointA );
- assertEndpointsEqual( targeted.back()->getEndpoint(), endpointB );
-
- // Emulate one-write-per-delete-per-host
- buildResponse( 1, &response );
-
- // Respond to two targeted batches for first multi-delete
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
- batchOp.noteBatchResponse( *targeted.back(), response, NULL );
- ASSERT( !batchOp.isFinished() );
-
- targetedOwned.clear();
- status = batchOp.targetBatch( targeter, false, &targeted );
-
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 2u );
- sortByEndpoint( &targeted );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 1u );
- ASSERT_EQUALS( targeted.back()->getWrites().size(), 1u );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpointA );
- assertEndpointsEqual( targeted.back()->getEndpoint(), endpointB );
-
- // Respond to two targeted batches for second multi-delete
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
- batchOp.noteBatchResponse( *targeted.back(), response, NULL );
- ASSERT( !batchOp.isFinished() );
-
- targetedOwned.clear();
- status = batchOp.targetBatch( targeter, false, &targeted );
-
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 1u );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 2u );
- assertEndpointsEqual( targeted.back()->getEndpoint(), endpointB );
-
- // Emulate one-write-per-delete-per-host
- buildResponse( 2, &response );
-
- // Respond to final targeted batch containing the last two single-host deletes
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 8 );
- }
+ // Second targeting round is ok, but should stop at first write
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 1u);
- TEST(WriteOpTests, MultiOpOneOrTwoShardsUnordered) {
-
- //
- // Multi-op (unordered) targeting test where first two ops go to one shard, second two ops
- // go to two shards.
- // Should batch all the ops together into two batches of four ops for each shard
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpointA( "shardA", ChunkVersion::IGNORED() );
- ShardEndpoint endpointB( "shardB", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterSplitRange( nss, endpointA, endpointB, &targeter );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Update );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- // These go to the same shard
- request.getUpdateRequest()->addToUpdates( buildUpdate( BSON( "x" << -1 ), false ) );
- request.getUpdateRequest()->addToUpdates( buildUpdate( BSON( "x" << -2 ), false ) );
- // These go to both shards
- BSONObj queryA = BSON( "x" << GTE << -1 << LT << 2 );
- request.getUpdateRequest()->addToUpdates( buildUpdate( queryA, true ) );
- BSONObj queryB = BSON( "x" << GTE << -2 << LT << 1 );
- request.getUpdateRequest()->addToUpdates( buildUpdate( queryB, true ) );
- // These go to the same shard
- request.getUpdateRequest()->addToUpdates( buildUpdate( BSON( "x" << 1 ), false ) );
- request.getUpdateRequest()->addToUpdates( buildUpdate( BSON( "x" << 2 ), false ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
- ASSERT( !batchOp.isFinished() );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 2u );
- sortByEndpoint( &targeted );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 4u );
- ASSERT_EQUALS( targeted.back()->getWrites().size(), 4u );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpointA );
- assertEndpointsEqual( targeted.back()->getEndpoint(), endpointB );
-
- BatchedCommandResponse response;
- // Emulate one-write-per-delete-per-host
- buildResponse( 4, &response );
-
- // Respond to first targeted batch containing the two single-host deletes
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
- batchOp.noteBatchResponse( *targeted.back(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 8 );
- }
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
- TEST(WriteOpTests, MultiOpSingleShardErrorUnordered) {
-
- //
- // Multi-op targeting test where two ops go to two separate shards and there's an error on
- // one op on one shard
- // There should be one set of two batches to each shard and an error reported
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpointA( "shardA", ChunkVersion::IGNORED() );
- ShardEndpoint endpointB( "shardB", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterSplitRange( nss, endpointA, endpointB, &targeter );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- request.getInsertRequest()->addToDocuments( BSON( "x" << -1 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
- ASSERT( !batchOp.isFinished() );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 2u );
- sortByEndpoint( &targeted );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpointA );
- assertEndpointsEqual( targeted.back()->getEndpoint(), endpointB );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 1u );
- ASSERT_EQUALS( targeted.back()->getWrites().size(), 1u );
-
- BatchedCommandResponse response;
- buildResponse( 1, &response );
-
- // No error on first shard
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
-
- buildResponse( 0, &response );
- addError( ErrorCodes::UnknownError, "mock error", 0, &response );
-
- // Error on second write on second shard
- batchOp.noteBatchResponse( *targeted.back(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 1 );
- ASSERT( clientResponse.isErrDetailsSet() );
- ASSERT_EQUALS( clientResponse.sizeErrDetails(), 1u );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt( 0 )->getErrCode(),
- response.getErrDetailsAt(0)->getErrCode() );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt( 0 )->getErrMessage(),
- response.getErrDetailsAt(0)->getErrMessage() );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt( 0 )->getIndex(), 1 );
- }
+ // First response ok
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
- TEST(WriteOpTests, MultiOpTwoShardErrorsUnordered) {
-
- //
- // Multi-op targeting test where two ops go to two separate shards and there's an error on
- // each op on each shard
- // There should be one set of two batches to each shard and and two errors reported
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpointA( "shardA", ChunkVersion::IGNORED() );
- ShardEndpoint endpointB( "shardB", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterSplitRange( nss, endpointA, endpointB, &targeter );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- request.getInsertRequest()->addToDocuments( BSON( "x" << -1 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
- ASSERT( !batchOp.isFinished() );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 2u );
- sortByEndpoint( &targeted );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpointA );
- assertEndpointsEqual( targeted.back()->getEndpoint(), endpointB );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 1u );
- ASSERT_EQUALS( targeted.back()->getWrites().size(), 1u );
-
- BatchedCommandResponse response;
- buildResponse( 0, &response );
- addError( ErrorCodes::UnknownError, "mock error", 0, &response );
-
- // Error on first write on first shard
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
-
- // Error on second write on second shard
- batchOp.noteBatchResponse( *targeted.back(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 0 );
- ASSERT( clientResponse.isErrDetailsSet() );
- ASSERT_EQUALS( clientResponse.sizeErrDetails(), 2u );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt( 0 )->getErrCode(),
- response.getErrDetailsAt(0)->getErrCode() );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt( 0 )->getErrMessage(),
- response.getErrDetailsAt(0)->getErrMessage() );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt( 0 )->getIndex(), 0 );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt( 1 )->getErrCode(),
- response.getErrDetailsAt(0)->getErrCode() );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt( 1 )->getErrMessage(),
- response.getErrDetailsAt(0)->getErrMessage() );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt( 1 )->getIndex(), 1 );
- }
+ targetedOwned.clear();
+ status = batchOp.targetBatch(targeter, true, &targeted);
- TEST(WriteOpTests, MultiOpPartialSingleShardErrorUnordered) {
-
- //
- // Multi-op targeting test where each op goes to both shards and there's an error on
- // one op on one shard
- // There should be one set of two batches to each shard and an error reported
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpointA( "shardA", ChunkVersion::IGNORED() );
- ShardEndpoint endpointB( "shardB", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterSplitRange( nss, endpointA, endpointB, &targeter );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Delete );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- BSONObj queryA = BSON( "x" << GTE << -1 << LT << 2 );
- request.getDeleteRequest()->addToDeletes( buildDelete( queryA, 0 ) );
- BSONObj queryB = BSON( "x" << GTE << -2 << LT << 1 );
- request.getDeleteRequest()->addToDeletes( buildDelete( queryB, 0 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
- ASSERT( !batchOp.isFinished() );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 2u );
- sortByEndpoint( &targeted );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpointA );
- assertEndpointsEqual( targeted.back()->getEndpoint(), endpointB );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 2u );
- ASSERT_EQUALS( targeted.back()->getWrites().size(), 2u );
-
- BatchedCommandResponse response;
- buildResponse( 2, &response );
-
- // No errors on first shard
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
-
- buildResponse( 1, &response );
- addError( ErrorCodes::UnknownError, "mock error", 1, &response );
-
- // Error on second write on second shard
- batchOp.noteBatchResponse( *targeted.back(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 3 );
- ASSERT( clientResponse.isErrDetailsSet() );
- ASSERT_EQUALS( clientResponse.sizeErrDetails(), 1u );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt( 0 )->getErrCode(),
- response.getErrDetailsAt(0)->getErrCode() );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt( 0 )->getErrMessage(),
- response.getErrDetailsAt(0)->getErrMessage() );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt( 0 )->getIndex(), 1 );
- }
+ // Second targeting round results in an error which finishes the batch
+ ASSERT(status.isOK());
+ ASSERT(batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 0u);
- TEST(WriteOpTests, MultiOpPartialSingleShardErrorOrdered) {
-
- //
- // Multi-op targeting test where each op goes to both shards and there's an error on
- // one op on one shard
- // There should be one set of two batches to each shard and an error reported, the second
- // op should not get run
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpointA( "shardA", ChunkVersion::IGNORED() );
- ShardEndpoint endpointB( "shardB", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterSplitRange( nss, endpointA, endpointB, &targeter );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Delete );
- request.setNS( nss.ns() );
- request.setOrdered( true );
- BSONObj queryA = BSON( "x" << GTE << -1 << LT << 2 );
- request.getDeleteRequest()->addToDeletes( buildDelete( queryA, 0 ) );
- BSONObj queryB = BSON( "x" << GTE << -2 << LT << 1 );
- request.getDeleteRequest()->addToDeletes( buildDelete( queryB, 0 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
- ASSERT( !batchOp.isFinished() );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 2u );
- sortByEndpoint( &targeted );
- assertEndpointsEqual( targeted.front()->getEndpoint(), endpointA );
- assertEndpointsEqual( targeted.back()->getEndpoint(), endpointB );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 1u );
- ASSERT_EQUALS( targeted.back()->getWrites().size(), 1u );
-
- BatchedCommandResponse response;
- buildResponse( 1, &response );
-
- // No errors on first shard
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
-
- buildResponse( 0, &response );
- addError( ErrorCodes::UnknownError, "mock error", 0, &response );
-
- // Error on second write on second shard
- batchOp.noteBatchResponse( *targeted.back(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 1 );
- ASSERT( clientResponse.isErrDetailsSet() );
- ASSERT_EQUALS( clientResponse.sizeErrDetails(), 1u );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt( 0 )->getErrCode(),
- response.getErrDetailsAt(0)->getErrCode() );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt( 0 )->getErrMessage(),
- response.getErrDetailsAt(0)->getErrMessage() );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt( 0 )->getIndex(), 0 );
- }
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 1);
+ ASSERT(clientResponse.isErrDetailsSet());
+ ASSERT_EQUALS(clientResponse.sizeErrDetails(), 1u);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getIndex(), 1);
+}
+TEST(WriteOpTests, MultiOpFailedTargetUnordered) {
//
- // Tests of edge-case functionality, lifecycle is assumed to be behaving normally
+ // Targeting failure on second op in batch op (unordered)
//
- TEST(WriteOpTests, MultiOpErrorAndWriteConcernErrorUnordered) {
-
- //
- // Multi-op (unordered) error and write concern error test
- // We never report the write concern error for single-doc batches, since the error means
- // there's no write concern applied.
- // Don't suppress the error if ordered : false
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpoint( "shard", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterFullRange( nss, endpoint, &targeter );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
- request.setWriteConcern( BSON( "w" << 3 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- BatchedCommandResponse response;
- buildResponse( 1, &response );
- addError( ErrorCodes::UnknownError, "mock error", 1, &response );
- addWCError( &response );
-
- // First stale response comes back, we should retry
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- // Unordered reports write concern error
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 1 );
- ASSERT_EQUALS( clientResponse.sizeErrDetails(), 1u );
- ASSERT( clientResponse.isWriteConcernErrorSet() );
- }
-
-
- TEST(WriteOpTests, SingleOpErrorAndWriteConcernErrorOrdered) {
-
- //
- // Single-op (ordered) error and write concern error test
- // Suppress the write concern error if ordered and we also have an error
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpointA( "shardA", ChunkVersion::IGNORED() );
- ShardEndpoint endpointB( "shardB", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterSplitRange( nss, endpointA, endpointB, &targeter );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Update );
- request.setNS( nss.ns() );
- request.setOrdered( true );
- BSONObj query = BSON( "x" << GTE << -1 << LT << 2 );
- request.getUpdateRequest()->addToUpdates( buildUpdate( query, true ) );
- request.setWriteConcern( BSON( "w" << 3 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- BatchedCommandResponse response;
- buildResponse( 1, &response );
- addWCError( &response );
-
- // First response comes back with write concern error
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
-
- buildResponse( 0, &response );
- addError( ErrorCodes::UnknownError, "mock error", 0, &response );
-
- // Second response comes back with write error
- batchOp.noteBatchResponse( *targeted.back(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- // Ordered doesn't report write concern error
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 1 );
- ASSERT( clientResponse.isErrDetailsSet() );
- ASSERT_EQUALS( clientResponse.sizeErrDetails(), 1u );
- ASSERT( !clientResponse.isWriteConcernErrorSet() );
- }
-
- TEST(WriteOpTests, MultiOpFailedTargetOrdered) {
-
- //
- // Targeting failure on second op in batch op (ordered)
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpoint( "shard", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterHalfRange( nss, endpoint, &targeter );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.getInsertRequest()->addToDocuments( BSON( "x" << -1 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 2 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << -2 ) );
-
- // Do single-target, multi-doc batch write op
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- // First targeting round fails since we may be stale
- ASSERT( !status.isOK() );
- ASSERT( !batchOp.isFinished() );
-
- targetedOwned.clear();
- status = batchOp.targetBatch( targeter, true, &targeted );
-
- // Second targeting round is ok, but should stop at first write
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 1u );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 1u );
-
- BatchedCommandResponse response;
- buildResponse( 1, &response );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterHalfRange(nss, endpoint, &targeter);
+
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ request.getInsertRequest()->addToDocuments(BSON("x" << -1));
+ request.getInsertRequest()->addToDocuments(BSON("x" << 2));
+ request.getInsertRequest()->addToDocuments(BSON("x" << -2));
+
+ // Do single-target, multi-doc batch write op
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ // First targeting round fails since we may be stale
+ ASSERT(!status.isOK());
+ ASSERT(!batchOp.isFinished());
+
+ targetedOwned.clear();
+ status = batchOp.targetBatch(targeter, true, &targeted);
+
+ // Second targeting round is ok, and should record an error
+ ASSERT(status.isOK());
+ ASSERT(!batchOp.isFinished());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 2u);
+
+ BatchedCommandResponse response;
+ buildResponse(2, &response);
+
+ // Response is ok for first and third write
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 2);
+ ASSERT(clientResponse.isErrDetailsSet());
+ ASSERT_EQUALS(clientResponse.sizeErrDetails(), 1u);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getIndex(), 1);
+}
+
+TEST(WriteOpTests, MultiOpFailedBatchOrdered) {
+ //
+ // Batch failure (ok : 0) reported in a multi-op batch (ordered)
+ // Expect this gets translated down into write errors for first affected write
+ //
- // First response ok
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpointA("shardA", ChunkVersion::IGNORED());
+ ShardEndpoint endpointB("shardB", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterSplitRange(nss, endpointA, endpointB, &targeter);
+
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.getInsertRequest()->addToDocuments(BSON("x" << -1));
+ request.getInsertRequest()->addToDocuments(BSON("x" << 2));
+ request.getInsertRequest()->addToDocuments(BSON("x" << 3));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
+
+ // First shard batch is ok
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+
+ targetedOwned.clear();
+ status = batchOp.targetBatch(targeter, true, &targeted);
+
+ buildErrResponse(ErrorCodes::UnknownError, "mock error", &response);
+
+ // Second shard batch fails
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ // We should have recorded an error for the second write
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 1);
+ ASSERT(clientResponse.isErrDetailsSet());
+ ASSERT_EQUALS(clientResponse.sizeErrDetails(), 1u);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getIndex(), 1);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getErrCode(), response.getErrCode());
+}
+
+TEST(WriteOpTests, MultiOpFailedBatchUnordered) {
+ //
+ // Batch failure (ok : 0) reported in a multi-op batch (unordered)
+ // Expect this gets translated down into write errors for all affected writes
+ //
- targetedOwned.clear();
- status = batchOp.targetBatch( targeter, true, &targeted );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpointA("shardA", ChunkVersion::IGNORED());
+ ShardEndpoint endpointB("shardB", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterSplitRange(nss, endpointA, endpointB, &targeter);
+
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ request.getInsertRequest()->addToDocuments(BSON("x" << -1));
+ request.getInsertRequest()->addToDocuments(BSON("x" << 2));
+ request.getInsertRequest()->addToDocuments(BSON("x" << 3));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
+
+ // First shard batch is ok
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+
+ buildErrResponse(ErrorCodes::UnknownError, "mock error", &response);
+
+ // Second shard batch fails
+ batchOp.noteBatchResponse(*targeted.back(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ // We should have recorded an error for the second and third write
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 1);
+ ASSERT(clientResponse.isErrDetailsSet());
+ ASSERT_EQUALS(clientResponse.sizeErrDetails(), 2u);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getIndex(), 1);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getErrCode(), response.getErrCode());
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(1)->getIndex(), 2);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(1)->getErrCode(), response.getErrCode());
+}
+
+TEST(WriteOpTests, MultiOpAbortOrdered) {
+ //
+ // Batch aborted (ordered)
+ // Expect this gets translated down into write error for first affected write
+ //
- // Second targeting round results in an error which finishes the batch
- ASSERT( status.isOK() );
- ASSERT( batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 0u );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpointA("shardA", ChunkVersion::IGNORED());
+ ShardEndpoint endpointB("shardB", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterSplitRange(nss, endpointA, endpointB, &targeter);
+
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.getInsertRequest()->addToDocuments(BSON("x" << -1));
+ request.getInsertRequest()->addToDocuments(BSON("x" << 2));
+ request.getInsertRequest()->addToDocuments(BSON("x" << 3));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
+
+ // First shard batch is ok
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+
+ WriteErrorDetail abortError;
+ abortError.setErrCode(ErrorCodes::UnknownError);
+ abortError.setErrMessage("mock abort");
+ batchOp.abortBatch(abortError);
+ ASSERT(batchOp.isFinished());
+
+ // We should have recorded an error for the second write
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 1);
+ ASSERT(clientResponse.isErrDetailsSet());
+ ASSERT_EQUALS(clientResponse.sizeErrDetails(), 1u);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getIndex(), 1);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getErrCode(), abortError.getErrCode());
+}
+
+TEST(WriteOpTests, MultiOpAbortUnordered) {
+ //
+ // Batch aborted (unordered)
+ // Expect this gets translated down into write errors for all affected writes
+ //
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 1 );
- ASSERT( clientResponse.isErrDetailsSet() );
- ASSERT_EQUALS( clientResponse.sizeErrDetails(), 1u );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt(0)->getIndex(), 1 );
- }
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpointA("shardA", ChunkVersion::IGNORED());
+ ShardEndpoint endpointB("shardB", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterSplitRange(nss, endpointA, endpointB, &targeter);
+
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.setOrdered(false);
+ request.getInsertRequest()->addToDocuments(BSON("x" << -1));
+ request.getInsertRequest()->addToDocuments(BSON("x" << -2));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+
+ WriteErrorDetail abortError;
+ abortError.setErrCode(ErrorCodes::UnknownError);
+ abortError.setErrMessage("mock abort");
+ batchOp.abortBatch(abortError);
+ ASSERT(batchOp.isFinished());
+
+ // We should have recorded an error for the first and second write
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 0);
+ ASSERT(clientResponse.isErrDetailsSet());
+ ASSERT_EQUALS(clientResponse.sizeErrDetails(), 2u);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getIndex(), 0);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(0)->getErrCode(), abortError.getErrCode());
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(1)->getIndex(), 1);
+ ASSERT_EQUALS(clientResponse.getErrDetailsAt(1)->getErrCode(), abortError.getErrCode());
+}
+
+TEST(WriteOpTests, MultiOpTwoWCErrors) {
+ //
+ // Multi-op targeting test where each op goes to both shards and both return a write concern
+ // error
+ //
- TEST(WriteOpTests, MultiOpFailedTargetUnordered) {
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpointA("shardA", ChunkVersion::IGNORED());
+ ShardEndpoint endpointB("shardB", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterSplitRange(nss, endpointA, endpointB, &targeter);
+
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.getInsertRequest()->addToDocuments(BSON("x" << -1));
+ request.getInsertRequest()->addToDocuments(BSON("x" << 2));
+ request.setWriteConcern(BSON("w" << 3));
+
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
+
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
+ addWCError(&response);
+
+ // First shard write write concern fails.
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
+
+ targetedOwned.clear();
+ status = batchOp.targetBatch(targeter, true, &targeted);
+
+ // Second shard write write concern fails.
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(batchOp.isFinished());
+
+ BatchedCommandResponse clientResponse;
+ batchOp.buildClientResponse(&clientResponse);
+ ASSERT(clientResponse.getOk());
+ ASSERT_EQUALS(clientResponse.getN(), 2);
+ ASSERT(!clientResponse.isErrDetailsSet());
+ ASSERT(clientResponse.isWriteConcernErrorSet());
+}
+
+//
+// Tests of batch size limit functionality
+//
+
+TEST(WriteOpLimitTests, OneBigDoc) {
+ //
+ // Big single operation test - should go through
+ //
- //
- // Targeting failure on second op in batch op (unordered)
- //
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterFullRange(nss, endpoint, &targeter);
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpoint( "shard", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterHalfRange( nss, endpoint, &targeter );
+ // Create a BSONObj (slightly) bigger than the maximum size by including a max-size string
+ string bigString(BSONObjMaxUserSize, 'x');
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- request.getInsertRequest()->addToDocuments( BSON( "x" << -1 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 2 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << -2 ) );
+ // Do single-target, single doc batch write op
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1 << "data" << bigString));
- // Do single-target, multi-doc batch write op
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(targeted.size(), 1u);
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
- // First targeting round fails since we may be stale
- ASSERT( !status.isOK() );
- ASSERT( !batchOp.isFinished() );
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(batchOp.isFinished());
+}
- targetedOwned.clear();
- status = batchOp.targetBatch( targeter, true, &targeted );
+TEST(WriteOpLimitTests, OneBigOneSmall) {
+ //
+ // Big doc with smaller additional doc - should go through as two batches
+ //
- // Second targeting round is ok, and should record an error
- ASSERT( status.isOK() );
- ASSERT( !batchOp.isFinished() );
- ASSERT_EQUALS( targeted.size(), 1u );
- ASSERT_EQUALS( targeted.front()->getWrites().size(), 2u );
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterFullRange(nss, endpoint, &targeter);
- BatchedCommandResponse response;
- buildResponse( 2, &response );
+ // Create a BSONObj (slightly) bigger than the maximum size by including a max-size string
+ string bigString(BSONObjMaxUserSize, 'x');
- // Response is ok for first and third write
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( batchOp.isFinished() );
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Update);
+ request.setNS(nss.ns());
+ BatchedUpdateDocument* bigUpdateDoc =
+ buildUpdate(BSON("x" << 1), BSON("data" << bigString), false);
+ request.getUpdateRequest()->addToUpdates(bigUpdateDoc);
+ request.getUpdateRequest()->addToUpdates(buildUpdate(BSON("x" << 2), BSONObj(), false));
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 2 );
- ASSERT( clientResponse.isErrDetailsSet() );
- ASSERT_EQUALS( clientResponse.sizeErrDetails(), 1u );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt(0)->getIndex(), 1 );
- }
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
- TEST(WriteOpTests, MultiOpFailedBatchOrdered) {
-
- //
- // Batch failure (ok : 0) reported in a multi-op batch (ordered)
- // Expect this gets translated down into write errors for first affected write
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpointA( "shardA", ChunkVersion::IGNORED() );
- ShardEndpoint endpointB( "shardB", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterSplitRange( nss, endpointA, endpointB, &targeter );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.getInsertRequest()->addToDocuments( BSON( "x" << -1 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 2 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 3 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- BatchedCommandResponse response;
- buildResponse( 1, &response );
-
- // First shard batch is ok
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
-
- targetedOwned.clear();
- status = batchOp.targetBatch( targeter, true, &targeted );
-
- buildErrResponse( ErrorCodes::UnknownError, "mock error", &response );
-
- // Second shard batch fails
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- // We should have recorded an error for the second write
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 1 );
- ASSERT( clientResponse.isErrDetailsSet() );
- ASSERT_EQUALS( clientResponse.sizeErrDetails(), 1u );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt(0)->getIndex(), 1 );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt(0)->getErrCode(), response.getErrCode() );
- }
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 1u);
- TEST(WriteOpTests, MultiOpFailedBatchUnordered) {
-
- //
- // Batch failure (ok : 0) reported in a multi-op batch (unordered)
- // Expect this gets translated down into write errors for all affected writes
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpointA( "shardA", ChunkVersion::IGNORED() );
- ShardEndpoint endpointB( "shardB", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterSplitRange( nss, endpointA, endpointB, &targeter );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- request.getInsertRequest()->addToDocuments( BSON( "x" << -1 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 2 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 3 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- BatchedCommandResponse response;
- buildResponse( 1, &response );
-
- // First shard batch is ok
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
-
- buildErrResponse( ErrorCodes::UnknownError, "mock error", &response );
-
- // Second shard batch fails
- batchOp.noteBatchResponse( *targeted.back(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- // We should have recorded an error for the second and third write
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 1 );
- ASSERT( clientResponse.isErrDetailsSet() );
- ASSERT_EQUALS( clientResponse.sizeErrDetails(), 2u );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt(0)->getIndex(), 1 );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt(0)->getErrCode(), response.getErrCode() );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt(1)->getIndex(), 2 );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt(1)->getErrCode(), response.getErrCode() );
- }
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
- TEST(WriteOpTests, MultiOpAbortOrdered) {
-
- //
- // Batch aborted (ordered)
- // Expect this gets translated down into write error for first affected write
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpointA( "shardA", ChunkVersion::IGNORED() );
- ShardEndpoint endpointB( "shardB", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterSplitRange( nss, endpointA, endpointB, &targeter );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.getInsertRequest()->addToDocuments( BSON( "x" << -1 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 2 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 3 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- BatchedCommandResponse response;
- buildResponse( 1, &response );
-
- // First shard batch is ok
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
-
- WriteErrorDetail abortError;
- abortError.setErrCode( ErrorCodes::UnknownError );
- abortError.setErrMessage( "mock abort" );
- batchOp.abortBatch( abortError );
- ASSERT( batchOp.isFinished() );
-
- // We should have recorded an error for the second write
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 1 );
- ASSERT( clientResponse.isErrDetailsSet() );
- ASSERT_EQUALS( clientResponse.sizeErrDetails(), 1u );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt(0)->getIndex(), 1 );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt(0)->getErrCode(), abortError.getErrCode() );
- }
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
- TEST(WriteOpTests, MultiOpAbortUnordered) {
-
- //
- // Batch aborted (unordered)
- // Expect this gets translated down into write errors for all affected writes
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpointA( "shardA", ChunkVersion::IGNORED() );
- ShardEndpoint endpointB( "shardB", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterSplitRange( nss, endpointA, endpointB, &targeter );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.setOrdered( false );
- request.getInsertRequest()->addToDocuments( BSON( "x" << -1 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << -2 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
-
- WriteErrorDetail abortError;
- abortError.setErrCode( ErrorCodes::UnknownError );
- abortError.setErrMessage( "mock abort" );
- batchOp.abortBatch( abortError );
- ASSERT( batchOp.isFinished() );
-
- // We should have recorded an error for the first and second write
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 0 );
- ASSERT( clientResponse.isErrDetailsSet() );
- ASSERT_EQUALS( clientResponse.sizeErrDetails(), 2u );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt(0)->getIndex(), 0 );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt(0)->getErrCode(), abortError.getErrCode() );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt(1)->getIndex(), 1 );
- ASSERT_EQUALS( clientResponse.getErrDetailsAt(1)->getErrCode(), abortError.getErrCode() );
- }
+ targetedOwned.clear();
+ status = batchOp.targetBatch(targeter, false, &targeted);
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 1u);
- TEST(WriteOpTests, MultiOpTwoWCErrors) {
-
- //
- // Multi-op targeting test where each op goes to both shards and both return a write concern
- // error
- //
-
- NamespaceString nss( "foo.bar" );
- ShardEndpoint endpointA( "shardA", ChunkVersion::IGNORED() );
- ShardEndpoint endpointB( "shardB", ChunkVersion::IGNORED() );
- MockNSTargeter targeter;
- initTargeterSplitRange( nss, endpointA, endpointB, &targeter );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.getInsertRequest()->addToDocuments( BSON( "x" << -1 ) );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 2 ) );
- request.setWriteConcern( BSON( "w" << 3 ) );
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest( &request );
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch( targeter, false, &targeted );
-
- BatchedCommandResponse response;
- buildResponse( 1, &response );
- addWCError( &response );
-
- // First shard write write concern fails.
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( !batchOp.isFinished() );
-
- targetedOwned.clear();
- status = batchOp.targetBatch( targeter, true, &targeted );
-
- // Second shard write write concern fails.
- batchOp.noteBatchResponse( *targeted.front(), response, NULL );
- ASSERT( batchOp.isFinished() );
-
- BatchedCommandResponse clientResponse;
- batchOp.buildClientResponse( &clientResponse );
- ASSERT( clientResponse.getOk() );
- ASSERT_EQUALS( clientResponse.getN(), 2 );
- ASSERT( !clientResponse.isErrDetailsSet() );
- ASSERT( clientResponse.isWriteConcernErrorSet() );
- }
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(batchOp.isFinished());
+}
+TEST(WriteOpLimitTests, TooManyOps) {
//
- // Tests of batch size limit functionality
+ // Batch of 1002 documents
//
- TEST(WriteOpLimitTests, OneBigDoc) {
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterFullRange(nss, endpoint, &targeter);
- //
- // Big single operation test - should go through
- //
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Delete);
+ request.setNS(nss.ns());
- NamespaceString nss("foo.bar");
- ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
- MockNSTargeter targeter;
- initTargeterFullRange(nss, endpoint, &targeter);
-
- // Create a BSONObj (slightly) bigger than the maximum size by including a max-size string
- string bigString(BSONObjMaxUserSize, 'x');
-
- // Do single-target, single doc batch write op
- BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
- request.setNS(nss.ns());
- request.getInsertRequest()->addToDocuments(BSON( "x" << 1 << "data" << bigString ));
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest(&request);
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch(targeter, false, &targeted);
- ASSERT(status.isOK());
- ASSERT_EQUALS(targeted.size(), 1u);
-
- BatchedCommandResponse response;
- buildResponse(1, &response);
-
- batchOp.noteBatchResponse(*targeted.front(), response, NULL);
- ASSERT(batchOp.isFinished());
+ // Add 2 more than the maximum to the batch
+ for (size_t i = 0; i < BatchedCommandRequest::kMaxWriteBatchSize + 2u; ++i) {
+ request.getDeleteRequest()->addToDeletes(buildDelete(BSON("x" << 2), 0));
}
- TEST(WriteOpLimitTests, OneBigOneSmall) {
-
- //
- // Big doc with smaller additional doc - should go through as two batches
- //
-
- NamespaceString nss("foo.bar");
- ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
- MockNSTargeter targeter;
- initTargeterFullRange(nss, endpoint, &targeter);
-
- // Create a BSONObj (slightly) bigger than the maximum size by including a max-size string
- string bigString(BSONObjMaxUserSize, 'x');
-
- BatchedCommandRequest request(BatchedCommandRequest::BatchType_Update);
- request.setNS(nss.ns());
- BatchedUpdateDocument* bigUpdateDoc = buildUpdate(BSON( "x" << 1 ),
- BSON( "data" << bigString ),
- false);
- request.getUpdateRequest()->addToUpdates(bigUpdateDoc);
- request.getUpdateRequest()->addToUpdates(buildUpdate(BSON( "x" << 2 ),
- BSONObj(),
- false));
-
- BatchWriteOp batchOp;
- batchOp.initClientRequest(&request);
-
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch(targeter, false, &targeted);
- ASSERT(status.isOK());
- ASSERT_EQUALS(targeted.size(), 1u);
- ASSERT_EQUALS(targeted.front()->getWrites().size(), 1u);
-
- BatchedCommandResponse response;
- buildResponse(1, &response);
-
- batchOp.noteBatchResponse(*targeted.front(), response, NULL);
- ASSERT(!batchOp.isFinished());
-
- targetedOwned.clear();
- status = batchOp.targetBatch(targeter, false, &targeted);
- ASSERT(status.isOK());
- ASSERT_EQUALS(targeted.size(), 1u);
- ASSERT_EQUALS(targeted.front()->getWrites().size(), 1u);
-
- batchOp.noteBatchResponse(*targeted.front(), response, NULL);
- ASSERT(batchOp.isFinished());
- }
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
- TEST(WriteOpLimitTests, TooManyOps) {
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 1000u);
- //
- // Batch of 1002 documents
- //
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
- NamespaceString nss("foo.bar");
- ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
- MockNSTargeter targeter;
- initTargeterFullRange(nss, endpoint, &targeter);
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
- BatchedCommandRequest request(BatchedCommandRequest::BatchType_Delete);
- request.setNS(nss.ns());
+ targetedOwned.clear();
+ status = batchOp.targetBatch(targeter, false, &targeted);
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ ASSERT_EQUALS(targeted.front()->getWrites().size(), 2u);
- // Add 2 more than the maximum to the batch
- for (size_t i = 0; i < BatchedCommandRequest::kMaxWriteBatchSize + 2u; ++i) {
- request.getDeleteRequest()->addToDeletes(buildDelete(BSON( "x" << 2 ), 0));
- }
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(batchOp.isFinished());
+}
- BatchWriteOp batchOp;
- batchOp.initClientRequest(&request);
+TEST(WriteOpLimitTests, UpdateOverheadIncluded) {
+ //
+ // Tests that the overhead of the extra fields in an update x 1000 is included in our size
+ // calculation
+ //
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch(targeter, false, &targeted);
- ASSERT(status.isOK());
- ASSERT_EQUALS(targeted.size(), 1u);
- ASSERT_EQUALS(targeted.front()->getWrites().size(), 1000u);
+ NamespaceString nss("foo.bar");
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
+ MockNSTargeter targeter;
+ initTargeterFullRange(nss, endpoint, &targeter);
- BatchedCommandResponse response;
- buildResponse(1, &response);
+ int updateDataBytes =
+ BSONObjMaxUserSize / static_cast<int>(BatchedCommandRequest::kMaxWriteBatchSize);
- batchOp.noteBatchResponse(*targeted.front(), response, NULL);
- ASSERT(!batchOp.isFinished());
+ string dataString(updateDataBytes -
+ BSON("x" << 1 << "data"
+ << "").objsize(),
+ 'x');
- targetedOwned.clear();
- status = batchOp.targetBatch(targeter, false, &targeted);
- ASSERT(status.isOK());
- ASSERT_EQUALS(targeted.size(), 1u);
- ASSERT_EQUALS(targeted.front()->getWrites().size(), 2u);
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Update);
+ request.setNS(nss.ns());
- batchOp.noteBatchResponse(*targeted.front(), response, NULL);
- ASSERT(batchOp.isFinished());
+ // Add the maximum number of updates
+ int estSizeBytes = 0;
+ for (size_t i = 0; i < BatchedCommandRequest::kMaxWriteBatchSize; ++i) {
+ BatchedUpdateDocument* updateDoc = new BatchedUpdateDocument;
+ updateDoc->setQuery(BSON("x" << 1 << "data" << dataString));
+ updateDoc->setUpdateExpr(BSONObj());
+ updateDoc->setMulti(false);
+ updateDoc->setUpsert(false);
+ request.getUpdateRequest()->addToUpdates(updateDoc);
+ estSizeBytes += updateDoc->toBSON().objsize();
}
- TEST(WriteOpLimitTests, UpdateOverheadIncluded) {
-
- //
- // Tests that the overhead of the extra fields in an update x 1000 is included in our size
- // calculation
- //
-
- NamespaceString nss("foo.bar");
- ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
- MockNSTargeter targeter;
- initTargeterFullRange(nss, endpoint, &targeter);
-
- int updateDataBytes = BSONObjMaxUserSize
- / static_cast<int>(BatchedCommandRequest::kMaxWriteBatchSize);
-
- string dataString(updateDataBytes - BSON( "x" << 1 << "data" << "" ).objsize(), 'x');
+ ASSERT_GREATER_THAN(estSizeBytes, BSONObjMaxInternalSize);
- BatchedCommandRequest request(BatchedCommandRequest::BatchType_Update);
- request.setNS(nss.ns());
+ BatchWriteOp batchOp;
+ batchOp.initClientRequest(&request);
- // Add the maximum number of updates
- int estSizeBytes = 0;
- for (size_t i = 0; i < BatchedCommandRequest::kMaxWriteBatchSize; ++i) {
- BatchedUpdateDocument* updateDoc = new BatchedUpdateDocument;
- updateDoc->setQuery(BSON( "x" << 1 << "data" << dataString ));
- updateDoc->setUpdateExpr(BSONObj());
- updateDoc->setMulti(false);
- updateDoc->setUpsert(false);
- request.getUpdateRequest()->addToUpdates(updateDoc);
- estSizeBytes += updateDoc->toBSON().objsize();
- }
+ OwnedPointerVector<TargetedWriteBatch> targetedOwned;
+ vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
+ Status status = batchOp.targetBatch(targeter, false, &targeted);
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ ASSERT_LESS_THAN(targeted.front()->getWrites().size(), 1000u);
- ASSERT_GREATER_THAN(estSizeBytes, BSONObjMaxInternalSize);
+ BatchedCommandRequest childRequest(BatchedCommandRequest::BatchType_Update);
+ batchOp.buildBatchRequest(*targeted.front(), &childRequest);
+ ASSERT_LESS_THAN(childRequest.toBSON().objsize(), BSONObjMaxInternalSize);
- BatchWriteOp batchOp;
- batchOp.initClientRequest(&request);
+ BatchedCommandResponse response;
+ buildResponse(1, &response);
- OwnedPointerVector<TargetedWriteBatch> targetedOwned;
- vector<TargetedWriteBatch*>& targeted = targetedOwned.mutableVector();
- Status status = batchOp.targetBatch(targeter, false, &targeted);
- ASSERT(status.isOK());
- ASSERT_EQUALS(targeted.size(), 1u);
- ASSERT_LESS_THAN(targeted.front()->getWrites().size(), 1000u);
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(!batchOp.isFinished());
- BatchedCommandRequest childRequest(BatchedCommandRequest::BatchType_Update);
- batchOp.buildBatchRequest(*targeted.front(), &childRequest);
- ASSERT_LESS_THAN(childRequest.toBSON().objsize(), BSONObjMaxInternalSize);
+ targetedOwned.clear();
+ status = batchOp.targetBatch(targeter, false, &targeted);
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(targeted.size(), 1u);
+ ASSERT_LESS_THAN(targeted.front()->getWrites().size(), 1000u);
- BatchedCommandResponse response;
- buildResponse(1, &response);
+ childRequest.clear();
+ batchOp.buildBatchRequest(*targeted.front(), &childRequest);
+ ASSERT_LESS_THAN(childRequest.toBSON().objsize(), BSONObjMaxInternalSize);
- batchOp.noteBatchResponse(*targeted.front(), response, NULL);
- ASSERT(!batchOp.isFinished());
-
- targetedOwned.clear();
- status = batchOp.targetBatch(targeter, false, &targeted);
- ASSERT(status.isOK());
- ASSERT_EQUALS(targeted.size(), 1u);
- ASSERT_LESS_THAN(targeted.front()->getWrites().size(), 1000u);
-
- childRequest.clear();
- batchOp.buildBatchRequest(*targeted.front(), &childRequest);
- ASSERT_LESS_THAN(childRequest.toBSON().objsize(), BSONObjMaxInternalSize);
-
- batchOp.noteBatchResponse(*targeted.front(), response, NULL);
- ASSERT(batchOp.isFinished());
- }
+ batchOp.noteBatchResponse(*targeted.front(), response, NULL);
+ ASSERT(batchOp.isFinished());
+}
-} // unnamed namespace
+} // unnamed namespace
diff --git a/src/mongo/s/write_ops/batched_command_request.cpp b/src/mongo/s/write_ops/batched_command_request.cpp
index b3f4d9911bb..99ef66e767d 100644
--- a/src/mongo/s/write_ops/batched_command_request.cpp
+++ b/src/mongo/s/write_ops/batched_command_request.cpp
@@ -33,368 +33,364 @@
namespace mongo {
- using std::unique_ptr;
- using std::string;
- using std::vector;
+using std::unique_ptr;
+using std::string;
+using std::vector;
- const size_t BatchedCommandRequest::kMaxWriteBatchSize = 1000;
+const size_t BatchedCommandRequest::kMaxWriteBatchSize = 1000;
- BatchedCommandRequest::BatchedCommandRequest( BatchType batchType ) :
- _batchType( batchType ) {
- switch ( getBatchType() ) {
+BatchedCommandRequest::BatchedCommandRequest(BatchType batchType) : _batchType(batchType) {
+ switch (getBatchType()) {
case BatchedCommandRequest::BatchType_Insert:
- _insertReq.reset( new BatchedInsertRequest );
+ _insertReq.reset(new BatchedInsertRequest);
return;
case BatchedCommandRequest::BatchType_Update:
- _updateReq.reset( new BatchedUpdateRequest );
+ _updateReq.reset(new BatchedUpdateRequest);
return;
default:
- dassert( getBatchType() == BatchedCommandRequest::BatchType_Delete );
- _deleteReq.reset( new BatchedDeleteRequest );
+ dassert(getBatchType() == BatchedCommandRequest::BatchType_Delete);
+ _deleteReq.reset(new BatchedDeleteRequest);
return;
- }
}
+}
// This macro just invokes a given method on one of the three types of ops with parameters
-#define INVOKE(M,...) \
-{\
- switch ( getBatchType() ) {\
- case BatchedCommandRequest::BatchType_Insert:\
- return _insertReq->M(__VA_ARGS__);\
- case BatchedCommandRequest::BatchType_Update:\
- return _updateReq->M(__VA_ARGS__);\
- default:\
- dassert( getBatchType() == BatchedCommandRequest::BatchType_Delete );\
- return _deleteReq->M(__VA_ARGS__);\
- }\
-}
-
- BatchedCommandRequest::BatchType BatchedCommandRequest::getBatchType() const {
- return _batchType;
- }
+#define INVOKE(M, ...) \
+ { \
+ switch (getBatchType()) { \
+ case BatchedCommandRequest::BatchType_Insert: \
+ return _insertReq->M(__VA_ARGS__); \
+ case BatchedCommandRequest::BatchType_Update: \
+ return _updateReq->M(__VA_ARGS__); \
+ default: \
+ dassert(getBatchType() == BatchedCommandRequest::BatchType_Delete); \
+ return _deleteReq->M(__VA_ARGS__); \
+ } \
+ }
+
+BatchedCommandRequest::BatchType BatchedCommandRequest::getBatchType() const {
+ return _batchType;
+}
- BatchedInsertRequest* BatchedCommandRequest::getInsertRequest() const {
- return _insertReq.get();
- }
+BatchedInsertRequest* BatchedCommandRequest::getInsertRequest() const {
+ return _insertReq.get();
+}
- BatchedUpdateRequest* BatchedCommandRequest::getUpdateRequest() const {
- return _updateReq.get();
- }
+BatchedUpdateRequest* BatchedCommandRequest::getUpdateRequest() const {
+ return _updateReq.get();
+}
- BatchedDeleteRequest* BatchedCommandRequest::getDeleteRequest() const {
- return _deleteReq.get();
- }
+BatchedDeleteRequest* BatchedCommandRequest::getDeleteRequest() const {
+ return _deleteReq.get();
+}
+
+bool BatchedCommandRequest::isInsertIndexRequest() const {
+ if (_batchType != BatchedCommandRequest::BatchType_Insert)
+ return false;
+ return getNSS().isSystemDotIndexes();
+}
- bool BatchedCommandRequest::isInsertIndexRequest() const {
- if ( _batchType != BatchedCommandRequest::BatchType_Insert ) return false;
- return getNSS().isSystemDotIndexes();
+static bool extractUniqueIndex(const BSONObj& indexDesc) {
+ return indexDesc["unique"].trueValue();
+}
+
+bool BatchedCommandRequest::isUniqueIndexRequest() const {
+ if (!isInsertIndexRequest())
+ return false;
+ return extractUniqueIndex(getInsertRequest()->getDocumentsAt(0));
+}
+
+bool BatchedCommandRequest::isValidIndexRequest(string* errMsg) const {
+ string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
+ dassert(isInsertIndexRequest());
+
+ if (sizeWriteOps() != 1) {
+ *errMsg = "invalid batch request for index creation";
+ return false;
}
- static bool extractUniqueIndex( const BSONObj& indexDesc ) {
- return indexDesc["unique"].trueValue();
+ const NamespaceString& targetNSS = getTargetingNSS();
+ if (!targetNSS.isValid()) {
+ *errMsg = targetNSS.ns() + " is not a valid namespace to index";
+ return false;
}
- bool BatchedCommandRequest::isUniqueIndexRequest() const {
- if ( !isInsertIndexRequest() ) return false;
- return extractUniqueIndex( getInsertRequest()->getDocumentsAt( 0 ) );
+ const NamespaceString& reqNSS = getNSS();
+ if (reqNSS.db().compare(targetNSS.db()) != 0) {
+ *errMsg =
+ targetNSS.ns() + " namespace is not in the request database " + reqNSS.db().toString();
+ return false;
}
- bool BatchedCommandRequest::isValidIndexRequest( string* errMsg ) const {
+ return true;
+}
- string dummy;
- if ( !errMsg )
- errMsg = &dummy;
- dassert( isInsertIndexRequest() );
+string BatchedCommandRequest::getTargetingNS() const {
+ return getTargetingNSS().toString();
+}
- if ( sizeWriteOps() != 1 ) {
- *errMsg = "invalid batch request for index creation";
- return false;
- }
+const NamespaceString& BatchedCommandRequest::getTargetingNSS() const {
+ if (!isInsertIndexRequest())
+ return getNSS();
+ INVOKE(getTargetingNSS);
+}
- const NamespaceString& targetNSS = getTargetingNSS();
- if ( !targetNSS.isValid() ) {
- *errMsg = targetNSS.ns() + " is not a valid namespace to index";
- return false;
- }
+static BSONObj extractIndexKeyPattern(const BSONObj& indexDesc) {
+ return indexDesc["key"].Obj();
+}
- const NamespaceString& reqNSS = getNSS();
- if ( reqNSS.db().compare( targetNSS.db() ) != 0 ) {
- *errMsg = targetNSS.ns() + " namespace is not in the request database "
- + reqNSS.db().toString();
- return false;
- }
+BSONObj BatchedCommandRequest::getIndexKeyPattern() const {
+ dassert(isInsertIndexRequest());
+ return extractIndexKeyPattern(getInsertRequest()->getDocumentsAt(0));
+}
+bool BatchedCommandRequest::isVerboseWC() const {
+ if (!isWriteConcernSet()) {
return true;
}
- string BatchedCommandRequest::getTargetingNS() const {
- return getTargetingNSS().toString();
- }
-
- const NamespaceString& BatchedCommandRequest::getTargetingNSS() const {
- if ( !isInsertIndexRequest() ) return getNSS();
- INVOKE(getTargetingNSS);
- }
-
- static BSONObj extractIndexKeyPattern( const BSONObj& indexDesc ) {
- return indexDesc["key"].Obj();
- }
-
- BSONObj BatchedCommandRequest::getIndexKeyPattern() const {
- dassert( isInsertIndexRequest() );
- return extractIndexKeyPattern( getInsertRequest()->getDocumentsAt( 0 ) );
+ BSONObj writeConcern = getWriteConcern();
+ BSONElement wElem = writeConcern["w"];
+ if (!wElem.isNumber() || wElem.Number() != 0) {
+ return true;
}
- bool BatchedCommandRequest::isVerboseWC() const {
- if ( !isWriteConcernSet() ) {
- return true;
- }
-
- BSONObj writeConcern = getWriteConcern();
- BSONElement wElem = writeConcern["w"];
- if ( !wElem.isNumber() || wElem.Number() != 0 ) {
- return true;
- }
-
- return false;
- }
+ return false;
+}
- void BatchedCommandRequest::cloneTo( BatchedCommandRequest* other ) const {
- other->_insertReq.reset();
- other->_updateReq.reset();
- other->_deleteReq.reset();
- other->_batchType = _batchType;
+void BatchedCommandRequest::cloneTo(BatchedCommandRequest* other) const {
+ other->_insertReq.reset();
+ other->_updateReq.reset();
+ other->_deleteReq.reset();
+ other->_batchType = _batchType;
- switch ( getBatchType() ) {
+ switch (getBatchType()) {
case BatchedCommandRequest::BatchType_Insert:
- other->_insertReq.reset( new BatchedInsertRequest );
- _insertReq->cloneTo( other->_insertReq.get() );
+ other->_insertReq.reset(new BatchedInsertRequest);
+ _insertReq->cloneTo(other->_insertReq.get());
return;
case BatchedCommandRequest::BatchType_Update:
- other->_updateReq.reset( new BatchedUpdateRequest );
- _updateReq->cloneTo( other->_updateReq.get() );
+ other->_updateReq.reset(new BatchedUpdateRequest);
+ _updateReq->cloneTo(other->_updateReq.get());
return;
default:
- dassert( getBatchType() == BatchedCommandRequest::BatchType_Delete );
- other->_deleteReq.reset( new BatchedDeleteRequest );
- _deleteReq->cloneTo( other->_deleteReq.get() );
+ dassert(getBatchType() == BatchedCommandRequest::BatchType_Delete);
+ other->_deleteReq.reset(new BatchedDeleteRequest);
+ _deleteReq->cloneTo(other->_deleteReq.get());
return;
- }
}
+}
- bool BatchedCommandRequest::isValid( std::string* errMsg ) const {
- INVOKE( isValid, errMsg );
- }
+bool BatchedCommandRequest::isValid(std::string* errMsg) const {
+ INVOKE(isValid, errMsg);
+}
- BSONObj BatchedCommandRequest::toBSON() const {
- INVOKE( toBSON );
- }
+BSONObj BatchedCommandRequest::toBSON() const {
+ INVOKE(toBSON);
+}
- bool BatchedCommandRequest::parseBSON( const BSONObj& source, std::string* errMsg ) {
- INVOKE( parseBSON, source, errMsg );
- }
+bool BatchedCommandRequest::parseBSON(const BSONObj& source, std::string* errMsg) {
+ INVOKE(parseBSON, source, errMsg);
+}
- void BatchedCommandRequest::clear() {
- INVOKE( clear );
- }
+void BatchedCommandRequest::clear() {
+ INVOKE(clear);
+}
- std::string BatchedCommandRequest::toString() const {
- INVOKE( toString );
- }
+std::string BatchedCommandRequest::toString() const {
+ INVOKE(toString);
+}
- void BatchedCommandRequest::setNSS( const NamespaceString& nss ) {
- INVOKE( setCollNameNS, nss );
- }
+void BatchedCommandRequest::setNSS(const NamespaceString& nss) {
+ INVOKE(setCollNameNS, nss);
+}
- void BatchedCommandRequest::setNS( StringData collName ) {
- INVOKE( setCollName, collName );
- }
+void BatchedCommandRequest::setNS(StringData collName) {
+ INVOKE(setCollName, collName);
+}
- const std::string& BatchedCommandRequest::getNS() const {
- INVOKE( getCollName );
- }
+const std::string& BatchedCommandRequest::getNS() const {
+ INVOKE(getCollName);
+}
- const NamespaceString& BatchedCommandRequest::getNSS() const {
- INVOKE(getCollNameNS);
- }
+const NamespaceString& BatchedCommandRequest::getNSS() const {
+ INVOKE(getCollNameNS);
+}
- std::size_t BatchedCommandRequest::sizeWriteOps() const {
- switch ( getBatchType() ) {
+std::size_t BatchedCommandRequest::sizeWriteOps() const {
+ switch (getBatchType()) {
case BatchedCommandRequest::BatchType_Insert:
return _insertReq->sizeDocuments();
case BatchedCommandRequest::BatchType_Update:
return _updateReq->sizeUpdates();
default:
return _deleteReq->sizeDeletes();
- }
}
+}
- void BatchedCommandRequest::setWriteConcern( const BSONObj& writeConcern ) {
- INVOKE( setWriteConcern, writeConcern );
- }
+void BatchedCommandRequest::setWriteConcern(const BSONObj& writeConcern) {
+ INVOKE(setWriteConcern, writeConcern);
+}
- void BatchedCommandRequest::unsetWriteConcern() {
- INVOKE( unsetWriteConcern );
- }
+void BatchedCommandRequest::unsetWriteConcern() {
+ INVOKE(unsetWriteConcern);
+}
- bool BatchedCommandRequest::isWriteConcernSet() const {
- INVOKE( isWriteConcernSet );
- }
+bool BatchedCommandRequest::isWriteConcernSet() const {
+ INVOKE(isWriteConcernSet);
+}
- const BSONObj& BatchedCommandRequest::getWriteConcern() const {
- INVOKE( getWriteConcern );
- }
+const BSONObj& BatchedCommandRequest::getWriteConcern() const {
+ INVOKE(getWriteConcern);
+}
- void BatchedCommandRequest::setOrdered( bool continueOnError ) {
- INVOKE( setOrdered, continueOnError );
- }
+void BatchedCommandRequest::setOrdered(bool continueOnError) {
+ INVOKE(setOrdered, continueOnError);
+}
- void BatchedCommandRequest::unsetOrdered() {
- INVOKE( unsetOrdered );
- }
+void BatchedCommandRequest::unsetOrdered() {
+ INVOKE(unsetOrdered);
+}
- bool BatchedCommandRequest::isOrderedSet() const {
- INVOKE( isOrderedSet );
- }
+bool BatchedCommandRequest::isOrderedSet() const {
+ INVOKE(isOrderedSet);
+}
- bool BatchedCommandRequest::getOrdered() const {
- INVOKE( getOrdered );
- }
+bool BatchedCommandRequest::getOrdered() const {
+ INVOKE(getOrdered);
+}
- void BatchedCommandRequest::setMetadata(BatchedRequestMetadata* metadata) {
- INVOKE( setMetadata, metadata );
- }
+void BatchedCommandRequest::setMetadata(BatchedRequestMetadata* metadata) {
+ INVOKE(setMetadata, metadata);
+}
- void BatchedCommandRequest::unsetMetadata() {
- INVOKE( unsetMetadata );
- }
+void BatchedCommandRequest::unsetMetadata() {
+ INVOKE(unsetMetadata);
+}
- bool BatchedCommandRequest::isMetadataSet() const {
- INVOKE( isMetadataSet );
- }
+bool BatchedCommandRequest::isMetadataSet() const {
+ INVOKE(isMetadataSet);
+}
- BatchedRequestMetadata* BatchedCommandRequest::getMetadata() const {
- INVOKE( getMetadata );
- }
+BatchedRequestMetadata* BatchedCommandRequest::getMetadata() const {
+ INVOKE(getMetadata);
+}
- void BatchedCommandRequest::setShouldBypassValidation(bool newVal) {
- INVOKE(setShouldBypassValidation, newVal);
- }
+void BatchedCommandRequest::setShouldBypassValidation(bool newVal) {
+ INVOKE(setShouldBypassValidation, newVal);
+}
- bool BatchedCommandRequest::shouldBypassValidation() const {
- INVOKE(shouldBypassValidation);
- }
+bool BatchedCommandRequest::shouldBypassValidation() const {
+ INVOKE(shouldBypassValidation);
+}
- /**
- * Generates a new request with insert _ids if required. Otherwise returns NULL.
- */
- BatchedCommandRequest* //
+/**
+ * Generates a new request with insert _ids if required. Otherwise returns NULL.
+ */
+BatchedCommandRequest* //
BatchedCommandRequest::cloneWithIds(const BatchedCommandRequest& origCmdRequest) {
+ if (origCmdRequest.getBatchType() != BatchedCommandRequest::BatchType_Insert ||
+ origCmdRequest.isInsertIndexRequest())
+ return NULL;
- if (origCmdRequest.getBatchType() != BatchedCommandRequest::BatchType_Insert
- || origCmdRequest.isInsertIndexRequest())
- return NULL;
-
- unique_ptr<BatchedInsertRequest> idRequest;
- BatchedInsertRequest* origRequest = origCmdRequest.getInsertRequest();
+ unique_ptr<BatchedInsertRequest> idRequest;
+ BatchedInsertRequest* origRequest = origCmdRequest.getInsertRequest();
- const vector<BSONObj>& inserts = origRequest->getDocuments();
+ const vector<BSONObj>& inserts = origRequest->getDocuments();
- size_t i = 0u;
- for (vector<BSONObj>::const_iterator it = inserts.begin(); it != inserts.end(); ++it, ++i) {
+ size_t i = 0u;
+ for (vector<BSONObj>::const_iterator it = inserts.begin(); it != inserts.end(); ++it, ++i) {
+ const BSONObj& insert = *it;
+ BSONObj idInsert;
- const BSONObj& insert = *it;
- BSONObj idInsert;
-
- if (insert["_id"].eoo()) {
- BSONObjBuilder idInsertB;
- idInsertB.append("_id", OID::gen());
- idInsertB.appendElements(insert);
- idInsert = idInsertB.obj();
- }
-
- if (NULL == idRequest.get() && !idInsert.isEmpty()) {
- idRequest.reset(new BatchedInsertRequest);
- origRequest->cloneTo(idRequest.get());
- }
-
- if (!idInsert.isEmpty()) {
- idRequest->setDocumentAt(i, idInsert);
- }
+ if (insert["_id"].eoo()) {
+ BSONObjBuilder idInsertB;
+ idInsertB.append("_id", OID::gen());
+ idInsertB.appendElements(insert);
+ idInsert = idInsertB.obj();
}
- if (NULL == idRequest.get())
- return NULL;
+ if (NULL == idRequest.get() && !idInsert.isEmpty()) {
+ idRequest.reset(new BatchedInsertRequest);
+ origRequest->cloneTo(idRequest.get());
+ }
- // Command request owns idRequest
- return new BatchedCommandRequest(idRequest.release());
+ if (!idInsert.isEmpty()) {
+ idRequest->setDocumentAt(i, idInsert);
+ }
}
- bool BatchedCommandRequest::containsNoIDUpsert(const BatchedCommandRequest& request) {
+ if (NULL == idRequest.get())
+ return NULL;
- if (request.getBatchType() != BatchedCommandRequest::BatchType_Update)
- return false;
-
- const vector<BatchedUpdateDocument*>& updates =
- request.getUpdateRequest()->getUpdates();
-
- for (vector<BatchedUpdateDocument*>::const_iterator it = updates.begin();
- it != updates.end(); ++it) {
-
- const BatchedUpdateDocument* updateDoc = *it;
- if (updateDoc->getUpsert() && updateDoc->getQuery()["_id"].eoo())
- return true;
- }
+ // Command request owns idRequest
+ return new BatchedCommandRequest(idRequest.release());
+}
+bool BatchedCommandRequest::containsNoIDUpsert(const BatchedCommandRequest& request) {
+ if (request.getBatchType() != BatchedCommandRequest::BatchType_Update)
return false;
- }
- bool BatchedCommandRequest::containsUpserts( const BSONObj& writeCmdObj ) {
+ const vector<BatchedUpdateDocument*>& updates = request.getUpdateRequest()->getUpdates();
- BSONElement updatesEl = writeCmdObj[BatchedUpdateRequest::updates()];
- if ( updatesEl.type() != Array ) {
- return false;
- }
+ for (vector<BatchedUpdateDocument*>::const_iterator it = updates.begin(); it != updates.end();
+ ++it) {
+ const BatchedUpdateDocument* updateDoc = *it;
+ if (updateDoc->getUpsert() && updateDoc->getQuery()["_id"].eoo())
+ return true;
+ }
- BSONObjIterator it( updatesEl.Obj() );
- while ( it.more() ) {
- BSONElement updateEl = it.next();
- if ( !updateEl.isABSONObj() ) continue;
- if ( updateEl.Obj()[BatchedUpdateDocument::upsert()].trueValue() ) return true;
- }
+ return false;
+}
+bool BatchedCommandRequest::containsUpserts(const BSONObj& writeCmdObj) {
+ BSONElement updatesEl = writeCmdObj[BatchedUpdateRequest::updates()];
+ if (updatesEl.type() != Array) {
return false;
}
- bool BatchedCommandRequest::getIndexedNS( const BSONObj& writeCmdObj,
- string* nsToIndex,
- string* errMsg ) {
+ BSONObjIterator it(updatesEl.Obj());
+ while (it.more()) {
+ BSONElement updateEl = it.next();
+ if (!updateEl.isABSONObj())
+ continue;
+ if (updateEl.Obj()[BatchedUpdateDocument::upsert()].trueValue())
+ return true;
+ }
- BSONElement documentsEl = writeCmdObj[BatchedInsertRequest::documents()];
- if ( documentsEl.type() != Array ) {
- *errMsg = "index write batch is invalid";
- return false;
- }
+ return false;
+}
- BSONObjIterator it( documentsEl.Obj() );
- if ( !it.more() ) {
- *errMsg = "index write batch is empty";
- return false;
- }
+bool BatchedCommandRequest::getIndexedNS(const BSONObj& writeCmdObj,
+ string* nsToIndex,
+ string* errMsg) {
+ BSONElement documentsEl = writeCmdObj[BatchedInsertRequest::documents()];
+ if (documentsEl.type() != Array) {
+ *errMsg = "index write batch is invalid";
+ return false;
+ }
- BSONElement indexDescEl = it.next();
- *nsToIndex = indexDescEl["ns"].str();
- if ( *nsToIndex == "" ) {
- *errMsg = "index write batch contains an invalid index descriptor";
- return false;
- }
+ BSONObjIterator it(documentsEl.Obj());
+ if (!it.more()) {
+ *errMsg = "index write batch is empty";
+ return false;
+ }
- if ( it.more() ) {
- *errMsg = "index write batches may only contain a single index descriptor";
- return false;
- }
+ BSONElement indexDescEl = it.next();
+ *nsToIndex = indexDescEl["ns"].str();
+ if (*nsToIndex == "") {
+ *errMsg = "index write batch contains an invalid index descriptor";
+ return false;
+ }
- return true;
+ if (it.more()) {
+ *errMsg = "index write batches may only contain a single index descriptor";
+ return false;
}
-} // namespace mongo
+ return true;
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/batched_command_request.h b/src/mongo/s/write_ops/batched_command_request.h
index 66e178dd5a8..b8e6e2f8e80 100644
--- a/src/mongo/s/write_ops/batched_command_request.h
+++ b/src/mongo/s/write_ops/batched_command_request.h
@@ -37,213 +37,204 @@
namespace mongo {
- class NamespaceString;
+class NamespaceString;
- /**
- * This class wraps the different kinds of command requests into a generically usable write
- * command request.
- *
- * Designed to be a very thin wrapper that mimics the underlying requests exactly. Owns the
- * wrapped request object once constructed.
- */
- class BatchedCommandRequest : public BSONSerializable {
- MONGO_DISALLOW_COPYING(BatchedCommandRequest);
- public:
-
- // Maximum number of write ops supported per batch
- static const size_t kMaxWriteBatchSize;
-
- enum BatchType {
- BatchType_Insert, BatchType_Update, BatchType_Delete, BatchType_Unknown
- };
+/**
+ * This class wraps the different kinds of command requests into a generically usable write
+ * command request.
+ *
+ * Designed to be a very thin wrapper that mimics the underlying requests exactly. Owns the
+ * wrapped request object once constructed.
+ */
+class BatchedCommandRequest : public BSONSerializable {
+ MONGO_DISALLOW_COPYING(BatchedCommandRequest);
- //
- // construction / destruction
- //
+public:
+ // Maximum number of write ops supported per batch
+ static const size_t kMaxWriteBatchSize;
- BatchedCommandRequest( BatchType batchType );
+ enum BatchType { BatchType_Insert, BatchType_Update, BatchType_Delete, BatchType_Unknown };
- /**
- * insertReq ownership is transferred to here.
- */
- BatchedCommandRequest( BatchedInsertRequest* insertReq ) :
- _batchType( BatchType_Insert ), _insertReq( insertReq ) {
- }
+ //
+ // construction / destruction
+ //
- /**
- * updateReq ownership is transferred to here.
- */
- BatchedCommandRequest( BatchedUpdateRequest* updateReq ) :
- _batchType( BatchType_Update ), _updateReq( updateReq ) {
- }
+ BatchedCommandRequest(BatchType batchType);
- /**
- * deleteReq ownership is transferred to here.
- */
- BatchedCommandRequest( BatchedDeleteRequest* deleteReq ) :
- _batchType( BatchType_Delete ), _deleteReq( deleteReq ) {
- }
-
- virtual ~BatchedCommandRequest() {};
-
- /** Copies all the fields present in 'this' to 'other'. */
- void cloneTo( BatchedCommandRequest* other ) const;
-
- //
- // bson serializable interface implementation
- //
-
- virtual bool isValid( std::string* errMsg ) const;
- virtual BSONObj toBSON() const;
- virtual bool parseBSON( const BSONObj& source, std::string* errMsg );
- virtual void clear();
- virtual std::string toString() const;
-
- //
- // Batch type accessors
- //
-
- BatchType getBatchType() const;
- BatchedInsertRequest* getInsertRequest() const;
- BatchedUpdateRequest* getUpdateRequest() const;
- BatchedDeleteRequest* getDeleteRequest() const;
- // Index creation is also an insert, but a weird one.
- bool isInsertIndexRequest() const;
- bool isUniqueIndexRequest() const;
- bool isValidIndexRequest( std::string* errMsg ) const;
- std::string getTargetingNS() const;
- const NamespaceString& getTargetingNSS() const;
- BSONObj getIndexKeyPattern() const;
-
- //
- // individual field accessors
- //
-
- bool isVerboseWC() const;
-
- void setNSS( const NamespaceString& nss );
- void setNS( StringData collName );
- const std::string& getNS() const;
- const NamespaceString& getNSS() const;
-
- std::size_t sizeWriteOps() const;
-
- void setWriteConcern( const BSONObj& writeConcern );
- void unsetWriteConcern();
- bool isWriteConcernSet() const;
- const BSONObj& getWriteConcern() const;
-
- void setOrdered( bool ordered );
- void unsetOrdered();
- bool isOrderedSet() const;
- bool getOrdered() const;
-
- void setMetadata(BatchedRequestMetadata* metadata);
- void unsetMetadata();
- bool isMetadataSet() const;
- BatchedRequestMetadata* getMetadata() const;
-
- void setShouldBypassValidation(bool newVal);
- bool shouldBypassValidation() const;
-
- //
- // Helpers for batch pre-processing
- //
-
- /**
- * Generates a new request, the same as the old, but with insert _ids if required.
- * Returns NULL if this is not an insert request or all inserts already have _ids.
- */
- static BatchedCommandRequest* cloneWithIds(const BatchedCommandRequest& origCmdRequest);
-
- /**
- * Whether or not this batch contains an upsert without an _id - these can't be sent
- * to multiple hosts.
- */
- static bool containsNoIDUpsert(const BatchedCommandRequest& request);
-
- //
- // Helpers for auth pre-parsing
- //
-
- /**
- * Helper to determine whether or not there are any upserts in the batch
- */
- static bool containsUpserts( const BSONObj& writeCmdObj );
-
- /**
- * Helper to extract the namespace being indexed from a raw BSON write command.
- *
- * Returns false with errMsg if the index write command seems invalid.
- * TODO: Remove when we have parsing hooked before authorization
- */
- static bool getIndexedNS( const BSONObj& writeCmdObj,
- std::string* nsToIndex,
- std::string* errMsg );
-
- private:
-
- BatchType _batchType;
- std::unique_ptr<BatchedInsertRequest> _insertReq;
- std::unique_ptr<BatchedUpdateRequest> _updateReq;
- std::unique_ptr<BatchedDeleteRequest> _deleteReq;
- };
+ /**
+ * insertReq ownership is transferred to here.
+ */
+ BatchedCommandRequest(BatchedInsertRequest* insertReq)
+ : _batchType(BatchType_Insert), _insertReq(insertReq) {}
/**
- * Similar to above, this class wraps the write items of a command request into a generically
- * usable type. Very thin wrapper, does not own the write item itself.
- *
- * TODO: Use in BatchedCommandRequest above
+ * updateReq ownership is transferred to here.
*/
- class BatchItemRef {
- public:
+ BatchedCommandRequest(BatchedUpdateRequest* updateReq)
+ : _batchType(BatchType_Update), _updateReq(updateReq) {}
- BatchItemRef( const BatchedCommandRequest* request, int itemIndex ) :
- _request( request ), _itemIndex( itemIndex ) {
- }
+ /**
+ * deleteReq ownership is transferred to here.
+ */
+ BatchedCommandRequest(BatchedDeleteRequest* deleteReq)
+ : _batchType(BatchType_Delete), _deleteReq(deleteReq) {}
+
+ virtual ~BatchedCommandRequest(){};
+
+ /** Copies all the fields present in 'this' to 'other'. */
+ void cloneTo(BatchedCommandRequest* other) const;
+
+ //
+ // bson serializable interface implementation
+ //
+
+ virtual bool isValid(std::string* errMsg) const;
+ virtual BSONObj toBSON() const;
+ virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
+ virtual void clear();
+ virtual std::string toString() const;
+
+ //
+ // Batch type accessors
+ //
+
+ BatchType getBatchType() const;
+ BatchedInsertRequest* getInsertRequest() const;
+ BatchedUpdateRequest* getUpdateRequest() const;
+ BatchedDeleteRequest* getDeleteRequest() const;
+ // Index creation is also an insert, but a weird one.
+ bool isInsertIndexRequest() const;
+ bool isUniqueIndexRequest() const;
+ bool isValidIndexRequest(std::string* errMsg) const;
+ std::string getTargetingNS() const;
+ const NamespaceString& getTargetingNSS() const;
+ BSONObj getIndexKeyPattern() const;
+
+ //
+ // individual field accessors
+ //
+
+ bool isVerboseWC() const;
+
+ void setNSS(const NamespaceString& nss);
+ void setNS(StringData collName);
+ const std::string& getNS() const;
+ const NamespaceString& getNSS() const;
+
+ std::size_t sizeWriteOps() const;
+
+ void setWriteConcern(const BSONObj& writeConcern);
+ void unsetWriteConcern();
+ bool isWriteConcernSet() const;
+ const BSONObj& getWriteConcern() const;
+
+ void setOrdered(bool ordered);
+ void unsetOrdered();
+ bool isOrderedSet() const;
+ bool getOrdered() const;
+
+ void setMetadata(BatchedRequestMetadata* metadata);
+ void unsetMetadata();
+ bool isMetadataSet() const;
+ BatchedRequestMetadata* getMetadata() const;
+
+ void setShouldBypassValidation(bool newVal);
+ bool shouldBypassValidation() const;
+
+ //
+ // Helpers for batch pre-processing
+ //
- const BatchedCommandRequest* getRequest() const {
- return _request;
- }
+ /**
+ * Generates a new request, the same as the old, but with insert _ids if required.
+ * Returns NULL if this is not an insert request or all inserts already have _ids.
+ */
+ static BatchedCommandRequest* cloneWithIds(const BatchedCommandRequest& origCmdRequest);
- int getItemIndex() const {
- return _itemIndex;
- }
+ /**
+ * Whether or not this batch contains an upsert without an _id - these can't be sent
+ * to multiple hosts.
+ */
+ static bool containsNoIDUpsert(const BatchedCommandRequest& request);
- BatchedCommandRequest::BatchType getOpType() const {
- return _request->getBatchType();
- }
+ //
+ // Helpers for auth pre-parsing
+ //
- const BSONObj& getDocument() const {
- dassert( _itemIndex < static_cast<int>( _request->sizeWriteOps() ) );
- return _request->getInsertRequest()->getDocumentsAt( _itemIndex );
- }
+ /**
+ * Helper to determine whether or not there are any upserts in the batch
+ */
+ static bool containsUpserts(const BSONObj& writeCmdObj);
- const BatchedUpdateDocument* getUpdate() const {
- dassert( _itemIndex < static_cast<int>( _request->sizeWriteOps() ) );
- return _request->getUpdateRequest()->getUpdatesAt( _itemIndex );
- }
+ /**
+ * Helper to extract the namespace being indexed from a raw BSON write command.
+ *
+ * Returns false with errMsg if the index write command seems invalid.
+ * TODO: Remove when we have parsing hooked before authorization
+ */
+ static bool getIndexedNS(const BSONObj& writeCmdObj,
+ std::string* nsToIndex,
+ std::string* errMsg);
- const BatchedDeleteDocument* getDelete() const {
- dassert( _itemIndex < static_cast<int>( _request->sizeWriteOps() ) );
- return _request->getDeleteRequest()->getDeletesAt( _itemIndex );
- }
+private:
+ BatchType _batchType;
+ std::unique_ptr<BatchedInsertRequest> _insertReq;
+ std::unique_ptr<BatchedUpdateRequest> _updateReq;
+ std::unique_ptr<BatchedDeleteRequest> _deleteReq;
+};
- BSONObj toBSON() const {
- switch ( getOpType() ) {
+/**
+ * Similar to above, this class wraps the write items of a command request into a generically
+ * usable type. Very thin wrapper, does not own the write item itself.
+ *
+ * TODO: Use in BatchedCommandRequest above
+ */
+class BatchItemRef {
+public:
+ BatchItemRef(const BatchedCommandRequest* request, int itemIndex)
+ : _request(request), _itemIndex(itemIndex) {}
+
+ const BatchedCommandRequest* getRequest() const {
+ return _request;
+ }
+
+ int getItemIndex() const {
+ return _itemIndex;
+ }
+
+ BatchedCommandRequest::BatchType getOpType() const {
+ return _request->getBatchType();
+ }
+
+ const BSONObj& getDocument() const {
+ dassert(_itemIndex < static_cast<int>(_request->sizeWriteOps()));
+ return _request->getInsertRequest()->getDocumentsAt(_itemIndex);
+ }
+
+ const BatchedUpdateDocument* getUpdate() const {
+ dassert(_itemIndex < static_cast<int>(_request->sizeWriteOps()));
+ return _request->getUpdateRequest()->getUpdatesAt(_itemIndex);
+ }
+
+ const BatchedDeleteDocument* getDelete() const {
+ dassert(_itemIndex < static_cast<int>(_request->sizeWriteOps()));
+ return _request->getDeleteRequest()->getDeletesAt(_itemIndex);
+ }
+
+ BSONObj toBSON() const {
+ switch (getOpType()) {
case BatchedCommandRequest::BatchType_Insert:
return getDocument();
case BatchedCommandRequest::BatchType_Update:
return getUpdate()->toBSON();
default:
return getDelete()->toBSON();
- }
}
+ }
- private:
-
- const BatchedCommandRequest* _request;
- const int _itemIndex;
- };
+private:
+ const BatchedCommandRequest* _request;
+ const int _itemIndex;
+};
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/batched_command_response.cpp b/src/mongo/s/write_ops/batched_command_response.cpp
index cce5fa660cc..4f137d1dfe6 100644
--- a/src/mongo/s/write_ops/batched_command_response.cpp
+++ b/src/mongo/s/write_ops/batched_command_response.cpp
@@ -33,524 +33,537 @@
namespace mongo {
- using std::unique_ptr;
- using std::string;
-
- using mongoutils::str::stream;
-
- const BSONField<int> BatchedCommandResponse::ok("ok");
- const BSONField<int> BatchedCommandResponse::errCode("code", ErrorCodes::UnknownError);
- const BSONField<string> BatchedCommandResponse::errMessage("errmsg");
- const BSONField<long long> BatchedCommandResponse::n("n", 0);
- const BSONField<long long> BatchedCommandResponse::nModified("nModified", 0);
- const BSONField<std::vector<BatchedUpsertDetail*> >
- BatchedCommandResponse::upsertDetails("upserted");
- const BSONField<Timestamp> BatchedCommandResponse::lastOp("lastOp");
- const BSONField<OID> BatchedCommandResponse::electionId("electionId");
- const BSONField<std::vector<WriteErrorDetail*> >
- BatchedCommandResponse::writeErrors("writeErrors");
- const BSONField<WCErrorDetail*> BatchedCommandResponse::writeConcernError("writeConcernError");
-
- BatchedCommandResponse::BatchedCommandResponse() {
- clear();
- }
+using std::unique_ptr;
+using std::string;
- BatchedCommandResponse::~BatchedCommandResponse() {
- unsetErrDetails();
- unsetUpsertDetails();
- }
+using mongoutils::str::stream;
- bool BatchedCommandResponse::isValid(std::string* errMsg) const {
- std::string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
+const BSONField<int> BatchedCommandResponse::ok("ok");
+const BSONField<int> BatchedCommandResponse::errCode("code", ErrorCodes::UnknownError);
+const BSONField<string> BatchedCommandResponse::errMessage("errmsg");
+const BSONField<long long> BatchedCommandResponse::n("n", 0);
+const BSONField<long long> BatchedCommandResponse::nModified("nModified", 0);
+const BSONField<std::vector<BatchedUpsertDetail*>> BatchedCommandResponse::upsertDetails(
+ "upserted");
+const BSONField<Timestamp> BatchedCommandResponse::lastOp("lastOp");
+const BSONField<OID> BatchedCommandResponse::electionId("electionId");
+const BSONField<std::vector<WriteErrorDetail*>> BatchedCommandResponse::writeErrors("writeErrors");
+const BSONField<WCErrorDetail*> BatchedCommandResponse::writeConcernError("writeConcernError");
- // All the mandatory fields must be present.
- if (!_isOkSet) {
- *errMsg = stream() << "missing " << ok.name() << " field";
- return false;
- }
+BatchedCommandResponse::BatchedCommandResponse() {
+ clear();
+}
+
+BatchedCommandResponse::~BatchedCommandResponse() {
+ unsetErrDetails();
+ unsetUpsertDetails();
+}
- return true;
+bool BatchedCommandResponse::isValid(std::string* errMsg) const {
+ std::string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
}
- BSONObj BatchedCommandResponse::toBSON() const {
- BSONObjBuilder builder;
+ // All the mandatory fields must be present.
+ if (!_isOkSet) {
+ *errMsg = stream() << "missing " << ok.name() << " field";
+ return false;
+ }
- if (_isOkSet) builder.append(ok(), _ok);
+ return true;
+}
- if (_isErrCodeSet) builder.append(errCode(), _errCode);
+BSONObj BatchedCommandResponse::toBSON() const {
+ BSONObjBuilder builder;
- if (_isErrMessageSet) builder.append(errMessage(), _errMessage);
+ if (_isOkSet)
+ builder.append(ok(), _ok);
- if (_isNModifiedSet) builder.appendNumber(nModified(), _nModified);
- if (_isNSet) builder.appendNumber(n(), _n);
+ if (_isErrCodeSet)
+ builder.append(errCode(), _errCode);
- if (_upsertDetails.get()) {
- BSONArrayBuilder upsertedBuilder(builder.subarrayStart(upsertDetails()));
- for (std::vector<BatchedUpsertDetail*>::const_iterator it = _upsertDetails->begin();
- it != _upsertDetails->end();
- ++it) {
- BSONObj upsertedDetailsDocument = (*it)->toBSON();
- upsertedBuilder.append(upsertedDetailsDocument);
- }
- upsertedBuilder.done();
- }
+ if (_isErrMessageSet)
+ builder.append(errMessage(), _errMessage);
- if (_isLastOpSet) builder.append(lastOp(), _lastOp);
- if (_isElectionIdSet) builder.appendOID(electionId(), const_cast<OID*>(&_electionId));
-
- if (_writeErrorDetails.get()) {
- BSONArrayBuilder errDetailsBuilder(builder.subarrayStart(writeErrors()));
- for (std::vector<WriteErrorDetail*>::const_iterator it = _writeErrorDetails->begin();
- it != _writeErrorDetails->end();
- ++it) {
- BSONObj errDetailsDocument = (*it)->toBSON();
- errDetailsBuilder.append(errDetailsDocument);
- }
- errDetailsBuilder.done();
- }
+ if (_isNModifiedSet)
+ builder.appendNumber(nModified(), _nModified);
+ if (_isNSet)
+ builder.appendNumber(n(), _n);
- if (_wcErrDetails.get()) {
- builder.append(writeConcernError(), _wcErrDetails->toBSON());
+ if (_upsertDetails.get()) {
+ BSONArrayBuilder upsertedBuilder(builder.subarrayStart(upsertDetails()));
+ for (std::vector<BatchedUpsertDetail*>::const_iterator it = _upsertDetails->begin();
+ it != _upsertDetails->end();
+ ++it) {
+ BSONObj upsertedDetailsDocument = (*it)->toBSON();
+ upsertedBuilder.append(upsertedDetailsDocument);
}
-
- return builder.obj();
+ upsertedBuilder.done();
}
- bool BatchedCommandResponse::parseBSON(const BSONObj& source, string* errMsg) {
- clear();
-
- std::string dummy;
- if (!errMsg) errMsg = &dummy;
-
- FieldParser::FieldState fieldState;
- fieldState = FieldParser::extractNumber(source, ok, &_ok, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isOkSet = fieldState == FieldParser::FIELD_SET;
-
- fieldState = FieldParser::extract(source, errCode, &_errCode, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isErrCodeSet = fieldState == FieldParser::FIELD_SET;
-
- fieldState = FieldParser::extract(source, errMessage, &_errMessage, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isErrMessageSet = fieldState == FieldParser::FIELD_SET;
-
- // We're using appendNumber on generation so we'll try a smaller type
- // (int) first and then fall back to the original type (long long).
- BSONField<int> fieldN(n());
- int tempN;
- fieldState = FieldParser::extract(source, fieldN, &tempN, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) {
- // try falling back to a larger type
- fieldState = FieldParser::extract(source, n, &_n, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isNSet = fieldState == FieldParser::FIELD_SET;
- }
- else if (fieldState == FieldParser::FIELD_SET) {
- _isNSet = true;
- _n = tempN;
- }
+ if (_isLastOpSet)
+ builder.append(lastOp(), _lastOp);
+ if (_isElectionIdSet)
+ builder.appendOID(electionId(), const_cast<OID*>(&_electionId));
- // We're using appendNumber on generation so we'll try a smaller type
- // (int) first and then fall back to the original type (long long).
- BSONField<int> fieldNModified(nModified());
- int intNModified;
- fieldState = FieldParser::extract(source, fieldNModified, &intNModified, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) {
- // try falling back to a larger type
- fieldState = FieldParser::extract(source, nModified, &_nModified, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isNModifiedSet = fieldState == FieldParser::FIELD_SET;
- }
- else if (fieldState == FieldParser::FIELD_SET) {
- _isNModifiedSet = true;
- _nModified = intNModified;
+ if (_writeErrorDetails.get()) {
+ BSONArrayBuilder errDetailsBuilder(builder.subarrayStart(writeErrors()));
+ for (std::vector<WriteErrorDetail*>::const_iterator it = _writeErrorDetails->begin();
+ it != _writeErrorDetails->end();
+ ++it) {
+ BSONObj errDetailsDocument = (*it)->toBSON();
+ errDetailsBuilder.append(errDetailsDocument);
}
-
- std::vector<BatchedUpsertDetail*>* tempUpsertDetails = NULL;
- fieldState = FieldParser::extract( source, upsertDetails, &tempUpsertDetails, errMsg );
- if ( fieldState == FieldParser::FIELD_INVALID ) return false;
- _upsertDetails.reset(tempUpsertDetails);
-
- fieldState = FieldParser::extract(source, lastOp, &_lastOp, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isLastOpSet = fieldState == FieldParser::FIELD_SET;
-
- fieldState = FieldParser::extract(source, electionId, &_electionId, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isElectionIdSet = fieldState == FieldParser::FIELD_SET;
-
- std::vector<WriteErrorDetail*>* tempErrDetails = NULL;
- fieldState = FieldParser::extract(source, writeErrors, &tempErrDetails, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _writeErrorDetails.reset(tempErrDetails);
-
- WCErrorDetail* wcError = NULL;
- fieldState = FieldParser::extract(source, writeConcernError, &wcError, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _wcErrDetails.reset(wcError);
-
- return true;
+ errDetailsBuilder.done();
}
- void BatchedCommandResponse::clear() {
- _ok = false;
- _isOkSet = false;
-
- _errCode = 0;
- _isErrCodeSet = false;
-
- _errMessage.clear();
- _isErrMessageSet = false;
+ if (_wcErrDetails.get()) {
+ builder.append(writeConcernError(), _wcErrDetails->toBSON());
+ }
- _nModified = 0;
- _isNModifiedSet = false;
+ return builder.obj();
+}
- _n = 0;
- _isNSet = false;
+bool BatchedCommandResponse::parseBSON(const BSONObj& source, string* errMsg) {
+ clear();
- _singleUpserted = BSONObj();
- _isSingleUpsertedSet = false;
+ std::string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
- if ( _upsertDetails.get() ) {
- for ( std::vector<BatchedUpsertDetail*>::const_iterator it = _upsertDetails->begin();
- it != _upsertDetails->end(); ++it ) {
- delete *it;
- };
- _upsertDetails.reset();
- }
+ FieldParser::FieldState fieldState;
+ fieldState = FieldParser::extractNumber(source, ok, &_ok, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isOkSet = fieldState == FieldParser::FIELD_SET;
- _lastOp = Timestamp();
- _isLastOpSet = false;
+ fieldState = FieldParser::extract(source, errCode, &_errCode, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isErrCodeSet = fieldState == FieldParser::FIELD_SET;
- _electionId = OID();
- _isElectionIdSet = false;
+ fieldState = FieldParser::extract(source, errMessage, &_errMessage, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isErrMessageSet = fieldState == FieldParser::FIELD_SET;
- if (_writeErrorDetails.get()) {
- for(std::vector<WriteErrorDetail*>::const_iterator it = _writeErrorDetails->begin();
- it != _writeErrorDetails->end();
- ++it) {
- delete *it;
- };
- _writeErrorDetails.reset();
- }
-
- _wcErrDetails.reset();
+ // We're using appendNumber on generation so we'll try a smaller type
+ // (int) first and then fall back to the original type (long long).
+ BSONField<int> fieldN(n());
+ int tempN;
+ fieldState = FieldParser::extract(source, fieldN, &tempN, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID) {
+ // try falling back to a larger type
+ fieldState = FieldParser::extract(source, n, &_n, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isNSet = fieldState == FieldParser::FIELD_SET;
+ } else if (fieldState == FieldParser::FIELD_SET) {
+ _isNSet = true;
+ _n = tempN;
+ }
+
+ // We're using appendNumber on generation so we'll try a smaller type
+ // (int) first and then fall back to the original type (long long).
+ BSONField<int> fieldNModified(nModified());
+ int intNModified;
+ fieldState = FieldParser::extract(source, fieldNModified, &intNModified, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID) {
+ // try falling back to a larger type
+ fieldState = FieldParser::extract(source, nModified, &_nModified, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isNModifiedSet = fieldState == FieldParser::FIELD_SET;
+ } else if (fieldState == FieldParser::FIELD_SET) {
+ _isNModifiedSet = true;
+ _nModified = intNModified;
}
- void BatchedCommandResponse::cloneTo(BatchedCommandResponse* other) const {
- other->clear();
+ std::vector<BatchedUpsertDetail*>* tempUpsertDetails = NULL;
+ fieldState = FieldParser::extract(source, upsertDetails, &tempUpsertDetails, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _upsertDetails.reset(tempUpsertDetails);
- other->_ok = _ok;
- other->_isOkSet = _isOkSet;
+ fieldState = FieldParser::extract(source, lastOp, &_lastOp, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isLastOpSet = fieldState == FieldParser::FIELD_SET;
- other->_errCode = _errCode;
- other->_isErrCodeSet = _isErrCodeSet;
+ fieldState = FieldParser::extract(source, electionId, &_electionId, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isElectionIdSet = fieldState == FieldParser::FIELD_SET;
- other->_errMessage = _errMessage;
- other->_isErrMessageSet = _isErrMessageSet;
+ std::vector<WriteErrorDetail*>* tempErrDetails = NULL;
+ fieldState = FieldParser::extract(source, writeErrors, &tempErrDetails, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _writeErrorDetails.reset(tempErrDetails);
- other->_nModified = _nModified;
- other->_isNModifiedSet = _isNModifiedSet;
+ WCErrorDetail* wcError = NULL;
+ fieldState = FieldParser::extract(source, writeConcernError, &wcError, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _wcErrDetails.reset(wcError);
- other->_n = _n;
- other->_isNSet = _isNSet;
+ return true;
+}
- other->_singleUpserted = _singleUpserted;
- other->_isSingleUpsertedSet = _isSingleUpsertedSet;
+void BatchedCommandResponse::clear() {
+ _ok = false;
+ _isOkSet = false;
- other->unsetUpsertDetails();
- if (_upsertDetails.get()) {
- for (std::vector<BatchedUpsertDetail*>::const_iterator it = _upsertDetails->begin();
- it != _upsertDetails->end();
- ++it) {
- BatchedUpsertDetail* upsertDetailsItem = new BatchedUpsertDetail;
- (*it)->cloneTo(upsertDetailsItem);
- other->addToUpsertDetails(upsertDetailsItem);
- }
- }
+ _errCode = 0;
+ _isErrCodeSet = false;
- other->_lastOp = _lastOp;
- other->_isLastOpSet = _isLastOpSet;
-
- other->_electionId = _electionId;
- other->_isElectionIdSet = _isElectionIdSet;
-
- other->unsetErrDetails();
- if (_writeErrorDetails.get()) {
- for(std::vector<WriteErrorDetail*>::const_iterator it = _writeErrorDetails->begin();
- it != _writeErrorDetails->end();
- ++it) {
- WriteErrorDetail* errDetailsItem = new WriteErrorDetail;
- (*it)->cloneTo(errDetailsItem);
- other->addToErrDetails(errDetailsItem);
- }
- }
+ _errMessage.clear();
+ _isErrMessageSet = false;
- if (_wcErrDetails.get()) {
- other->_wcErrDetails.reset(new WCErrorDetail());
- _wcErrDetails->cloneTo(other->_wcErrDetails.get());
- }
- }
-
- std::string BatchedCommandResponse::toString() const {
- return toBSON().toString();
- }
+ _nModified = 0;
+ _isNModifiedSet = false;
- void BatchedCommandResponse::setOk(int ok) {
- _ok = ok;
- _isOkSet = true;
- }
+ _n = 0;
+ _isNSet = false;
- void BatchedCommandResponse::unsetOk() {
- _isOkSet = false;
- }
+ _singleUpserted = BSONObj();
+ _isSingleUpsertedSet = false;
- bool BatchedCommandResponse::isOkSet() const {
- return _isOkSet;
- }
-
- int BatchedCommandResponse::getOk() const {
- dassert(_isOkSet);
- return _ok;
+ if (_upsertDetails.get()) {
+ for (std::vector<BatchedUpsertDetail*>::const_iterator it = _upsertDetails->begin();
+ it != _upsertDetails->end();
+ ++it) {
+ delete *it;
+ };
+ _upsertDetails.reset();
}
- void BatchedCommandResponse::setErrCode(int errCode) {
- _errCode = errCode;
- _isErrCodeSet = true;
- }
+ _lastOp = Timestamp();
+ _isLastOpSet = false;
- void BatchedCommandResponse::unsetErrCode() {
- _isErrCodeSet = false;
- }
+ _electionId = OID();
+ _isElectionIdSet = false;
- bool BatchedCommandResponse::isErrCodeSet() const {
- return _isErrCodeSet;
+ if (_writeErrorDetails.get()) {
+ for (std::vector<WriteErrorDetail*>::const_iterator it = _writeErrorDetails->begin();
+ it != _writeErrorDetails->end();
+ ++it) {
+ delete *it;
+ };
+ _writeErrorDetails.reset();
}
- int BatchedCommandResponse::getErrCode() const {
- if ( _isErrCodeSet ) {
- return _errCode;
- }
- else {
- return errCode.getDefault();
- }
- }
+ _wcErrDetails.reset();
+}
- void BatchedCommandResponse::setErrMessage(StringData errMessage) {
- _errMessage = errMessage.toString();
- _isErrMessageSet = true;
- }
+void BatchedCommandResponse::cloneTo(BatchedCommandResponse* other) const {
+ other->clear();
- void BatchedCommandResponse::unsetErrMessage() {
- _isErrMessageSet = false;
- }
+ other->_ok = _ok;
+ other->_isOkSet = _isOkSet;
- bool BatchedCommandResponse::isErrMessageSet() const {
- return _isErrMessageSet;
- }
+ other->_errCode = _errCode;
+ other->_isErrCodeSet = _isErrCodeSet;
- const std::string& BatchedCommandResponse::getErrMessage() const {
- dassert(_isErrMessageSet);
- return _errMessage;
- }
+ other->_errMessage = _errMessage;
+ other->_isErrMessageSet = _isErrMessageSet;
- void BatchedCommandResponse::setNModified(long long n) {
- _nModified = n;
- _isNModifiedSet = true;
- }
+ other->_nModified = _nModified;
+ other->_isNModifiedSet = _isNModifiedSet;
- void BatchedCommandResponse::unsetNModified() {
- _isNModifiedSet = false;
- }
+ other->_n = _n;
+ other->_isNSet = _isNSet;
- bool BatchedCommandResponse::isNModified() const {
- return _isNModifiedSet;
- }
+ other->_singleUpserted = _singleUpserted;
+ other->_isSingleUpsertedSet = _isSingleUpsertedSet;
- long long BatchedCommandResponse::getNModified() const {
- if ( _isNModifiedSet ) {
- return _nModified;
- }
- else {
- return nModified.getDefault();
+ other->unsetUpsertDetails();
+ if (_upsertDetails.get()) {
+ for (std::vector<BatchedUpsertDetail*>::const_iterator it = _upsertDetails->begin();
+ it != _upsertDetails->end();
+ ++it) {
+ BatchedUpsertDetail* upsertDetailsItem = new BatchedUpsertDetail;
+ (*it)->cloneTo(upsertDetailsItem);
+ other->addToUpsertDetails(upsertDetailsItem);
}
}
- void BatchedCommandResponse::setN(long long n) {
- _n = n;
- _isNSet = true;
- }
-
- void BatchedCommandResponse::unsetN() {
- _isNSet = false;
- }
+ other->_lastOp = _lastOp;
+ other->_isLastOpSet = _isLastOpSet;
- bool BatchedCommandResponse::isNSet() const {
- return _isNSet;
- }
+ other->_electionId = _electionId;
+ other->_isElectionIdSet = _isElectionIdSet;
- long long BatchedCommandResponse::getN() const {
- if ( _isNSet ) {
- return _n;
- }
- else {
- return n.getDefault();
- }
- }
-
- void BatchedCommandResponse::setUpsertDetails(
- const std::vector<BatchedUpsertDetail*>& upsertDetails) {
- unsetUpsertDetails();
- for (std::vector<BatchedUpsertDetail*>::const_iterator it = upsertDetails.begin();
- it != upsertDetails.end();
+ other->unsetErrDetails();
+ if (_writeErrorDetails.get()) {
+ for (std::vector<WriteErrorDetail*>::const_iterator it = _writeErrorDetails->begin();
+ it != _writeErrorDetails->end();
++it) {
- unique_ptr<BatchedUpsertDetail> tempBatchedUpsertDetail(new BatchedUpsertDetail);
- (*it)->cloneTo(tempBatchedUpsertDetail.get());
- addToUpsertDetails(tempBatchedUpsertDetail.release());
+ WriteErrorDetail* errDetailsItem = new WriteErrorDetail;
+ (*it)->cloneTo(errDetailsItem);
+ other->addToErrDetails(errDetailsItem);
}
}
- void BatchedCommandResponse::addToUpsertDetails(BatchedUpsertDetail* upsertDetails) {
- if (_upsertDetails.get() == NULL) {
- _upsertDetails.reset(new std::vector<BatchedUpsertDetail*>);
- }
- _upsertDetails->push_back(upsertDetails);
- }
-
- void BatchedCommandResponse::unsetUpsertDetails() {
- if (_upsertDetails.get() != NULL) {
- for (std::vector<BatchedUpsertDetail*>::iterator it = _upsertDetails->begin();
- it != _upsertDetails->end();
- ++it) {
- delete *it;
- }
- _upsertDetails.reset();
- }
- }
-
- bool BatchedCommandResponse::isUpsertDetailsSet() const {
- return _upsertDetails.get() != NULL;
- }
-
- size_t BatchedCommandResponse::sizeUpsertDetails() const {
- dassert(_upsertDetails.get());
- return _upsertDetails->size();
- }
-
- const std::vector<BatchedUpsertDetail*>& BatchedCommandResponse::getUpsertDetails() const {
- dassert(_upsertDetails.get());
- return *_upsertDetails;
- }
-
- const BatchedUpsertDetail* BatchedCommandResponse::getUpsertDetailsAt(size_t pos) const {
- dassert(_upsertDetails.get());
- dassert(_upsertDetails->size() > pos);
- return _upsertDetails->at(pos);
- }
-
- void BatchedCommandResponse::setLastOp(Timestamp lastOp) {
- _lastOp = lastOp;
- _isLastOpSet = true;
+ if (_wcErrDetails.get()) {
+ other->_wcErrDetails.reset(new WCErrorDetail());
+ _wcErrDetails->cloneTo(other->_wcErrDetails.get());
}
+}
+
+std::string BatchedCommandResponse::toString() const {
+ return toBSON().toString();
+}
- void BatchedCommandResponse::unsetLastOp() {
- _isLastOpSet = false;
- }
+void BatchedCommandResponse::setOk(int ok) {
+ _ok = ok;
+ _isOkSet = true;
+}
+
+void BatchedCommandResponse::unsetOk() {
+ _isOkSet = false;
+}
+
+bool BatchedCommandResponse::isOkSet() const {
+ return _isOkSet;
+}
+
+int BatchedCommandResponse::getOk() const {
+ dassert(_isOkSet);
+ return _ok;
+}
- bool BatchedCommandResponse::isLastOpSet() const {
- return _isLastOpSet;
- }
-
- Timestamp BatchedCommandResponse::getLastOp() const {
- dassert(_isLastOpSet);
- return _lastOp;
- }
-
- void BatchedCommandResponse::setElectionId(const OID& electionId) {
- _electionId = electionId;
- _isElectionIdSet = true;
- }
-
- void BatchedCommandResponse::unsetElectionId() {
- _isElectionIdSet = false;
- }
-
- bool BatchedCommandResponse::isElectionIdSet() const {
- return _isElectionIdSet;
- }
-
- OID BatchedCommandResponse::getElectionId() const {
- dassert(_isElectionIdSet);
- return _electionId;
- }
-
- void BatchedCommandResponse::setErrDetails(const std::vector<WriteErrorDetail*>& errDetails) {
- unsetErrDetails();
- for (std::vector<WriteErrorDetail*>::const_iterator it = errDetails.begin();
- it != errDetails.end();
+void BatchedCommandResponse::setErrCode(int errCode) {
+ _errCode = errCode;
+ _isErrCodeSet = true;
+}
+
+void BatchedCommandResponse::unsetErrCode() {
+ _isErrCodeSet = false;
+}
+
+bool BatchedCommandResponse::isErrCodeSet() const {
+ return _isErrCodeSet;
+}
+
+int BatchedCommandResponse::getErrCode() const {
+ if (_isErrCodeSet) {
+ return _errCode;
+ } else {
+ return errCode.getDefault();
+ }
+}
+
+void BatchedCommandResponse::setErrMessage(StringData errMessage) {
+ _errMessage = errMessage.toString();
+ _isErrMessageSet = true;
+}
+
+void BatchedCommandResponse::unsetErrMessage() {
+ _isErrMessageSet = false;
+}
+
+bool BatchedCommandResponse::isErrMessageSet() const {
+ return _isErrMessageSet;
+}
+
+const std::string& BatchedCommandResponse::getErrMessage() const {
+ dassert(_isErrMessageSet);
+ return _errMessage;
+}
+
+void BatchedCommandResponse::setNModified(long long n) {
+ _nModified = n;
+ _isNModifiedSet = true;
+}
+
+void BatchedCommandResponse::unsetNModified() {
+ _isNModifiedSet = false;
+}
+
+bool BatchedCommandResponse::isNModified() const {
+ return _isNModifiedSet;
+}
+
+long long BatchedCommandResponse::getNModified() const {
+ if (_isNModifiedSet) {
+ return _nModified;
+ } else {
+ return nModified.getDefault();
+ }
+}
+
+void BatchedCommandResponse::setN(long long n) {
+ _n = n;
+ _isNSet = true;
+}
+
+void BatchedCommandResponse::unsetN() {
+ _isNSet = false;
+}
+
+bool BatchedCommandResponse::isNSet() const {
+ return _isNSet;
+}
+
+long long BatchedCommandResponse::getN() const {
+ if (_isNSet) {
+ return _n;
+ } else {
+ return n.getDefault();
+ }
+}
+
+void BatchedCommandResponse::setUpsertDetails(
+ const std::vector<BatchedUpsertDetail*>& upsertDetails) {
+ unsetUpsertDetails();
+ for (std::vector<BatchedUpsertDetail*>::const_iterator it = upsertDetails.begin();
+ it != upsertDetails.end();
+ ++it) {
+ unique_ptr<BatchedUpsertDetail> tempBatchedUpsertDetail(new BatchedUpsertDetail);
+ (*it)->cloneTo(tempBatchedUpsertDetail.get());
+ addToUpsertDetails(tempBatchedUpsertDetail.release());
+ }
+}
+
+void BatchedCommandResponse::addToUpsertDetails(BatchedUpsertDetail* upsertDetails) {
+ if (_upsertDetails.get() == NULL) {
+ _upsertDetails.reset(new std::vector<BatchedUpsertDetail*>);
+ }
+ _upsertDetails->push_back(upsertDetails);
+}
+
+void BatchedCommandResponse::unsetUpsertDetails() {
+ if (_upsertDetails.get() != NULL) {
+ for (std::vector<BatchedUpsertDetail*>::iterator it = _upsertDetails->begin();
+ it != _upsertDetails->end();
++it) {
- unique_ptr<WriteErrorDetail> tempBatchErrorDetail(new WriteErrorDetail);
- (*it)->cloneTo(tempBatchErrorDetail.get());
- addToErrDetails(tempBatchErrorDetail.release());
+ delete *it;
}
- }
-
- void BatchedCommandResponse::addToErrDetails(WriteErrorDetail* errDetails) {
- if (_writeErrorDetails.get() == NULL) {
- _writeErrorDetails.reset(new std::vector<WriteErrorDetail*>);
- }
- _writeErrorDetails->push_back(errDetails);
- }
-
- void BatchedCommandResponse::unsetErrDetails() {
- if (_writeErrorDetails.get() != NULL) {
- for(std::vector<WriteErrorDetail*>::iterator it = _writeErrorDetails->begin();
- it != _writeErrorDetails->end();
- ++it) {
- delete *it;
- }
- _writeErrorDetails.reset();
+ _upsertDetails.reset();
+ }
+}
+
+bool BatchedCommandResponse::isUpsertDetailsSet() const {
+ return _upsertDetails.get() != NULL;
+}
+
+size_t BatchedCommandResponse::sizeUpsertDetails() const {
+ dassert(_upsertDetails.get());
+ return _upsertDetails->size();
+}
+
+const std::vector<BatchedUpsertDetail*>& BatchedCommandResponse::getUpsertDetails() const {
+ dassert(_upsertDetails.get());
+ return *_upsertDetails;
+}
+
+const BatchedUpsertDetail* BatchedCommandResponse::getUpsertDetailsAt(size_t pos) const {
+ dassert(_upsertDetails.get());
+ dassert(_upsertDetails->size() > pos);
+ return _upsertDetails->at(pos);
+}
+
+void BatchedCommandResponse::setLastOp(Timestamp lastOp) {
+ _lastOp = lastOp;
+ _isLastOpSet = true;
+}
+
+void BatchedCommandResponse::unsetLastOp() {
+ _isLastOpSet = false;
+}
+
+bool BatchedCommandResponse::isLastOpSet() const {
+ return _isLastOpSet;
+}
+
+Timestamp BatchedCommandResponse::getLastOp() const {
+ dassert(_isLastOpSet);
+ return _lastOp;
+}
+
+void BatchedCommandResponse::setElectionId(const OID& electionId) {
+ _electionId = electionId;
+ _isElectionIdSet = true;
+}
+
+void BatchedCommandResponse::unsetElectionId() {
+ _isElectionIdSet = false;
+}
+
+bool BatchedCommandResponse::isElectionIdSet() const {
+ return _isElectionIdSet;
+}
+
+OID BatchedCommandResponse::getElectionId() const {
+ dassert(_isElectionIdSet);
+ return _electionId;
+}
+
+void BatchedCommandResponse::setErrDetails(const std::vector<WriteErrorDetail*>& errDetails) {
+ unsetErrDetails();
+ for (std::vector<WriteErrorDetail*>::const_iterator it = errDetails.begin();
+ it != errDetails.end();
+ ++it) {
+ unique_ptr<WriteErrorDetail> tempBatchErrorDetail(new WriteErrorDetail);
+ (*it)->cloneTo(tempBatchErrorDetail.get());
+ addToErrDetails(tempBatchErrorDetail.release());
+ }
+}
+
+void BatchedCommandResponse::addToErrDetails(WriteErrorDetail* errDetails) {
+ if (_writeErrorDetails.get() == NULL) {
+ _writeErrorDetails.reset(new std::vector<WriteErrorDetail*>);
+ }
+ _writeErrorDetails->push_back(errDetails);
+}
+
+void BatchedCommandResponse::unsetErrDetails() {
+ if (_writeErrorDetails.get() != NULL) {
+ for (std::vector<WriteErrorDetail*>::iterator it = _writeErrorDetails->begin();
+ it != _writeErrorDetails->end();
+ ++it) {
+ delete *it;
}
+ _writeErrorDetails.reset();
}
+}
- bool BatchedCommandResponse::isErrDetailsSet() const {
- return _writeErrorDetails.get() != NULL;
- }
+bool BatchedCommandResponse::isErrDetailsSet() const {
+ return _writeErrorDetails.get() != NULL;
+}
- size_t BatchedCommandResponse::sizeErrDetails() const {
- dassert(_writeErrorDetails.get());
- return _writeErrorDetails->size();
- }
+size_t BatchedCommandResponse::sizeErrDetails() const {
+ dassert(_writeErrorDetails.get());
+ return _writeErrorDetails->size();
+}
- const std::vector<WriteErrorDetail*>& BatchedCommandResponse::getErrDetails() const {
- dassert(_writeErrorDetails.get());
- return *_writeErrorDetails;
- }
+const std::vector<WriteErrorDetail*>& BatchedCommandResponse::getErrDetails() const {
+ dassert(_writeErrorDetails.get());
+ return *_writeErrorDetails;
+}
- const WriteErrorDetail* BatchedCommandResponse::getErrDetailsAt(size_t pos) const {
- dassert(_writeErrorDetails.get());
- dassert(_writeErrorDetails->size() > pos);
- return _writeErrorDetails->at(pos);
- }
+const WriteErrorDetail* BatchedCommandResponse::getErrDetailsAt(size_t pos) const {
+ dassert(_writeErrorDetails.get());
+ dassert(_writeErrorDetails->size() > pos);
+ return _writeErrorDetails->at(pos);
+}
- void BatchedCommandResponse::setWriteConcernError(WCErrorDetail* error) {
- _wcErrDetails.reset(error);
- }
+void BatchedCommandResponse::setWriteConcernError(WCErrorDetail* error) {
+ _wcErrDetails.reset(error);
+}
- void BatchedCommandResponse::unsetWriteConcernError() {
- _wcErrDetails.reset();
- }
+void BatchedCommandResponse::unsetWriteConcernError() {
+ _wcErrDetails.reset();
+}
- bool BatchedCommandResponse::isWriteConcernErrorSet() const {
- return _wcErrDetails.get();
- }
+bool BatchedCommandResponse::isWriteConcernErrorSet() const {
+ return _wcErrDetails.get();
+}
- const WCErrorDetail* BatchedCommandResponse::getWriteConcernError() const {
- return _wcErrDetails.get();
- }
+const WCErrorDetail* BatchedCommandResponse::getWriteConcernError() const {
+ return _wcErrDetails.get();
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/batched_command_response.h b/src/mongo/s/write_ops/batched_command_response.h
index 68cee2abaa6..370f836b921 100644
--- a/src/mongo/s/write_ops/batched_command_response.h
+++ b/src/mongo/s/write_ops/batched_command_response.h
@@ -40,161 +40,161 @@
namespace mongo {
- /**
- * This class represents the layout and content of a insert/update/delete runCommand,
- * the response side.
- */
- class BatchedCommandResponse : public BSONSerializable {
- MONGO_DISALLOW_COPYING(BatchedCommandResponse);
- public:
-
- //
- // schema declarations
- //
-
- static const BSONField<int> ok;
- static const BSONField<int> errCode;
- static const BSONField<std::string> errMessage;
- static const BSONField<long long> n;
- static const BSONField<long long> nModified;
- static const BSONField<std::vector<BatchedUpsertDetail*> > upsertDetails;
- static const BSONField<Timestamp> lastOp;
- static const BSONField<OID> electionId;
- static const BSONField<std::vector<WriteErrorDetail*> > writeErrors;
- static const BSONField<WCErrorDetail*> writeConcernError;
-
- //
- // construction / destruction
- //
-
- BatchedCommandResponse();
- virtual ~BatchedCommandResponse();
-
- /** Copies all the fields present in 'this' to 'other'. */
- void cloneTo(BatchedCommandResponse* other) const;
-
- //
- // bson serializable interface implementation
- //
-
- virtual bool isValid(std::string* errMsg) const;
- virtual BSONObj toBSON() const;
- virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
- virtual void clear();
- virtual std::string toString() const;
-
- //
- // individual field accessors
- //
-
- void setOk(int ok);
- void unsetOk();
- bool isOkSet() const;
- int getOk() const;
-
- void setErrCode(int errCode);
- void unsetErrCode();
- bool isErrCodeSet() const;
- int getErrCode() const;
-
- void setErrMessage(StringData errMessage);
- void unsetErrMessage();
- bool isErrMessageSet() const;
- const std::string& getErrMessage() const;
-
- void setNModified(long long n);
- void unsetNModified();
- bool isNModified() const;
- long long getNModified() const;
-
- void setN(long long n);
- void unsetN();
- bool isNSet() const;
- long long getN() const;
-
- void setUpsertDetails(const std::vector<BatchedUpsertDetail*>& upsertDetails);
- void addToUpsertDetails(BatchedUpsertDetail* upsertDetails);
- void unsetUpsertDetails();
- bool isUpsertDetailsSet() const;
- std::size_t sizeUpsertDetails() const;
- const std::vector<BatchedUpsertDetail*>& getUpsertDetails() const;
- const BatchedUpsertDetail* getUpsertDetailsAt(std::size_t pos) const;
-
- void setLastOp(Timestamp lastOp);
- void unsetLastOp();
- bool isLastOpSet() const;
- Timestamp getLastOp() const;
-
- void setElectionId(const OID& electionId);
- void unsetElectionId();
- bool isElectionIdSet() const;
- OID getElectionId() const;
-
- void setErrDetails(const std::vector<WriteErrorDetail*>& errDetails);
- // errDetails ownership is transferred to here.
- void addToErrDetails(WriteErrorDetail* errDetails);
- void unsetErrDetails();
- bool isErrDetailsSet() const;
- std::size_t sizeErrDetails() const;
- const std::vector<WriteErrorDetail*>& getErrDetails() const;
- const WriteErrorDetail* getErrDetailsAt(std::size_t pos) const;
-
- void setWriteConcernError(WCErrorDetail* error);
- void unsetWriteConcernError();
- bool isWriteConcernErrorSet() const;
- const WCErrorDetail* getWriteConcernError() const;
-
- private:
- // Convention: (M)andatory, (O)ptional
-
- // (M) 0 if batch didn't get to be applied for any reason
- int _ok;
- bool _isOkSet;
-
- // (O) whether all items in the batch applied correctly
- int _errCode;
- bool _isErrCodeSet;
-
- // (O) whether all items in the batch applied correctly
- std::string _errMessage;
- bool _isErrMessageSet;
-
- // (M) number of documents affected
- long long _n;
- bool _isNSet;
-
- // (O) number of documents updated
- long long _nModified;
- bool _isNModifiedSet;
-
- // (O) "promoted" _upserted, if the corresponding request contained only one batch item
- // Should only be present if _upserted is not.
- BSONObj _singleUpserted;
- bool _isSingleUpsertedSet;
-
- // (O) Array of upserted items' _id's
- // Should only be present if _singleUpserted is not.
- std::unique_ptr<std::vector<BatchedUpsertDetail*> >_upsertDetails;
-
- // (O) Timestamp assigned to the write op when it was written to the oplog.
- // Normally, getLastError can use Client::_lastOp, but this is not valid for
- // mongos which loses track of the session due to RCAR. Therefore, we must
- // keep track of the lastOp manually ourselves.
- Timestamp _lastOp;
- bool _isLastOpSet;
-
- // (O) In addition to keeping track of the above lastOp timestamp, we must also keep
- // track of the primary we talked to. This is because if the primary moves,
- // subsequent calls to getLastError are invalid. The only way we know if an
- // election has occurred is to use the unique electionId.
- OID _electionId;
- bool _isElectionIdSet;
-
- // (O) Array of item-level error information
- std::unique_ptr<std::vector<WriteErrorDetail*> >_writeErrorDetails;
-
- // (O) errors that occurred while trying to satisfy the write concern.
- std::unique_ptr<WCErrorDetail> _wcErrDetails;
- };
-
-} // namespace mongo
+/**
+ * This class represents the layout and content of a insert/update/delete runCommand,
+ * the response side.
+ */
+class BatchedCommandResponse : public BSONSerializable {
+ MONGO_DISALLOW_COPYING(BatchedCommandResponse);
+
+public:
+ //
+ // schema declarations
+ //
+
+ static const BSONField<int> ok;
+ static const BSONField<int> errCode;
+ static const BSONField<std::string> errMessage;
+ static const BSONField<long long> n;
+ static const BSONField<long long> nModified;
+ static const BSONField<std::vector<BatchedUpsertDetail*>> upsertDetails;
+ static const BSONField<Timestamp> lastOp;
+ static const BSONField<OID> electionId;
+ static const BSONField<std::vector<WriteErrorDetail*>> writeErrors;
+ static const BSONField<WCErrorDetail*> writeConcernError;
+
+ //
+ // construction / destruction
+ //
+
+ BatchedCommandResponse();
+ virtual ~BatchedCommandResponse();
+
+ /** Copies all the fields present in 'this' to 'other'. */
+ void cloneTo(BatchedCommandResponse* other) const;
+
+ //
+ // bson serializable interface implementation
+ //
+
+ virtual bool isValid(std::string* errMsg) const;
+ virtual BSONObj toBSON() const;
+ virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
+ virtual void clear();
+ virtual std::string toString() const;
+
+ //
+ // individual field accessors
+ //
+
+ void setOk(int ok);
+ void unsetOk();
+ bool isOkSet() const;
+ int getOk() const;
+
+ void setErrCode(int errCode);
+ void unsetErrCode();
+ bool isErrCodeSet() const;
+ int getErrCode() const;
+
+ void setErrMessage(StringData errMessage);
+ void unsetErrMessage();
+ bool isErrMessageSet() const;
+ const std::string& getErrMessage() const;
+
+ void setNModified(long long n);
+ void unsetNModified();
+ bool isNModified() const;
+ long long getNModified() const;
+
+ void setN(long long n);
+ void unsetN();
+ bool isNSet() const;
+ long long getN() const;
+
+ void setUpsertDetails(const std::vector<BatchedUpsertDetail*>& upsertDetails);
+ void addToUpsertDetails(BatchedUpsertDetail* upsertDetails);
+ void unsetUpsertDetails();
+ bool isUpsertDetailsSet() const;
+ std::size_t sizeUpsertDetails() const;
+ const std::vector<BatchedUpsertDetail*>& getUpsertDetails() const;
+ const BatchedUpsertDetail* getUpsertDetailsAt(std::size_t pos) const;
+
+ void setLastOp(Timestamp lastOp);
+ void unsetLastOp();
+ bool isLastOpSet() const;
+ Timestamp getLastOp() const;
+
+ void setElectionId(const OID& electionId);
+ void unsetElectionId();
+ bool isElectionIdSet() const;
+ OID getElectionId() const;
+
+ void setErrDetails(const std::vector<WriteErrorDetail*>& errDetails);
+ // errDetails ownership is transferred to here.
+ void addToErrDetails(WriteErrorDetail* errDetails);
+ void unsetErrDetails();
+ bool isErrDetailsSet() const;
+ std::size_t sizeErrDetails() const;
+ const std::vector<WriteErrorDetail*>& getErrDetails() const;
+ const WriteErrorDetail* getErrDetailsAt(std::size_t pos) const;
+
+ void setWriteConcernError(WCErrorDetail* error);
+ void unsetWriteConcernError();
+ bool isWriteConcernErrorSet() const;
+ const WCErrorDetail* getWriteConcernError() const;
+
+private:
+ // Convention: (M)andatory, (O)ptional
+
+ // (M) 0 if batch didn't get to be applied for any reason
+ int _ok;
+ bool _isOkSet;
+
+ // (O) whether all items in the batch applied correctly
+ int _errCode;
+ bool _isErrCodeSet;
+
+ // (O) whether all items in the batch applied correctly
+ std::string _errMessage;
+ bool _isErrMessageSet;
+
+ // (M) number of documents affected
+ long long _n;
+ bool _isNSet;
+
+ // (O) number of documents updated
+ long long _nModified;
+ bool _isNModifiedSet;
+
+ // (O) "promoted" _upserted, if the corresponding request contained only one batch item
+ // Should only be present if _upserted is not.
+ BSONObj _singleUpserted;
+ bool _isSingleUpsertedSet;
+
+ // (O) Array of upserted items' _id's
+ // Should only be present if _singleUpserted is not.
+ std::unique_ptr<std::vector<BatchedUpsertDetail*>> _upsertDetails;
+
+ // (O) Timestamp assigned to the write op when it was written to the oplog.
+ // Normally, getLastError can use Client::_lastOp, but this is not valid for
+ // mongos which loses track of the session due to RCAR. Therefore, we must
+ // keep track of the lastOp manually ourselves.
+ Timestamp _lastOp;
+ bool _isLastOpSet;
+
+ // (O) In addition to keeping track of the above lastOp timestamp, we must also keep
+ // track of the primary we talked to. This is because if the primary moves,
+ // subsequent calls to getLastError are invalid. The only way we know if an
+ // election has occurred is to use the unique electionId.
+ OID _electionId;
+ bool _isElectionIdSet;
+
+ // (O) Array of item-level error information
+ std::unique_ptr<std::vector<WriteErrorDetail*>> _writeErrorDetails;
+
+ // (O) errors that occurred while trying to satisfy the write concern.
+ std::unique_ptr<WCErrorDetail> _wcErrDetails;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/batched_command_response_test.cpp b/src/mongo/s/write_ops/batched_command_response_test.cpp
index e97b567e761..ba53f201128 100644
--- a/src/mongo/s/write_ops/batched_command_response_test.cpp
+++ b/src/mongo/s/write_ops/batched_command_response_test.cpp
@@ -37,51 +37,42 @@
namespace {
- using mongo::BSONArray;
- using mongo::BSONObj;
- using mongo::BatchedCommandResponse;
- using mongo::WriteErrorDetail;
- using mongo::WCErrorDetail;
- using mongo::Date_t;
- using std::string;
+using mongo::BSONArray;
+using mongo::BSONObj;
+using mongo::BatchedCommandResponse;
+using mongo::WriteErrorDetail;
+using mongo::WCErrorDetail;
+using mongo::Date_t;
+using std::string;
- TEST(RoundTrip, Normal) {
+TEST(RoundTrip, Normal) {
+ BSONArray writeErrorsArray = BSON_ARRAY(
+ BSON(WriteErrorDetail::index(0) << WriteErrorDetail::errCode(-2)
+ << WriteErrorDetail::errInfo(BSON("more info" << 1))
+ << WriteErrorDetail::errMessage("index 0 failed"))
+ << BSON(WriteErrorDetail::index(1) << WriteErrorDetail::errCode(-3)
+ << WriteErrorDetail::errInfo(BSON("more info" << 1))
+ << WriteErrorDetail::errMessage("index 1 failed too")));
- BSONArray writeErrorsArray =
- BSON_ARRAY(
- BSON(WriteErrorDetail::index(0) <<
- WriteErrorDetail::errCode(-2) <<
- WriteErrorDetail::errInfo(BSON("more info" << 1)) <<
- WriteErrorDetail::errMessage("index 0 failed")
- ) <<
- BSON(WriteErrorDetail::index(1) <<
- WriteErrorDetail::errCode(-3) <<
- WriteErrorDetail::errInfo(BSON("more info" << 1)) <<
- WriteErrorDetail::errMessage("index 1 failed too")
- )
- );
+ BSONObj writeConcernError(BSON(WCErrorDetail::errCode(8)
+ << WCErrorDetail::errInfo(BSON("a" << 1))
+ << WCErrorDetail::errMessage("norepl")));
- BSONObj writeConcernError(
- BSON(WCErrorDetail::errCode(8) <<
- WCErrorDetail::errInfo(BSON("a" << 1)) <<
- WCErrorDetail::errMessage("norepl")));
+ BSONObj origResponseObj = BSON(
+ BatchedCommandResponse::ok(false)
+ << BatchedCommandResponse::errCode(-1)
+ << BatchedCommandResponse::errMessage("this batch didn't work")
+ << BatchedCommandResponse::n(0) << BatchedCommandResponse::lastOp(mongo::Timestamp(1ULL))
+ << BatchedCommandResponse::writeErrors() << writeErrorsArray
+ << BatchedCommandResponse::writeConcernError() << writeConcernError);
- BSONObj origResponseObj =
- BSON(BatchedCommandResponse::ok(false) <<
- BatchedCommandResponse::errCode(-1) <<
- BatchedCommandResponse::errMessage("this batch didn't work") <<
- BatchedCommandResponse::n(0) <<
- BatchedCommandResponse::lastOp(mongo::Timestamp(1ULL)) <<
- BatchedCommandResponse::writeErrors() << writeErrorsArray <<
- BatchedCommandResponse::writeConcernError() << writeConcernError);
+ string errMsg;
+ BatchedCommandResponse response;
+ bool ok = response.parseBSON(origResponseObj, &errMsg);
+ ASSERT_TRUE(ok);
- string errMsg;
- BatchedCommandResponse response;
- bool ok = response.parseBSON(origResponseObj, &errMsg);
- ASSERT_TRUE(ok);
+ BSONObj genResponseObj = response.toBSON();
+ ASSERT_EQUALS(0, genResponseObj.woCompare(origResponseObj));
+}
- BSONObj genResponseObj = response.toBSON();
- ASSERT_EQUALS(0, genResponseObj.woCompare(origResponseObj));
- }
-
-} // unnamed namespace
+} // unnamed namespace
diff --git a/src/mongo/s/write_ops/batched_delete_document.cpp b/src/mongo/s/write_ops/batched_delete_document.cpp
index e12e5ad7a86..2cca2deea4d 100644
--- a/src/mongo/s/write_ops/batched_delete_document.cpp
+++ b/src/mongo/s/write_ops/batched_delete_document.cpp
@@ -33,130 +33,133 @@
namespace mongo {
- using std::string;
+using std::string;
- using mongoutils::str::stream;
- const BSONField<BSONObj> BatchedDeleteDocument::query("q");
- const BSONField<int> BatchedDeleteDocument::limit("limit");
+using mongoutils::str::stream;
+const BSONField<BSONObj> BatchedDeleteDocument::query("q");
+const BSONField<int> BatchedDeleteDocument::limit("limit");
- BatchedDeleteDocument::BatchedDeleteDocument() {
- clear();
+BatchedDeleteDocument::BatchedDeleteDocument() {
+ clear();
+}
+
+BatchedDeleteDocument::~BatchedDeleteDocument() {}
+
+bool BatchedDeleteDocument::isValid(std::string* errMsg) const {
+ std::string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
}
- BatchedDeleteDocument::~BatchedDeleteDocument() {
+ // All the mandatory fields must be present.
+ if (!_isQuerySet) {
+ *errMsg = stream() << "missing " << query.name() << " field";
+ return false;
}
- bool BatchedDeleteDocument::isValid(std::string* errMsg) const {
- std::string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
-
- // All the mandatory fields must be present.
- if (!_isQuerySet) {
- *errMsg = stream() << "missing " << query.name() << " field";
- return false;
- }
-
- if (!_isLimitSet) {
- *errMsg = stream() << "missing " << limit.name() << " field";
- return false;
- }
-
- if (_limit != 0 && _limit != 1) {
- *errMsg = stream() << "specify either a 0 to delete all"
- << "matching documents or 1 to delete a single document";
- return false;
- }
-
- return true;
+ if (!_isLimitSet) {
+ *errMsg = stream() << "missing " << limit.name() << " field";
+ return false;
}
- BSONObj BatchedDeleteDocument::toBSON() const {
- BSONObjBuilder builder;
+ if (_limit != 0 && _limit != 1) {
+ *errMsg = stream() << "specify either a 0 to delete all"
+ << "matching documents or 1 to delete a single document";
+ return false;
+ }
- if (_isQuerySet) builder.append(query(), _query);
+ return true;
+}
- if (_isLimitSet) builder.append(limit(), _limit);
+BSONObj BatchedDeleteDocument::toBSON() const {
+ BSONObjBuilder builder;
- return builder.obj();
- }
+ if (_isQuerySet)
+ builder.append(query(), _query);
- bool BatchedDeleteDocument::parseBSON(const BSONObj& source, string* errMsg) {
- clear();
+ if (_isLimitSet)
+ builder.append(limit(), _limit);
- std::string dummy;
- if (!errMsg) errMsg = &dummy;
+ return builder.obj();
+}
- FieldParser::FieldState fieldState;
- fieldState = FieldParser::extract(source, query, &_query, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isQuerySet = fieldState == FieldParser::FIELD_SET;
+bool BatchedDeleteDocument::parseBSON(const BSONObj& source, string* errMsg) {
+ clear();
- fieldState = FieldParser::extractNumber(source, limit, &_limit, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isLimitSet = fieldState == FieldParser::FIELD_SET;
+ std::string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
- return true;
- }
+ FieldParser::FieldState fieldState;
+ fieldState = FieldParser::extract(source, query, &_query, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isQuerySet = fieldState == FieldParser::FIELD_SET;
- void BatchedDeleteDocument::clear() {
- _query = BSONObj();
- _isQuerySet = false;
+ fieldState = FieldParser::extractNumber(source, limit, &_limit, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isLimitSet = fieldState == FieldParser::FIELD_SET;
- _limit = 0;
- _isLimitSet = false;
+ return true;
+}
- }
+void BatchedDeleteDocument::clear() {
+ _query = BSONObj();
+ _isQuerySet = false;
- void BatchedDeleteDocument::cloneTo(BatchedDeleteDocument* other) const {
- other->clear();
+ _limit = 0;
+ _isLimitSet = false;
+}
- other->_query = _query;
- other->_isQuerySet = _isQuerySet;
+void BatchedDeleteDocument::cloneTo(BatchedDeleteDocument* other) const {
+ other->clear();
- other->_limit = _limit;
- other->_isLimitSet = _isLimitSet;
- }
+ other->_query = _query;
+ other->_isQuerySet = _isQuerySet;
- std::string BatchedDeleteDocument::toString() const {
- return toBSON().toString();
- }
+ other->_limit = _limit;
+ other->_isLimitSet = _isLimitSet;
+}
- void BatchedDeleteDocument::setQuery(const BSONObj& query) {
- _query = query.getOwned();
- _isQuerySet = true;
- }
+std::string BatchedDeleteDocument::toString() const {
+ return toBSON().toString();
+}
- void BatchedDeleteDocument::unsetQuery() {
- _isQuerySet = false;
- }
+void BatchedDeleteDocument::setQuery(const BSONObj& query) {
+ _query = query.getOwned();
+ _isQuerySet = true;
+}
- bool BatchedDeleteDocument::isQuerySet() const {
- return _isQuerySet;
- }
+void BatchedDeleteDocument::unsetQuery() {
+ _isQuerySet = false;
+}
- const BSONObj& BatchedDeleteDocument::getQuery() const {
- dassert(_isQuerySet);
- return _query;
- }
+bool BatchedDeleteDocument::isQuerySet() const {
+ return _isQuerySet;
+}
- void BatchedDeleteDocument::setLimit(int limit) {
- _limit = limit;
- _isLimitSet = true;
- }
+const BSONObj& BatchedDeleteDocument::getQuery() const {
+ dassert(_isQuerySet);
+ return _query;
+}
- void BatchedDeleteDocument::unsetLimit() {
- _isLimitSet = false;
- }
+void BatchedDeleteDocument::setLimit(int limit) {
+ _limit = limit;
+ _isLimitSet = true;
+}
- bool BatchedDeleteDocument::isLimitSet() const {
- return _isLimitSet;
- }
+void BatchedDeleteDocument::unsetLimit() {
+ _isLimitSet = false;
+}
- int BatchedDeleteDocument::getLimit() const {
- dassert(_isLimitSet);
- return _limit;
- }
+bool BatchedDeleteDocument::isLimitSet() const {
+ return _isLimitSet;
+}
+
+int BatchedDeleteDocument::getLimit() const {
+ dassert(_isLimitSet);
+ return _limit;
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/batched_delete_document.h b/src/mongo/s/write_ops/batched_delete_document.h
index 346d2cdb77d..26ab8c44716 100644
--- a/src/mongo/s/write_ops/batched_delete_document.h
+++ b/src/mongo/s/write_ops/batched_delete_document.h
@@ -37,65 +37,65 @@
namespace mongo {
- /**
- * This class represents the layout and content of a delete document runCommand,
- * in the resquest side.
- */
- class BatchedDeleteDocument : public BSONSerializable {
- MONGO_DISALLOW_COPYING(BatchedDeleteDocument);
- public:
-
- //
- // schema declarations
- //
-
- static const BSONField<BSONObj> query;
- static const BSONField<int> limit;
-
- //
- // construction / destruction
- //
-
- BatchedDeleteDocument();
- virtual ~BatchedDeleteDocument();
-
- /** Copies all the fields present in 'this' to 'other'. */
- void cloneTo(BatchedDeleteDocument* other) const;
-
- //
- // bson serializable interface implementation
- //
-
- virtual bool isValid(std::string* errMsg) const;
- virtual BSONObj toBSON() const;
- virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
- virtual void clear();
- virtual std::string toString() const;
-
- //
- // individual field accessors
- //
-
- void setQuery(const BSONObj& query);
- void unsetQuery();
- bool isQuerySet() const;
- const BSONObj& getQuery() const;
-
- void setLimit(int limit);
- void unsetLimit();
- bool isLimitSet() const;
- int getLimit() const;
-
- private:
- // Convention: (M)andatory, (O)ptional
-
- // (M) query whose result the delete will remove
- BSONObj _query;
- bool _isQuerySet;
-
- // (M) the maximum number of documents to be deleted
- int _limit;
- bool _isLimitSet;
- };
-
-} // namespace mongo
+/**
+ * This class represents the layout and content of a delete document runCommand,
+ * in the resquest side.
+ */
+class BatchedDeleteDocument : public BSONSerializable {
+ MONGO_DISALLOW_COPYING(BatchedDeleteDocument);
+
+public:
+ //
+ // schema declarations
+ //
+
+ static const BSONField<BSONObj> query;
+ static const BSONField<int> limit;
+
+ //
+ // construction / destruction
+ //
+
+ BatchedDeleteDocument();
+ virtual ~BatchedDeleteDocument();
+
+ /** Copies all the fields present in 'this' to 'other'. */
+ void cloneTo(BatchedDeleteDocument* other) const;
+
+ //
+ // bson serializable interface implementation
+ //
+
+ virtual bool isValid(std::string* errMsg) const;
+ virtual BSONObj toBSON() const;
+ virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
+ virtual void clear();
+ virtual std::string toString() const;
+
+ //
+ // individual field accessors
+ //
+
+ void setQuery(const BSONObj& query);
+ void unsetQuery();
+ bool isQuerySet() const;
+ const BSONObj& getQuery() const;
+
+ void setLimit(int limit);
+ void unsetLimit();
+ bool isLimitSet() const;
+ int getLimit() const;
+
+private:
+ // Convention: (M)andatory, (O)ptional
+
+ // (M) query whose result the delete will remove
+ BSONObj _query;
+ bool _isQuerySet;
+
+ // (M) the maximum number of documents to be deleted
+ int _limit;
+ bool _isLimitSet;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/batched_delete_request.cpp b/src/mongo/s/write_ops/batched_delete_request.cpp
index b0c6a3f7a7a..d99df0ffbfc 100644
--- a/src/mongo/s/write_ops/batched_delete_request.cpp
+++ b/src/mongo/s/write_ops/batched_delete_request.cpp
@@ -33,279 +33,287 @@
namespace mongo {
- using std::unique_ptr;
- using std::string;
-
- using mongoutils::str::stream;
-
- const std::string BatchedDeleteRequest::BATCHED_DELETE_REQUEST = "delete";
- const BSONField<std::string> BatchedDeleteRequest::collName( "delete" );
- const BSONField<std::vector<BatchedDeleteDocument*> > BatchedDeleteRequest::deletes( "deletes" );
- const BSONField<BSONObj> BatchedDeleteRequest::writeConcern( "writeConcern" );
- const BSONField<bool> BatchedDeleteRequest::ordered( "ordered", true );
- const BSONField<BSONObj> BatchedDeleteRequest::metadata("metadata");
-
- BatchedDeleteRequest::BatchedDeleteRequest() {
- clear();
+using std::unique_ptr;
+using std::string;
+
+using mongoutils::str::stream;
+
+const std::string BatchedDeleteRequest::BATCHED_DELETE_REQUEST = "delete";
+const BSONField<std::string> BatchedDeleteRequest::collName("delete");
+const BSONField<std::vector<BatchedDeleteDocument*>> BatchedDeleteRequest::deletes("deletes");
+const BSONField<BSONObj> BatchedDeleteRequest::writeConcern("writeConcern");
+const BSONField<bool> BatchedDeleteRequest::ordered("ordered", true);
+const BSONField<BSONObj> BatchedDeleteRequest::metadata("metadata");
+
+BatchedDeleteRequest::BatchedDeleteRequest() {
+ clear();
+}
+
+BatchedDeleteRequest::~BatchedDeleteRequest() {
+ unsetDeletes();
+}
+
+bool BatchedDeleteRequest::isValid(std::string* errMsg) const {
+ std::string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
}
- BatchedDeleteRequest::~BatchedDeleteRequest() {
- unsetDeletes();
+ // All the mandatory fields must be present.
+ if (!_isCollNameSet) {
+ *errMsg = stream() << "missing " << collName.name() << " field";
+ return false;
}
- bool BatchedDeleteRequest::isValid(std::string* errMsg) const {
- std::string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
-
- // All the mandatory fields must be present.
- if (!_isCollNameSet) {
- *errMsg = stream() << "missing " << collName.name() << " field";
- return false;
- }
-
- if (!_isDeletesSet) {
- *errMsg = stream() << "missing " << deletes.name() << " field";
- return false;
- }
-
- return true;
+ if (!_isDeletesSet) {
+ *errMsg = stream() << "missing " << deletes.name() << " field";
+ return false;
}
- BSONObj BatchedDeleteRequest::toBSON() const {
- BSONObjBuilder builder;
-
- if (_isCollNameSet) builder.append(collName(), _collName);
+ return true;
+}
- if (_isDeletesSet) {
- BSONArrayBuilder deletesBuilder(builder.subarrayStart(deletes()));
- for (std::vector<BatchedDeleteDocument*>::const_iterator it = _deletes.begin();
- it != _deletes.end();
- ++it) {
- BSONObj deleteDocument = (*it)->toBSON();
- deletesBuilder.append(deleteDocument);
- }
- deletesBuilder.done();
- }
-
- if (_isWriteConcernSet) builder.append(writeConcern(), _writeConcern);
+BSONObj BatchedDeleteRequest::toBSON() const {
+ BSONObjBuilder builder;
- if (_isOrderedSet) builder.append(ordered(), _ordered);
+ if (_isCollNameSet)
+ builder.append(collName(), _collName);
- if (_metadata) builder.append(metadata(), _metadata->toBSON());
-
- return builder.obj();
- }
-
- bool BatchedDeleteRequest::parseBSON(const BSONObj& source, string* errMsg) {
- clear();
-
- std::string dummy;
- if (!errMsg) errMsg = &dummy;
-
- FieldParser::FieldState fieldState;
- std::string collNameTemp;
- fieldState = FieldParser::extract(source, collName, &collNameTemp, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _collName = NamespaceString(collNameTemp);
- _isCollNameSet = fieldState == FieldParser::FIELD_SET;
-
- fieldState = FieldParser::extract(source, deletes, &_deletes, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isDeletesSet = fieldState == FieldParser::FIELD_SET;
-
- fieldState = FieldParser::extract(source, writeConcern, &_writeConcern, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isWriteConcernSet = fieldState == FieldParser::FIELD_SET;
-
- fieldState = FieldParser::extract(source, ordered, &_ordered, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isOrderedSet = fieldState == FieldParser::FIELD_SET;
-
- BSONObj metadataObj;
- fieldState = FieldParser::extract(source, metadata, &metadataObj, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
-
- if (!metadataObj.isEmpty()) {
- _metadata.reset(new BatchedRequestMetadata());
- if (!_metadata->parseBSON(metadataObj, errMsg)) {
- return false;
- }
+ if (_isDeletesSet) {
+ BSONArrayBuilder deletesBuilder(builder.subarrayStart(deletes()));
+ for (std::vector<BatchedDeleteDocument*>::const_iterator it = _deletes.begin();
+ it != _deletes.end();
+ ++it) {
+ BSONObj deleteDocument = (*it)->toBSON();
+ deletesBuilder.append(deleteDocument);
}
-
- return true;
+ deletesBuilder.done();
}
- void BatchedDeleteRequest::clear() {
- _collName = NamespaceString();
- _isCollNameSet = false;
+ if (_isWriteConcernSet)
+ builder.append(writeConcern(), _writeConcern);
- unsetDeletes();
+ if (_isOrderedSet)
+ builder.append(ordered(), _ordered);
- _writeConcern = BSONObj();
- _isWriteConcernSet = false;
+ if (_metadata)
+ builder.append(metadata(), _metadata->toBSON());
- _ordered = false;
- _isOrderedSet = false;
+ return builder.obj();
+}
- _metadata.reset();
- }
+bool BatchedDeleteRequest::parseBSON(const BSONObj& source, string* errMsg) {
+ clear();
- void BatchedDeleteRequest::cloneTo(BatchedDeleteRequest* other) const {
- other->clear();
+ std::string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
- other->_collName = _collName;
- other->_isCollNameSet = _isCollNameSet;
+ FieldParser::FieldState fieldState;
+ std::string collNameTemp;
+ fieldState = FieldParser::extract(source, collName, &collNameTemp, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _collName = NamespaceString(collNameTemp);
+ _isCollNameSet = fieldState == FieldParser::FIELD_SET;
- for(std::vector<BatchedDeleteDocument*>::const_iterator it = _deletes.begin();
- it != _deletes.end();
- ++it) {
- unique_ptr<BatchedDeleteDocument> tempBatchDeleteDocument(new BatchedDeleteDocument);
- (*it)->cloneTo(tempBatchDeleteDocument.get());
- other->addToDeletes(tempBatchDeleteDocument.release());
- }
- other->_isDeletesSet = _isDeletesSet;
+ fieldState = FieldParser::extract(source, deletes, &_deletes, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isDeletesSet = fieldState == FieldParser::FIELD_SET;
- other->_writeConcern = _writeConcern;
- other->_isWriteConcernSet = _isWriteConcernSet;
+ fieldState = FieldParser::extract(source, writeConcern, &_writeConcern, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isWriteConcernSet = fieldState == FieldParser::FIELD_SET;
- other->_ordered = _ordered;
- other->_isOrderedSet = _isOrderedSet;
+ fieldState = FieldParser::extract(source, ordered, &_ordered, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isOrderedSet = fieldState == FieldParser::FIELD_SET;
- if (_metadata) {
- other->_metadata.reset(new BatchedRequestMetadata());
- _metadata->cloneTo(other->_metadata.get());
- }
- }
+ BSONObj metadataObj;
+ fieldState = FieldParser::extract(source, metadata, &metadataObj, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
- std::string BatchedDeleteRequest::toString() const {
- return toBSON().toString();
- }
-
- void BatchedDeleteRequest::setCollName(StringData collName) {
- _collName = NamespaceString(collName);
- _isCollNameSet = true;
- }
-
- const std::string& BatchedDeleteRequest::getCollName() const {
- dassert(_isCollNameSet);
- return _collName.ns();
+ if (!metadataObj.isEmpty()) {
+ _metadata.reset(new BatchedRequestMetadata());
+ if (!_metadata->parseBSON(metadataObj, errMsg)) {
+ return false;
+ }
}
- void BatchedDeleteRequest::setCollNameNS(const NamespaceString& collName) {
- _collName = collName;
- _isCollNameSet = true;
- }
+ return true;
+}
- const NamespaceString& BatchedDeleteRequest::getCollNameNS() const {
- dassert(_isCollNameSet);
- return _collName;
- }
+void BatchedDeleteRequest::clear() {
+ _collName = NamespaceString();
+ _isCollNameSet = false;
- const NamespaceString& BatchedDeleteRequest::getTargetingNSS() const {
- return getCollNameNS();
- }
+ unsetDeletes();
- void BatchedDeleteRequest::setDeletes(const std::vector<BatchedDeleteDocument*>& deletes) {
- for (std::vector<BatchedDeleteDocument*>::const_iterator it = deletes.begin();
- it != deletes.end();
- ++it) {
- unique_ptr<BatchedDeleteDocument> tempBatchDeleteDocument(new BatchedDeleteDocument);
- (*it)->cloneTo(tempBatchDeleteDocument.get());
- addToDeletes(tempBatchDeleteDocument.release());
- }
- _isDeletesSet = deletes.size() > 0;
- }
-
- void BatchedDeleteRequest::addToDeletes(BatchedDeleteDocument* deletes) {
- _deletes.push_back(deletes);
- _isDeletesSet = true;
- }
-
- void BatchedDeleteRequest::unsetDeletes() {
- for(std::vector<BatchedDeleteDocument*>::iterator it = _deletes.begin();
- it != _deletes.end();
- ++it) {
- delete *it;
- }
- _deletes.clear();
- _isDeletesSet = false;
- }
+ _writeConcern = BSONObj();
+ _isWriteConcernSet = false;
- bool BatchedDeleteRequest::isDeletesSet() const {
- return _isDeletesSet;
- }
+ _ordered = false;
+ _isOrderedSet = false;
- size_t BatchedDeleteRequest::sizeDeletes() const {
- return _deletes.size();
- }
+ _metadata.reset();
+}
- const std::vector<BatchedDeleteDocument*>& BatchedDeleteRequest::getDeletes() const {
- dassert(_isDeletesSet);
- return _deletes;
- }
+void BatchedDeleteRequest::cloneTo(BatchedDeleteRequest* other) const {
+ other->clear();
- const BatchedDeleteDocument* BatchedDeleteRequest::getDeletesAt(size_t pos) const {
- dassert(_isDeletesSet);
- dassert(_deletes.size() > pos);
- return _deletes.at(pos);
- }
+ other->_collName = _collName;
+ other->_isCollNameSet = _isCollNameSet;
- void BatchedDeleteRequest::setWriteConcern(const BSONObj& writeConcern) {
- _writeConcern = writeConcern.getOwned();
- _isWriteConcernSet = true;
+ for (std::vector<BatchedDeleteDocument*>::const_iterator it = _deletes.begin();
+ it != _deletes.end();
+ ++it) {
+ unique_ptr<BatchedDeleteDocument> tempBatchDeleteDocument(new BatchedDeleteDocument);
+ (*it)->cloneTo(tempBatchDeleteDocument.get());
+ other->addToDeletes(tempBatchDeleteDocument.release());
}
+ other->_isDeletesSet = _isDeletesSet;
- void BatchedDeleteRequest::unsetWriteConcern() {
- _isWriteConcernSet = false;
- }
+ other->_writeConcern = _writeConcern;
+ other->_isWriteConcernSet = _isWriteConcernSet;
- bool BatchedDeleteRequest::isWriteConcernSet() const {
- return _isWriteConcernSet;
- }
+ other->_ordered = _ordered;
+ other->_isOrderedSet = _isOrderedSet;
- const BSONObj& BatchedDeleteRequest::getWriteConcern() const {
- dassert(_isWriteConcernSet);
- return _writeConcern;
+ if (_metadata) {
+ other->_metadata.reset(new BatchedRequestMetadata());
+ _metadata->cloneTo(other->_metadata.get());
}
-
- void BatchedDeleteRequest::setOrdered(bool ordered) {
- _ordered = ordered;
- _isOrderedSet = true;
+}
+
+std::string BatchedDeleteRequest::toString() const {
+ return toBSON().toString();
+}
+
+void BatchedDeleteRequest::setCollName(StringData collName) {
+ _collName = NamespaceString(collName);
+ _isCollNameSet = true;
+}
+
+const std::string& BatchedDeleteRequest::getCollName() const {
+ dassert(_isCollNameSet);
+ return _collName.ns();
+}
+
+void BatchedDeleteRequest::setCollNameNS(const NamespaceString& collName) {
+ _collName = collName;
+ _isCollNameSet = true;
+}
+
+const NamespaceString& BatchedDeleteRequest::getCollNameNS() const {
+ dassert(_isCollNameSet);
+ return _collName;
+}
+
+const NamespaceString& BatchedDeleteRequest::getTargetingNSS() const {
+ return getCollNameNS();
+}
+
+void BatchedDeleteRequest::setDeletes(const std::vector<BatchedDeleteDocument*>& deletes) {
+ for (std::vector<BatchedDeleteDocument*>::const_iterator it = deletes.begin();
+ it != deletes.end();
+ ++it) {
+ unique_ptr<BatchedDeleteDocument> tempBatchDeleteDocument(new BatchedDeleteDocument);
+ (*it)->cloneTo(tempBatchDeleteDocument.get());
+ addToDeletes(tempBatchDeleteDocument.release());
}
-
- void BatchedDeleteRequest::unsetOrdered() {
- _isOrderedSet = false;
- }
-
- bool BatchedDeleteRequest::isOrderedSet() const {
- return _isOrderedSet;
+ _isDeletesSet = deletes.size() > 0;
+}
+
+void BatchedDeleteRequest::addToDeletes(BatchedDeleteDocument* deletes) {
+ _deletes.push_back(deletes);
+ _isDeletesSet = true;
+}
+
+void BatchedDeleteRequest::unsetDeletes() {
+ for (std::vector<BatchedDeleteDocument*>::iterator it = _deletes.begin(); it != _deletes.end();
+ ++it) {
+ delete *it;
}
-
- bool BatchedDeleteRequest::getOrdered() const {
- if (_isOrderedSet) {
- return _ordered;
- }
- else {
- return ordered.getDefault();
- }
+ _deletes.clear();
+ _isDeletesSet = false;
+}
+
+bool BatchedDeleteRequest::isDeletesSet() const {
+ return _isDeletesSet;
+}
+
+size_t BatchedDeleteRequest::sizeDeletes() const {
+ return _deletes.size();
+}
+
+const std::vector<BatchedDeleteDocument*>& BatchedDeleteRequest::getDeletes() const {
+ dassert(_isDeletesSet);
+ return _deletes;
+}
+
+const BatchedDeleteDocument* BatchedDeleteRequest::getDeletesAt(size_t pos) const {
+ dassert(_isDeletesSet);
+ dassert(_deletes.size() > pos);
+ return _deletes.at(pos);
+}
+
+void BatchedDeleteRequest::setWriteConcern(const BSONObj& writeConcern) {
+ _writeConcern = writeConcern.getOwned();
+ _isWriteConcernSet = true;
+}
+
+void BatchedDeleteRequest::unsetWriteConcern() {
+ _isWriteConcernSet = false;
+}
+
+bool BatchedDeleteRequest::isWriteConcernSet() const {
+ return _isWriteConcernSet;
+}
+
+const BSONObj& BatchedDeleteRequest::getWriteConcern() const {
+ dassert(_isWriteConcernSet);
+ return _writeConcern;
+}
+
+void BatchedDeleteRequest::setOrdered(bool ordered) {
+ _ordered = ordered;
+ _isOrderedSet = true;
+}
+
+void BatchedDeleteRequest::unsetOrdered() {
+ _isOrderedSet = false;
+}
+
+bool BatchedDeleteRequest::isOrderedSet() const {
+ return _isOrderedSet;
+}
+
+bool BatchedDeleteRequest::getOrdered() const {
+ if (_isOrderedSet) {
+ return _ordered;
+ } else {
+ return ordered.getDefault();
}
+}
- void BatchedDeleteRequest::setMetadata(BatchedRequestMetadata* metadata) {
- _metadata.reset(metadata);
- }
+void BatchedDeleteRequest::setMetadata(BatchedRequestMetadata* metadata) {
+ _metadata.reset(metadata);
+}
- void BatchedDeleteRequest::unsetMetadata() {
- _metadata.reset();
- }
+void BatchedDeleteRequest::unsetMetadata() {
+ _metadata.reset();
+}
- bool BatchedDeleteRequest::isMetadataSet() const {
- return _metadata.get();
- }
+bool BatchedDeleteRequest::isMetadataSet() const {
+ return _metadata.get();
+}
- BatchedRequestMetadata* BatchedDeleteRequest::getMetadata() const {
- return _metadata.get();
- }
+BatchedRequestMetadata* BatchedDeleteRequest::getMetadata() const {
+ return _metadata.get();
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/batched_delete_request.h b/src/mongo/s/write_ops/batched_delete_request.h
index e3846833bec..ff73155360d 100644
--- a/src/mongo/s/write_ops/batched_delete_request.h
+++ b/src/mongo/s/write_ops/batched_delete_request.h
@@ -40,117 +40,119 @@
namespace mongo {
+/**
+ * This class represents the layout and content of a batched delete runCommand,
+ * the request side.
+ */
+class BatchedDeleteRequest : public BSONSerializable {
+ MONGO_DISALLOW_COPYING(BatchedDeleteRequest);
+
+public:
+ //
+ // schema declarations
+ //
+
+ // Name used for the batched delete invocation.
+ static const std::string BATCHED_DELETE_REQUEST;
+
+ // Field names and types in the batched delete command type.
+ static const BSONField<std::string> collName;
+ static const BSONField<std::vector<BatchedDeleteDocument*>> deletes;
+ static const BSONField<BSONObj> writeConcern;
+ static const BSONField<bool> ordered;
+ static const BSONField<BSONObj> metadata;
+
+ //
+ // construction / destruction
+ //
+
+ BatchedDeleteRequest();
+ virtual ~BatchedDeleteRequest();
+
+ /** Copies all the fields present in 'this' to 'other'. */
+ void cloneTo(BatchedDeleteRequest* other) const;
+
+ //
+ // bson serializable interface implementation
+ //
+
+ virtual bool isValid(std::string* errMsg) const;
+ virtual BSONObj toBSON() const;
+ virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
+ virtual void clear();
+ virtual std::string toString() const;
+
+ //
+ // individual field accessors
+ //
+
+ void setCollName(StringData collName);
+ void setCollNameNS(const NamespaceString& collName);
+ const std::string& getCollName() const;
+ const NamespaceString& getCollNameNS() const;
+
+ const NamespaceString& getTargetingNSS() const;
+
+ void setDeletes(const std::vector<BatchedDeleteDocument*>& deletes);
+
/**
- * This class represents the layout and content of a batched delete runCommand,
- * the request side.
+ * deletes ownership is transferred to here.
*/
- class BatchedDeleteRequest : public BSONSerializable {
- MONGO_DISALLOW_COPYING(BatchedDeleteRequest);
- public:
-
- //
- // schema declarations
- //
-
- // Name used for the batched delete invocation.
- static const std::string BATCHED_DELETE_REQUEST;
-
- // Field names and types in the batched delete command type.
- static const BSONField<std::string> collName;
- static const BSONField<std::vector<BatchedDeleteDocument*> > deletes;
- static const BSONField<BSONObj> writeConcern;
- static const BSONField<bool> ordered;
- static const BSONField<BSONObj> metadata;
-
- //
- // construction / destruction
- //
-
- BatchedDeleteRequest();
- virtual ~BatchedDeleteRequest();
-
- /** Copies all the fields present in 'this' to 'other'. */
- void cloneTo(BatchedDeleteRequest* other) const;
-
- //
- // bson serializable interface implementation
- //
-
- virtual bool isValid(std::string* errMsg) const;
- virtual BSONObj toBSON() const;
- virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
- virtual void clear();
- virtual std::string toString() const;
-
- //
- // individual field accessors
- //
-
- void setCollName(StringData collName);
- void setCollNameNS(const NamespaceString& collName);
- const std::string& getCollName() const;
- const NamespaceString& getCollNameNS() const;
-
- const NamespaceString& getTargetingNSS() const;
-
- void setDeletes(const std::vector<BatchedDeleteDocument*>& deletes);
-
- /**
- * deletes ownership is transferred to here.
- */
- void addToDeletes(BatchedDeleteDocument* deletes);
- void unsetDeletes();
- bool isDeletesSet() const;
- std::size_t sizeDeletes() const;
- const std::vector<BatchedDeleteDocument*>& getDeletes() const;
- const BatchedDeleteDocument* getDeletesAt(std::size_t pos) const;
-
- void setWriteConcern(const BSONObj& writeConcern);
- void unsetWriteConcern();
- bool isWriteConcernSet() const;
- const BSONObj& getWriteConcern() const;
-
- void setOrdered(bool ordered);
- void unsetOrdered();
- bool isOrderedSet() const;
- bool getOrdered() const;
-
- /*
- * metadata ownership will be transferred to this.
- */
- void setMetadata(BatchedRequestMetadata* metadata);
- void unsetMetadata();
- bool isMetadataSet() const;
- BatchedRequestMetadata* getMetadata() const;
-
- /**
- * These are no-ops since delete never validates documents. They only exist to fulfill the
- * unified API.
- */
- void setShouldBypassValidation(bool newVal) {}
- bool shouldBypassValidation() const { return false; }
-
- private:
- // Convention: (M)andatory, (O)ptional
-
- // (M) collection we're deleting from
- NamespaceString _collName;
- bool _isCollNameSet;
-
- // (M) array of individual deletes
- std::vector<BatchedDeleteDocument*> _deletes;
- bool _isDeletesSet;
-
- // (O) to be issued after the batch applied
- BSONObj _writeConcern;
- bool _isWriteConcernSet;
-
- // (O) whether batch is issued in parallel or not
- bool _ordered;
- bool _isOrderedSet;
-
- // (O) metadata associated with this request for internal use.
- std::unique_ptr<BatchedRequestMetadata> _metadata;
- };
-
-} // namespace mongo
+ void addToDeletes(BatchedDeleteDocument* deletes);
+ void unsetDeletes();
+ bool isDeletesSet() const;
+ std::size_t sizeDeletes() const;
+ const std::vector<BatchedDeleteDocument*>& getDeletes() const;
+ const BatchedDeleteDocument* getDeletesAt(std::size_t pos) const;
+
+ void setWriteConcern(const BSONObj& writeConcern);
+ void unsetWriteConcern();
+ bool isWriteConcernSet() const;
+ const BSONObj& getWriteConcern() const;
+
+ void setOrdered(bool ordered);
+ void unsetOrdered();
+ bool isOrderedSet() const;
+ bool getOrdered() const;
+
+ /*
+ * metadata ownership will be transferred to this.
+ */
+ void setMetadata(BatchedRequestMetadata* metadata);
+ void unsetMetadata();
+ bool isMetadataSet() const;
+ BatchedRequestMetadata* getMetadata() const;
+
+ /**
+ * These are no-ops since delete never validates documents. They only exist to fulfill the
+ * unified API.
+ */
+ void setShouldBypassValidation(bool newVal) {}
+ bool shouldBypassValidation() const {
+ return false;
+ }
+
+private:
+ // Convention: (M)andatory, (O)ptional
+
+ // (M) collection we're deleting from
+ NamespaceString _collName;
+ bool _isCollNameSet;
+
+ // (M) array of individual deletes
+ std::vector<BatchedDeleteDocument*> _deletes;
+ bool _isDeletesSet;
+
+ // (O) to be issued after the batch applied
+ BSONObj _writeConcern;
+ bool _isWriteConcernSet;
+
+ // (O) whether batch is issued in parallel or not
+ bool _ordered;
+ bool _isOrderedSet;
+
+ // (O) metadata associated with this request for internal use.
+ std::unique_ptr<BatchedRequestMetadata> _metadata;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/batched_delete_request_test.cpp b/src/mongo/s/write_ops/batched_delete_request_test.cpp
index 33aa331259a..3ecf8aedea4 100644
--- a/src/mongo/s/write_ops/batched_delete_request_test.cpp
+++ b/src/mongo/s/write_ops/batched_delete_request_test.cpp
@@ -36,53 +36,46 @@
namespace {
- using mongo::BSONArray;
- using mongo::BSONObj;
- using mongo::BatchedDeleteRequest;
- using mongo::BatchedDeleteDocument;
- using mongo::BatchedRequestMetadata;
- using mongo::BSONArrayBuilder;
- using mongo::OID;
- using mongo::Timestamp;
- using std::string;
+using mongo::BSONArray;
+using mongo::BSONObj;
+using mongo::BatchedDeleteRequest;
+using mongo::BatchedDeleteDocument;
+using mongo::BatchedRequestMetadata;
+using mongo::BSONArrayBuilder;
+using mongo::OID;
+using mongo::Timestamp;
+using std::string;
- TEST(RoundTrip, Normal) {
- BSONArray deleteArray =
- BSON_ARRAY(
- BSON(BatchedDeleteDocument::query(BSON("a" << 1)) <<
- BatchedDeleteDocument::limit(1)
- ) <<
- BSON(BatchedDeleteDocument::query(BSON("b" << 1)) <<
- BatchedDeleteDocument::limit(1)
- )
- );
+TEST(RoundTrip, Normal) {
+ BSONArray deleteArray = BSON_ARRAY(
+ BSON(BatchedDeleteDocument::query(BSON("a" << 1)) << BatchedDeleteDocument::limit(1))
+ << BSON(BatchedDeleteDocument::query(BSON("b" << 1)) << BatchedDeleteDocument::limit(1)));
- BSONObj writeConcernObj = BSON("w" << 1);
+ BSONObj writeConcernObj = BSON("w" << 1);
- // The BSON_ARRAY macro doesn't support Timestamps.
- BSONArrayBuilder arrBuilder;
- arrBuilder.append(Timestamp(1,1));
- arrBuilder.append(OID::gen());
- BSONArray shardVersionArray = arrBuilder.arr();
+ // The BSON_ARRAY macro doesn't support Timestamps.
+ BSONArrayBuilder arrBuilder;
+ arrBuilder.append(Timestamp(1, 1));
+ arrBuilder.append(OID::gen());
+ BSONArray shardVersionArray = arrBuilder.arr();
- BSONObj origDeleteRequestObj =
- BSON(BatchedDeleteRequest::collName("test") <<
- BatchedDeleteRequest::deletes() << deleteArray <<
- BatchedDeleteRequest::writeConcern(writeConcernObj) <<
- BatchedDeleteRequest::ordered(true) <<
- BatchedDeleteRequest::metadata() <<
- BSON(BatchedRequestMetadata::shardName("shard000") <<
- BatchedRequestMetadata::shardVersion() << shardVersionArray <<
- BatchedRequestMetadata::session(0)));
+ BSONObj origDeleteRequestObj =
+ BSON(BatchedDeleteRequest::collName("test")
+ << BatchedDeleteRequest::deletes() << deleteArray
+ << BatchedDeleteRequest::writeConcern(writeConcernObj)
+ << BatchedDeleteRequest::ordered(true) << BatchedDeleteRequest::metadata()
+ << BSON(BatchedRequestMetadata::shardName("shard000")
+ << BatchedRequestMetadata::shardVersion() << shardVersionArray
+ << BatchedRequestMetadata::session(0)));
- string errMsg;
- BatchedDeleteRequest request;
- bool ok = request.parseBSON(origDeleteRequestObj, &errMsg);
- ASSERT_TRUE(ok);
+ string errMsg;
+ BatchedDeleteRequest request;
+ bool ok = request.parseBSON(origDeleteRequestObj, &errMsg);
+ ASSERT_TRUE(ok);
- BSONObj genDeleteRequestObj = request.toBSON();
- ASSERT_EQUALS(0, genDeleteRequestObj.woCompare(origDeleteRequestObj));
- }
+ BSONObj genDeleteRequestObj = request.toBSON();
+ ASSERT_EQUALS(0, genDeleteRequestObj.woCompare(origDeleteRequestObj));
+}
-} // unnamed namespace
+} // unnamed namespace
diff --git a/src/mongo/s/write_ops/batched_insert_request.cpp b/src/mongo/s/write_ops/batched_insert_request.cpp
index 94f7daabe0a..f5b862098c7 100644
--- a/src/mongo/s/write_ops/batched_insert_request.cpp
+++ b/src/mongo/s/write_ops/batched_insert_request.cpp
@@ -34,295 +34,296 @@
namespace mongo {
- using std::string;
+using std::string;
- using mongoutils::str::stream;
+using mongoutils::str::stream;
- const std::string BatchedInsertRequest::BATCHED_INSERT_REQUEST = "insert";
- const BSONField<std::string> BatchedInsertRequest::collName("insert");
- const BSONField<std::vector<BSONObj> > BatchedInsertRequest::documents("documents");
- const BSONField<BSONObj> BatchedInsertRequest::writeConcern("writeConcern");
- const BSONField<bool> BatchedInsertRequest::ordered("ordered", true);
- const BSONField<BSONObj> BatchedInsertRequest::metadata("metadata");
+const std::string BatchedInsertRequest::BATCHED_INSERT_REQUEST = "insert";
+const BSONField<std::string> BatchedInsertRequest::collName("insert");
+const BSONField<std::vector<BSONObj>> BatchedInsertRequest::documents("documents");
+const BSONField<BSONObj> BatchedInsertRequest::writeConcern("writeConcern");
+const BSONField<bool> BatchedInsertRequest::ordered("ordered", true);
+const BSONField<BSONObj> BatchedInsertRequest::metadata("metadata");
- BatchedInsertRequest::BatchedInsertRequest() {
- clear();
- }
+BatchedInsertRequest::BatchedInsertRequest() {
+ clear();
+}
- BatchedInsertRequest::~BatchedInsertRequest() {
- }
+BatchedInsertRequest::~BatchedInsertRequest() {}
- bool BatchedInsertRequest::isValid(std::string* errMsg) const {
- std::string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
-
- // All the mandatory fields must be present.
- if (!_isCollNameSet) {
- *errMsg = stream() << "missing " << collName.name() << " field";
- return false;
- }
-
- if (!_isDocumentsSet) {
- *errMsg = stream() << "missing " << documents.name() << " field";
- return false;
- }
-
- return true;
+bool BatchedInsertRequest::isValid(std::string* errMsg) const {
+ std::string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
}
- BSONObj BatchedInsertRequest::toBSON() const {
- BSONObjBuilder builder;
-
- if (_isCollNameSet) builder.append(collName(), _collName);
-
- if (_isDocumentsSet) {
- BSONArrayBuilder documentsBuilder(builder.subarrayStart(documents()));
- for (std::vector<BSONObj>::const_iterator it = _documents.begin();
- it != _documents.end();
- ++it) {
- documentsBuilder.append(*it);
- }
- documentsBuilder.done();
- }
-
- if (_isWriteConcernSet) builder.append(writeConcern(), _writeConcern);
-
- if (_isOrderedSet) builder.append(ordered(), _ordered);
-
- if (_metadata) builder.append(metadata(), _metadata->toBSON());
-
- if (_shouldBypassValidation) builder.append(bypassDocumentValidationCommandOption(), true);
-
- return builder.obj();
+ // All the mandatory fields must be present.
+ if (!_isCollNameSet) {
+ *errMsg = stream() << "missing " << collName.name() << " field";
+ return false;
}
- static void extractIndexNSS(const BSONObj& indexDesc, NamespaceString* indexNSS) {
- *indexNSS = NamespaceString(indexDesc["ns"].str());
+ if (!_isDocumentsSet) {
+ *errMsg = stream() << "missing " << documents.name() << " field";
+ return false;
}
- bool BatchedInsertRequest::parseBSON(const BSONObj& source, string* errMsg) {
- clear();
-
- std::string dummy;
- if (!errMsg) errMsg = &dummy;
+ return true;
+}
- BSONObjIterator sourceIt(source);
+BSONObj BatchedInsertRequest::toBSON() const {
+ BSONObjBuilder builder;
- while ( sourceIt.more() ) {
+ if (_isCollNameSet)
+ builder.append(collName(), _collName);
- BSONElement sourceEl = sourceIt.next();
-
- if ( collName() == sourceEl.fieldName() ) {
- std::string temp;
- FieldParser::FieldState fieldState =
- FieldParser::extract( sourceEl, collName, &temp, errMsg );
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _collName = NamespaceString(temp);
- _isCollNameSet = fieldState == FieldParser::FIELD_SET;
- }
- else if ( documents() == sourceEl.fieldName() ) {
- FieldParser::FieldState fieldState =
- FieldParser::extract( sourceEl, documents, &_documents, errMsg );
- if ( fieldState == FieldParser::FIELD_INVALID ) return false;
- _isDocumentsSet = fieldState == FieldParser::FIELD_SET;
- if (_documents.size() >= 1)
- extractIndexNSS(_documents.at(0), &_targetNSS);
- }
- else if ( writeConcern() == sourceEl.fieldName() ) {
- FieldParser::FieldState fieldState =
- FieldParser::extract(sourceEl, writeConcern, &_writeConcern, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isWriteConcernSet = fieldState == FieldParser::FIELD_SET;
- }
- else if ( ordered() == sourceEl.fieldName() ) {
- FieldParser::FieldState fieldState =
- FieldParser::extract(sourceEl, ordered, &_ordered, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isOrderedSet = fieldState == FieldParser::FIELD_SET;
- }
- else if ( metadata() == sourceEl.fieldName() ) {
- BSONObj metadataObj;
- FieldParser::FieldState fieldState =
- FieldParser::extract(sourceEl, metadata, &metadataObj, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
-
- if (!metadataObj.isEmpty()) {
- _metadata.reset(new BatchedRequestMetadata());
- if (!_metadata->parseBSON(metadataObj, errMsg)) {
- return false;
- }
- }
- }
- else if (bypassDocumentValidationCommandOption() == sourceEl.fieldNameStringData()) {
- _shouldBypassValidation = sourceEl.trueValue();
- }
+ if (_isDocumentsSet) {
+ BSONArrayBuilder documentsBuilder(builder.subarrayStart(documents()));
+ for (std::vector<BSONObj>::const_iterator it = _documents.begin(); it != _documents.end();
+ ++it) {
+ documentsBuilder.append(*it);
}
-
- return true;
+ documentsBuilder.done();
}
- void BatchedInsertRequest::clear() {
- _collName = NamespaceString();
- _targetNSS = NamespaceString();
- _isCollNameSet = false;
-
- _documents.clear();
- _isDocumentsSet =false;
-
- _writeConcern = BSONObj();
- _isWriteConcernSet = false;
-
- _ordered = false;
- _isOrderedSet = false;
-
- _shouldBypassValidation = false;
-
- _metadata.reset();
- }
-
- void BatchedInsertRequest::cloneTo(BatchedInsertRequest* other) const {
- other->clear();
-
- other->_collName = _collName;
- other->_targetNSS = _targetNSS;
- other->_isCollNameSet = _isCollNameSet;
-
- for(std::vector<BSONObj>::const_iterator it = _documents.begin();
- it != _documents.end();
- ++it) {
- other->addToDocuments(*it);
- }
- other->_isDocumentsSet = _isDocumentsSet;
-
- other->_writeConcern = _writeConcern;
- other->_isWriteConcernSet = _isWriteConcernSet;
-
- other->_ordered = _ordered;
- other->_isOrderedSet = _isOrderedSet;
-
- if (_metadata) {
- other->_metadata.reset(new BatchedRequestMetadata());
- _metadata->cloneTo(other->_metadata.get());
+ if (_isWriteConcernSet)
+ builder.append(writeConcern(), _writeConcern);
+
+ if (_isOrderedSet)
+ builder.append(ordered(), _ordered);
+
+ if (_metadata)
+ builder.append(metadata(), _metadata->toBSON());
+
+ if (_shouldBypassValidation)
+ builder.append(bypassDocumentValidationCommandOption(), true);
+
+ return builder.obj();
+}
+
+static void extractIndexNSS(const BSONObj& indexDesc, NamespaceString* indexNSS) {
+ *indexNSS = NamespaceString(indexDesc["ns"].str());
+}
+
+bool BatchedInsertRequest::parseBSON(const BSONObj& source, string* errMsg) {
+ clear();
+
+ std::string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
+
+ BSONObjIterator sourceIt(source);
+
+ while (sourceIt.more()) {
+ BSONElement sourceEl = sourceIt.next();
+
+ if (collName() == sourceEl.fieldName()) {
+ std::string temp;
+ FieldParser::FieldState fieldState =
+ FieldParser::extract(sourceEl, collName, &temp, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _collName = NamespaceString(temp);
+ _isCollNameSet = fieldState == FieldParser::FIELD_SET;
+ } else if (documents() == sourceEl.fieldName()) {
+ FieldParser::FieldState fieldState =
+ FieldParser::extract(sourceEl, documents, &_documents, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isDocumentsSet = fieldState == FieldParser::FIELD_SET;
+ if (_documents.size() >= 1)
+ extractIndexNSS(_documents.at(0), &_targetNSS);
+ } else if (writeConcern() == sourceEl.fieldName()) {
+ FieldParser::FieldState fieldState =
+ FieldParser::extract(sourceEl, writeConcern, &_writeConcern, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isWriteConcernSet = fieldState == FieldParser::FIELD_SET;
+ } else if (ordered() == sourceEl.fieldName()) {
+ FieldParser::FieldState fieldState =
+ FieldParser::extract(sourceEl, ordered, &_ordered, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isOrderedSet = fieldState == FieldParser::FIELD_SET;
+ } else if (metadata() == sourceEl.fieldName()) {
+ BSONObj metadataObj;
+ FieldParser::FieldState fieldState =
+ FieldParser::extract(sourceEl, metadata, &metadataObj, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+
+ if (!metadataObj.isEmpty()) {
+ _metadata.reset(new BatchedRequestMetadata());
+ if (!_metadata->parseBSON(metadataObj, errMsg)) {
+ return false;
+ }
+ }
+ } else if (bypassDocumentValidationCommandOption() == sourceEl.fieldNameStringData()) {
+ _shouldBypassValidation = sourceEl.trueValue();
}
}
- std::string BatchedInsertRequest::toString() const {
- return toBSON().toString();
- }
+ return true;
+}
- void BatchedInsertRequest::setCollName(StringData collName) {
- _collName = NamespaceString(collName);
- _isCollNameSet = true;
- }
+void BatchedInsertRequest::clear() {
+ _collName = NamespaceString();
+ _targetNSS = NamespaceString();
+ _isCollNameSet = false;
- const std::string& BatchedInsertRequest::getCollName() const {
- dassert(_isCollNameSet);
- return _collName.ns();
- }
+ _documents.clear();
+ _isDocumentsSet = false;
- void BatchedInsertRequest::setCollNameNS(const NamespaceString& collName) {
- _collName = collName;
- _isCollNameSet = true;
- }
+ _writeConcern = BSONObj();
+ _isWriteConcernSet = false;
- const NamespaceString& BatchedInsertRequest::getCollNameNS() const {
- dassert(_isCollNameSet);
- return _collName;
- }
+ _ordered = false;
+ _isOrderedSet = false;
- const NamespaceString& BatchedInsertRequest::getTargetingNSS() const {
- return _targetNSS;
- }
+ _shouldBypassValidation = false;
- void BatchedInsertRequest::addToDocuments(const BSONObj& documents) {
- _documents.push_back(documents);
- _isDocumentsSet = true;
+ _metadata.reset();
+}
- if (_documents.size() == 1)
- extractIndexNSS(_documents.at(0), &_targetNSS);
- }
+void BatchedInsertRequest::cloneTo(BatchedInsertRequest* other) const {
+ other->clear();
- bool BatchedInsertRequest::isDocumentsSet() const {
- return _isDocumentsSet;
- }
+ other->_collName = _collName;
+ other->_targetNSS = _targetNSS;
+ other->_isCollNameSet = _isCollNameSet;
- size_t BatchedInsertRequest::sizeDocuments() const {
- return _documents.size();
+ for (std::vector<BSONObj>::const_iterator it = _documents.begin(); it != _documents.end();
+ ++it) {
+ other->addToDocuments(*it);
}
+ other->_isDocumentsSet = _isDocumentsSet;
- const std::vector<BSONObj>& BatchedInsertRequest::getDocuments() const {
- dassert(_isDocumentsSet);
- return _documents;
- }
+ other->_writeConcern = _writeConcern;
+ other->_isWriteConcernSet = _isWriteConcernSet;
- const BSONObj& BatchedInsertRequest::getDocumentsAt(size_t pos) const {
- dassert(_isDocumentsSet);
- dassert(_documents.size() > pos);
- return _documents.at(pos);
- }
+ other->_ordered = _ordered;
+ other->_isOrderedSet = _isOrderedSet;
- void BatchedInsertRequest::setDocumentAt(size_t pos, const BSONObj& doc) {
- dassert(_isDocumentsSet);
- dassert(_documents.size() > pos);
- _documents[pos] = doc;
+ if (_metadata) {
+ other->_metadata.reset(new BatchedRequestMetadata());
+ _metadata->cloneTo(other->_metadata.get());
}
-
- void BatchedInsertRequest::setWriteConcern(const BSONObj& writeConcern) {
- _writeConcern = writeConcern.getOwned();
- _isWriteConcernSet = true;
+}
+
+std::string BatchedInsertRequest::toString() const {
+ return toBSON().toString();
+}
+
+void BatchedInsertRequest::setCollName(StringData collName) {
+ _collName = NamespaceString(collName);
+ _isCollNameSet = true;
+}
+
+const std::string& BatchedInsertRequest::getCollName() const {
+ dassert(_isCollNameSet);
+ return _collName.ns();
+}
+
+void BatchedInsertRequest::setCollNameNS(const NamespaceString& collName) {
+ _collName = collName;
+ _isCollNameSet = true;
+}
+
+const NamespaceString& BatchedInsertRequest::getCollNameNS() const {
+ dassert(_isCollNameSet);
+ return _collName;
+}
+
+const NamespaceString& BatchedInsertRequest::getTargetingNSS() const {
+ return _targetNSS;
+}
+
+void BatchedInsertRequest::addToDocuments(const BSONObj& documents) {
+ _documents.push_back(documents);
+ _isDocumentsSet = true;
+
+ if (_documents.size() == 1)
+ extractIndexNSS(_documents.at(0), &_targetNSS);
+}
+
+bool BatchedInsertRequest::isDocumentsSet() const {
+ return _isDocumentsSet;
+}
+
+size_t BatchedInsertRequest::sizeDocuments() const {
+ return _documents.size();
+}
+
+const std::vector<BSONObj>& BatchedInsertRequest::getDocuments() const {
+ dassert(_isDocumentsSet);
+ return _documents;
+}
+
+const BSONObj& BatchedInsertRequest::getDocumentsAt(size_t pos) const {
+ dassert(_isDocumentsSet);
+ dassert(_documents.size() > pos);
+ return _documents.at(pos);
+}
+
+void BatchedInsertRequest::setDocumentAt(size_t pos, const BSONObj& doc) {
+ dassert(_isDocumentsSet);
+ dassert(_documents.size() > pos);
+ _documents[pos] = doc;
+}
+
+void BatchedInsertRequest::setWriteConcern(const BSONObj& writeConcern) {
+ _writeConcern = writeConcern.getOwned();
+ _isWriteConcernSet = true;
+}
+
+void BatchedInsertRequest::unsetWriteConcern() {
+ _isWriteConcernSet = false;
+}
+
+bool BatchedInsertRequest::isWriteConcernSet() const {
+ return _isWriteConcernSet;
+}
+
+const BSONObj& BatchedInsertRequest::getWriteConcern() const {
+ dassert(_isWriteConcernSet);
+ return _writeConcern;
+}
+
+void BatchedInsertRequest::setOrdered(bool ordered) {
+ _ordered = ordered;
+ _isOrderedSet = true;
+}
+
+void BatchedInsertRequest::unsetOrdered() {
+ _isOrderedSet = false;
+}
+
+bool BatchedInsertRequest::isOrderedSet() const {
+ return _isOrderedSet;
+}
+
+bool BatchedInsertRequest::getOrdered() const {
+ if (_isOrderedSet) {
+ return _ordered;
+ } else {
+ return ordered.getDefault();
}
+}
- void BatchedInsertRequest::unsetWriteConcern() {
- _isWriteConcernSet = false;
- }
+void BatchedInsertRequest::setMetadata(BatchedRequestMetadata* metadata) {
+ _metadata.reset(metadata);
+}
- bool BatchedInsertRequest::isWriteConcernSet() const {
- return _isWriteConcernSet;
- }
+void BatchedInsertRequest::unsetMetadata() {
+ _metadata.reset();
+}
- const BSONObj& BatchedInsertRequest::getWriteConcern() const {
- dassert(_isWriteConcernSet);
- return _writeConcern;
- }
-
- void BatchedInsertRequest::setOrdered(bool ordered) {
- _ordered = ordered;
- _isOrderedSet = true;
- }
+bool BatchedInsertRequest::isMetadataSet() const {
+ return _metadata.get();
+}
- void BatchedInsertRequest::unsetOrdered() {
- _isOrderedSet = false;
- }
-
- bool BatchedInsertRequest::isOrderedSet() const {
- return _isOrderedSet;
- }
-
- bool BatchedInsertRequest::getOrdered() const {
- if (_isOrderedSet) {
- return _ordered;
- }
- else {
- return ordered.getDefault();
- }
- }
-
- void BatchedInsertRequest::setMetadata(BatchedRequestMetadata* metadata) {
- _metadata.reset(metadata);
- }
-
- void BatchedInsertRequest::unsetMetadata() {
- _metadata.reset();
- }
-
- bool BatchedInsertRequest::isMetadataSet() const {
- return _metadata.get();
- }
-
- BatchedRequestMetadata* BatchedInsertRequest::getMetadata() const {
- return _metadata.get();
- }
+BatchedRequestMetadata* BatchedInsertRequest::getMetadata() const {
+ return _metadata.get();
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/batched_insert_request.h b/src/mongo/s/write_ops/batched_insert_request.h
index 8b5238b888e..baf0fbbbe2b 100644
--- a/src/mongo/s/write_ops/batched_insert_request.h
+++ b/src/mongo/s/write_ops/batched_insert_request.h
@@ -39,114 +39,118 @@
namespace mongo {
- /**
- * This class represents the layout and content of a batched insert runCommand,
- * the request side.
+/**
+ * This class represents the layout and content of a batched insert runCommand,
+ * the request side.
+ */
+class BatchedInsertRequest : public BSONSerializable {
+ MONGO_DISALLOW_COPYING(BatchedInsertRequest);
+
+public:
+ //
+ // schema declarations
+ //
+
+ // Name used for the batched insert invocation.
+ static const std::string BATCHED_INSERT_REQUEST;
+
+ // Field names and types in the batched insert command type.
+ static const BSONField<std::string> collName;
+ static const BSONField<std::vector<BSONObj>> documents;
+ static const BSONField<BSONObj> writeConcern;
+ static const BSONField<bool> ordered;
+ static const BSONField<BSONObj> metadata;
+
+ //
+ // construction / destruction
+ //
+
+ BatchedInsertRequest();
+ virtual ~BatchedInsertRequest();
+
+ /** Copies all the fields present in 'this' to 'other'. */
+ void cloneTo(BatchedInsertRequest* other) const;
+
+ //
+ // bson serializable interface implementation
+ //
+
+ virtual bool isValid(std::string* errMsg) const;
+ virtual BSONObj toBSON() const;
+ virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
+ virtual void clear();
+ virtual std::string toString() const;
+
+ //
+ // individual field accessors
+ //
+
+ void setCollName(StringData collName);
+ void setCollNameNS(const NamespaceString& collName);
+ const std::string& getCollName() const;
+ const NamespaceString& getCollNameNS() const;
+
+ const NamespaceString& getTargetingNSS() const;
+
+ void addToDocuments(const BSONObj& documents);
+ bool isDocumentsSet() const;
+ std::size_t sizeDocuments() const;
+ const std::vector<BSONObj>& getDocuments() const;
+ const BSONObj& getDocumentsAt(std::size_t pos) const;
+ void setDocumentAt(std::size_t pos, const BSONObj& doc);
+
+ void setWriteConcern(const BSONObj& writeConcern);
+ void unsetWriteConcern();
+ bool isWriteConcernSet() const;
+ const BSONObj& getWriteConcern() const;
+
+ void setOrdered(bool ordered);
+ void unsetOrdered();
+ bool isOrderedSet() const;
+ bool getOrdered() const;
+
+ void setShouldBypassValidation(bool newVal) {
+ _shouldBypassValidation = newVal;
+ }
+ bool shouldBypassValidation() const {
+ return _shouldBypassValidation;
+ }
+
+ /*
+ * metadata ownership will be transferred to this.
*/
- class BatchedInsertRequest : public BSONSerializable {
- MONGO_DISALLOW_COPYING(BatchedInsertRequest);
- public:
-
- //
- // schema declarations
- //
-
- // Name used for the batched insert invocation.
- static const std::string BATCHED_INSERT_REQUEST;
-
- // Field names and types in the batched insert command type.
- static const BSONField<std::string> collName;
- static const BSONField<std::vector<BSONObj> > documents;
- static const BSONField<BSONObj> writeConcern;
- static const BSONField<bool> ordered;
- static const BSONField<BSONObj> metadata;
-
- //
- // construction / destruction
- //
-
- BatchedInsertRequest();
- virtual ~BatchedInsertRequest();
-
- /** Copies all the fields present in 'this' to 'other'. */
- void cloneTo(BatchedInsertRequest* other) const;
-
- //
- // bson serializable interface implementation
- //
-
- virtual bool isValid(std::string* errMsg) const;
- virtual BSONObj toBSON() const;
- virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
- virtual void clear();
- virtual std::string toString() const;
-
- //
- // individual field accessors
- //
-
- void setCollName(StringData collName);
- void setCollNameNS(const NamespaceString& collName);
- const std::string& getCollName() const;
- const NamespaceString& getCollNameNS() const;
-
- const NamespaceString& getTargetingNSS() const;
-
- void addToDocuments(const BSONObj& documents);
- bool isDocumentsSet() const;
- std::size_t sizeDocuments() const;
- const std::vector<BSONObj>& getDocuments() const;
- const BSONObj& getDocumentsAt(std::size_t pos) const;
- void setDocumentAt(std::size_t pos, const BSONObj& doc);
-
- void setWriteConcern(const BSONObj& writeConcern);
- void unsetWriteConcern();
- bool isWriteConcernSet() const;
- const BSONObj& getWriteConcern() const;
-
- void setOrdered(bool ordered);
- void unsetOrdered();
- bool isOrderedSet() const;
- bool getOrdered() const;
-
- void setShouldBypassValidation(bool newVal) { _shouldBypassValidation = newVal; }
- bool shouldBypassValidation() const { return _shouldBypassValidation; }
-
- /*
- * metadata ownership will be transferred to this.
- */
- void setMetadata(BatchedRequestMetadata* metadata);
- void unsetMetadata();
- bool isMetadataSet() const;
- BatchedRequestMetadata* getMetadata() const;
-
- private:
- // Convention: (M)andatory, (O)ptional
-
- // (M) collection we're inserting on
- NamespaceString _collName;
- bool _isCollNameSet;
-
- // (M) array of documents to be inserted
- std::vector<BSONObj> _documents;
- bool _isDocumentsSet;
-
- // (O) to be issued after the batch applied
- BSONObj _writeConcern;
- bool _isWriteConcernSet;
-
- // (O) whether batch is issued in parallel or not
- bool _ordered;
- bool _isOrderedSet;
-
- // (O) metadata associated with this request for internal use.
- std::unique_ptr<BatchedRequestMetadata> _metadata;
-
- // (O) cached copied of target ns
- NamespaceString _targetNSS;
-
- // (O) should document validation be bypassed (default false)
- bool _shouldBypassValidation;
- };
-
-} // namespace mongo
+ void setMetadata(BatchedRequestMetadata* metadata);
+ void unsetMetadata();
+ bool isMetadataSet() const;
+ BatchedRequestMetadata* getMetadata() const;
+
+private:
+ // Convention: (M)andatory, (O)ptional
+
+ // (M) collection we're inserting on
+ NamespaceString _collName;
+ bool _isCollNameSet;
+
+ // (M) array of documents to be inserted
+ std::vector<BSONObj> _documents;
+ bool _isDocumentsSet;
+
+ // (O) to be issued after the batch applied
+ BSONObj _writeConcern;
+ bool _isWriteConcernSet;
+
+ // (O) whether batch is issued in parallel or not
+ bool _ordered;
+ bool _isOrderedSet;
+
+ // (O) metadata associated with this request for internal use.
+ std::unique_ptr<BatchedRequestMetadata> _metadata;
+
+ // (O) cached copied of target ns
+ NamespaceString _targetNSS;
+
+ // (O) should document validation be bypassed (default false)
+ bool _shouldBypassValidation;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/batched_insert_request_test.cpp b/src/mongo/s/write_ops/batched_insert_request_test.cpp
index 5be837ae17a..66f99c7ffc6 100644
--- a/src/mongo/s/write_ops/batched_insert_request_test.cpp
+++ b/src/mongo/s/write_ops/batched_insert_request_test.cpp
@@ -36,115 +36,111 @@
namespace {
- using namespace mongo;
- using std::unique_ptr;
- using std::string;
-
- TEST(RoundTrip, Normal) {
- BSONArray insertArray = BSON_ARRAY(BSON("a" << 1) << BSON("b" << 1));
-
- BSONObj writeConcernObj = BSON("w" << 1);
-
- // The BSON_ARRAY macro doesn't support Timestamps.
- BSONArrayBuilder arrBuilder;
- arrBuilder.append(Timestamp(1,1));
- arrBuilder.append(OID::gen());
- BSONArray shardVersionArray = arrBuilder.arr();
-
- BSONObj origInsertRequestObj =
- BSON(BatchedInsertRequest::collName("test") <<
- BatchedInsertRequest::documents() << insertArray <<
- BatchedInsertRequest::writeConcern(writeConcernObj) <<
- BatchedInsertRequest::ordered(true) <<
- BatchedInsertRequest::metadata() << BSON(
- BatchedRequestMetadata::shardName("shard0000") <<
- BatchedRequestMetadata::shardVersion() << shardVersionArray <<
- BatchedRequestMetadata::session(0)));
-
- string errMsg;
- BatchedInsertRequest request;
- bool ok = request.parseBSON(origInsertRequestObj, &errMsg);
- ASSERT_TRUE(ok);
-
- BSONObj genInsertRequestObj = request.toBSON();
- ASSERT_EQUALS(0, genInsertRequestObj.woCompare(origInsertRequestObj));
- }
-
- TEST(GenID, All) {
-
- BatchedCommandRequest cmdRequest(BatchedCommandRequest::BatchType_Insert);
- BatchedInsertRequest& request = *cmdRequest.getInsertRequest();
-
- request.setCollName("foo.bar");
- request.setOrdered(false);
-
- BSONObj insertA = BSON( "a" << 1 );
- BSONObj insertB = BSON( "b" << 1 );
- request.addToDocuments(insertA);
- request.addToDocuments(insertB);
-
- unique_ptr<BatchedCommandRequest> idCmdRequest;
- idCmdRequest.reset(BatchedCommandRequest::cloneWithIds(cmdRequest));
- ASSERT(idCmdRequest.get());
-
- BatchedInsertRequest* idRequest = idCmdRequest->getInsertRequest();
- ASSERT_EQUALS(idRequest->getCollName(), request.getCollName());
- ASSERT_EQUALS(idRequest->getOrdered(), request.getOrdered());
-
- ASSERT(!idRequest->getDocumentsAt(0)["_id"].eoo());
- ASSERT_EQUALS(idRequest->getDocumentsAt(0).nFields(), 2);
- ASSERT(!idRequest->getDocumentsAt(1)["_id"].eoo());
- ASSERT_EQUALS(idRequest->getDocumentsAt(1).nFields(), 2);
- }
-
- TEST(GenID, Partial) {
-
- BatchedCommandRequest cmdRequest(BatchedCommandRequest::BatchType_Insert);
- BatchedInsertRequest& request = *cmdRequest.getInsertRequest();
-
- request.setCollName("foo.bar");
- request.setOrdered(false);
-
- BSONObj insertA = BSON( "a" << 1 );
- BSONObj insertB = BSON( "b" << 1 << "_id" << 1 );
- BSONObj insertC = BSON( "c" << 1 );
- request.addToDocuments(insertA);
- request.addToDocuments(insertB);
- request.addToDocuments(insertC);
-
- unique_ptr<BatchedCommandRequest> idCmdRequest;
- idCmdRequest.reset(BatchedCommandRequest::cloneWithIds(cmdRequest));
- ASSERT(idCmdRequest.get());
-
- BatchedInsertRequest* idRequest = idCmdRequest->getInsertRequest();
- ASSERT_EQUALS(idRequest->getCollName(), request.getCollName());
- ASSERT_EQUALS(idRequest->getOrdered(), request.getOrdered());
-
- ASSERT(!idRequest->getDocumentsAt(0)["_id"].eoo());
- ASSERT_EQUALS(idRequest->getDocumentsAt(0).nFields(), 2);
- ASSERT(!idRequest->getDocumentsAt(1)["_id"].eoo());
- ASSERT_EQUALS(idRequest->getDocumentsAt(1).nFields(), 2);
- ASSERT(!idRequest->getDocumentsAt(2)["_id"].eoo());
- ASSERT_EQUALS(idRequest->getDocumentsAt(1).nFields(), 2);
- }
-
- TEST(GenID, None) {
-
- BatchedCommandRequest cmdRequest(BatchedCommandRequest::BatchType_Insert);
- BatchedInsertRequest& request = *cmdRequest.getInsertRequest();
-
- // We need to check for system.indexes namespace
- request.setCollName("foo.bar");
-
- BSONObj insertA = BSON( "_id" << 0 << "a" << 1 );
- BSONObj insertB = BSON( "b" << 1 << "_id" << 1 );
- request.addToDocuments(insertA);
- request.addToDocuments(insertB);
-
- unique_ptr<BatchedCommandRequest> idCmdRequest;
- idCmdRequest.reset(BatchedCommandRequest::cloneWithIds(cmdRequest));
- ASSERT(!idCmdRequest.get());
- }
-
-
-} // unnamed namespace
+using namespace mongo;
+using std::unique_ptr;
+using std::string;
+
+TEST(RoundTrip, Normal) {
+ BSONArray insertArray = BSON_ARRAY(BSON("a" << 1) << BSON("b" << 1));
+
+ BSONObj writeConcernObj = BSON("w" << 1);
+
+ // The BSON_ARRAY macro doesn't support Timestamps.
+ BSONArrayBuilder arrBuilder;
+ arrBuilder.append(Timestamp(1, 1));
+ arrBuilder.append(OID::gen());
+ BSONArray shardVersionArray = arrBuilder.arr();
+
+ BSONObj origInsertRequestObj =
+ BSON(BatchedInsertRequest::collName("test")
+ << BatchedInsertRequest::documents() << insertArray
+ << BatchedInsertRequest::writeConcern(writeConcernObj)
+ << BatchedInsertRequest::ordered(true) << BatchedInsertRequest::metadata()
+ << BSON(BatchedRequestMetadata::shardName("shard0000")
+ << BatchedRequestMetadata::shardVersion() << shardVersionArray
+ << BatchedRequestMetadata::session(0)));
+
+ string errMsg;
+ BatchedInsertRequest request;
+ bool ok = request.parseBSON(origInsertRequestObj, &errMsg);
+ ASSERT_TRUE(ok);
+
+ BSONObj genInsertRequestObj = request.toBSON();
+ ASSERT_EQUALS(0, genInsertRequestObj.woCompare(origInsertRequestObj));
+}
+
+TEST(GenID, All) {
+ BatchedCommandRequest cmdRequest(BatchedCommandRequest::BatchType_Insert);
+ BatchedInsertRequest& request = *cmdRequest.getInsertRequest();
+
+ request.setCollName("foo.bar");
+ request.setOrdered(false);
+
+ BSONObj insertA = BSON("a" << 1);
+ BSONObj insertB = BSON("b" << 1);
+ request.addToDocuments(insertA);
+ request.addToDocuments(insertB);
+
+ unique_ptr<BatchedCommandRequest> idCmdRequest;
+ idCmdRequest.reset(BatchedCommandRequest::cloneWithIds(cmdRequest));
+ ASSERT(idCmdRequest.get());
+
+ BatchedInsertRequest* idRequest = idCmdRequest->getInsertRequest();
+ ASSERT_EQUALS(idRequest->getCollName(), request.getCollName());
+ ASSERT_EQUALS(idRequest->getOrdered(), request.getOrdered());
+
+ ASSERT(!idRequest->getDocumentsAt(0)["_id"].eoo());
+ ASSERT_EQUALS(idRequest->getDocumentsAt(0).nFields(), 2);
+ ASSERT(!idRequest->getDocumentsAt(1)["_id"].eoo());
+ ASSERT_EQUALS(idRequest->getDocumentsAt(1).nFields(), 2);
+}
+
+TEST(GenID, Partial) {
+ BatchedCommandRequest cmdRequest(BatchedCommandRequest::BatchType_Insert);
+ BatchedInsertRequest& request = *cmdRequest.getInsertRequest();
+
+ request.setCollName("foo.bar");
+ request.setOrdered(false);
+
+ BSONObj insertA = BSON("a" << 1);
+ BSONObj insertB = BSON("b" << 1 << "_id" << 1);
+ BSONObj insertC = BSON("c" << 1);
+ request.addToDocuments(insertA);
+ request.addToDocuments(insertB);
+ request.addToDocuments(insertC);
+
+ unique_ptr<BatchedCommandRequest> idCmdRequest;
+ idCmdRequest.reset(BatchedCommandRequest::cloneWithIds(cmdRequest));
+ ASSERT(idCmdRequest.get());
+
+ BatchedInsertRequest* idRequest = idCmdRequest->getInsertRequest();
+ ASSERT_EQUALS(idRequest->getCollName(), request.getCollName());
+ ASSERT_EQUALS(idRequest->getOrdered(), request.getOrdered());
+
+ ASSERT(!idRequest->getDocumentsAt(0)["_id"].eoo());
+ ASSERT_EQUALS(idRequest->getDocumentsAt(0).nFields(), 2);
+ ASSERT(!idRequest->getDocumentsAt(1)["_id"].eoo());
+ ASSERT_EQUALS(idRequest->getDocumentsAt(1).nFields(), 2);
+ ASSERT(!idRequest->getDocumentsAt(2)["_id"].eoo());
+ ASSERT_EQUALS(idRequest->getDocumentsAt(1).nFields(), 2);
+}
+
+TEST(GenID, None) {
+ BatchedCommandRequest cmdRequest(BatchedCommandRequest::BatchType_Insert);
+ BatchedInsertRequest& request = *cmdRequest.getInsertRequest();
+
+ // We need to check for system.indexes namespace
+ request.setCollName("foo.bar");
+
+ BSONObj insertA = BSON("_id" << 0 << "a" << 1);
+ BSONObj insertB = BSON("b" << 1 << "_id" << 1);
+ request.addToDocuments(insertA);
+ request.addToDocuments(insertB);
+
+ unique_ptr<BatchedCommandRequest> idCmdRequest;
+ idCmdRequest.reset(BatchedCommandRequest::cloneWithIds(cmdRequest));
+ ASSERT(!idCmdRequest.get());
+}
+
+
+} // unnamed namespace
diff --git a/src/mongo/s/write_ops/batched_request_metadata.cpp b/src/mongo/s/write_ops/batched_request_metadata.cpp
index 29d4b5e97fc..a2e21f99e4c 100644
--- a/src/mongo/s/write_ops/batched_request_metadata.cpp
+++ b/src/mongo/s/write_ops/batched_request_metadata.cpp
@@ -32,146 +32,147 @@
namespace mongo {
- using std::unique_ptr;
- using std::string;
+using std::unique_ptr;
+using std::string;
- const BSONField<string> BatchedRequestMetadata::shardName("shardName");
- const BSONField<ChunkVersion> BatchedRequestMetadata::shardVersion("shardVersion");
- const BSONField<long long> BatchedRequestMetadata::session("session");
+const BSONField<string> BatchedRequestMetadata::shardName("shardName");
+const BSONField<ChunkVersion> BatchedRequestMetadata::shardVersion("shardVersion");
+const BSONField<long long> BatchedRequestMetadata::session("session");
- BatchedRequestMetadata::BatchedRequestMetadata():
- _isShardNameSet(false),
- _session(0),
- _isSessionSet(false) {
- }
-
- BatchedRequestMetadata::~BatchedRequestMetadata() {
-
- }
-
- bool BatchedRequestMetadata::isValid(string* errMsg) const {
- // all fields are mandatory.
- return true;
- }
+BatchedRequestMetadata::BatchedRequestMetadata()
+ : _isShardNameSet(false), _session(0), _isSessionSet(false) {}
- BSONObj BatchedRequestMetadata::toBSON() const {
- BSONObjBuilder metadataBuilder;
+BatchedRequestMetadata::~BatchedRequestMetadata() {}
- if (_isShardNameSet) metadataBuilder << shardName(_shardName);
+bool BatchedRequestMetadata::isValid(string* errMsg) const {
+ // all fields are mandatory.
+ return true;
+}
- if (_shardVersion.get()) {
- // ChunkVersion wants to be an array.
- metadataBuilder.append(shardVersion(),
- static_cast<BSONArray>(_shardVersion->toBSON()));
- }
+BSONObj BatchedRequestMetadata::toBSON() const {
+ BSONObjBuilder metadataBuilder;
- if (_isSessionSet) metadataBuilder << session(_session);
+ if (_isShardNameSet)
+ metadataBuilder << shardName(_shardName);
- return metadataBuilder.obj();
+ if (_shardVersion.get()) {
+ // ChunkVersion wants to be an array.
+ metadataBuilder.append(shardVersion(), static_cast<BSONArray>(_shardVersion->toBSON()));
}
- bool BatchedRequestMetadata::parseBSON(const BSONObj& source, string* errMsg) {
- clear();
+ if (_isSessionSet)
+ metadataBuilder << session(_session);
- string dummy;
- if (!errMsg) errMsg = &dummy;
+ return metadataBuilder.obj();
+}
- FieldParser::FieldState fieldState;
- fieldState = FieldParser::extract(source, shardName, &_shardName, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isShardNameSet = fieldState == FieldParser::FIELD_SET;
+bool BatchedRequestMetadata::parseBSON(const BSONObj& source, string* errMsg) {
+ clear();
- {
- std::unique_ptr<ChunkVersion> tempChunkVersion(new ChunkVersion);
- fieldState = FieldParser::extract(source, shardVersion,
- tempChunkVersion.get(), errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- if (fieldState == FieldParser::FIELD_SET) _shardVersion.swap(tempChunkVersion);
- }
+ string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
- fieldState = FieldParser::extract(source, session, &_session, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isSessionSet = fieldState == FieldParser::FIELD_SET;
+ FieldParser::FieldState fieldState;
+ fieldState = FieldParser::extract(source, shardName, &_shardName, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isShardNameSet = fieldState == FieldParser::FIELD_SET;
- return true;
+ {
+ std::unique_ptr<ChunkVersion> tempChunkVersion(new ChunkVersion);
+ fieldState = FieldParser::extract(source, shardVersion, tempChunkVersion.get(), errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ if (fieldState == FieldParser::FIELD_SET)
+ _shardVersion.swap(tempChunkVersion);
}
- void BatchedRequestMetadata::clear() {
- _shardName.clear();
- _isShardNameSet = false;
+ fieldState = FieldParser::extract(source, session, &_session, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isSessionSet = fieldState == FieldParser::FIELD_SET;
- _shardVersion.reset();
+ return true;
+}
- _session = 0;
- _isSessionSet = false;
- }
+void BatchedRequestMetadata::clear() {
+ _shardName.clear();
+ _isShardNameSet = false;
- string BatchedRequestMetadata::toString() const {
- return toBSON().toString();
- }
+ _shardVersion.reset();
- void BatchedRequestMetadata::cloneTo(BatchedRequestMetadata* other) const {
- other->_shardName = _shardName;
- other->_isShardNameSet = _isShardNameSet;
+ _session = 0;
+ _isSessionSet = false;
+}
- if (other->_shardVersion.get()) _shardVersion->cloneTo(other->_shardVersion.get());
+string BatchedRequestMetadata::toString() const {
+ return toBSON().toString();
+}
- other->_session = _session;
- other->_isSessionSet = _isSessionSet;
- }
+void BatchedRequestMetadata::cloneTo(BatchedRequestMetadata* other) const {
+ other->_shardName = _shardName;
+ other->_isShardNameSet = _isShardNameSet;
- void BatchedRequestMetadata::setShardName( StringData shardName ) {
- _shardName = shardName.toString();
- _isShardNameSet = true;
- }
+ if (other->_shardVersion.get())
+ _shardVersion->cloneTo(other->_shardVersion.get());
- void BatchedRequestMetadata::unsetShardName() {
- _isShardNameSet = false;
- }
+ other->_session = _session;
+ other->_isSessionSet = _isSessionSet;
+}
- bool BatchedRequestMetadata::isShardNameSet() const {
- return _isShardNameSet;
- }
+void BatchedRequestMetadata::setShardName(StringData shardName) {
+ _shardName = shardName.toString();
+ _isShardNameSet = true;
+}
- const string& BatchedRequestMetadata::getShardName() const {
- dassert( _isShardNameSet );
- return _shardName;
- }
+void BatchedRequestMetadata::unsetShardName() {
+ _isShardNameSet = false;
+}
- void BatchedRequestMetadata::setShardVersion(const ChunkVersion& shardVersion) {
- unique_ptr<ChunkVersion> temp(new ChunkVersion);
- shardVersion.cloneTo(temp.get());
- _shardVersion.reset(temp.release());
- }
+bool BatchedRequestMetadata::isShardNameSet() const {
+ return _isShardNameSet;
+}
- void BatchedRequestMetadata::unsetShardVersion() {
- _shardVersion.reset();
- }
+const string& BatchedRequestMetadata::getShardName() const {
+ dassert(_isShardNameSet);
+ return _shardName;
+}
- bool BatchedRequestMetadata::isShardVersionSet() const {
- return _shardVersion.get() != NULL;
- }
+void BatchedRequestMetadata::setShardVersion(const ChunkVersion& shardVersion) {
+ unique_ptr<ChunkVersion> temp(new ChunkVersion);
+ shardVersion.cloneTo(temp.get());
+ _shardVersion.reset(temp.release());
+}
- const ChunkVersion& BatchedRequestMetadata::getShardVersion() const {
- dassert(_shardVersion.get());
- return *_shardVersion;
- }
+void BatchedRequestMetadata::unsetShardVersion() {
+ _shardVersion.reset();
+}
- void BatchedRequestMetadata::setSession(long long session) {
- _session = session;
- _isSessionSet = true;
- }
+bool BatchedRequestMetadata::isShardVersionSet() const {
+ return _shardVersion.get() != NULL;
+}
- void BatchedRequestMetadata::unsetSession() {
- _isSessionSet = false;
- }
+const ChunkVersion& BatchedRequestMetadata::getShardVersion() const {
+ dassert(_shardVersion.get());
+ return *_shardVersion;
+}
- bool BatchedRequestMetadata::isSessionSet() const {
- return _isSessionSet;
- }
+void BatchedRequestMetadata::setSession(long long session) {
+ _session = session;
+ _isSessionSet = true;
+}
- long long BatchedRequestMetadata::getSession() const {
- dassert(_isSessionSet);
- return _session;
- }
+void BatchedRequestMetadata::unsetSession() {
+ _isSessionSet = false;
+}
+
+bool BatchedRequestMetadata::isSessionSet() const {
+ return _isSessionSet;
+}
+
+long long BatchedRequestMetadata::getSession() const {
+ dassert(_isSessionSet);
+ return _session;
+}
}
diff --git a/src/mongo/s/write_ops/batched_request_metadata.h b/src/mongo/s/write_ops/batched_request_metadata.h
index afe639adfaa..6e31cb60713 100644
--- a/src/mongo/s/write_ops/batched_request_metadata.h
+++ b/src/mongo/s/write_ops/batched_request_metadata.h
@@ -36,59 +36,58 @@
#include "mongo/s/chunk_version.h"
namespace mongo {
- class BatchedRequestMetadata : public BSONSerializable {
- MONGO_DISALLOW_COPYING(BatchedRequestMetadata);
- public:
-
- static const BSONField<std::string> shardName;
- static const BSONField<ChunkVersion> shardVersion;
- static const BSONField<long long> session;
-
- BatchedRequestMetadata();
- virtual ~BatchedRequestMetadata();
-
- //
- // bson serializable interface implementation
- //
-
- virtual bool isValid(std::string* errMsg) const;
- virtual BSONObj toBSON() const;
- virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
- virtual void clear();
- virtual std::string toString() const;
-
- void cloneTo(BatchedRequestMetadata* other) const;
-
- //
- // individual field accessors
- //
-
- void setShardName(StringData shardName);
- void unsetShardName();
- bool isShardNameSet() const;
- const std::string& getShardName() const;
-
- void setShardVersion(const ChunkVersion& shardVersion);
- void unsetShardVersion();
- bool isShardVersionSet() const;
- const ChunkVersion& getShardVersion() const;
-
- void setSession(long long session);
- void unsetSession();
- bool isSessionSet() const;
- long long getSession() const;
-
- private:
-
- // (O) shard name we're sending this batch to
- std::string _shardName;
- bool _isShardNameSet;
-
- // (O) version for this collection on a given shard
- std::unique_ptr<ChunkVersion> _shardVersion;
-
- // (O) session number the inserts belong to
- long long _session;
- bool _isSessionSet;
- };
+class BatchedRequestMetadata : public BSONSerializable {
+ MONGO_DISALLOW_COPYING(BatchedRequestMetadata);
+
+public:
+ static const BSONField<std::string> shardName;
+ static const BSONField<ChunkVersion> shardVersion;
+ static const BSONField<long long> session;
+
+ BatchedRequestMetadata();
+ virtual ~BatchedRequestMetadata();
+
+ //
+ // bson serializable interface implementation
+ //
+
+ virtual bool isValid(std::string* errMsg) const;
+ virtual BSONObj toBSON() const;
+ virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
+ virtual void clear();
+ virtual std::string toString() const;
+
+ void cloneTo(BatchedRequestMetadata* other) const;
+
+ //
+ // individual field accessors
+ //
+
+ void setShardName(StringData shardName);
+ void unsetShardName();
+ bool isShardNameSet() const;
+ const std::string& getShardName() const;
+
+ void setShardVersion(const ChunkVersion& shardVersion);
+ void unsetShardVersion();
+ bool isShardVersionSet() const;
+ const ChunkVersion& getShardVersion() const;
+
+ void setSession(long long session);
+ void unsetSession();
+ bool isSessionSet() const;
+ long long getSession() const;
+
+private:
+ // (O) shard name we're sending this batch to
+ std::string _shardName;
+ bool _isShardNameSet;
+
+ // (O) version for this collection on a given shard
+ std::unique_ptr<ChunkVersion> _shardVersion;
+
+ // (O) session number the inserts belong to
+ long long _session;
+ bool _isSessionSet;
+};
}
diff --git a/src/mongo/s/write_ops/batched_request_metadata_test.cpp b/src/mongo/s/write_ops/batched_request_metadata_test.cpp
index 562497b5d27..e0ca03231cc 100644
--- a/src/mongo/s/write_ops/batched_request_metadata_test.cpp
+++ b/src/mongo/s/write_ops/batched_request_metadata_test.cpp
@@ -22,32 +22,32 @@
namespace {
- using mongo::BSONArray;
- using mongo::BSONArrayBuilder;
- using mongo::BSONObj;
- using mongo::BatchedRequestMetadata;
- using mongo::OID;
- using mongo::Timestamp;
- using std::string;
-
- TEST(RoundTrip, Normal) {
- // The BSON_ARRAY macro doesn't support Timestamps.
- BSONArrayBuilder arrBuilder;
- arrBuilder.append(Timestamp(1,1));
- arrBuilder.append(OID::gen());
- BSONArray shardVersionArray = arrBuilder.arr();
-
- BSONObj metadataObj(BSON(BatchedRequestMetadata::shardName("shard0000") <<
- BatchedRequestMetadata::shardVersion() << shardVersionArray <<
- BatchedRequestMetadata::session(100)));
-
- string errMsg;
- BatchedRequestMetadata metadata;
- bool ok = metadata.parseBSON(metadataObj, &errMsg);
- ASSERT_TRUE(ok);
-
- BSONObj genMetadataObj = metadata.toBSON();
- ASSERT_EQUALS(0, genMetadataObj.woCompare(metadataObj));
- }
-
-} // unnamed namespace
+using mongo::BSONArray;
+using mongo::BSONArrayBuilder;
+using mongo::BSONObj;
+using mongo::BatchedRequestMetadata;
+using mongo::OID;
+using mongo::Timestamp;
+using std::string;
+
+TEST(RoundTrip, Normal) {
+ // The BSON_ARRAY macro doesn't support Timestamps.
+ BSONArrayBuilder arrBuilder;
+ arrBuilder.append(Timestamp(1, 1));
+ arrBuilder.append(OID::gen());
+ BSONArray shardVersionArray = arrBuilder.arr();
+
+ BSONObj metadataObj(BSON(BatchedRequestMetadata::shardName("shard0000")
+ << BatchedRequestMetadata::shardVersion() << shardVersionArray
+ << BatchedRequestMetadata::session(100)));
+
+ string errMsg;
+ BatchedRequestMetadata metadata;
+ bool ok = metadata.parseBSON(metadataObj, &errMsg);
+ ASSERT_TRUE(ok);
+
+ BSONObj genMetadataObj = metadata.toBSON();
+ ASSERT_EQUALS(0, genMetadataObj.woCompare(metadataObj));
+}
+
+} // unnamed namespace
diff --git a/src/mongo/s/write_ops/batched_update_document.cpp b/src/mongo/s/write_ops/batched_update_document.cpp
index ffebede8787..7e388a78b32 100644
--- a/src/mongo/s/write_ops/batched_update_document.cpp
+++ b/src/mongo/s/write_ops/batched_update_document.cpp
@@ -33,207 +33,209 @@
namespace mongo {
- using std::string;
+using std::string;
- using mongoutils::str::stream;
+using mongoutils::str::stream;
- const BSONField<BSONObj> BatchedUpdateDocument::query("q");
- const BSONField<BSONObj> BatchedUpdateDocument::updateExpr("u");
- const BSONField<bool> BatchedUpdateDocument::multi("multi", false);
- const BSONField<bool> BatchedUpdateDocument::upsert("upsert", false);
+const BSONField<BSONObj> BatchedUpdateDocument::query("q");
+const BSONField<BSONObj> BatchedUpdateDocument::updateExpr("u");
+const BSONField<bool> BatchedUpdateDocument::multi("multi", false);
+const BSONField<bool> BatchedUpdateDocument::upsert("upsert", false);
- BatchedUpdateDocument::BatchedUpdateDocument() {
- clear();
+BatchedUpdateDocument::BatchedUpdateDocument() {
+ clear();
+}
+
+BatchedUpdateDocument::~BatchedUpdateDocument() {}
+
+bool BatchedUpdateDocument::isValid(std::string* errMsg) const {
+ std::string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
}
- BatchedUpdateDocument::~BatchedUpdateDocument() {
+ // All the mandatory fields must be present.
+ if (!_isQuerySet) {
+ *errMsg = stream() << "missing " << query.name() << " field";
+ return false;
}
- bool BatchedUpdateDocument::isValid(std::string* errMsg) const {
- std::string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
+ if (!_isUpdateExprSet) {
+ *errMsg = stream() << "missing " << updateExpr.name() << " field";
+ return false;
+ }
- // All the mandatory fields must be present.
- if (!_isQuerySet) {
- *errMsg = stream() << "missing " << query.name() << " field";
- return false;
- }
+ return true;
+}
- if (!_isUpdateExprSet) {
- *errMsg = stream() << "missing " << updateExpr.name() << " field";
- return false;
- }
+BSONObj BatchedUpdateDocument::toBSON() const {
+ BSONObjBuilder builder;
- return true;
- }
+ if (_isQuerySet)
+ builder.append(query(), _query);
- BSONObj BatchedUpdateDocument::toBSON() const {
- BSONObjBuilder builder;
+ if (_isUpdateExprSet)
+ builder.append(updateExpr(), _updateExpr);
- if (_isQuerySet) builder.append(query(), _query);
+ if (_isMultiSet)
+ builder.append(multi(), _multi);
- if (_isUpdateExprSet) builder.append(updateExpr(), _updateExpr);
+ if (_isUpsertSet)
+ builder.append(upsert(), _upsert);
- if (_isMultiSet) builder.append(multi(), _multi);
+ return builder.obj();
+}
- if (_isUpsertSet) builder.append(upsert(), _upsert);
+bool BatchedUpdateDocument::parseBSON(const BSONObj& source, string* errMsg) {
+ clear();
- return builder.obj();
- }
+ std::string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
- bool BatchedUpdateDocument::parseBSON(const BSONObj& source, string* errMsg) {
- clear();
-
- std::string dummy;
- if (!errMsg) errMsg = &dummy;
-
- FieldParser::FieldState fieldState;
-
- BSONObjIterator it(source);
- while ( it.more() ) {
- BSONElement elem = it.next();
- StringData fieldName = elem.fieldNameStringData();
-
- if ( fieldName == query.name() ) {
- fieldState = FieldParser::extract(elem, query, &_query, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isQuerySet = fieldState == FieldParser::FIELD_SET;
- }
- else if ( fieldName == updateExpr.name() ) {
- fieldState = FieldParser::extract(elem, updateExpr, &_updateExpr, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isUpdateExprSet = fieldState == FieldParser::FIELD_SET;
- }
- else if ( fieldName == multi.name() ) {
- fieldState = FieldParser::extract(elem, multi, &_multi, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isMultiSet = fieldState == FieldParser::FIELD_SET;
- }
- else if ( fieldName == upsert.name() ) {
- fieldState = FieldParser::extract(elem, upsert, &_upsert, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isUpsertSet = fieldState == FieldParser::FIELD_SET;
- }
- }
+ FieldParser::FieldState fieldState;
- return true;
+ BSONObjIterator it(source);
+ while (it.more()) {
+ BSONElement elem = it.next();
+ StringData fieldName = elem.fieldNameStringData();
+
+ if (fieldName == query.name()) {
+ fieldState = FieldParser::extract(elem, query, &_query, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isQuerySet = fieldState == FieldParser::FIELD_SET;
+ } else if (fieldName == updateExpr.name()) {
+ fieldState = FieldParser::extract(elem, updateExpr, &_updateExpr, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isUpdateExprSet = fieldState == FieldParser::FIELD_SET;
+ } else if (fieldName == multi.name()) {
+ fieldState = FieldParser::extract(elem, multi, &_multi, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isMultiSet = fieldState == FieldParser::FIELD_SET;
+ } else if (fieldName == upsert.name()) {
+ fieldState = FieldParser::extract(elem, upsert, &_upsert, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isUpsertSet = fieldState == FieldParser::FIELD_SET;
+ }
}
- void BatchedUpdateDocument::clear() {
- _query = BSONObj();
- _isQuerySet = false;
+ return true;
+}
- _updateExpr = BSONObj();
- _isUpdateExprSet = false;
+void BatchedUpdateDocument::clear() {
+ _query = BSONObj();
+ _isQuerySet = false;
- _multi = false;
- _isMultiSet = false;
+ _updateExpr = BSONObj();
+ _isUpdateExprSet = false;
- _upsert = false;
- _isUpsertSet = false;
+ _multi = false;
+ _isMultiSet = false;
- }
+ _upsert = false;
+ _isUpsertSet = false;
+}
- void BatchedUpdateDocument::cloneTo(BatchedUpdateDocument* other) const {
- other->clear();
+void BatchedUpdateDocument::cloneTo(BatchedUpdateDocument* other) const {
+ other->clear();
- other->_query = _query;
- other->_isQuerySet = _isQuerySet;
+ other->_query = _query;
+ other->_isQuerySet = _isQuerySet;
- other->_updateExpr = _updateExpr;
- other->_isUpdateExprSet = _isUpdateExprSet;
+ other->_updateExpr = _updateExpr;
+ other->_isUpdateExprSet = _isUpdateExprSet;
- other->_multi = _multi;
- other->_isMultiSet = _isMultiSet;
+ other->_multi = _multi;
+ other->_isMultiSet = _isMultiSet;
- other->_upsert = _upsert;
- other->_isUpsertSet = _isUpsertSet;
- }
+ other->_upsert = _upsert;
+ other->_isUpsertSet = _isUpsertSet;
+}
- std::string BatchedUpdateDocument::toString() const {
- return toBSON().toString();
- }
+std::string BatchedUpdateDocument::toString() const {
+ return toBSON().toString();
+}
- void BatchedUpdateDocument::setQuery(const BSONObj& query) {
- _query = query.getOwned();
- _isQuerySet = true;
- }
+void BatchedUpdateDocument::setQuery(const BSONObj& query) {
+ _query = query.getOwned();
+ _isQuerySet = true;
+}
- void BatchedUpdateDocument::unsetQuery() {
- _isQuerySet = false;
- }
+void BatchedUpdateDocument::unsetQuery() {
+ _isQuerySet = false;
+}
- bool BatchedUpdateDocument::isQuerySet() const {
- return _isQuerySet;
- }
+bool BatchedUpdateDocument::isQuerySet() const {
+ return _isQuerySet;
+}
- const BSONObj& BatchedUpdateDocument::getQuery() const {
- dassert(_isQuerySet);
- return _query;
- }
+const BSONObj& BatchedUpdateDocument::getQuery() const {
+ dassert(_isQuerySet);
+ return _query;
+}
- void BatchedUpdateDocument::setUpdateExpr(const BSONObj& updateExpr) {
- _updateExpr = updateExpr.getOwned();
- _isUpdateExprSet = true;
- }
+void BatchedUpdateDocument::setUpdateExpr(const BSONObj& updateExpr) {
+ _updateExpr = updateExpr.getOwned();
+ _isUpdateExprSet = true;
+}
- void BatchedUpdateDocument::unsetUpdateExpr() {
- _isUpdateExprSet = false;
- }
+void BatchedUpdateDocument::unsetUpdateExpr() {
+ _isUpdateExprSet = false;
+}
- bool BatchedUpdateDocument::isUpdateExprSet() const {
- return _isUpdateExprSet;
- }
+bool BatchedUpdateDocument::isUpdateExprSet() const {
+ return _isUpdateExprSet;
+}
- const BSONObj& BatchedUpdateDocument::getUpdateExpr() const {
- dassert(_isUpdateExprSet);
- return _updateExpr;
- }
+const BSONObj& BatchedUpdateDocument::getUpdateExpr() const {
+ dassert(_isUpdateExprSet);
+ return _updateExpr;
+}
- void BatchedUpdateDocument::setMulti(bool multi) {
- _multi = multi;
- _isMultiSet = true;
- }
+void BatchedUpdateDocument::setMulti(bool multi) {
+ _multi = multi;
+ _isMultiSet = true;
+}
- void BatchedUpdateDocument::unsetMulti() {
- _isMultiSet = false;
- }
+void BatchedUpdateDocument::unsetMulti() {
+ _isMultiSet = false;
+}
- bool BatchedUpdateDocument::isMultiSet() const {
- return _isMultiSet;
- }
+bool BatchedUpdateDocument::isMultiSet() const {
+ return _isMultiSet;
+}
- bool BatchedUpdateDocument::getMulti() const {
- if (_isMultiSet) {
- return _multi;
- }
- else {
- return multi.getDefault();
- }
+bool BatchedUpdateDocument::getMulti() const {
+ if (_isMultiSet) {
+ return _multi;
+ } else {
+ return multi.getDefault();
}
+}
- void BatchedUpdateDocument::setUpsert(bool upsert) {
- _upsert = upsert;
- _isUpsertSet = true;
- }
+void BatchedUpdateDocument::setUpsert(bool upsert) {
+ _upsert = upsert;
+ _isUpsertSet = true;
+}
- void BatchedUpdateDocument::unsetUpsert() {
- _isUpsertSet = false;
- }
+void BatchedUpdateDocument::unsetUpsert() {
+ _isUpsertSet = false;
+}
- bool BatchedUpdateDocument::isUpsertSet() const {
- return _isUpsertSet;
- }
+bool BatchedUpdateDocument::isUpsertSet() const {
+ return _isUpsertSet;
+}
- bool BatchedUpdateDocument::getUpsert() const {
- if (_isUpsertSet) {
- return _upsert;
- }
- else {
- return upsert.getDefault();
- }
+bool BatchedUpdateDocument::getUpsert() const {
+ if (_isUpsertSet) {
+ return _upsert;
+ } else {
+ return upsert.getDefault();
}
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/batched_update_document.h b/src/mongo/s/write_ops/batched_update_document.h
index 0830e6c7f3c..ed9b6bc275b 100644
--- a/src/mongo/s/write_ops/batched_update_document.h
+++ b/src/mongo/s/write_ops/batched_update_document.h
@@ -37,85 +37,85 @@
namespace mongo {
- /**
- * This class represents the layout and content of a update document runCommand,
- * in the request side.
- */
- class BatchedUpdateDocument : public BSONSerializable {
- MONGO_DISALLOW_COPYING(BatchedUpdateDocument);
- public:
-
- //
- // schema declarations
- //
-
- static const BSONField<BSONObj> query;
- static const BSONField<BSONObj> updateExpr;
- static const BSONField<bool> multi;
- static const BSONField<bool> upsert;
-
- //
- // construction / destruction
- //
-
- BatchedUpdateDocument();
- virtual ~BatchedUpdateDocument();
-
- /** Copies all the fields present in 'this' to 'other'. */
- void cloneTo(BatchedUpdateDocument* other) const;
-
- //
- // bson serializable interface implementation
- //
-
- virtual bool isValid(std::string* errMsg) const;
- virtual BSONObj toBSON() const;
- virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
- virtual void clear();
- virtual std::string toString() const;
-
- //
- // individual field accessors
- //
-
- void setQuery(const BSONObj& query);
- void unsetQuery();
- bool isQuerySet() const;
- const BSONObj& getQuery() const;
-
- void setUpdateExpr(const BSONObj& updateExpr);
- void unsetUpdateExpr();
- bool isUpdateExprSet() const;
- const BSONObj& getUpdateExpr() const;
-
- void setMulti(bool multi);
- void unsetMulti();
- bool isMultiSet() const;
- bool getMulti() const;
-
- void setUpsert(bool upsert);
- void unsetUpsert();
- bool isUpsertSet() const;
- bool getUpsert() const;
-
- private:
- // Convention: (M)andatory, (O)ptional
-
- // (M) query whose result the update will manipulate
- BSONObj _query;
- bool _isQuerySet;
-
- // (M) the update expression itself
- BSONObj _updateExpr;
- bool _isUpdateExprSet;
-
- // (O) whether multiple documents are to be updated
- bool _multi;
- bool _isMultiSet;
-
- // (O) whether upserts are allowed
- bool _upsert;
- bool _isUpsertSet;
- };
-
-} // namespace mongo
+/**
+ * This class represents the layout and content of a update document runCommand,
+ * in the request side.
+ */
+class BatchedUpdateDocument : public BSONSerializable {
+ MONGO_DISALLOW_COPYING(BatchedUpdateDocument);
+
+public:
+ //
+ // schema declarations
+ //
+
+ static const BSONField<BSONObj> query;
+ static const BSONField<BSONObj> updateExpr;
+ static const BSONField<bool> multi;
+ static const BSONField<bool> upsert;
+
+ //
+ // construction / destruction
+ //
+
+ BatchedUpdateDocument();
+ virtual ~BatchedUpdateDocument();
+
+ /** Copies all the fields present in 'this' to 'other'. */
+ void cloneTo(BatchedUpdateDocument* other) const;
+
+ //
+ // bson serializable interface implementation
+ //
+
+ virtual bool isValid(std::string* errMsg) const;
+ virtual BSONObj toBSON() const;
+ virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
+ virtual void clear();
+ virtual std::string toString() const;
+
+ //
+ // individual field accessors
+ //
+
+ void setQuery(const BSONObj& query);
+ void unsetQuery();
+ bool isQuerySet() const;
+ const BSONObj& getQuery() const;
+
+ void setUpdateExpr(const BSONObj& updateExpr);
+ void unsetUpdateExpr();
+ bool isUpdateExprSet() const;
+ const BSONObj& getUpdateExpr() const;
+
+ void setMulti(bool multi);
+ void unsetMulti();
+ bool isMultiSet() const;
+ bool getMulti() const;
+
+ void setUpsert(bool upsert);
+ void unsetUpsert();
+ bool isUpsertSet() const;
+ bool getUpsert() const;
+
+private:
+ // Convention: (M)andatory, (O)ptional
+
+ // (M) query whose result the update will manipulate
+ BSONObj _query;
+ bool _isQuerySet;
+
+ // (M) the update expression itself
+ BSONObj _updateExpr;
+ bool _isUpdateExprSet;
+
+ // (O) whether multiple documents are to be updated
+ bool _multi;
+ bool _isMultiSet;
+
+ // (O) whether upserts are allowed
+ bool _upsert;
+ bool _isUpsertSet;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/batched_update_request.cpp b/src/mongo/s/write_ops/batched_update_request.cpp
index f2dda0be1de..0994cb04c6f 100644
--- a/src/mongo/s/write_ops/batched_update_request.cpp
+++ b/src/mongo/s/write_ops/batched_update_request.cpp
@@ -34,299 +34,303 @@
namespace mongo {
- using std::unique_ptr;
- using std::string;
-
- using mongoutils::str::stream;
-
- const std::string BatchedUpdateRequest::BATCHED_UPDATE_REQUEST = "update";
- const BSONField<std::string> BatchedUpdateRequest::collName("update");
- const BSONField<std::vector<BatchedUpdateDocument*> > BatchedUpdateRequest::updates("updates");
- const BSONField<BSONObj> BatchedUpdateRequest::writeConcern("writeConcern");
- const BSONField<bool> BatchedUpdateRequest::ordered("ordered", true);
- const BSONField<BSONObj> BatchedUpdateRequest::metadata("metadata");
-
- BatchedUpdateRequest::BatchedUpdateRequest() {
- clear();
+using std::unique_ptr;
+using std::string;
+
+using mongoutils::str::stream;
+
+const std::string BatchedUpdateRequest::BATCHED_UPDATE_REQUEST = "update";
+const BSONField<std::string> BatchedUpdateRequest::collName("update");
+const BSONField<std::vector<BatchedUpdateDocument*>> BatchedUpdateRequest::updates("updates");
+const BSONField<BSONObj> BatchedUpdateRequest::writeConcern("writeConcern");
+const BSONField<bool> BatchedUpdateRequest::ordered("ordered", true);
+const BSONField<BSONObj> BatchedUpdateRequest::metadata("metadata");
+
+BatchedUpdateRequest::BatchedUpdateRequest() {
+ clear();
+}
+
+BatchedUpdateRequest::~BatchedUpdateRequest() {
+ unsetUpdates();
+}
+
+bool BatchedUpdateRequest::isValid(std::string* errMsg) const {
+ std::string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
}
- BatchedUpdateRequest::~BatchedUpdateRequest() {
- unsetUpdates();
+ // All the mandatory fields must be present.
+ if (!_isCollNameSet) {
+ *errMsg = stream() << "missing " << collName.name() << " field";
+ return false;
}
- bool BatchedUpdateRequest::isValid(std::string* errMsg) const {
- std::string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
-
- // All the mandatory fields must be present.
- if (!_isCollNameSet) {
- *errMsg = stream() << "missing " << collName.name() << " field";
- return false;
- }
-
- if (!_isUpdatesSet) {
- *errMsg = stream() << "missing " << updates.name() << " field";
- return false;
- }
-
- return true;
+ if (!_isUpdatesSet) {
+ *errMsg = stream() << "missing " << updates.name() << " field";
+ return false;
}
- BSONObj BatchedUpdateRequest::toBSON() const {
- BSONObjBuilder builder;
+ return true;
+}
- if (_isCollNameSet) builder.append(collName(), _collName);
+BSONObj BatchedUpdateRequest::toBSON() const {
+ BSONObjBuilder builder;
- if (_isUpdatesSet) {
- BSONArrayBuilder updatesBuilder(builder.subarrayStart(updates()));
- for (std::vector<BatchedUpdateDocument*>::const_iterator it = _updates.begin();
- it != _updates.end();
- ++it) {
- BSONObj updateDocument = (*it)->toBSON();
- updatesBuilder.append(updateDocument);
- }
- updatesBuilder.done();
- }
-
- if (_isWriteConcernSet) builder.append(writeConcern(), _writeConcern);
-
- if (_isOrderedSet) builder.append(ordered(), _ordered);
-
- if (_metadata) builder.append(metadata(), _metadata->toBSON());
-
- if (_shouldBypassValidation) builder.append(bypassDocumentValidationCommandOption(), true);
+ if (_isCollNameSet)
+ builder.append(collName(), _collName);
- return builder.obj();
+ if (_isUpdatesSet) {
+ BSONArrayBuilder updatesBuilder(builder.subarrayStart(updates()));
+ for (std::vector<BatchedUpdateDocument*>::const_iterator it = _updates.begin();
+ it != _updates.end();
+ ++it) {
+ BSONObj updateDocument = (*it)->toBSON();
+ updatesBuilder.append(updateDocument);
+ }
+ updatesBuilder.done();
}
- bool BatchedUpdateRequest::parseBSON(const BSONObj& source, string* errMsg) {
- clear();
-
- std::string dummy;
- if (!errMsg) errMsg = &dummy;
-
- FieldParser::FieldState fieldState;
-
- BSONObjIterator it( source );
- while ( it.more() ) {
- const BSONElement& elem = it.next();
- StringData fieldName = elem.fieldNameStringData();
-
- if ( fieldName == collName.name() ) {
- std::string collNameTemp;
- fieldState = FieldParser::extract(elem, collName, &collNameTemp, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _collName = NamespaceString(collNameTemp);
- _isCollNameSet = fieldState == FieldParser::FIELD_SET;
- }
- else if ( fieldName == updates.name() ) {
- fieldState = FieldParser::extract(elem, updates, &_updates, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isUpdatesSet = fieldState == FieldParser::FIELD_SET;
- }
- else if ( fieldName == writeConcern.name() ) {
- fieldState = FieldParser::extract(elem, writeConcern, &_writeConcern, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isWriteConcernSet = fieldState == FieldParser::FIELD_SET;
- }
- else if ( fieldName == ordered.name() ) {
- fieldState = FieldParser::extract(elem, ordered, &_ordered, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isOrderedSet = fieldState == FieldParser::FIELD_SET;
- }
- else if ( fieldName == metadata.name() ) {
- BSONObj metadataObj;
- fieldState = FieldParser::extract(elem, metadata, &metadataObj, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
-
- if (!metadataObj.isEmpty()) {
- _metadata.reset(new BatchedRequestMetadata());
- if (!_metadata->parseBSON(metadataObj, errMsg)) {
- return false;
- }
+ if (_isWriteConcernSet)
+ builder.append(writeConcern(), _writeConcern);
+
+ if (_isOrderedSet)
+ builder.append(ordered(), _ordered);
+
+ if (_metadata)
+ builder.append(metadata(), _metadata->toBSON());
+
+ if (_shouldBypassValidation)
+ builder.append(bypassDocumentValidationCommandOption(), true);
+
+ return builder.obj();
+}
+
+bool BatchedUpdateRequest::parseBSON(const BSONObj& source, string* errMsg) {
+ clear();
+
+ std::string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
+
+ FieldParser::FieldState fieldState;
+
+ BSONObjIterator it(source);
+ while (it.more()) {
+ const BSONElement& elem = it.next();
+ StringData fieldName = elem.fieldNameStringData();
+
+ if (fieldName == collName.name()) {
+ std::string collNameTemp;
+ fieldState = FieldParser::extract(elem, collName, &collNameTemp, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _collName = NamespaceString(collNameTemp);
+ _isCollNameSet = fieldState == FieldParser::FIELD_SET;
+ } else if (fieldName == updates.name()) {
+ fieldState = FieldParser::extract(elem, updates, &_updates, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isUpdatesSet = fieldState == FieldParser::FIELD_SET;
+ } else if (fieldName == writeConcern.name()) {
+ fieldState = FieldParser::extract(elem, writeConcern, &_writeConcern, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isWriteConcernSet = fieldState == FieldParser::FIELD_SET;
+ } else if (fieldName == ordered.name()) {
+ fieldState = FieldParser::extract(elem, ordered, &_ordered, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isOrderedSet = fieldState == FieldParser::FIELD_SET;
+ } else if (fieldName == metadata.name()) {
+ BSONObj metadataObj;
+ fieldState = FieldParser::extract(elem, metadata, &metadataObj, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+
+ if (!metadataObj.isEmpty()) {
+ _metadata.reset(new BatchedRequestMetadata());
+ if (!_metadata->parseBSON(metadataObj, errMsg)) {
+ return false;
}
}
- else if (fieldName == bypassDocumentValidationCommandOption()) {
- _shouldBypassValidation = elem.trueValue();
- }
- }
- return true;
- }
-
- void BatchedUpdateRequest::clear() {
- _collName = NamespaceString();
- _isCollNameSet = false;
-
- unsetUpdates();
-
- _writeConcern = BSONObj();
- _isWriteConcernSet = false;
-
- _ordered = false;
- _isOrderedSet = false;
-
- _shouldBypassValidation = false;
-
- _metadata.reset();
- }
-
- void BatchedUpdateRequest::cloneTo(BatchedUpdateRequest* other) const {
- other->clear();
-
- other->_collName = _collName;
- other->_isCollNameSet = _isCollNameSet;
-
- for(std::vector<BatchedUpdateDocument*>::const_iterator it = _updates.begin();
- it != _updates.end();
- ++it) {
- unique_ptr<BatchedUpdateDocument> tempBatchUpdateDocument(new BatchedUpdateDocument);
- (*it)->cloneTo(tempBatchUpdateDocument.get());
- other->addToUpdates(tempBatchUpdateDocument.release());
- }
- other->_isUpdatesSet = _isUpdatesSet;
-
- other->_writeConcern = _writeConcern;
- other->_isWriteConcernSet = _isWriteConcernSet;
-
- other->_ordered = _ordered;
- other->_isOrderedSet = _isOrderedSet;
-
- if (_metadata) {
- other->_metadata.reset(new BatchedRequestMetadata());
- _metadata->cloneTo(other->_metadata.get());
+ } else if (fieldName == bypassDocumentValidationCommandOption()) {
+ _shouldBypassValidation = elem.trueValue();
}
}
+ return true;
+}
- std::string BatchedUpdateRequest::toString() const {
- return toBSON().toString();
- }
+void BatchedUpdateRequest::clear() {
+ _collName = NamespaceString();
+ _isCollNameSet = false;
- void BatchedUpdateRequest::setCollName(StringData collName) {
- _collName = NamespaceString(collName);
- _isCollNameSet = true;
- }
+ unsetUpdates();
- const std::string& BatchedUpdateRequest::getCollName() const {
- dassert(_isCollNameSet);
- return _collName.ns();
- }
+ _writeConcern = BSONObj();
+ _isWriteConcernSet = false;
- void BatchedUpdateRequest::setCollNameNS(const NamespaceString& collName) {
- _collName = collName;
- _isCollNameSet = true;
- }
+ _ordered = false;
+ _isOrderedSet = false;
- const NamespaceString& BatchedUpdateRequest::getCollNameNS() const {
- dassert(_isCollNameSet);
- return _collName;
- }
+ _shouldBypassValidation = false;
- const NamespaceString& BatchedUpdateRequest::getTargetingNSS() const {
- return getCollNameNS();
- }
+ _metadata.reset();
+}
- void BatchedUpdateRequest::setUpdates(const std::vector<BatchedUpdateDocument*>& updates) {
- unsetUpdates();
- for (std::vector<BatchedUpdateDocument*>::const_iterator it = updates.begin();
- it != updates.end();
- ++it) {
- unique_ptr<BatchedUpdateDocument> tempBatchUpdateDocument(new BatchedUpdateDocument);
- (*it)->cloneTo(tempBatchUpdateDocument.get());
- addToUpdates(tempBatchUpdateDocument.release());
- }
- _isUpdatesSet = updates.size() > 0;
- }
+void BatchedUpdateRequest::cloneTo(BatchedUpdateRequest* other) const {
+ other->clear();
- void BatchedUpdateRequest::addToUpdates(BatchedUpdateDocument* updates) {
- _updates.push_back(updates);
- _isUpdatesSet = true;
- }
+ other->_collName = _collName;
+ other->_isCollNameSet = _isCollNameSet;
- void BatchedUpdateRequest::unsetUpdates() {
- for(std::vector<BatchedUpdateDocument*>::iterator it = _updates.begin();
- it != _updates.end();
- ++it) {
- delete *it;
- }
- _updates.clear();
- _isUpdatesSet = false;
+ for (std::vector<BatchedUpdateDocument*>::const_iterator it = _updates.begin();
+ it != _updates.end();
+ ++it) {
+ unique_ptr<BatchedUpdateDocument> tempBatchUpdateDocument(new BatchedUpdateDocument);
+ (*it)->cloneTo(tempBatchUpdateDocument.get());
+ other->addToUpdates(tempBatchUpdateDocument.release());
}
+ other->_isUpdatesSet = _isUpdatesSet;
- bool BatchedUpdateRequest::isUpdatesSet() const {
- return _isUpdatesSet;
- }
+ other->_writeConcern = _writeConcern;
+ other->_isWriteConcernSet = _isWriteConcernSet;
- size_t BatchedUpdateRequest::sizeUpdates() const {
- return _updates.size();
- }
+ other->_ordered = _ordered;
+ other->_isOrderedSet = _isOrderedSet;
- const std::vector<BatchedUpdateDocument*>& BatchedUpdateRequest::getUpdates() const {
- dassert(_isUpdatesSet);
- return _updates;
+ if (_metadata) {
+ other->_metadata.reset(new BatchedRequestMetadata());
+ _metadata->cloneTo(other->_metadata.get());
}
-
- const BatchedUpdateDocument* BatchedUpdateRequest::getUpdatesAt(size_t pos) const {
- dassert(_isUpdatesSet);
- dassert(_updates.size() > pos);
- return _updates.at(pos);
+}
+
+std::string BatchedUpdateRequest::toString() const {
+ return toBSON().toString();
+}
+
+void BatchedUpdateRequest::setCollName(StringData collName) {
+ _collName = NamespaceString(collName);
+ _isCollNameSet = true;
+}
+
+const std::string& BatchedUpdateRequest::getCollName() const {
+ dassert(_isCollNameSet);
+ return _collName.ns();
+}
+
+void BatchedUpdateRequest::setCollNameNS(const NamespaceString& collName) {
+ _collName = collName;
+ _isCollNameSet = true;
+}
+
+const NamespaceString& BatchedUpdateRequest::getCollNameNS() const {
+ dassert(_isCollNameSet);
+ return _collName;
+}
+
+const NamespaceString& BatchedUpdateRequest::getTargetingNSS() const {
+ return getCollNameNS();
+}
+
+void BatchedUpdateRequest::setUpdates(const std::vector<BatchedUpdateDocument*>& updates) {
+ unsetUpdates();
+ for (std::vector<BatchedUpdateDocument*>::const_iterator it = updates.begin();
+ it != updates.end();
+ ++it) {
+ unique_ptr<BatchedUpdateDocument> tempBatchUpdateDocument(new BatchedUpdateDocument);
+ (*it)->cloneTo(tempBatchUpdateDocument.get());
+ addToUpdates(tempBatchUpdateDocument.release());
}
-
- void BatchedUpdateRequest::setWriteConcern(const BSONObj& writeConcern) {
- _writeConcern = writeConcern.getOwned();
- _isWriteConcernSet = true;
- }
-
- void BatchedUpdateRequest::unsetWriteConcern() {
- _isWriteConcernSet = false;
- }
-
- bool BatchedUpdateRequest::isWriteConcernSet() const {
- return _isWriteConcernSet;
- }
-
- const BSONObj& BatchedUpdateRequest::getWriteConcern() const {
- dassert(_isWriteConcernSet);
- return _writeConcern;
- }
-
- void BatchedUpdateRequest::setOrdered(bool ordered) {
- _ordered = ordered;
- _isOrderedSet = true;
- }
-
- void BatchedUpdateRequest::unsetOrdered() {
- _isOrderedSet = false;
- }
-
- bool BatchedUpdateRequest::isOrderedSet() const {
- return _isOrderedSet;
+ _isUpdatesSet = updates.size() > 0;
+}
+
+void BatchedUpdateRequest::addToUpdates(BatchedUpdateDocument* updates) {
+ _updates.push_back(updates);
+ _isUpdatesSet = true;
+}
+
+void BatchedUpdateRequest::unsetUpdates() {
+ for (std::vector<BatchedUpdateDocument*>::iterator it = _updates.begin(); it != _updates.end();
+ ++it) {
+ delete *it;
}
-
- bool BatchedUpdateRequest::getOrdered() const {
- if (_isOrderedSet) {
- return _ordered;
- }
- else {
- return ordered.getDefault();
- }
+ _updates.clear();
+ _isUpdatesSet = false;
+}
+
+bool BatchedUpdateRequest::isUpdatesSet() const {
+ return _isUpdatesSet;
+}
+
+size_t BatchedUpdateRequest::sizeUpdates() const {
+ return _updates.size();
+}
+
+const std::vector<BatchedUpdateDocument*>& BatchedUpdateRequest::getUpdates() const {
+ dassert(_isUpdatesSet);
+ return _updates;
+}
+
+const BatchedUpdateDocument* BatchedUpdateRequest::getUpdatesAt(size_t pos) const {
+ dassert(_isUpdatesSet);
+ dassert(_updates.size() > pos);
+ return _updates.at(pos);
+}
+
+void BatchedUpdateRequest::setWriteConcern(const BSONObj& writeConcern) {
+ _writeConcern = writeConcern.getOwned();
+ _isWriteConcernSet = true;
+}
+
+void BatchedUpdateRequest::unsetWriteConcern() {
+ _isWriteConcernSet = false;
+}
+
+bool BatchedUpdateRequest::isWriteConcernSet() const {
+ return _isWriteConcernSet;
+}
+
+const BSONObj& BatchedUpdateRequest::getWriteConcern() const {
+ dassert(_isWriteConcernSet);
+ return _writeConcern;
+}
+
+void BatchedUpdateRequest::setOrdered(bool ordered) {
+ _ordered = ordered;
+ _isOrderedSet = true;
+}
+
+void BatchedUpdateRequest::unsetOrdered() {
+ _isOrderedSet = false;
+}
+
+bool BatchedUpdateRequest::isOrderedSet() const {
+ return _isOrderedSet;
+}
+
+bool BatchedUpdateRequest::getOrdered() const {
+ if (_isOrderedSet) {
+ return _ordered;
+ } else {
+ return ordered.getDefault();
}
+}
- void BatchedUpdateRequest::setMetadata(BatchedRequestMetadata* metadata) {
- _metadata.reset(metadata);
- }
+void BatchedUpdateRequest::setMetadata(BatchedRequestMetadata* metadata) {
+ _metadata.reset(metadata);
+}
- void BatchedUpdateRequest::unsetMetadata() {
- _metadata.reset();
- }
+void BatchedUpdateRequest::unsetMetadata() {
+ _metadata.reset();
+}
- bool BatchedUpdateRequest::isMetadataSet() const {
- return _metadata.get();
- }
+bool BatchedUpdateRequest::isMetadataSet() const {
+ return _metadata.get();
+}
- BatchedRequestMetadata* BatchedUpdateRequest::getMetadata() const {
- return _metadata.get();
- }
+BatchedRequestMetadata* BatchedUpdateRequest::getMetadata() const {
+ return _metadata.get();
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/batched_update_request.h b/src/mongo/s/write_ops/batched_update_request.h
index db99188b7b3..6042f4c6cf2 100644
--- a/src/mongo/s/write_ops/batched_update_request.h
+++ b/src/mongo/s/write_ops/batched_update_request.h
@@ -40,116 +40,120 @@
namespace mongo {
+/**
+ * This class represents the layout and content of a batched update runCommand,
+ * the request side.
+ */
+class BatchedUpdateRequest : public BSONSerializable {
+ MONGO_DISALLOW_COPYING(BatchedUpdateRequest);
+
+public:
+ //
+ // schema declarations
+ //
+
+ // Name used for the batched update invocation.
+ static const std::string BATCHED_UPDATE_REQUEST;
+
+ // Field names and types in the batched update command type.
+ static const BSONField<std::string> collName;
+ static const BSONField<std::vector<BatchedUpdateDocument*>> updates;
+ static const BSONField<BSONObj> writeConcern;
+ static const BSONField<bool> ordered;
+ static const BSONField<BSONObj> metadata;
+
+ //
+ // construction / destruction
+ //
+
+ BatchedUpdateRequest();
+ virtual ~BatchedUpdateRequest();
+
+ /** Copies all the fields present in 'this' to 'other'. */
+ void cloneTo(BatchedUpdateRequest* other) const;
+
+ //
+ // bson serializable interface implementation
+ //
+
+ virtual bool isValid(std::string* errMsg) const;
+ virtual BSONObj toBSON() const;
+ virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
+ virtual void clear();
+ virtual std::string toString() const;
+
+ //
+ // individual field accessors
+ //
+
+ void setCollName(StringData collName);
+ void setCollNameNS(const NamespaceString& collName);
+ const std::string& getCollName() const;
+ const NamespaceString& getCollNameNS() const;
+
+ const NamespaceString& getTargetingNSS() const;
+
+ void setUpdates(const std::vector<BatchedUpdateDocument*>& updates);
+
/**
- * This class represents the layout and content of a batched update runCommand,
- * the request side.
+ * updates ownership is transferred to here.
*/
- class BatchedUpdateRequest : public BSONSerializable {
- MONGO_DISALLOW_COPYING(BatchedUpdateRequest);
- public:
-
- //
- // schema declarations
- //
-
- // Name used for the batched update invocation.
- static const std::string BATCHED_UPDATE_REQUEST;
-
- // Field names and types in the batched update command type.
- static const BSONField<std::string> collName;
- static const BSONField<std::vector<BatchedUpdateDocument*> > updates;
- static const BSONField<BSONObj> writeConcern;
- static const BSONField<bool> ordered;
- static const BSONField<BSONObj> metadata;
-
- //
- // construction / destruction
- //
-
- BatchedUpdateRequest();
- virtual ~BatchedUpdateRequest();
-
- /** Copies all the fields present in 'this' to 'other'. */
- void cloneTo(BatchedUpdateRequest* other) const;
-
- //
- // bson serializable interface implementation
- //
-
- virtual bool isValid(std::string* errMsg) const;
- virtual BSONObj toBSON() const;
- virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
- virtual void clear();
- virtual std::string toString() const;
-
- //
- // individual field accessors
- //
-
- void setCollName(StringData collName);
- void setCollNameNS(const NamespaceString& collName);
- const std::string& getCollName() const;
- const NamespaceString& getCollNameNS() const;
-
- const NamespaceString& getTargetingNSS() const;
-
- void setUpdates(const std::vector<BatchedUpdateDocument*>& updates);
-
- /**
- * updates ownership is transferred to here.
- */
- void addToUpdates(BatchedUpdateDocument* updates);
- void unsetUpdates();
- bool isUpdatesSet() const;
- std::size_t sizeUpdates() const;
- const std::vector<BatchedUpdateDocument*>& getUpdates() const;
- const BatchedUpdateDocument* getUpdatesAt(std::size_t pos) const;
-
- void setWriteConcern(const BSONObj& writeConcern);
- void unsetWriteConcern();
- bool isWriteConcernSet() const;
- const BSONObj& getWriteConcern() const;
-
- void setOrdered(bool ordered);
- void unsetOrdered();
- bool isOrderedSet() const;
- bool getOrdered() const;
-
- void setShouldBypassValidation(bool newVal) { _shouldBypassValidation = newVal; }
- bool shouldBypassValidation() const { return _shouldBypassValidation; }
-
- /*
- * metadata ownership will be transferred to this.
- */
- void setMetadata(BatchedRequestMetadata* metadata);
- void unsetMetadata();
- bool isMetadataSet() const;
- BatchedRequestMetadata* getMetadata() const;
-
- private:
- // Convention: (M)andatory, (O)ptional
-
- // (M) collection we're updating from
- NamespaceString _collName;
- bool _isCollNameSet;
-
- // (M) array of individual updates
- std::vector<BatchedUpdateDocument*> _updates;
- bool _isUpdatesSet;
-
- // (O) to be issued after the batch applied
- BSONObj _writeConcern;
- bool _isWriteConcernSet;
-
- // (O) whether batch is issued in parallel or not
- bool _ordered;
- bool _isOrderedSet;
-
- // (O) should document validation be bypassed (default false)
- bool _shouldBypassValidation;
-
- // (O) metadata associated with this request for internal use.
- std::unique_ptr<BatchedRequestMetadata> _metadata;
- };
-
-} // namespace mongo
+ void addToUpdates(BatchedUpdateDocument* updates);
+ void unsetUpdates();
+ bool isUpdatesSet() const;
+ std::size_t sizeUpdates() const;
+ const std::vector<BatchedUpdateDocument*>& getUpdates() const;
+ const BatchedUpdateDocument* getUpdatesAt(std::size_t pos) const;
+
+ void setWriteConcern(const BSONObj& writeConcern);
+ void unsetWriteConcern();
+ bool isWriteConcernSet() const;
+ const BSONObj& getWriteConcern() const;
+
+ void setOrdered(bool ordered);
+ void unsetOrdered();
+ bool isOrderedSet() const;
+ bool getOrdered() const;
+
+ void setShouldBypassValidation(bool newVal) {
+ _shouldBypassValidation = newVal;
+ }
+ bool shouldBypassValidation() const {
+ return _shouldBypassValidation;
+ }
+
+ /*
+ * metadata ownership will be transferred to this.
+ */
+ void setMetadata(BatchedRequestMetadata* metadata);
+ void unsetMetadata();
+ bool isMetadataSet() const;
+ BatchedRequestMetadata* getMetadata() const;
+
+private:
+ // Convention: (M)andatory, (O)ptional
+
+ // (M) collection we're updating from
+ NamespaceString _collName;
+ bool _isCollNameSet;
+
+ // (M) array of individual updates
+ std::vector<BatchedUpdateDocument*> _updates;
+ bool _isUpdatesSet;
+
+ // (O) to be issued after the batch applied
+ BSONObj _writeConcern;
+ bool _isWriteConcernSet;
+
+ // (O) whether batch is issued in parallel or not
+ bool _ordered;
+ bool _isOrderedSet;
+
+ // (O) should document validation be bypassed (default false)
+ bool _shouldBypassValidation;
+
+ // (O) metadata associated with this request for internal use.
+ std::unique_ptr<BatchedRequestMetadata> _metadata;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/batched_update_request_test.cpp b/src/mongo/s/write_ops/batched_update_request_test.cpp
index c8e69b92b06..662bf079b9f 100644
--- a/src/mongo/s/write_ops/batched_update_request_test.cpp
+++ b/src/mongo/s/write_ops/batched_update_request_test.cpp
@@ -36,56 +36,49 @@
namespace {
- using std::string;
- using mongo::BatchedUpdateDocument;
- using mongo::BatchedUpdateRequest;
- using mongo::BatchedRequestMetadata;
- using mongo::BSONArray;
- using mongo::BSONArrayBuilder;
- using mongo::BSONObj;
- using mongo::OID;
- using mongo::Timestamp;
+using std::string;
+using mongo::BatchedUpdateDocument;
+using mongo::BatchedUpdateRequest;
+using mongo::BatchedRequestMetadata;
+using mongo::BSONArray;
+using mongo::BSONArrayBuilder;
+using mongo::BSONObj;
+using mongo::OID;
+using mongo::Timestamp;
- TEST(RoundTrip, Normal) {
- BSONArray updateArray =
- BSON_ARRAY(
- BSON(BatchedUpdateDocument::query(BSON("a" << 1)) <<
- BatchedUpdateDocument::updateExpr(BSON("$set" << BSON("a" << 1))) <<
- BatchedUpdateDocument::multi(false) <<
- BatchedUpdateDocument::upsert(false)
- ) <<
- BSON(BatchedUpdateDocument::query(BSON("b" << 1)) <<
- BatchedUpdateDocument::updateExpr(BSON("$set" << BSON("b" << 2))) <<
- BatchedUpdateDocument::multi(false) <<
- BatchedUpdateDocument::upsert(false)
- )
- );
+TEST(RoundTrip, Normal) {
+ BSONArray updateArray = BSON_ARRAY(
+ BSON(BatchedUpdateDocument::query(BSON("a" << 1))
+ << BatchedUpdateDocument::updateExpr(BSON("$set" << BSON("a" << 1)))
+ << BatchedUpdateDocument::multi(false) << BatchedUpdateDocument::upsert(false))
+ << BSON(BatchedUpdateDocument::query(BSON("b" << 1))
+ << BatchedUpdateDocument::updateExpr(BSON("$set" << BSON("b" << 2)))
+ << BatchedUpdateDocument::multi(false) << BatchedUpdateDocument::upsert(false)));
- BSONObj writeConcernObj = BSON("w" << 1);
+ BSONObj writeConcernObj = BSON("w" << 1);
- // The BSON_ARRAY macro doesn't support Timestamps.
- BSONArrayBuilder arrBuilder;
- arrBuilder.append(Timestamp(1,1));
- arrBuilder.append(OID::gen());
- BSONArray shardVersionArray = arrBuilder.arr();
+ // The BSON_ARRAY macro doesn't support Timestamps.
+ BSONArrayBuilder arrBuilder;
+ arrBuilder.append(Timestamp(1, 1));
+ arrBuilder.append(OID::gen());
+ BSONArray shardVersionArray = arrBuilder.arr();
- BSONObj origUpdateRequestObj =
- BSON(BatchedUpdateRequest::collName("test") <<
- BatchedUpdateRequest::updates() << updateArray <<
- BatchedUpdateRequest::writeConcern(writeConcernObj) <<
- BatchedUpdateRequest::ordered(true) <<
- BatchedUpdateRequest::metadata() << BSON(
- BatchedRequestMetadata::shardName("shard0000") <<
- BatchedRequestMetadata::shardVersion() << shardVersionArray <<
- BatchedRequestMetadata::session(0)));
+ BSONObj origUpdateRequestObj =
+ BSON(BatchedUpdateRequest::collName("test")
+ << BatchedUpdateRequest::updates() << updateArray
+ << BatchedUpdateRequest::writeConcern(writeConcernObj)
+ << BatchedUpdateRequest::ordered(true) << BatchedUpdateRequest::metadata()
+ << BSON(BatchedRequestMetadata::shardName("shard0000")
+ << BatchedRequestMetadata::shardVersion() << shardVersionArray
+ << BatchedRequestMetadata::session(0)));
- string errMsg;
- BatchedUpdateRequest request;
- bool ok = request.parseBSON(origUpdateRequestObj, &errMsg);
- ASSERT_TRUE(ok);
+ string errMsg;
+ BatchedUpdateRequest request;
+ bool ok = request.parseBSON(origUpdateRequestObj, &errMsg);
+ ASSERT_TRUE(ok);
- BSONObj genUpdateRequestObj = request.toBSON();
- ASSERT_EQUALS(0, genUpdateRequestObj.woCompare(origUpdateRequestObj));
- }
+ BSONObj genUpdateRequestObj = request.toBSON();
+ ASSERT_EQUALS(0, genUpdateRequestObj.woCompare(origUpdateRequestObj));
+}
-} // unnamed namespace
+} // unnamed namespace
diff --git a/src/mongo/s/write_ops/batched_upsert_detail.cpp b/src/mongo/s/write_ops/batched_upsert_detail.cpp
index d6764a22c39..cbc26eadcef 100644
--- a/src/mongo/s/write_ops/batched_upsert_detail.cpp
+++ b/src/mongo/s/write_ops/batched_upsert_detail.cpp
@@ -33,128 +33,130 @@
namespace mongo {
- using std::string;
+using std::string;
- using mongoutils::str::stream;
+using mongoutils::str::stream;
- const BSONField<int> BatchedUpsertDetail::index("index");
- const BSONField<BSONObj> BatchedUpsertDetail::upsertedID("_id");
+const BSONField<int> BatchedUpsertDetail::index("index");
+const BSONField<BSONObj> BatchedUpsertDetail::upsertedID("_id");
- BatchedUpsertDetail::BatchedUpsertDetail() {
- clear();
- }
-
- BatchedUpsertDetail::~BatchedUpsertDetail() {
- }
+BatchedUpsertDetail::BatchedUpsertDetail() {
+ clear();
+}
- bool BatchedUpsertDetail::isValid(std::string* errMsg) const {
- std::string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
+BatchedUpsertDetail::~BatchedUpsertDetail() {}
- // All the mandatory fields must be present.
- if (!_isIndexSet) {
- *errMsg = stream() << "missing " << index.name() << " field";
- return false;
- }
+bool BatchedUpsertDetail::isValid(std::string* errMsg) const {
+ std::string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
+ }
- if (!_isUpsertedIDSet) {
- *errMsg = stream() << "missing " << upsertedID.name() << " field";
- return false;
- }
+ // All the mandatory fields must be present.
+ if (!_isIndexSet) {
+ *errMsg = stream() << "missing " << index.name() << " field";
+ return false;
+ }
- return true;
+ if (!_isUpsertedIDSet) {
+ *errMsg = stream() << "missing " << upsertedID.name() << " field";
+ return false;
}
- BSONObj BatchedUpsertDetail::toBSON() const {
- BSONObjBuilder builder;
+ return true;
+}
- if (_isIndexSet) builder.append(index(), _index);
+BSONObj BatchedUpsertDetail::toBSON() const {
+ BSONObjBuilder builder;
- // We're using the BSONObj to store the _id value.
- if (_isUpsertedIDSet) {
- builder.appendAs(_upsertedID.firstElement(), upsertedID());
- }
+ if (_isIndexSet)
+ builder.append(index(), _index);
- return builder.obj();
+ // We're using the BSONObj to store the _id value.
+ if (_isUpsertedIDSet) {
+ builder.appendAs(_upsertedID.firstElement(), upsertedID());
}
- bool BatchedUpsertDetail::parseBSON(const BSONObj& source, string* errMsg) {
- clear();
+ return builder.obj();
+}
- std::string dummy;
- if (!errMsg) errMsg = &dummy;
+bool BatchedUpsertDetail::parseBSON(const BSONObj& source, string* errMsg) {
+ clear();
- FieldParser::FieldState fieldState;
- fieldState = FieldParser::extract(source, index, &_index, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isIndexSet = fieldState == FieldParser::FIELD_SET;
+ std::string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
- fieldState = FieldParser::extractID(source, upsertedID, &_upsertedID, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isUpsertedIDSet = fieldState == FieldParser::FIELD_SET;
+ FieldParser::FieldState fieldState;
+ fieldState = FieldParser::extract(source, index, &_index, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isIndexSet = fieldState == FieldParser::FIELD_SET;
- return true;
- }
+ fieldState = FieldParser::extractID(source, upsertedID, &_upsertedID, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isUpsertedIDSet = fieldState == FieldParser::FIELD_SET;
- void BatchedUpsertDetail::clear() {
- _index = 0;
- _isIndexSet = false;
+ return true;
+}
- _upsertedID = BSONObj();
- _isUpsertedIDSet = false;
+void BatchedUpsertDetail::clear() {
+ _index = 0;
+ _isIndexSet = false;
- }
+ _upsertedID = BSONObj();
+ _isUpsertedIDSet = false;
+}
- void BatchedUpsertDetail::cloneTo(BatchedUpsertDetail* other) const {
- other->clear();
+void BatchedUpsertDetail::cloneTo(BatchedUpsertDetail* other) const {
+ other->clear();
- other->_index = _index;
- other->_isIndexSet = _isIndexSet;
+ other->_index = _index;
+ other->_isIndexSet = _isIndexSet;
- other->_upsertedID = _upsertedID;
- other->_isUpsertedIDSet = _isUpsertedIDSet;
- }
+ other->_upsertedID = _upsertedID;
+ other->_isUpsertedIDSet = _isUpsertedIDSet;
+}
- std::string BatchedUpsertDetail::toString() const {
- return "implement me";
- }
+std::string BatchedUpsertDetail::toString() const {
+ return "implement me";
+}
- void BatchedUpsertDetail::setIndex(int index) {
- _index = index;
- _isIndexSet = true;
- }
+void BatchedUpsertDetail::setIndex(int index) {
+ _index = index;
+ _isIndexSet = true;
+}
- void BatchedUpsertDetail::unsetIndex() {
- _isIndexSet = false;
- }
+void BatchedUpsertDetail::unsetIndex() {
+ _isIndexSet = false;
+}
- bool BatchedUpsertDetail::isIndexSet() const {
- return _isIndexSet;
- }
+bool BatchedUpsertDetail::isIndexSet() const {
+ return _isIndexSet;
+}
- int BatchedUpsertDetail::getIndex() const {
- dassert(_isIndexSet);
- return _index;
- }
+int BatchedUpsertDetail::getIndex() const {
+ dassert(_isIndexSet);
+ return _index;
+}
- void BatchedUpsertDetail::setUpsertedID(const BSONObj& upsertedID) {
- _upsertedID = upsertedID.firstElement().wrap( "" ).getOwned();
- _isUpsertedIDSet = true;
- }
+void BatchedUpsertDetail::setUpsertedID(const BSONObj& upsertedID) {
+ _upsertedID = upsertedID.firstElement().wrap("").getOwned();
+ _isUpsertedIDSet = true;
+}
- void BatchedUpsertDetail::unsetUpsertedID() {
- _isUpsertedIDSet = false;
- }
+void BatchedUpsertDetail::unsetUpsertedID() {
+ _isUpsertedIDSet = false;
+}
- bool BatchedUpsertDetail::isUpsertedIDSet() const {
- return _isUpsertedIDSet;
- }
+bool BatchedUpsertDetail::isUpsertedIDSet() const {
+ return _isUpsertedIDSet;
+}
- const BSONObj& BatchedUpsertDetail::getUpsertedID() const {
- dassert(_isUpsertedIDSet);
- return _upsertedID;
- }
+const BSONObj& BatchedUpsertDetail::getUpsertedID() const {
+ dassert(_isUpsertedIDSet);
+ return _upsertedID;
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/batched_upsert_detail.h b/src/mongo/s/write_ops/batched_upsert_detail.h
index cff8122f7ee..0f065ce6984 100644
--- a/src/mongo/s/write_ops/batched_upsert_detail.h
+++ b/src/mongo/s/write_ops/batched_upsert_detail.h
@@ -37,65 +37,65 @@
namespace mongo {
- /**
- * This class represents the layout and content of an idem inside the 'upserted' array
- * of a write command's response (see batched_command_response.h)
- */
- class BatchedUpsertDetail : public BSONSerializable {
- MONGO_DISALLOW_COPYING(BatchedUpsertDetail);
- public:
-
- //
- // schema declarations
- //
-
- static const BSONField<int> index;
- static const BSONField<BSONObj> upsertedID; // ID type
-
- //
- // construction / destruction
- //
-
- BatchedUpsertDetail();
- virtual ~BatchedUpsertDetail();
-
- /** Copies all the fields present in 'this' to 'other'. */
- void cloneTo(BatchedUpsertDetail* other) const;
-
- //
- // bson serializable interface implementation
- //
-
- virtual bool isValid(std::string* errMsg) const;
- virtual BSONObj toBSON() const;
- virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
- virtual void clear();
- virtual std::string toString() const;
-
- //
- // individual field accessors
- //
-
- void setIndex(int index);
- void unsetIndex();
- bool isIndexSet() const;
- int getIndex() const;
-
- void setUpsertedID(const BSONObj& upsertedID);
- void unsetUpsertedID();
- bool isUpsertedIDSet() const;
- const BSONObj& getUpsertedID() const;
-
- private:
- // Convention: (M)andatory, (O)ptional
-
- // (M) number of the batch item the upsert refers to
- int _index;
- bool _isIndexSet;
-
- // (M) _id for the upserted document
- BSONObj _upsertedID;
- bool _isUpsertedIDSet;
- };
-
-} // namespace mongo
+/**
+ * This class represents the layout and content of an idem inside the 'upserted' array
+ * of a write command's response (see batched_command_response.h)
+ */
+class BatchedUpsertDetail : public BSONSerializable {
+ MONGO_DISALLOW_COPYING(BatchedUpsertDetail);
+
+public:
+ //
+ // schema declarations
+ //
+
+ static const BSONField<int> index;
+ static const BSONField<BSONObj> upsertedID; // ID type
+
+ //
+ // construction / destruction
+ //
+
+ BatchedUpsertDetail();
+ virtual ~BatchedUpsertDetail();
+
+ /** Copies all the fields present in 'this' to 'other'. */
+ void cloneTo(BatchedUpsertDetail* other) const;
+
+ //
+ // bson serializable interface implementation
+ //
+
+ virtual bool isValid(std::string* errMsg) const;
+ virtual BSONObj toBSON() const;
+ virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
+ virtual void clear();
+ virtual std::string toString() const;
+
+ //
+ // individual field accessors
+ //
+
+ void setIndex(int index);
+ void unsetIndex();
+ bool isIndexSet() const;
+ int getIndex() const;
+
+ void setUpsertedID(const BSONObj& upsertedID);
+ void unsetUpsertedID();
+ bool isUpsertedIDSet() const;
+ const BSONObj& getUpsertedID() const;
+
+private:
+ // Convention: (M)andatory, (O)ptional
+
+ // (M) number of the batch item the upsert refers to
+ int _index;
+ bool _isIndexSet;
+
+ // (M) _id for the upserted document
+ BSONObj _upsertedID;
+ bool _isUpsertedIDSet;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/wc_error_detail.cpp b/src/mongo/s/write_ops/wc_error_detail.cpp
index 0f7536639ba..40474c819c7 100644
--- a/src/mongo/s/write_ops/wc_error_detail.cpp
+++ b/src/mongo/s/write_ops/wc_error_detail.cpp
@@ -33,150 +33,155 @@
namespace mongo {
- using std::string;
+using std::string;
- using mongoutils::str::stream;
- const BSONField<int> WCErrorDetail::errCode("code");
- const BSONField<BSONObj> WCErrorDetail::errInfo("errInfo");
- const BSONField<std::string> WCErrorDetail::errMessage("errmsg");
+using mongoutils::str::stream;
+const BSONField<int> WCErrorDetail::errCode("code");
+const BSONField<BSONObj> WCErrorDetail::errInfo("errInfo");
+const BSONField<std::string> WCErrorDetail::errMessage("errmsg");
- WCErrorDetail::WCErrorDetail() {
- clear();
- }
-
- WCErrorDetail::~WCErrorDetail() {
- }
+WCErrorDetail::WCErrorDetail() {
+ clear();
+}
- bool WCErrorDetail::isValid(std::string* errMsg) const {
- std::string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
+WCErrorDetail::~WCErrorDetail() {}
- // All the mandatory fields must be present.
- if (!_isErrCodeSet) {
- *errMsg = stream() << "missing " << errCode.name() << " field";
- return false;
- }
+bool WCErrorDetail::isValid(std::string* errMsg) const {
+ std::string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
+ }
- return true;
+ // All the mandatory fields must be present.
+ if (!_isErrCodeSet) {
+ *errMsg = stream() << "missing " << errCode.name() << " field";
+ return false;
}
- BSONObj WCErrorDetail::toBSON() const {
- BSONObjBuilder builder;
+ return true;
+}
- if (_isErrCodeSet) builder.append(errCode(), _errCode);
+BSONObj WCErrorDetail::toBSON() const {
+ BSONObjBuilder builder;
- if (_isErrInfoSet) builder.append(errInfo(), _errInfo);
+ if (_isErrCodeSet)
+ builder.append(errCode(), _errCode);
- if (_isErrMessageSet) builder.append(errMessage(), _errMessage);
+ if (_isErrInfoSet)
+ builder.append(errInfo(), _errInfo);
- return builder.obj();
- }
+ if (_isErrMessageSet)
+ builder.append(errMessage(), _errMessage);
- bool WCErrorDetail::parseBSON(const BSONObj& source, string* errMsg) {
- clear();
+ return builder.obj();
+}
- std::string dummy;
- if (!errMsg) errMsg = &dummy;
+bool WCErrorDetail::parseBSON(const BSONObj& source, string* errMsg) {
+ clear();
- FieldParser::FieldState fieldState;
- fieldState = FieldParser::extract(source, errCode, &_errCode, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isErrCodeSet = fieldState == FieldParser::FIELD_SET;
+ std::string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
- fieldState = FieldParser::extract(source, errInfo, &_errInfo, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isErrInfoSet = fieldState == FieldParser::FIELD_SET;
+ FieldParser::FieldState fieldState;
+ fieldState = FieldParser::extract(source, errCode, &_errCode, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isErrCodeSet = fieldState == FieldParser::FIELD_SET;
- fieldState = FieldParser::extract(source, errMessage, &_errMessage, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isErrMessageSet = fieldState == FieldParser::FIELD_SET;
+ fieldState = FieldParser::extract(source, errInfo, &_errInfo, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isErrInfoSet = fieldState == FieldParser::FIELD_SET;
- return true;
- }
+ fieldState = FieldParser::extract(source, errMessage, &_errMessage, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isErrMessageSet = fieldState == FieldParser::FIELD_SET;
- void WCErrorDetail::clear() {
- _errCode = 0;
- _isErrCodeSet = false;
+ return true;
+}
- _errInfo = BSONObj();
- _isErrInfoSet = false;
+void WCErrorDetail::clear() {
+ _errCode = 0;
+ _isErrCodeSet = false;
- _errMessage.clear();
- _isErrMessageSet = false;
+ _errInfo = BSONObj();
+ _isErrInfoSet = false;
- }
+ _errMessage.clear();
+ _isErrMessageSet = false;
+}
- void WCErrorDetail::cloneTo(WCErrorDetail* other) const {
- other->clear();
+void WCErrorDetail::cloneTo(WCErrorDetail* other) const {
+ other->clear();
- other->_errCode = _errCode;
- other->_isErrCodeSet = _isErrCodeSet;
+ other->_errCode = _errCode;
+ other->_isErrCodeSet = _isErrCodeSet;
- other->_errInfo = _errInfo;
- other->_isErrInfoSet = _isErrInfoSet;
+ other->_errInfo = _errInfo;
+ other->_isErrInfoSet = _isErrInfoSet;
- other->_errMessage = _errMessage;
- other->_isErrMessageSet = _isErrMessageSet;
- }
+ other->_errMessage = _errMessage;
+ other->_isErrMessageSet = _isErrMessageSet;
+}
- std::string WCErrorDetail::toString() const {
- return "implement me";
- }
+std::string WCErrorDetail::toString() const {
+ return "implement me";
+}
- void WCErrorDetail::setErrCode(int errCode) {
- _errCode = errCode;
- _isErrCodeSet = true;
- }
+void WCErrorDetail::setErrCode(int errCode) {
+ _errCode = errCode;
+ _isErrCodeSet = true;
+}
- void WCErrorDetail::unsetErrCode() {
- _isErrCodeSet = false;
- }
+void WCErrorDetail::unsetErrCode() {
+ _isErrCodeSet = false;
+}
- bool WCErrorDetail::isErrCodeSet() const {
- return _isErrCodeSet;
- }
+bool WCErrorDetail::isErrCodeSet() const {
+ return _isErrCodeSet;
+}
- int WCErrorDetail::getErrCode() const {
- dassert(_isErrCodeSet);
- return _errCode;
- }
+int WCErrorDetail::getErrCode() const {
+ dassert(_isErrCodeSet);
+ return _errCode;
+}
- void WCErrorDetail::setErrInfo(const BSONObj& errInfo) {
- _errInfo = errInfo.getOwned();
- _isErrInfoSet = true;
- }
+void WCErrorDetail::setErrInfo(const BSONObj& errInfo) {
+ _errInfo = errInfo.getOwned();
+ _isErrInfoSet = true;
+}
- void WCErrorDetail::unsetErrInfo() {
- _isErrInfoSet = false;
- }
+void WCErrorDetail::unsetErrInfo() {
+ _isErrInfoSet = false;
+}
- bool WCErrorDetail::isErrInfoSet() const {
- return _isErrInfoSet;
- }
+bool WCErrorDetail::isErrInfoSet() const {
+ return _isErrInfoSet;
+}
- const BSONObj& WCErrorDetail::getErrInfo() const {
- dassert(_isErrInfoSet);
- return _errInfo;
- }
+const BSONObj& WCErrorDetail::getErrInfo() const {
+ dassert(_isErrInfoSet);
+ return _errInfo;
+}
- void WCErrorDetail::setErrMessage(StringData errMessage) {
- _errMessage = errMessage.toString();
- _isErrMessageSet = true;
- }
-
- void WCErrorDetail::unsetErrMessage() {
- _isErrMessageSet = false;
- }
+void WCErrorDetail::setErrMessage(StringData errMessage) {
+ _errMessage = errMessage.toString();
+ _isErrMessageSet = true;
+}
- bool WCErrorDetail::isErrMessageSet() const {
- return _isErrMessageSet;
- }
+void WCErrorDetail::unsetErrMessage() {
+ _isErrMessageSet = false;
+}
- const std::string& WCErrorDetail::getErrMessage() const {
- dassert(_isErrMessageSet);
- return _errMessage;
- }
+bool WCErrorDetail::isErrMessageSet() const {
+ return _isErrMessageSet;
+}
-} // namespace mongo
+const std::string& WCErrorDetail::getErrMessage() const {
+ dassert(_isErrMessageSet);
+ return _errMessage;
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/wc_error_detail.h b/src/mongo/s/write_ops/wc_error_detail.h
index 2f07008b02b..6bf1a757997 100644
--- a/src/mongo/s/write_ops/wc_error_detail.h
+++ b/src/mongo/s/write_ops/wc_error_detail.h
@@ -37,75 +37,75 @@
namespace mongo {
- /**
- * This class represents the layout and content of the error that occurs while trying
- * to satisfy the write concern after executing the insert/update/delete runCommand.
- */
- class WCErrorDetail : public BSONSerializable {
- MONGO_DISALLOW_COPYING(WCErrorDetail);
- public:
-
- //
- // schema declarations
- //
-
- static const BSONField<int> errCode;
- static const BSONField<BSONObj> errInfo;
- static const BSONField<std::string> errMessage;
-
- //
- // construction / destruction
- //
-
- WCErrorDetail();
- virtual ~WCErrorDetail();
-
- /** Copies all the fields present in 'this' to 'other'. */
- void cloneTo(WCErrorDetail* other) const;
-
- //
- // bson serializable interface implementation
- //
-
- virtual bool isValid(std::string* errMsg) const;
- virtual BSONObj toBSON() const;
- virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
- virtual void clear();
- virtual std::string toString() const;
-
- //
- // individual field accessors
- //
-
- void setErrCode(int errCode);
- void unsetErrCode();
- bool isErrCodeSet() const;
- int getErrCode() const;
-
- void setErrInfo(const BSONObj& errInfo);
- void unsetErrInfo();
- bool isErrInfoSet() const;
- const BSONObj& getErrInfo() const;
-
- void setErrMessage(StringData errMessage);
- void unsetErrMessage();
- bool isErrMessageSet() const;
- const std::string& getErrMessage() const;
-
- private:
- // Convention: (M)andatory, (O)ptional
-
- // (M) error code for the write concern error.
- int _errCode;
- bool _isErrCodeSet;
-
- // (O) further details about the write concern error.
- BSONObj _errInfo;
- bool _isErrInfoSet;
-
- // (O) user readable explanation about the write concern error.
- std::string _errMessage;
- bool _isErrMessageSet;
- };
-
-} // namespace mongo
+/**
+ * This class represents the layout and content of the error that occurs while trying
+ * to satisfy the write concern after executing the insert/update/delete runCommand.
+ */
+class WCErrorDetail : public BSONSerializable {
+ MONGO_DISALLOW_COPYING(WCErrorDetail);
+
+public:
+ //
+ // schema declarations
+ //
+
+ static const BSONField<int> errCode;
+ static const BSONField<BSONObj> errInfo;
+ static const BSONField<std::string> errMessage;
+
+ //
+ // construction / destruction
+ //
+
+ WCErrorDetail();
+ virtual ~WCErrorDetail();
+
+ /** Copies all the fields present in 'this' to 'other'. */
+ void cloneTo(WCErrorDetail* other) const;
+
+ //
+ // bson serializable interface implementation
+ //
+
+ virtual bool isValid(std::string* errMsg) const;
+ virtual BSONObj toBSON() const;
+ virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
+ virtual void clear();
+ virtual std::string toString() const;
+
+ //
+ // individual field accessors
+ //
+
+ void setErrCode(int errCode);
+ void unsetErrCode();
+ bool isErrCodeSet() const;
+ int getErrCode() const;
+
+ void setErrInfo(const BSONObj& errInfo);
+ void unsetErrInfo();
+ bool isErrInfoSet() const;
+ const BSONObj& getErrInfo() const;
+
+ void setErrMessage(StringData errMessage);
+ void unsetErrMessage();
+ bool isErrMessageSet() const;
+ const std::string& getErrMessage() const;
+
+private:
+ // Convention: (M)andatory, (O)ptional
+
+ // (M) error code for the write concern error.
+ int _errCode;
+ bool _isErrCodeSet;
+
+ // (O) further details about the write concern error.
+ BSONObj _errInfo;
+ bool _isErrInfoSet;
+
+ // (O) user readable explanation about the write concern error.
+ std::string _errMessage;
+ bool _isErrMessageSet;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/write_error_detail.cpp b/src/mongo/s/write_ops/write_error_detail.cpp
index 3d5b9725fdf..b374ff47638 100644
--- a/src/mongo/s/write_ops/write_error_detail.cpp
+++ b/src/mongo/s/write_ops/write_error_detail.cpp
@@ -33,186 +33,193 @@
namespace mongo {
- using std::string;
+using std::string;
- using mongoutils::str::stream;
- const BSONField<int> WriteErrorDetail::index("index");
- const BSONField<int> WriteErrorDetail::errCode("code");
- const BSONField<BSONObj> WriteErrorDetail::errInfo("errInfo");
- const BSONField<std::string> WriteErrorDetail::errMessage("errmsg");
+using mongoutils::str::stream;
+const BSONField<int> WriteErrorDetail::index("index");
+const BSONField<int> WriteErrorDetail::errCode("code");
+const BSONField<BSONObj> WriteErrorDetail::errInfo("errInfo");
+const BSONField<std::string> WriteErrorDetail::errMessage("errmsg");
- WriteErrorDetail::WriteErrorDetail() {
- clear();
- }
-
- WriteErrorDetail::~WriteErrorDetail() {
- }
-
- bool WriteErrorDetail::isValid(std::string* errMsg) const {
- std::string dummy;
- if (errMsg == NULL) {
- errMsg = &dummy;
- }
+WriteErrorDetail::WriteErrorDetail() {
+ clear();
+}
- // All the mandatory fields must be present.
- if (!_isIndexSet) {
- *errMsg = stream() << "missing " << index.name() << " field";
- return false;
- }
+WriteErrorDetail::~WriteErrorDetail() {}
- if (!_isErrCodeSet) {
- *errMsg = stream() << "missing " << errCode.name() << " field";
- return false;
- }
-
- return true;
+bool WriteErrorDetail::isValid(std::string* errMsg) const {
+ std::string dummy;
+ if (errMsg == NULL) {
+ errMsg = &dummy;
}
- BSONObj WriteErrorDetail::toBSON() const {
- BSONObjBuilder builder;
-
- if (_isIndexSet) builder.append(index(), _index);
-
- if (_isErrCodeSet) builder.append(errCode(), _errCode);
-
- if (_isErrInfoSet) builder.append(errInfo(), _errInfo);
-
- if (_isErrMessageSet) builder.append(errMessage(), _errMessage);
+ // All the mandatory fields must be present.
+ if (!_isIndexSet) {
+ *errMsg = stream() << "missing " << index.name() << " field";
+ return false;
+ }
- return builder.obj();
+ if (!_isErrCodeSet) {
+ *errMsg = stream() << "missing " << errCode.name() << " field";
+ return false;
}
- bool WriteErrorDetail::parseBSON(const BSONObj& source, string* errMsg) {
- clear();
+ return true;
+}
- std::string dummy;
- if (!errMsg) errMsg = &dummy;
+BSONObj WriteErrorDetail::toBSON() const {
+ BSONObjBuilder builder;
- FieldParser::FieldState fieldState;
- fieldState = FieldParser::extract(source, index, &_index, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isIndexSet = fieldState == FieldParser::FIELD_SET;
+ if (_isIndexSet)
+ builder.append(index(), _index);
- fieldState = FieldParser::extract(source, errCode, &_errCode, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isErrCodeSet = fieldState == FieldParser::FIELD_SET;
+ if (_isErrCodeSet)
+ builder.append(errCode(), _errCode);
- fieldState = FieldParser::extract(source, errInfo, &_errInfo, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isErrInfoSet = fieldState == FieldParser::FIELD_SET;
+ if (_isErrInfoSet)
+ builder.append(errInfo(), _errInfo);
- fieldState = FieldParser::extract(source, errMessage, &_errMessage, errMsg);
- if (fieldState == FieldParser::FIELD_INVALID) return false;
- _isErrMessageSet = fieldState == FieldParser::FIELD_SET;
+ if (_isErrMessageSet)
+ builder.append(errMessage(), _errMessage);
- return true;
- }
+ return builder.obj();
+}
- void WriteErrorDetail::clear() {
- _index = 0;
- _isIndexSet = false;
+bool WriteErrorDetail::parseBSON(const BSONObj& source, string* errMsg) {
+ clear();
- _errCode = 0;
- _isErrCodeSet = false;
+ std::string dummy;
+ if (!errMsg)
+ errMsg = &dummy;
- _errInfo = BSONObj();
- _isErrInfoSet = false;
+ FieldParser::FieldState fieldState;
+ fieldState = FieldParser::extract(source, index, &_index, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isIndexSet = fieldState == FieldParser::FIELD_SET;
- _errMessage.clear();
- _isErrMessageSet = false;
+ fieldState = FieldParser::extract(source, errCode, &_errCode, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isErrCodeSet = fieldState == FieldParser::FIELD_SET;
- }
+ fieldState = FieldParser::extract(source, errInfo, &_errInfo, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isErrInfoSet = fieldState == FieldParser::FIELD_SET;
- void WriteErrorDetail::cloneTo(WriteErrorDetail* other) const {
- other->clear();
+ fieldState = FieldParser::extract(source, errMessage, &_errMessage, errMsg);
+ if (fieldState == FieldParser::FIELD_INVALID)
+ return false;
+ _isErrMessageSet = fieldState == FieldParser::FIELD_SET;
- other->_index = _index;
- other->_isIndexSet = _isIndexSet;
+ return true;
+}
- other->_errCode = _errCode;
- other->_isErrCodeSet = _isErrCodeSet;
+void WriteErrorDetail::clear() {
+ _index = 0;
+ _isIndexSet = false;
- other->_errInfo = _errInfo;
- other->_isErrInfoSet = _isErrInfoSet;
+ _errCode = 0;
+ _isErrCodeSet = false;
- other->_errMessage = _errMessage;
- other->_isErrMessageSet = _isErrMessageSet;
- }
+ _errInfo = BSONObj();
+ _isErrInfoSet = false;
- std::string WriteErrorDetail::toString() const {
- return "implement me";
- }
+ _errMessage.clear();
+ _isErrMessageSet = false;
+}
- void WriteErrorDetail::setIndex(int index) {
- _index = index;
- _isIndexSet = true;
- }
+void WriteErrorDetail::cloneTo(WriteErrorDetail* other) const {
+ other->clear();
- void WriteErrorDetail::unsetIndex() {
- _isIndexSet = false;
- }
+ other->_index = _index;
+ other->_isIndexSet = _isIndexSet;
- bool WriteErrorDetail::isIndexSet() const {
- return _isIndexSet;
- }
+ other->_errCode = _errCode;
+ other->_isErrCodeSet = _isErrCodeSet;
- int WriteErrorDetail::getIndex() const {
- dassert(_isIndexSet);
- return _index;
- }
+ other->_errInfo = _errInfo;
+ other->_isErrInfoSet = _isErrInfoSet;
- void WriteErrorDetail::setErrCode(int errCode) {
- _errCode = errCode;
- _isErrCodeSet = true;
- }
+ other->_errMessage = _errMessage;
+ other->_isErrMessageSet = _isErrMessageSet;
+}
- void WriteErrorDetail::unsetErrCode() {
- _isErrCodeSet = false;
- }
+std::string WriteErrorDetail::toString() const {
+ return "implement me";
+}
- bool WriteErrorDetail::isErrCodeSet() const {
- return _isErrCodeSet;
- }
+void WriteErrorDetail::setIndex(int index) {
+ _index = index;
+ _isIndexSet = true;
+}
- int WriteErrorDetail::getErrCode() const {
- dassert(_isErrCodeSet);
- return _errCode;
- }
+void WriteErrorDetail::unsetIndex() {
+ _isIndexSet = false;
+}
- void WriteErrorDetail::setErrInfo(const BSONObj& errInfo) {
- _errInfo = errInfo.getOwned();
- _isErrInfoSet = true;
- }
+bool WriteErrorDetail::isIndexSet() const {
+ return _isIndexSet;
+}
- void WriteErrorDetail::unsetErrInfo() {
- _isErrInfoSet = false;
- }
+int WriteErrorDetail::getIndex() const {
+ dassert(_isIndexSet);
+ return _index;
+}
- bool WriteErrorDetail::isErrInfoSet() const {
- return _isErrInfoSet;
- }
+void WriteErrorDetail::setErrCode(int errCode) {
+ _errCode = errCode;
+ _isErrCodeSet = true;
+}
- const BSONObj& WriteErrorDetail::getErrInfo() const {
- dassert(_isErrInfoSet);
- return _errInfo;
- }
+void WriteErrorDetail::unsetErrCode() {
+ _isErrCodeSet = false;
+}
- void WriteErrorDetail::setErrMessage(StringData errMessage) {
- _errMessage = errMessage.toString();
- _isErrMessageSet = true;
- }
+bool WriteErrorDetail::isErrCodeSet() const {
+ return _isErrCodeSet;
+}
- void WriteErrorDetail::unsetErrMessage() {
- _isErrMessageSet = false;
- }
+int WriteErrorDetail::getErrCode() const {
+ dassert(_isErrCodeSet);
+ return _errCode;
+}
+
+void WriteErrorDetail::setErrInfo(const BSONObj& errInfo) {
+ _errInfo = errInfo.getOwned();
+ _isErrInfoSet = true;
+}
+
+void WriteErrorDetail::unsetErrInfo() {
+ _isErrInfoSet = false;
+}
+
+bool WriteErrorDetail::isErrInfoSet() const {
+ return _isErrInfoSet;
+}
+
+const BSONObj& WriteErrorDetail::getErrInfo() const {
+ dassert(_isErrInfoSet);
+ return _errInfo;
+}
+
+void WriteErrorDetail::setErrMessage(StringData errMessage) {
+ _errMessage = errMessage.toString();
+ _isErrMessageSet = true;
+}
- bool WriteErrorDetail::isErrMessageSet() const {
- return _isErrMessageSet;
- }
+void WriteErrorDetail::unsetErrMessage() {
+ _isErrMessageSet = false;
+}
- const std::string& WriteErrorDetail::getErrMessage() const {
- dassert(_isErrMessageSet);
- return _errMessage;
- }
+bool WriteErrorDetail::isErrMessageSet() const {
+ return _isErrMessageSet;
+}
+
+const std::string& WriteErrorDetail::getErrMessage() const {
+ dassert(_isErrMessageSet);
+ return _errMessage;
+}
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/write_error_detail.h b/src/mongo/s/write_ops/write_error_detail.h
index 49f9b2d8384..9ac64a85b5d 100644
--- a/src/mongo/s/write_ops/write_error_detail.h
+++ b/src/mongo/s/write_ops/write_error_detail.h
@@ -37,85 +37,85 @@
namespace mongo {
- /**
- * This class represents the layout and content of a insert/update/delete runCommand,
- * the response side.
- */
- class WriteErrorDetail : public BSONSerializable {
- MONGO_DISALLOW_COPYING(WriteErrorDetail);
- public:
-
- //
- // schema declarations
- //
-
- static const BSONField<int> index;
- static const BSONField<int> errCode;
- static const BSONField<BSONObj> errInfo;
- static const BSONField<std::string> errMessage;
-
- //
- // construction / destruction
- //
-
- WriteErrorDetail();
- virtual ~WriteErrorDetail();
-
- /** Copies all the fields present in 'this' to 'other'. */
- void cloneTo(WriteErrorDetail* other) const;
-
- //
- // bson serializable interface implementation
- //
-
- virtual bool isValid(std::string* errMsg) const;
- virtual BSONObj toBSON() const;
- virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
- virtual void clear();
- virtual std::string toString() const;
-
- //
- // individual field accessors
- //
-
- void setIndex(int index);
- void unsetIndex();
- bool isIndexSet() const;
- int getIndex() const;
-
- void setErrCode(int errCode);
- void unsetErrCode();
- bool isErrCodeSet() const;
- int getErrCode() const;
-
- void setErrInfo(const BSONObj& errInfo);
- void unsetErrInfo();
- bool isErrInfoSet() const;
- const BSONObj& getErrInfo() const;
-
- void setErrMessage(StringData errMessage);
- void unsetErrMessage();
- bool isErrMessageSet() const;
- const std::string& getErrMessage() const;
-
- private:
- // Convention: (M)andatory, (O)ptional
-
- // (M) number of the batch item the error refers to
- int _index;
- bool _isIndexSet;
-
- // (M) whether all items in the batch applied correctly
- int _errCode;
- bool _isErrCodeSet;
-
- // (O) further details about the batch item error
- BSONObj _errInfo;
- bool _isErrInfoSet;
-
- // (O) user readable explanation about the batch item error
- std::string _errMessage;
- bool _isErrMessageSet;
- };
-
-} // namespace mongo
+/**
+ * This class represents the layout and content of a insert/update/delete runCommand,
+ * the response side.
+ */
+class WriteErrorDetail : public BSONSerializable {
+ MONGO_DISALLOW_COPYING(WriteErrorDetail);
+
+public:
+ //
+ // schema declarations
+ //
+
+ static const BSONField<int> index;
+ static const BSONField<int> errCode;
+ static const BSONField<BSONObj> errInfo;
+ static const BSONField<std::string> errMessage;
+
+ //
+ // construction / destruction
+ //
+
+ WriteErrorDetail();
+ virtual ~WriteErrorDetail();
+
+ /** Copies all the fields present in 'this' to 'other'. */
+ void cloneTo(WriteErrorDetail* other) const;
+
+ //
+ // bson serializable interface implementation
+ //
+
+ virtual bool isValid(std::string* errMsg) const;
+ virtual BSONObj toBSON() const;
+ virtual bool parseBSON(const BSONObj& source, std::string* errMsg);
+ virtual void clear();
+ virtual std::string toString() const;
+
+ //
+ // individual field accessors
+ //
+
+ void setIndex(int index);
+ void unsetIndex();
+ bool isIndexSet() const;
+ int getIndex() const;
+
+ void setErrCode(int errCode);
+ void unsetErrCode();
+ bool isErrCodeSet() const;
+ int getErrCode() const;
+
+ void setErrInfo(const BSONObj& errInfo);
+ void unsetErrInfo();
+ bool isErrInfoSet() const;
+ const BSONObj& getErrInfo() const;
+
+ void setErrMessage(StringData errMessage);
+ void unsetErrMessage();
+ bool isErrMessageSet() const;
+ const std::string& getErrMessage() const;
+
+private:
+ // Convention: (M)andatory, (O)ptional
+
+ // (M) number of the batch item the error refers to
+ int _index;
+ bool _isIndexSet;
+
+ // (M) whether all items in the batch applied correctly
+ int _errCode;
+ bool _isErrCodeSet;
+
+ // (O) further details about the batch item error
+ BSONObj _errInfo;
+ bool _isErrInfoSet;
+
+ // (O) user readable explanation about the batch item error
+ std::string _errMessage;
+ bool _isErrMessageSet;
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/write_op.cpp b/src/mongo/s/write_ops/write_op.cpp
index 9532922237f..f630802121d 100644
--- a/src/mongo/s/write_ops/write_op.cpp
+++ b/src/mongo/s/write_ops/write_op.cpp
@@ -34,258 +34,239 @@
namespace mongo {
- using std::stringstream;
- using std::vector;
+using std::stringstream;
+using std::vector;
- static void clear( vector<ChildWriteOp*>* childOps ) {
- for ( vector<ChildWriteOp*>::const_iterator it = childOps->begin(); it != childOps->end();
- ++it ) {
- delete *it;
- }
- childOps->clear();
- }
-
- WriteOp::~WriteOp() {
- clear( &_childOps );
- clear( &_history );
- }
-
- const BatchItemRef& WriteOp::getWriteItem() const {
- return _itemRef;
- }
-
- WriteOpState WriteOp::getWriteState() const {
- return _state;
- }
-
- const WriteErrorDetail& WriteOp::getOpError() const {
- dassert( _state == WriteOpState_Error );
- return *_error;
+static void clear(vector<ChildWriteOp*>* childOps) {
+ for (vector<ChildWriteOp*>::const_iterator it = childOps->begin(); it != childOps->end();
+ ++it) {
+ delete *it;
}
+ childOps->clear();
+}
- Status WriteOp::targetWrites( const NSTargeter& targeter,
- std::vector<TargetedWrite*>* targetedWrites ) {
-
- bool isUpdate = _itemRef.getOpType() == BatchedCommandRequest::BatchType_Update;
- bool isDelete = _itemRef.getOpType() == BatchedCommandRequest::BatchType_Delete;
- bool isIndexInsert = _itemRef.getRequest()->isInsertIndexRequest();
-
- Status targetStatus = Status::OK();
- OwnedPointerVector<ShardEndpoint> endpointsOwned;
- vector<ShardEndpoint*>& endpoints = endpointsOwned.mutableVector();
+WriteOp::~WriteOp() {
+ clear(&_childOps);
+ clear(&_history);
+}
- if ( isUpdate ) {
- targetStatus = targeter.targetUpdate( *_itemRef.getUpdate(), &endpoints );
- }
- else if ( isDelete ) {
- targetStatus = targeter.targetDelete( *_itemRef.getDelete(), &endpoints );
- }
- else {
- dassert( _itemRef.getOpType() == BatchedCommandRequest::BatchType_Insert );
+const BatchItemRef& WriteOp::getWriteItem() const {
+ return _itemRef;
+}
- ShardEndpoint* endpoint = NULL;
- // TODO: Remove the index targeting stuff once there is a command for it
- if ( !isIndexInsert ) {
- targetStatus = targeter.targetInsert( _itemRef.getDocument(), &endpoint );
- }
- else {
- // TODO: Retry index writes with stale version?
- targetStatus = targeter.targetCollection( &endpoints );
- }
+WriteOpState WriteOp::getWriteState() const {
+ return _state;
+}
- if ( !targetStatus.isOK() ) {
- dassert( NULL == endpoint );
- return targetStatus;
- }
+const WriteErrorDetail& WriteOp::getOpError() const {
+ dassert(_state == WriteOpState_Error);
+ return *_error;
+}
- // Store single endpoint result if we targeted a single endpoint
- if ( endpoint ) endpoints.push_back( endpoint );
+Status WriteOp::targetWrites(const NSTargeter& targeter,
+ std::vector<TargetedWrite*>* targetedWrites) {
+ bool isUpdate = _itemRef.getOpType() == BatchedCommandRequest::BatchType_Update;
+ bool isDelete = _itemRef.getOpType() == BatchedCommandRequest::BatchType_Delete;
+ bool isIndexInsert = _itemRef.getRequest()->isInsertIndexRequest();
+
+ Status targetStatus = Status::OK();
+ OwnedPointerVector<ShardEndpoint> endpointsOwned;
+ vector<ShardEndpoint*>& endpoints = endpointsOwned.mutableVector();
+
+ if (isUpdate) {
+ targetStatus = targeter.targetUpdate(*_itemRef.getUpdate(), &endpoints);
+ } else if (isDelete) {
+ targetStatus = targeter.targetDelete(*_itemRef.getDelete(), &endpoints);
+ } else {
+ dassert(_itemRef.getOpType() == BatchedCommandRequest::BatchType_Insert);
+
+ ShardEndpoint* endpoint = NULL;
+ // TODO: Remove the index targeting stuff once there is a command for it
+ if (!isIndexInsert) {
+ targetStatus = targeter.targetInsert(_itemRef.getDocument(), &endpoint);
+ } else {
+ // TODO: Retry index writes with stale version?
+ targetStatus = targeter.targetCollection(&endpoints);
}
- // If we're targeting more than one endpoint with an update/delete, we have to target
- // everywhere since we cannot currently retry partial results.
- // NOTE: Index inserts are currently specially targeted only at the current collection to
- // avoid creating collections everywhere.
- if ( targetStatus.isOK() && endpoints.size() > 1u && !isIndexInsert ) {
- endpointsOwned.clear();
- invariant( endpoints.empty() );
- targetStatus = targeter.targetAllShards( &endpoints );
+ if (!targetStatus.isOK()) {
+ dassert(NULL == endpoint);
+ return targetStatus;
}
- // If we had an error, stop here
- if ( !targetStatus.isOK() ) return targetStatus;
+ // Store single endpoint result if we targeted a single endpoint
+ if (endpoint)
+ endpoints.push_back(endpoint);
+ }
- for ( vector<ShardEndpoint*>::iterator it = endpoints.begin(); it != endpoints.end();
- ++it ) {
+ // If we're targeting more than one endpoint with an update/delete, we have to target
+ // everywhere since we cannot currently retry partial results.
+ // NOTE: Index inserts are currently specially targeted only at the current collection to
+ // avoid creating collections everywhere.
+ if (targetStatus.isOK() && endpoints.size() > 1u && !isIndexInsert) {
+ endpointsOwned.clear();
+ invariant(endpoints.empty());
+ targetStatus = targeter.targetAllShards(&endpoints);
+ }
- ShardEndpoint* endpoint = *it;
+ // If we had an error, stop here
+ if (!targetStatus.isOK())
+ return targetStatus;
- _childOps.push_back( new ChildWriteOp( this ) );
+ for (vector<ShardEndpoint*>::iterator it = endpoints.begin(); it != endpoints.end(); ++it) {
+ ShardEndpoint* endpoint = *it;
- WriteOpRef ref( _itemRef.getItemIndex(), _childOps.size() - 1 );
+ _childOps.push_back(new ChildWriteOp(this));
- // For now, multiple endpoints imply no versioning - we can't retry half a multi-write
- if ( endpoints.size() == 1u ) {
- targetedWrites->push_back( new TargetedWrite( *endpoint, ref ) );
- }
- else {
- ShardEndpoint broadcastEndpoint( endpoint->shardName,
- ChunkVersion::IGNORED() );
- targetedWrites->push_back( new TargetedWrite( broadcastEndpoint, ref ) );
- }
+ WriteOpRef ref(_itemRef.getItemIndex(), _childOps.size() - 1);
- _childOps.back()->pendingWrite = targetedWrites->back();
- _childOps.back()->state = WriteOpState_Pending;
+ // For now, multiple endpoints imply no versioning - we can't retry half a multi-write
+ if (endpoints.size() == 1u) {
+ targetedWrites->push_back(new TargetedWrite(*endpoint, ref));
+ } else {
+ ShardEndpoint broadcastEndpoint(endpoint->shardName, ChunkVersion::IGNORED());
+ targetedWrites->push_back(new TargetedWrite(broadcastEndpoint, ref));
}
- _state = WriteOpState_Pending;
- return Status::OK();
- }
-
- size_t WriteOp::getNumTargeted() {
- return _childOps.size();
+ _childOps.back()->pendingWrite = targetedWrites->back();
+ _childOps.back()->state = WriteOpState_Pending;
}
- static bool isRetryErrCode( int errCode ) {
- return errCode == ErrorCodes::StaleShardVersion;
- }
+ _state = WriteOpState_Pending;
+ return Status::OK();
+}
- // Aggregate a bunch of errors for a single op together
- static void combineOpErrors( const vector<ChildWriteOp*>& errOps, WriteErrorDetail* error ) {
+size_t WriteOp::getNumTargeted() {
+ return _childOps.size();
+}
- // Special case single response
- if ( errOps.size() == 1 ) {
- errOps.front()->error->cloneTo( error );
- return;
- }
+static bool isRetryErrCode(int errCode) {
+ return errCode == ErrorCodes::StaleShardVersion;
+}
- error->setErrCode( ErrorCodes::MultipleErrorsOccurred );
+// Aggregate a bunch of errors for a single op together
+static void combineOpErrors(const vector<ChildWriteOp*>& errOps, WriteErrorDetail* error) {
+ // Special case single response
+ if (errOps.size() == 1) {
+ errOps.front()->error->cloneTo(error);
+ return;
+ }
- // Generate the multi-error message below
- stringstream msg;
- msg << "multiple errors for op : ";
+ error->setErrCode(ErrorCodes::MultipleErrorsOccurred);
- BSONArrayBuilder errB;
- for ( vector<ChildWriteOp*>::const_iterator it = errOps.begin(); it != errOps.end();
- ++it ) {
- const ChildWriteOp* errOp = *it;
- if ( it != errOps.begin() ) msg << " :: and :: ";
- msg << errOp->error->getErrMessage();
- errB.append( errOp->error->toBSON() );
- }
+ // Generate the multi-error message below
+ stringstream msg;
+ msg << "multiple errors for op : ";
- error->setErrInfo( BSON( "causedBy" << errB.arr() ) );
- error->setIndex( errOps.front()->error->getIndex() );
- error->setErrMessage( msg.str() );
+ BSONArrayBuilder errB;
+ for (vector<ChildWriteOp*>::const_iterator it = errOps.begin(); it != errOps.end(); ++it) {
+ const ChildWriteOp* errOp = *it;
+ if (it != errOps.begin())
+ msg << " :: and :: ";
+ msg << errOp->error->getErrMessage();
+ errB.append(errOp->error->toBSON());
}
- /**
- * This is the core function which aggregates all the results of a write operation on multiple
- * shards and updates the write operation's state.
- */
- void WriteOp::updateOpState() {
-
- vector<ChildWriteOp*> childErrors;
-
- bool isRetryError = true;
- for ( vector<ChildWriteOp*>::iterator it = _childOps.begin(); it != _childOps.end();
- it++ ) {
+ error->setErrInfo(BSON("causedBy" << errB.arr()));
+ error->setIndex(errOps.front()->error->getIndex());
+ error->setErrMessage(msg.str());
+}
- ChildWriteOp* childOp = *it;
+/**
+ * This is the core function which aggregates all the results of a write operation on multiple
+ * shards and updates the write operation's state.
+ */
+void WriteOp::updateOpState() {
+ vector<ChildWriteOp*> childErrors;
- // Don't do anything till we have all the info
- if ( childOp->state != WriteOpState_Completed
- && childOp->state != WriteOpState_Error ) {
- return;
- }
+ bool isRetryError = true;
+ for (vector<ChildWriteOp*>::iterator it = _childOps.begin(); it != _childOps.end(); it++) {
+ ChildWriteOp* childOp = *it;
- if ( childOp->state == WriteOpState_Error ) {
- childErrors.push_back( childOp );
- // Any non-retry error aborts all
- if ( !isRetryErrCode( childOp->error->getErrCode() ) ) isRetryError = false;
- }
+ // Don't do anything till we have all the info
+ if (childOp->state != WriteOpState_Completed && childOp->state != WriteOpState_Error) {
+ return;
}
- if ( !childErrors.empty() && isRetryError ) {
- // Since we're using broadcast mode for multi-shard writes, which cannot SCE
- dassert( childErrors.size() == 1u );
- _state = WriteOpState_Ready;
- }
- else if ( !childErrors.empty() ) {
- _error.reset( new WriteErrorDetail );
- combineOpErrors( childErrors, _error.get() );
- _state = WriteOpState_Error;
+ if (childOp->state == WriteOpState_Error) {
+ childErrors.push_back(childOp);
+ // Any non-retry error aborts all
+ if (!isRetryErrCode(childOp->error->getErrCode()))
+ isRetryError = false;
}
- else {
- _state = WriteOpState_Completed;
- }
-
- // Now that we're done with the child ops, do something with them
- // TODO: Don't store unlimited history?
- dassert( _state != WriteOpState_Pending );
- _history.insert( _history.end(), _childOps.begin(), _childOps.end() );
- _childOps.clear();
}
- void WriteOp::cancelWrites( const WriteErrorDetail* why ) {
+ if (!childErrors.empty() && isRetryError) {
+ // Since we're using broadcast mode for multi-shard writes, which cannot SCE
+ dassert(childErrors.size() == 1u);
+ _state = WriteOpState_Ready;
+ } else if (!childErrors.empty()) {
+ _error.reset(new WriteErrorDetail);
+ combineOpErrors(childErrors, _error.get());
+ _state = WriteOpState_Error;
+ } else {
+ _state = WriteOpState_Completed;
+ }
- dassert( _state == WriteOpState_Pending || _state == WriteOpState_Ready );
- for ( vector<ChildWriteOp*>::iterator it = _childOps.begin(); it != _childOps.end();
- ++it ) {
+ // Now that we're done with the child ops, do something with them
+ // TODO: Don't store unlimited history?
+ dassert(_state != WriteOpState_Pending);
+ _history.insert(_history.end(), _childOps.begin(), _childOps.end());
+ _childOps.clear();
+}
- ChildWriteOp* childOp = *it;
+void WriteOp::cancelWrites(const WriteErrorDetail* why) {
+ dassert(_state == WriteOpState_Pending || _state == WriteOpState_Ready);
+ for (vector<ChildWriteOp*>::iterator it = _childOps.begin(); it != _childOps.end(); ++it) {
+ ChildWriteOp* childOp = *it;
- if ( childOp->state == WriteOpState_Pending ) {
- childOp->endpoint.reset( new ShardEndpoint( childOp->pendingWrite->endpoint ) );
- if ( why ) {
- childOp->error.reset( new WriteErrorDetail );
- why->cloneTo( childOp->error.get() );
- }
- childOp->state = WriteOpState_Cancelled;
+ if (childOp->state == WriteOpState_Pending) {
+ childOp->endpoint.reset(new ShardEndpoint(childOp->pendingWrite->endpoint));
+ if (why) {
+ childOp->error.reset(new WriteErrorDetail);
+ why->cloneTo(childOp->error.get());
}
+ childOp->state = WriteOpState_Cancelled;
}
-
- _history.insert( _history.end(), _childOps.begin(), _childOps.end() );
- _childOps.clear();
-
- _state = WriteOpState_Ready;
}
- void WriteOp::noteWriteComplete( const TargetedWrite& targetedWrite ) {
-
- const WriteOpRef& ref = targetedWrite.writeOpRef;
- dassert( static_cast<size_t>( ref.second ) < _childOps.size() );
- ChildWriteOp& childOp = *_childOps.at( ref.second );
-
- childOp.pendingWrite = NULL;
- childOp.endpoint.reset( new ShardEndpoint( targetedWrite.endpoint ) );
- childOp.state = WriteOpState_Completed;
- updateOpState();
- }
+ _history.insert(_history.end(), _childOps.begin(), _childOps.end());
+ _childOps.clear();
- void WriteOp::noteWriteError( const TargetedWrite& targetedWrite,
- const WriteErrorDetail& error ) {
+ _state = WriteOpState_Ready;
+}
- const WriteOpRef& ref = targetedWrite.writeOpRef;
- ChildWriteOp& childOp = *_childOps.at( ref.second );
+void WriteOp::noteWriteComplete(const TargetedWrite& targetedWrite) {
+ const WriteOpRef& ref = targetedWrite.writeOpRef;
+ dassert(static_cast<size_t>(ref.second) < _childOps.size());
+ ChildWriteOp& childOp = *_childOps.at(ref.second);
- childOp.pendingWrite = NULL;
- childOp.endpoint.reset( new ShardEndpoint( targetedWrite.endpoint ) );
- childOp.error.reset( new WriteErrorDetail );
- error.cloneTo( childOp.error.get() );
- dassert( ref.first == _itemRef.getItemIndex() );
- childOp.error->setIndex( _itemRef.getItemIndex() );
- childOp.state = WriteOpState_Error;
- updateOpState();
- }
+ childOp.pendingWrite = NULL;
+ childOp.endpoint.reset(new ShardEndpoint(targetedWrite.endpoint));
+ childOp.state = WriteOpState_Completed;
+ updateOpState();
+}
- void WriteOp::setOpError( const WriteErrorDetail& error ) {
- dassert( _state == WriteOpState_Ready );
- _error.reset( new WriteErrorDetail );
- error.cloneTo( _error.get() );
- _error->setIndex( _itemRef.getItemIndex() );
- _state = WriteOpState_Error;
- // No need to updateOpState, set directly
- }
+void WriteOp::noteWriteError(const TargetedWrite& targetedWrite, const WriteErrorDetail& error) {
+ const WriteOpRef& ref = targetedWrite.writeOpRef;
+ ChildWriteOp& childOp = *_childOps.at(ref.second);
+
+ childOp.pendingWrite = NULL;
+ childOp.endpoint.reset(new ShardEndpoint(targetedWrite.endpoint));
+ childOp.error.reset(new WriteErrorDetail);
+ error.cloneTo(childOp.error.get());
+ dassert(ref.first == _itemRef.getItemIndex());
+ childOp.error->setIndex(_itemRef.getItemIndex());
+ childOp.state = WriteOpState_Error;
+ updateOpState();
+}
+void WriteOp::setOpError(const WriteErrorDetail& error) {
+ dassert(_state == WriteOpState_Ready);
+ _error.reset(new WriteErrorDetail);
+ error.cloneTo(_error.get());
+ _error->setIndex(_itemRef.getItemIndex());
+ _state = WriteOpState_Error;
+ // No need to updateOpState, set directly
+}
}
diff --git a/src/mongo/s/write_ops/write_op.h b/src/mongo/s/write_ops/write_op.h
index 98ccf0d691d..fb189edfffb 100644
--- a/src/mongo/s/write_ops/write_op.h
+++ b/src/mongo/s/write_ops/write_op.h
@@ -38,206 +38,196 @@
namespace mongo {
- struct TargetedWrite;
- struct ChildWriteOp;
+struct TargetedWrite;
+struct ChildWriteOp;
- enum WriteOpState {
+enum WriteOpState {
- // Item is ready to be targeted
- WriteOpState_Ready,
+ // Item is ready to be targeted
+ WriteOpState_Ready,
- // Item is targeted and we're waiting for outstanding shard requests to populate
- // responses
- WriteOpState_Pending,
+ // Item is targeted and we're waiting for outstanding shard requests to populate
+ // responses
+ WriteOpState_Pending,
- // Op was successful, write completed
- // We assume all states higher than this one are *final*
- WriteOpState_Completed,
+ // Op was successful, write completed
+ // We assume all states higher than this one are *final*
+ WriteOpState_Completed,
- // Op failed with some error
- WriteOpState_Error,
+ // Op failed with some error
+ WriteOpState_Error,
- // Op was cancelled before sending (only child write ops can be cancelled)
- WriteOpState_Cancelled,
+ // Op was cancelled before sending (only child write ops can be cancelled)
+ WriteOpState_Cancelled,
- // Catch-all error state.
- WriteOpState_Unknown
- };
+ // Catch-all error state.
+ WriteOpState_Unknown
+};
+
+/**
+ * State of a single write item in-progress from a client request.
+ *
+ * The lifecyle of a write op:
+ *
+ * 0. Begins at _Ready,
+ *
+ * 1a. Targeted, and a ChildWriteOp created to track the state of each returned TargetedWrite.
+ * The state is changed to _Pending.
+ * 1b. If the op cannot be targeted, the error is set directly (_Error), and the write op is
+ * completed.
+ *
+ * 2a. The current TargetedWrites are cancelled, and the op state is reset to _Ready
+ * 2b. TargetedWrites finish successfully and unsuccessfully.
+ *
+ * On the last error arriving...
+ *
+ * 3a. If the errors allow for retry, the WriteOp is reset to _Ready, previous ChildWriteOps
+ * are placed in the history, and goto 0.
+ * 3b. If the errors don't allow for retry, they are combined into a single error and the
+ * state is changed to _Error.
+ * 3c. If there are no errors, the state is changed to _Completed.
+ *
+ * WriteOps finish in a _Completed or _Error state.
+ */
+class WriteOp {
+public:
+ WriteOp(const BatchItemRef& itemRef) : _itemRef(itemRef), _state(WriteOpState_Ready) {}
+
+ ~WriteOp();
/**
- * State of a single write item in-progress from a client request.
- *
- * The lifecyle of a write op:
+ * Returns the write item for this operation
+ */
+ const BatchItemRef& getWriteItem() const;
+
+ /**
+ * Returns the op's current state.
+ */
+ WriteOpState getWriteState() const;
+
+ /**
+ * Returns the op's error.
*
- * 0. Begins at _Ready,
+ * Can only be used in state _Error
+ */
+ const WriteErrorDetail& getOpError() const;
+
+ /**
+ * Creates TargetedWrite operations for every applicable shard, which contain the
+ * information needed to send the child writes generated from this write item.
*
- * 1a. Targeted, and a ChildWriteOp created to track the state of each returned TargetedWrite.
- * The state is changed to _Pending.
- * 1b. If the op cannot be targeted, the error is set directly (_Error), and the write op is
- * completed.
+ * The ShardTargeter determines the ShardEndpoints to send child writes to, but is not
+ * modified by this operation.
*
- * 2a. The current TargetedWrites are cancelled, and the op state is reset to _Ready
- * 2b. TargetedWrites finish successfully and unsuccessfully.
+ * Returns !OK if the targeting process itself fails
+ * (no TargetedWrites will be added, state unchanged)
+ */
+ Status targetWrites(const NSTargeter& targeter, std::vector<TargetedWrite*>* targetedWrites);
+
+ /**
+ * Returns the number of child writes that were last targeted.
+ */
+ size_t getNumTargeted();
+
+ /**
+ * Resets the state of this write op to _Ready and stops waiting for any outstanding
+ * TargetedWrites. Optional error can be provided for reporting.
*
- * On the last error arriving...
+ * Can only be called when state is _Pending, or is a no-op if called when the state
+ * is still _Ready (and therefore no writes are pending).
+ */
+ void cancelWrites(const WriteErrorDetail* why);
+
+ /**
+ * Marks the targeted write as finished for this write op.
*
- * 3a. If the errors allow for retry, the WriteOp is reset to _Ready, previous ChildWriteOps
- * are placed in the history, and goto 0.
- * 3b. If the errors don't allow for retry, they are combined into a single error and the
- * state is changed to _Error.
- * 3c. If there are no errors, the state is changed to _Completed.
+ * One of noteWriteComplete or noteWriteError should be called exactly once for every
+ * TargetedWrite.
+ */
+ void noteWriteComplete(const TargetedWrite& targetedWrite);
+
+ /**
+ * Stores the error response of a TargetedWrite for later use, marks the write as finished.
*
- * WriteOps finish in a _Completed or _Error state.
+ * As above, one of noteWriteComplete or noteWriteError should be called exactly once for
+ * every TargetedWrite.
*/
- class WriteOp {
- public:
-
- WriteOp( const BatchItemRef& itemRef ) :
- _itemRef( itemRef ), _state( WriteOpState_Ready ) {
- }
-
- ~WriteOp();
-
- /**
- * Returns the write item for this operation
- */
- const BatchItemRef& getWriteItem() const;
-
- /**
- * Returns the op's current state.
- */
- WriteOpState getWriteState() const;
-
- /**
- * Returns the op's error.
- *
- * Can only be used in state _Error
- */
- const WriteErrorDetail& getOpError() const;
-
- /**
- * Creates TargetedWrite operations for every applicable shard, which contain the
- * information needed to send the child writes generated from this write item.
- *
- * The ShardTargeter determines the ShardEndpoints to send child writes to, but is not
- * modified by this operation.
- *
- * Returns !OK if the targeting process itself fails
- * (no TargetedWrites will be added, state unchanged)
- */
- Status targetWrites( const NSTargeter& targeter,
- std::vector<TargetedWrite*>* targetedWrites );
-
- /**
- * Returns the number of child writes that were last targeted.
- */
- size_t getNumTargeted();
-
- /**
- * Resets the state of this write op to _Ready and stops waiting for any outstanding
- * TargetedWrites. Optional error can be provided for reporting.
- *
- * Can only be called when state is _Pending, or is a no-op if called when the state
- * is still _Ready (and therefore no writes are pending).
- */
- void cancelWrites( const WriteErrorDetail* why );
-
- /**
- * Marks the targeted write as finished for this write op.
- *
- * One of noteWriteComplete or noteWriteError should be called exactly once for every
- * TargetedWrite.
- */
- void noteWriteComplete( const TargetedWrite& targetedWrite );
-
- /**
- * Stores the error response of a TargetedWrite for later use, marks the write as finished.
- *
- * As above, one of noteWriteComplete or noteWriteError should be called exactly once for
- * every TargetedWrite.
- */
- void noteWriteError( const TargetedWrite& targetedWrite, const WriteErrorDetail& error );
-
- /**
- * Sets the error for this write op directly, and forces the state to _Error.
- *
- * Should only be used when in state _Ready.
- */
- void setOpError( const WriteErrorDetail& error );
-
- private:
-
- /**
- * Updates the op state after new information is received.
- */
- void updateOpState();
-
- // Owned elsewhere, reference to a batch with a write item
- const BatchItemRef _itemRef;
-
- // What stage of the operation we are at
- WriteOpState _state;
-
- // filled when state == _Pending
- std::vector<ChildWriteOp*> _childOps;
-
- // filled when state == _Error
- std::unique_ptr<WriteErrorDetail> _error;
-
- // Finished child operations, for debugging
- std::vector<ChildWriteOp*> _history;
- };
+ void noteWriteError(const TargetedWrite& targetedWrite, const WriteErrorDetail& error);
/**
- * State of a write in-progress (to a single shard) which is one part of a larger write
- * operation.
+ * Sets the error for this write op directly, and forces the state to _Error.
*
- * As above, the write op may finish in either a successful (_Completed) or unsuccessful
- * (_Error) state.
+ * Should only be used when in state _Ready.
*/
- struct ChildWriteOp {
+ void setOpError(const WriteErrorDetail& error);
- ChildWriteOp( WriteOp* const parent ) :
- parentOp( parent ), state( WriteOpState_Ready ), pendingWrite( NULL ) {
- }
+private:
+ /**
+ * Updates the op state after new information is received.
+ */
+ void updateOpState();
- const WriteOp* const parentOp;
- WriteOpState state;
+ // Owned elsewhere, reference to a batch with a write item
+ const BatchItemRef _itemRef;
- // non-zero when state == _Pending
- // Not owned here but tracked for reporting
- TargetedWrite* pendingWrite;
+ // What stage of the operation we are at
+ WriteOpState _state;
- // filled when state > _Pending
- std::unique_ptr<ShardEndpoint> endpoint;
+ // filled when state == _Pending
+ std::vector<ChildWriteOp*> _childOps;
- // filled when state == _Error or (optionally) when state == _Cancelled
- std::unique_ptr<WriteErrorDetail> error;
- };
+ // filled when state == _Error
+ std::unique_ptr<WriteErrorDetail> _error;
- // First value is write item index in the batch, second value is child write op index
- typedef std::pair<int, int> WriteOpRef;
+ // Finished child operations, for debugging
+ std::vector<ChildWriteOp*> _history;
+};
- /**
- * A write with A) a request targeted at a particular shard endpoint, and B) a response targeted
- * at a particular WriteOp.
- *
- * TargetedWrites are the link between the RPC layer and the in-progress write
- * operation.
- */
- struct TargetedWrite {
+/**
+ * State of a write in-progress (to a single shard) which is one part of a larger write
+ * operation.
+ *
+ * As above, the write op may finish in either a successful (_Completed) or unsuccessful
+ * (_Error) state.
+ */
+struct ChildWriteOp {
+ ChildWriteOp(WriteOp* const parent)
+ : parentOp(parent), state(WriteOpState_Ready), pendingWrite(NULL) {}
- TargetedWrite( const ShardEndpoint& endpoint, WriteOpRef writeOpRef ) :
- endpoint( endpoint ), writeOpRef( writeOpRef ) {
- }
+ const WriteOp* const parentOp;
+ WriteOpState state;
- // Where to send the write
- ShardEndpoint endpoint;
+ // non-zero when state == _Pending
+ // Not owned here but tracked for reporting
+ TargetedWrite* pendingWrite;
- // Where to find the write item and put the response
- // TODO: Could be a more complex handle, shared between write state and networking code if
- // we need to be able to cancel ops.
- WriteOpRef writeOpRef;
- };
+ // filled when state > _Pending
+ std::unique_ptr<ShardEndpoint> endpoint;
+ // filled when state == _Error or (optionally) when state == _Cancelled
+ std::unique_ptr<WriteErrorDetail> error;
+};
+
+// First value is write item index in the batch, second value is child write op index
+typedef std::pair<int, int> WriteOpRef;
+
+/**
+ * A write with A) a request targeted at a particular shard endpoint, and B) a response targeted
+ * at a particular WriteOp.
+ *
+ * TargetedWrites are the link between the RPC layer and the in-progress write
+ * operation.
+ */
+struct TargetedWrite {
+ TargetedWrite(const ShardEndpoint& endpoint, WriteOpRef writeOpRef)
+ : endpoint(endpoint), writeOpRef(writeOpRef) {}
+
+ // Where to send the write
+ ShardEndpoint endpoint;
+
+ // Where to find the write item and put the response
+ // TODO: Could be a more complex handle, shared between write state and networking code if
+ // we need to be able to cancel ops.
+ WriteOpRef writeOpRef;
+};
}
diff --git a/src/mongo/s/write_ops/write_op_test.cpp b/src/mongo/s/write_ops/write_op_test.cpp
index fc5caa05dfb..b836caae80c 100644
--- a/src/mongo/s/write_ops/write_op_test.cpp
+++ b/src/mongo/s/write_ops/write_op_test.cpp
@@ -39,365 +39,322 @@
namespace {
- using std::unique_ptr;
- using std::string;
- using std::vector;
+using std::unique_ptr;
+using std::string;
+using std::vector;
- using namespace mongo;
+using namespace mongo;
- WriteErrorDetail* buildError( int code, const BSONObj& info, const string& message ) {
+WriteErrorDetail* buildError(int code, const BSONObj& info, const string& message) {
+ WriteErrorDetail* error = new WriteErrorDetail();
+ error->setErrCode(code);
+ error->setErrInfo(info);
+ error->setErrMessage(message);
- WriteErrorDetail* error = new WriteErrorDetail();
- error->setErrCode( code );
- error->setErrInfo( info );
- error->setErrMessage( message );
+ return error;
+}
- return error;
- }
+TEST(WriteOpTests, BasicError) {
+ //
+ // Test of basic error-setting on write op
+ //
- TEST(WriteOpTests, BasicError) {
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS("foo.bar");
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
- //
- // Test of basic error-setting on write op
- //
+ WriteOp writeOp(BatchItemRef(&request, 0));
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Ready);
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( "foo.bar" );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
+ unique_ptr<WriteErrorDetail> error(
+ buildError(ErrorCodes::UnknownError, BSON("data" << 12345), "some message"));
- WriteOp writeOp( BatchItemRef( &request, 0 ) );
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Ready );
+ writeOp.setOpError(*error);
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Error);
+ ASSERT_EQUALS(writeOp.getOpError().getErrCode(), error->getErrCode());
+ ASSERT_EQUALS(writeOp.getOpError().getErrInfo()["data"].Int(),
+ error->getErrInfo()["data"].Int());
+ ASSERT_EQUALS(writeOp.getOpError().getErrMessage(), error->getErrMessage());
+}
- unique_ptr<WriteErrorDetail> error( buildError( ErrorCodes::UnknownError,
- BSON( "data" << 12345 ),
- "some message" ) );
+TEST(WriteOpTests, TargetSingle) {
+ //
+ // Basic targeting test
+ //
- writeOp.setOpError( *error );
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Error );
- ASSERT_EQUALS( writeOp.getOpError().getErrCode(), error->getErrCode() );
- ASSERT_EQUALS( writeOp.getOpError().getErrInfo()["data"].Int(),
- error->getErrInfo()["data"].Int() );
- ASSERT_EQUALS( writeOp.getOpError().getErrMessage(), error->getErrMessage() );
- }
+ NamespaceString nss("foo.bar");
- TEST(WriteOpTests, TargetSingle) {
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
- //
- // Basic targeting test
- //
+ vector<MockRange*> mockRanges;
+ mockRanges.push_back(new MockRange(endpoint, nss, BSON("x" << MINKEY), BSON("x" << MAXKEY)));
- NamespaceString nss( "foo.bar" );
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
- ShardEndpoint endpoint( "shard", ChunkVersion::IGNORED() );
+ // Do single-target write op
- vector<MockRange*> mockRanges;
- mockRanges.push_back( new MockRange( endpoint,
- nss,
- BSON( "x" << MINKEY ),
- BSON( "x" << MAXKEY ) ) );
+ WriteOp writeOp(BatchItemRef(&request, 0));
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Ready);
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
+ MockNSTargeter targeter;
+ targeter.init(mockRanges);
- // Do single-target write op
+ OwnedPointerVector<TargetedWrite> targetedOwned;
+ vector<TargetedWrite*>& targeted = targetedOwned.mutableVector();
+ Status status = writeOp.targetWrites(targeter, &targeted);
- WriteOp writeOp( BatchItemRef( &request, 0 ) );
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Ready );
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Pending);
+ ASSERT_EQUALS(targeted.size(), 1u);
+ assertEndpointsEqual(targeted.front()->endpoint, endpoint);
- MockNSTargeter targeter;
- targeter.init( mockRanges );
+ writeOp.noteWriteComplete(*targeted.front());
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Completed);
+}
- OwnedPointerVector<TargetedWrite> targetedOwned;
- vector<TargetedWrite*>& targeted = targetedOwned.mutableVector();
- Status status = writeOp.targetWrites( targeter, &targeted );
+BatchedDeleteDocument* buildDeleteDoc(const BSONObj& doc) {
+ BatchedDeleteDocument* deleteDoc = new BatchedDeleteDocument();
- ASSERT( status.isOK() );
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Pending );
- ASSERT_EQUALS( targeted.size(), 1u );
- assertEndpointsEqual( targeted.front()->endpoint, endpoint );
+ string errMsg;
+ bool ok = deleteDoc->parseBSON(doc, &errMsg);
+ ASSERT_EQUALS(errMsg, "");
+ ASSERT(ok);
+ return deleteDoc;
+}
- writeOp.noteWriteComplete( *targeted.front() );
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Completed );
+struct EndpointComp {
+ bool operator()(const TargetedWrite* writeA, const TargetedWrite* writeB) const {
+ return writeA->endpoint.shardName.compare(writeB->endpoint.shardName) < 0;
}
+};
- BatchedDeleteDocument* buildDeleteDoc( const BSONObj& doc ) {
+inline void sortByEndpoint(vector<TargetedWrite*>* writes) {
+ std::sort(writes->begin(), writes->end(), EndpointComp());
+}
- BatchedDeleteDocument* deleteDoc = new BatchedDeleteDocument();
+TEST(WriteOpTests, TargetMultiOneShard) {
+ //
+ // Multi-write targeting test where our query goes to one shard
+ //
- string errMsg;
- bool ok = deleteDoc->parseBSON( doc, &errMsg );
- ASSERT_EQUALS( errMsg, "" );
- ASSERT( ok );
- return deleteDoc;
- }
+ NamespaceString nss("foo.bar");
- struct EndpointComp {
- bool operator()( const TargetedWrite* writeA, const TargetedWrite* writeB ) const {
- return writeA->endpoint.shardName.compare( writeB->endpoint.shardName ) < 0;
- }
- };
+ ShardEndpoint endpointA("shardA", ChunkVersion(10, 0, OID()));
+ ShardEndpoint endpointB("shardB", ChunkVersion(20, 0, OID()));
+ ShardEndpoint endpointC("shardB", ChunkVersion(20, 0, OID()));
- inline void sortByEndpoint( vector<TargetedWrite*>* writes ) {
- std::sort( writes->begin(), writes->end(), EndpointComp() );
- }
+ vector<MockRange*> mockRanges;
+ mockRanges.push_back(new MockRange(endpointA, nss, BSON("x" << MINKEY), BSON("x" << 0)));
+ mockRanges.push_back(new MockRange(endpointB, nss, BSON("x" << 0), BSON("x" << 10)));
+ mockRanges.push_back(new MockRange(endpointC, nss, BSON("x" << 10), BSON("x" << MAXKEY)));
- TEST(WriteOpTests, TargetMultiOneShard) {
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Delete);
+ request.setNS(nss.ns());
+ // Only hits first shard
+ BSONObj query = BSON("x" << GTE << -2 << LT << -1);
+ request.getDeleteRequest()->addToDeletes(buildDeleteDoc(BSON("q" << query)));
- //
- // Multi-write targeting test where our query goes to one shard
- //
+ WriteOp writeOp(BatchItemRef(&request, 0));
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Ready);
- NamespaceString nss( "foo.bar" );
+ MockNSTargeter targeter;
+ targeter.init(mockRanges);
- ShardEndpoint endpointA( "shardA", ChunkVersion(10, 0, OID()) );
- ShardEndpoint endpointB( "shardB", ChunkVersion(20, 0, OID()) );
- ShardEndpoint endpointC( "shardB", ChunkVersion(20, 0, OID()) );
+ OwnedPointerVector<TargetedWrite> targetedOwned;
+ vector<TargetedWrite*>& targeted = targetedOwned.mutableVector();
+ Status status = writeOp.targetWrites(targeter, &targeted);
- vector<MockRange*> mockRanges;
- mockRanges.push_back( new MockRange( endpointA,
- nss,
- BSON( "x" << MINKEY ),
- BSON( "x" << 0 ) ) );
- mockRanges.push_back( new MockRange( endpointB,
- nss,
- BSON( "x" << 0 ),
- BSON( "x" << 10 ) ) );
- mockRanges.push_back( new MockRange( endpointC,
- nss,
- BSON( "x" << 10 ),
- BSON( "x" << MAXKEY ) ) );
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Pending);
+ ASSERT_EQUALS(targeted.size(), 1u);
+ assertEndpointsEqual(targeted.front()->endpoint, endpointA);
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Delete );
- request.setNS( nss.ns() );
- // Only hits first shard
- BSONObj query = BSON( "x" << GTE << -2 << LT << -1 );
- request.getDeleteRequest()->addToDeletes( buildDeleteDoc( BSON( "q" << query ) ) );
+ writeOp.noteWriteComplete(*targeted.front());
- WriteOp writeOp( BatchItemRef( &request, 0 ) );
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Ready );
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Completed);
+}
- MockNSTargeter targeter;
- targeter.init( mockRanges );
+TEST(WriteOpTests, TargetMultiAllShards) {
+ //
+ // Multi-write targeting test where our write goes to more than one shard
+ //
- OwnedPointerVector<TargetedWrite> targetedOwned;
- vector<TargetedWrite*>& targeted = targetedOwned.mutableVector();
- Status status = writeOp.targetWrites( targeter, &targeted );
+ NamespaceString nss("foo.bar");
- ASSERT( status.isOK() );
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Pending );
- ASSERT_EQUALS( targeted.size(), 1u );
- assertEndpointsEqual( targeted.front()->endpoint, endpointA );
+ ShardEndpoint endpointA("shardA", ChunkVersion(10, 0, OID()));
+ ShardEndpoint endpointB("shardB", ChunkVersion(20, 0, OID()));
+ ShardEndpoint endpointC("shardB", ChunkVersion(20, 0, OID()));
- writeOp.noteWriteComplete( *targeted.front() );
+ vector<MockRange*> mockRanges;
+ mockRanges.push_back(new MockRange(endpointA, nss, BSON("x" << MINKEY), BSON("x" << 0)));
+ mockRanges.push_back(new MockRange(endpointB, nss, BSON("x" << 0), BSON("x" << 10)));
+ mockRanges.push_back(new MockRange(endpointC, nss, BSON("x" << 10), BSON("x" << MAXKEY)));
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Completed );
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Delete);
+ request.setNS(nss.ns());
+ BSONObj query = BSON("x" << GTE << -1 << LT << 1);
+ request.getDeleteRequest()->addToDeletes(buildDeleteDoc(BSON("q" << query)));
- }
+ // Do multi-target write op
- TEST(WriteOpTests, TargetMultiAllShards) {
-
- //
- // Multi-write targeting test where our write goes to more than one shard
- //
-
- NamespaceString nss( "foo.bar" );
-
- ShardEndpoint endpointA( "shardA", ChunkVersion(10, 0, OID()) );
- ShardEndpoint endpointB( "shardB", ChunkVersion(20, 0, OID()) );
- ShardEndpoint endpointC( "shardB", ChunkVersion(20, 0, OID()) );
-
- vector<MockRange*> mockRanges;
- mockRanges.push_back( new MockRange( endpointA,
- nss,
- BSON( "x" << MINKEY ),
- BSON( "x" << 0 ) ) );
- mockRanges.push_back( new MockRange( endpointB,
- nss,
- BSON( "x" << 0 ),
- BSON( "x" << 10 ) ) );
- mockRanges.push_back( new MockRange( endpointC,
- nss,
- BSON( "x" << 10 ),
- BSON( "x" << MAXKEY ) ) );
-
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Delete );
- request.setNS( nss.ns() );
- BSONObj query = BSON( "x" << GTE << -1 << LT << 1 );
- request.getDeleteRequest()->addToDeletes( buildDeleteDoc( BSON( "q" << query ) ) );
-
- // Do multi-target write op
-
- WriteOp writeOp( BatchItemRef( &request, 0 ) );
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Ready );
-
- MockNSTargeter targeter;
- targeter.init( mockRanges );
-
- OwnedPointerVector<TargetedWrite> targetedOwned;
- vector<TargetedWrite*>& targeted = targetedOwned.mutableVector();
- Status status = writeOp.targetWrites( targeter, &targeted );
-
- ASSERT( status.isOK() );
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Pending );
- ASSERT_EQUALS( targeted.size(), 3u );
- sortByEndpoint( &targeted );
- ASSERT_EQUALS( targeted[0]->endpoint.shardName, endpointA.shardName );
- ASSERT( ChunkVersion::isIgnoredVersion( targeted[0]->endpoint.shardVersion ) );
- ASSERT_EQUALS( targeted[1]->endpoint.shardName, endpointB.shardName );
- ASSERT( ChunkVersion::isIgnoredVersion( targeted[1]->endpoint.shardVersion ) );
- ASSERT_EQUALS( targeted[2]->endpoint.shardName, endpointC.shardName );
- ASSERT( ChunkVersion::isIgnoredVersion( targeted[2]->endpoint.shardVersion ) );
-
- writeOp.noteWriteComplete( *targeted[0] );
- writeOp.noteWriteComplete( *targeted[1] );
- writeOp.noteWriteComplete( *targeted[2] );
-
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Completed );
- }
+ WriteOp writeOp(BatchItemRef(&request, 0));
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Ready);
- TEST(WriteOpTests, ErrorSingle) {
+ MockNSTargeter targeter;
+ targeter.init(mockRanges);
- //
- // Single error after targeting test
- //
+ OwnedPointerVector<TargetedWrite> targetedOwned;
+ vector<TargetedWrite*>& targeted = targetedOwned.mutableVector();
+ Status status = writeOp.targetWrites(targeter, &targeted);
- NamespaceString nss( "foo.bar" );
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Pending);
+ ASSERT_EQUALS(targeted.size(), 3u);
+ sortByEndpoint(&targeted);
+ ASSERT_EQUALS(targeted[0]->endpoint.shardName, endpointA.shardName);
+ ASSERT(ChunkVersion::isIgnoredVersion(targeted[0]->endpoint.shardVersion));
+ ASSERT_EQUALS(targeted[1]->endpoint.shardName, endpointB.shardName);
+ ASSERT(ChunkVersion::isIgnoredVersion(targeted[1]->endpoint.shardVersion));
+ ASSERT_EQUALS(targeted[2]->endpoint.shardName, endpointC.shardName);
+ ASSERT(ChunkVersion::isIgnoredVersion(targeted[2]->endpoint.shardVersion));
- ShardEndpoint endpoint( "shard", ChunkVersion::IGNORED() );
+ writeOp.noteWriteComplete(*targeted[0]);
+ writeOp.noteWriteComplete(*targeted[1]);
+ writeOp.noteWriteComplete(*targeted[2]);
- vector<MockRange*> mockRanges;
- mockRanges.push_back( new MockRange( endpoint,
- nss,
- BSON( "x" << MINKEY ),
- BSON( "x" << MAXKEY ) ) );
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Completed);
+}
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
+TEST(WriteOpTests, ErrorSingle) {
+ //
+ // Single error after targeting test
+ //
- // Do single-target write op
+ NamespaceString nss("foo.bar");
- WriteOp writeOp( BatchItemRef( &request, 0 ) );
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Ready );
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
- MockNSTargeter targeter;
- targeter.init( mockRanges );
+ vector<MockRange*> mockRanges;
+ mockRanges.push_back(new MockRange(endpoint, nss, BSON("x" << MINKEY), BSON("x" << MAXKEY)));
- OwnedPointerVector<TargetedWrite> targetedOwned;
- vector<TargetedWrite*>& targeted = targetedOwned.mutableVector();
- Status status = writeOp.targetWrites( targeter, &targeted );
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
- ASSERT( status.isOK() );
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Pending );
- ASSERT_EQUALS( targeted.size(), 1u );
- assertEndpointsEqual( targeted.front()->endpoint, endpoint );
+ // Do single-target write op
- unique_ptr<WriteErrorDetail> error( buildError( ErrorCodes::UnknownError,
- BSON( "data" << 12345 ),
- "some message" ) );
+ WriteOp writeOp(BatchItemRef(&request, 0));
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Ready);
- writeOp.noteWriteError( *targeted.front(), *error );
+ MockNSTargeter targeter;
+ targeter.init(mockRanges);
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Error );
- ASSERT_EQUALS( writeOp.getOpError().getErrCode(), error->getErrCode() );
- ASSERT_EQUALS( writeOp.getOpError().getErrInfo()["data"].Int(),
- error->getErrInfo()["data"].Int() );
- ASSERT_EQUALS( writeOp.getOpError().getErrMessage(), error->getErrMessage() );
- }
+ OwnedPointerVector<TargetedWrite> targetedOwned;
+ vector<TargetedWrite*>& targeted = targetedOwned.mutableVector();
+ Status status = writeOp.targetWrites(targeter, &targeted);
- TEST(WriteOpTests, CancelSingle) {
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Pending);
+ ASSERT_EQUALS(targeted.size(), 1u);
+ assertEndpointsEqual(targeted.front()->endpoint, endpoint);
- //
- // Cancel single targeting test
- //
+ unique_ptr<WriteErrorDetail> error(
+ buildError(ErrorCodes::UnknownError, BSON("data" << 12345), "some message"));
- NamespaceString nss( "foo.bar" );
+ writeOp.noteWriteError(*targeted.front(), *error);
- ShardEndpoint endpoint( "shard", ChunkVersion::IGNORED() );
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Error);
+ ASSERT_EQUALS(writeOp.getOpError().getErrCode(), error->getErrCode());
+ ASSERT_EQUALS(writeOp.getOpError().getErrInfo()["data"].Int(),
+ error->getErrInfo()["data"].Int());
+ ASSERT_EQUALS(writeOp.getOpError().getErrMessage(), error->getErrMessage());
+}
- vector<MockRange*> mockRanges;
- mockRanges.push_back( new MockRange( endpoint,
- nss,
- BSON( "x" << MINKEY ),
- BSON( "x" << MAXKEY ) ) );
+TEST(WriteOpTests, CancelSingle) {
+ //
+ // Cancel single targeting test
+ //
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
+ NamespaceString nss("foo.bar");
- // Do single-target write op
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
- WriteOp writeOp( BatchItemRef( &request, 0 ) );
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Ready );
+ vector<MockRange*> mockRanges;
+ mockRanges.push_back(new MockRange(endpoint, nss, BSON("x" << MINKEY), BSON("x" << MAXKEY)));
- MockNSTargeter targeter;
- targeter.init( mockRanges );
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
- OwnedPointerVector<TargetedWrite> targetedOwned;
- vector<TargetedWrite*>& targeted = targetedOwned.mutableVector();
- Status status = writeOp.targetWrites( targeter, &targeted );
+ // Do single-target write op
- ASSERT( status.isOK() );
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Pending );
- ASSERT_EQUALS( targeted.size(), 1u );
- assertEndpointsEqual( targeted.front()->endpoint, endpoint );
+ WriteOp writeOp(BatchItemRef(&request, 0));
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Ready);
- writeOp.cancelWrites( NULL );
+ MockNSTargeter targeter;
+ targeter.init(mockRanges);
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Ready );
- }
+ OwnedPointerVector<TargetedWrite> targetedOwned;
+ vector<TargetedWrite*>& targeted = targetedOwned.mutableVector();
+ Status status = writeOp.targetWrites(targeter, &targeted);
- //
- // Test retryable errors
- //
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Pending);
+ ASSERT_EQUALS(targeted.size(), 1u);
+ assertEndpointsEqual(targeted.front()->endpoint, endpoint);
+
+ writeOp.cancelWrites(NULL);
+
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Ready);
+}
- TEST(WriteOpTests, RetrySingleOp) {
+//
+// Test retryable errors
+//
- //
- // Retry single targeting test
- //
+TEST(WriteOpTests, RetrySingleOp) {
+ //
+ // Retry single targeting test
+ //
- NamespaceString nss( "foo.bar" );
+ NamespaceString nss("foo.bar");
- ShardEndpoint endpoint( "shard", ChunkVersion::IGNORED() );
+ ShardEndpoint endpoint("shard", ChunkVersion::IGNORED());
- vector<MockRange*> mockRanges;
- mockRanges.push_back( new MockRange( endpoint,
- nss,
- BSON( "x" << MINKEY ),
- BSON( "x" << MAXKEY ) ) );
+ vector<MockRange*> mockRanges;
+ mockRanges.push_back(new MockRange(endpoint, nss, BSON("x" << MINKEY), BSON("x" << MAXKEY)));
- BatchedCommandRequest request( BatchedCommandRequest::BatchType_Insert );
- request.setNS( nss.ns() );
- request.getInsertRequest()->addToDocuments( BSON( "x" << 1 ) );
+ BatchedCommandRequest request(BatchedCommandRequest::BatchType_Insert);
+ request.setNS(nss.ns());
+ request.getInsertRequest()->addToDocuments(BSON("x" << 1));
- // Do single-target write op
+ // Do single-target write op
- WriteOp writeOp( BatchItemRef( &request, 0 ) );
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Ready );
+ WriteOp writeOp(BatchItemRef(&request, 0));
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Ready);
- MockNSTargeter targeter;
- targeter.init( mockRanges );
+ MockNSTargeter targeter;
+ targeter.init(mockRanges);
- OwnedPointerVector<TargetedWrite> targetedOwned;
- vector<TargetedWrite*>& targeted = targetedOwned.mutableVector();
- Status status = writeOp.targetWrites( targeter, &targeted );
+ OwnedPointerVector<TargetedWrite> targetedOwned;
+ vector<TargetedWrite*>& targeted = targetedOwned.mutableVector();
+ Status status = writeOp.targetWrites(targeter, &targeted);
- ASSERT( status.isOK() );
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Pending );
- ASSERT_EQUALS( targeted.size(), 1u );
- assertEndpointsEqual( targeted.front()->endpoint, endpoint );
+ ASSERT(status.isOK());
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Pending);
+ ASSERT_EQUALS(targeted.size(), 1u);
+ assertEndpointsEqual(targeted.front()->endpoint, endpoint);
- // Stale exception
+ // Stale exception
- unique_ptr<WriteErrorDetail> error( buildError( ErrorCodes::StaleShardVersion,
- BSON( "data" << 12345 ),
- "some message" ) );
+ unique_ptr<WriteErrorDetail> error(
+ buildError(ErrorCodes::StaleShardVersion, BSON("data" << 12345), "some message"));
- writeOp.noteWriteError( *targeted.front(), *error );
+ writeOp.noteWriteError(*targeted.front(), *error);
- ASSERT_EQUALS( writeOp.getWriteState(), WriteOpState_Ready );
- }
+ ASSERT_EQUALS(writeOp.getWriteState(), WriteOpState_Ready);
+}
-} // unnamed namespace
+} // unnamed namespace