SERVER-118828 Allow applyOps to execute upgradeDowngradeViewlessTimeseries c-entry for system-authenticated users (#51066)

GitOrigin-RevId: 3db433cd8fb97a5159e85947f45f76d53ffad6f4
This commit is contained in:
Tommaso Tocci 2026-04-10 18:29:26 +02:00 committed by MongoDB Bot
parent 4258f2ba01
commit ecbdf9983c
5 changed files with 217 additions and 2 deletions

View File

@ -0,0 +1,92 @@
/**
* Tests that the upgradeDowngradeViewlessTimeseries oplog entry applied via applyOps requires
* the __system role when auth is enabled.
*
* TODO(SERVER-114573): Remove once 9.0 becomes last-lts, as the oplog entry won't be
* supported anymore.
*
* @tags: [
* featureFlagCreateViewlessTimeseriesCollections,
* ]
*/
import {ReplSetTest} from "jstests/libs/replsettest.js";
if (lastLTSFCV != "8.0") {
jsTest.log.info("Skipping test because last LTS FCV is no longer 8.0");
quit();
}
const keyFile = "jstests/libs/key1";
const rst = new ReplSetTest({nodes: 1, keyFile: keyFile});
rst.startSet();
rst.initiate();
const primary = rst.getPrimary();
const adminDB = primary.getDB("admin");
adminDB.createUser({user: "admin", pwd: "pwd", roles: ["root"]});
assert(adminDB.auth("admin", "pwd"));
const dbName = jsTestName();
const collName = "ts";
const testDB = primary.getDB(dbName);
const bucketsColl = testDB.getCollection("system.buckets." + collName);
const timeseriesOptions = {
timeField: "t",
granularity: "seconds",
bucketMaxSpanSeconds: 3600,
};
function makeCreateCmd(collectionName) {
return {
applyOps: [
{
op: "c",
ns: dbName + ".$cmd",
o: {
create: collectionName,
clusteredIndex: true,
timeseries: timeseriesOptions,
},
},
],
};
}
function makeUpgradeDowngradeCmd(isUpgrade, uuid) {
return {
applyOps: [
{
op: "c",
ns: dbName + ".$cmd",
o: {upgradeDowngradeViewlessTimeseries: collName, isUpgrade: isUpgrade},
ui: uuid,
},
],
};
}
// Create a legacy (viewful) timeseries collection via applyOps at FCV 9.0.
assert.commandWorked(testDB.adminCommand(makeCreateCmd("system.buckets." + collName)));
const collUUID = bucketsColl.getUUID();
// A root user should not be able to applyOps an internal-only oplog entry.
assert.commandFailedWithCode(testDB.adminCommand(makeUpgradeDowngradeCmd(true, collUUID)), ErrorCodes.Unauthorized);
adminDB.logout();
// The __system role is required to applyOps internal-only oplog entries.
rst.asCluster(primary, () => {
// Upgrade: viewful -> viewless via applyOps.
assert.commandWorked(testDB.adminCommand(makeUpgradeDowngradeCmd(true, collUUID)));
assert.eq(null, bucketsColl.exists(), "Expected no system.buckets collection after upgrade");
// Downgrade: viewless -> viewful via applyOps.
// First downgrade FCV so the feature flag is disabled (required for downgrade path).
assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: lastLTSFCV, confirm: true}));
assert.commandWorked(testDB.adminCommand(makeUpgradeDowngradeCmd(false, collUUID)));
assert.neq(null, bucketsColl.exists(), "Expected system.buckets collection after downgrade");
});
rst.stopSet();

View File

@ -24,3 +24,6 @@ filters:
- "delinquent*":
approvers:
- 10gen/server-workload-resilience
- "timeseries_upgrade_downgrade_apply_ops.js":
approvers:
- 10gen/server-catalog-and-routing-shard-catalog

View File

@ -0,0 +1,102 @@
/**
* Tests that the upgradeDowngradeViewlessTimeseries oplog entry can be applied via applyOps.
*
* TODO(SERVER-114573): Remove once 9.0 becomes last-lts, as the oplog entry won't be
* supported anymore.
*
* @tags: [
* featureFlagCreateViewlessTimeseriesCollections,
* ]
*/
import {ReplSetTest} from "jstests/libs/replsettest.js";
if (lastLTSFCV != "8.0") {
jsTest.log.info("Skipping test because last LTS FCV is no longer 8.0");
quit();
}
const rst = new ReplSetTest({nodes: 1});
rst.startSet();
rst.initiate();
const primary = rst.getPrimary();
const adminDB = primary.getDB("admin");
const dbName = jsTestName();
const collName = "ts";
const testDB = primary.getDB(dbName);
const bucketsColl = testDB.getCollection("system.buckets." + collName);
const mainColl = testDB.getCollection(collName);
const timeseriesOptions = {
timeField: "t",
granularity: "seconds",
bucketMaxSpanSeconds: 3600,
};
function makeCreateCmd(collectionName) {
return {
applyOps: [
{
op: "c",
ns: dbName + ".$cmd",
o: {
create: collectionName,
clusteredIndex: true,
timeseries: timeseriesOptions,
},
},
],
};
}
function makeUpgradeDowngradeCmd(isUpgrade, uuid) {
return {
applyOps: [
{
op: "c",
ns: dbName + ".$cmd",
o: {upgradeDowngradeViewlessTimeseries: collName, isUpgrade: isUpgrade},
ui: uuid,
},
],
};
}
// Phase 1: Test upgrade path at FCV 9.0 (feature flag enabled).
// Use applyOps to create a legacy (viewful) timeseries collection, then upgrade it.
{
jsTest.log("Phase 1: Testing timeseries upgrade via applyOps at latest FCV");
// Create a legacy system.buckets collection via applyOps.
assert.commandWorked(testDB.adminCommand(makeCreateCmd("system.buckets." + collName)));
const collUUID = bucketsColl.getUUID();
assert.neq(null, bucketsColl.exists(), "system.buckets collection should exist after create");
// Upgrade: viewful -> viewless via applyOps.
assert.commandWorked(testDB.adminCommand(makeUpgradeDowngradeCmd(true, collUUID)));
assert.eq(null, bucketsColl.exists(), "Expected no system.buckets collection after upgrade");
assert.neq(null, mainColl.exists(), "Expected main collection after upgrade");
// Clean up for phase 2.
assert.commandWorked(testDB.runCommand({drop: collName}));
}
// Phase 2: Test downgrade path at FCV 8.0 (feature flag disabled).
// Use applyOps to create a viewless timeseries collection, then downgrade it.
{
jsTest.log("Phase 2: Testing downgrade via applyOps at last-LTS FCV");
assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: lastLTSFCV, confirm: true}));
// Create a viewless timeseries collection via applyOps.
assert.commandWorked(testDB.adminCommand(makeCreateCmd(collName)));
const collUUID = mainColl.getUUID();
assert.neq(null, mainColl.exists(), "Main collection should exist after create");
assert.eq(null, bucketsColl.exists(), "system.buckets should not exist for viewless collection");
// Downgrade: viewless -> viewful via applyOps.
assert.commandWorked(testDB.adminCommand(makeUpgradeDowngradeCmd(false, collUUID)));
assert.neq(null, bucketsColl.exists(), "Expected system.buckets collection after downgrade");
}
rst.stopSet();

View File

@ -54,6 +54,7 @@
#include "mongo/rpc/op_msg.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/str.h"
#include "mongo/util/string_map.h"
#include <memory>
#include <string>
@ -111,6 +112,21 @@ Status OplogApplicationChecks::checkOperationAuthorization(OperationContext* opC
StringData commandName = o.firstElement().fieldNameStringData();
Command* commandInOplogEntry = CommandHelpers::findCommand(opCtx, commandName);
if (!commandInOplogEntry) {
// Some oplog entries are internal-only and not registered in the global command
// registry. Allow them through if the user has ActionType::internal (e.g. __system).
// TODO(SERVER-114573): Remove upgradeDowngradeViewlessTimeseries from this list once
// 9.0 becomes last-lts, as the oplog entry will no longer exist.
static const StringDataSet kInternalOplogCommands{
"upgradeDowngradeViewlessTimeseries"_sd,
};
if (kInternalOplogCommands.contains(commandName)) {
if (!authSession->isAuthorizedForActionsOnResource(
ResourcePattern::forClusterResource(dbName.tenantId()),
ActionType::internal)) {
return Status(ErrorCodes::Unauthorized, "Unauthorized");
}
return Status::OK();
}
return Status(ErrorCodes::FailedToParse, "Unrecognized command in op");
}
@ -123,8 +139,8 @@ Status OplogApplicationChecks::checkOperationAuthorization(OperationContext* opC
nss.tenantId(), "admin", SerializationContext::stateDefault());
}
// TODO reuse the parse result for when we run() later. Note that when running,
// we must use a potentially different dbname.
// TODO SERVER-123371 reuse the parse result for when we run() later. Note that when
// running, we must use a potentially different dbname.
return [&] {
try {
boost::optional<auth::ValidatedTenancyScope> vts;

View File

@ -1368,6 +1368,8 @@ const StringMap<ApplyOpMetadata> kOpsMap = {
return Status::OK();
},
{ErrorCodes::NamespaceNotFound}}},
// TODO(SERVER-114573): Remove once 9.0 becomes last-lts, as this oplog entry won't be
// supported anymore.
{"upgradeDowngradeViewlessTimeseries",
{[](OperationContext* opCtx, const ApplierOperation& op, OplogApplication::Mode mode)
-> Status {