Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions lib/storage/metadata/mongoclient/MongoClientInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2524,21 +2524,36 @@ class MongoClientInterface {

return this.db.command({ dbStats: 1, scale: 1 })
Comment thread
delthas marked this conversation as resolved.
.then(stats => {
const result = {
available: stats.fsFreeSize || 0,
// Same as available in MongoDB context
free: stats.fsFreeSize || 0,
total: stats.fsTotalSize || 0
};
return cb(null, result);
if (stats.fsTotalSize === undefined || stats.fsUsedSize === undefined) {
this.logger.error('unexpected dbStats response: missing fsTotalSize or fsUsedSize',
{ stats });
return cb(errors.InternalError);
}
const free = stats.fsTotalSize - stats.fsUsedSize;
return cb(null, { available: free, free, total: stats.fsTotalSize });
})
.catch(err => {
this.logger.error('Error getting MongoDB disk stats',
this.logger.error('Error getting MongoDB disk stats',
{ error: err.message });
return cb(errors.InternalError);
});
}

getCollectionStats(bucketName: string, log: werelogs.Logger, cb: ArsenalCallback<any>) {
if (!this.db || !this.client) {
return cb(errors.InternalError.customizeDescription(
'Cannot get collection stats: database not connected'));
}
const c = this.getCollection(bucketName);
return this.db.command({ collStats: c.collectionName })
Comment thread
delthas marked this conversation as resolved.
.then(stats => cb(null, stats))
.catch(err => {
log.error('error getting collection stats',
{ error: err.message, bucketName });
return cb(errors.InternalError);
});
}
Comment thread
delthas marked this conversation as resolved.

readCountItems(log: werelogs.Logger, cb: ArsenalCallback<ObjectMDStats | string>) {
const i = this.getCollection<InfostoreDocument>(INFOSTORE);
if (!i) {
Expand Down
114 changes: 114 additions & 0 deletions tests/functional/metadata/mongodb/diskUsage.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
const assert = require('assert');
const util = require('util');
const werelogs = require('werelogs');
const { MongoMemoryReplSet } = require('mongodb-memory-server');
const logger = new werelogs.Logger('MongoClientInterface', 'debug', 'debug');
const BucketInfo = require('../../../../lib/models/BucketInfo').default;
const MetadataWrapper =
require('../../../../lib/storage/metadata/MetadataWrapper');

const IMPL_NAME = 'mongodb';
const DB_NAME = 'metadata';
const BUCKET_NAME = 'test-bucket';
const BUCKET_MD = {
_owner: 'testowner',
_ownerDisplayName: 'testdisplayname',
_creationDate: new Date().toJSON(),
_acl: {
Canned: 'private',
FULL_CONTROL: [],
WRITE: [],
WRITE_ACP: [],
READ: [],
READ_ACP: [],
},
_mdBucketModelVersion: 10,
_transient: false,
_deleted: false,
_serverSideEncryption: null,
_versioningConfiguration: null,
_locationConstraint: 'us-east-1',
_readLocationConstraint: null,
_cors: null,
_replicationConfiguration: null,
_lifecycleConfiguration: null,
_uid: '',
_isNFS: null,
ingestion: null,
};

const mongoserver = new MongoMemoryReplSet({
debug: false,
instanceOpts: [
{ port: 27023 },
],
replSet: {
name: 'rs0',
count: 1,
DB_NAME,
storageEngine: 'wiredTiger',
},
});

describe('MongoClientInterface:getDiskUsage and getCollectionStats', () => {
let metadata;

beforeAll(async () => {
await mongoserver.start();
await mongoserver.waitUntilRunning();

const opts = {
mongodb: {
replicaSetHosts: 'localhost:27023',
writeConcern: 'majority',
replicaSet: 'rs0',
readPreference: 'primary',
database: DB_NAME,
},
};
metadata = new MetadataWrapper(IMPL_NAME, opts, null, logger);
metadata.setup = util.promisify(metadata.setup.bind(metadata));
metadata.createBucket = util.promisify(metadata.createBucket.bind(metadata));
metadata.close = util.promisify(metadata.close.bind(metadata));

await metadata.setup();

const bucketMD = BucketInfo.fromObj({
_name: BUCKET_NAME,
...BUCKET_MD,
});
await metadata.createBucket(BUCKET_NAME, bucketMD, logger);
});

afterAll(async () => {
await metadata.close();
await mongoserver.stop();
});

it('getDiskUsage should return disk usage with numeric values', done => {
metadata.client.getDiskUsage((err, result) => {
assert.ifError(err);
assert(typeof result.available === 'number');
assert(typeof result.free === 'number');
assert(typeof result.total === 'number');
assert(result.total > 0);
assert(result.free >= 0);
assert(result.available >= 0);
assert.strictEqual(result.free, result.available);
assert(result.free <= result.total);
done();
});
});

it('getCollectionStats should return stats with index sizes', done => {
metadata.client.getCollectionStats(BUCKET_NAME, logger, (err, stats) => {
assert.ifError(err);
assert(typeof stats.totalIndexSize === 'number');
assert(stats.totalIndexSize > 0);
assert(typeof stats.indexSizes === 'object');
assert(typeof stats.indexSizes._id_ === 'number');
assert(stats.indexSizes._id_ > 0);
done();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -307,21 +307,21 @@ describe('MongoClientInterface::getDiskUsage', () => {

// Mock MongoDB stats response
const mockStats = {
fsFreeSize: 1000000,
fsUsedSize: 4000000,
fsTotalSize: 5000000
};

// Mock the database and command
testClient.db = {
command: sinon.stub().resolves(mockStats)
};
testClient.client = {}; // Just to pass the initial check

testClient.getDiskUsage((err, result) => {
assert.strictEqual(err, null);
assert.deepStrictEqual(result, {
available: mockStats.fsFreeSize,
free: mockStats.fsFreeSize,
available: 1000000,
free: 1000000,
total: mockStats.fsTotalSize
});
assert(testClient.db.command.calledOnce);
Expand All @@ -330,7 +330,7 @@ describe('MongoClientInterface::getDiskUsage', () => {
});
});

it('should handle missing stats properties gracefully', done => {
it('should return error when stats properties are missing', done => {
// Setup a client with mock db
const testClient = new MongoClientInterface({
logger,
Expand All @@ -344,29 +344,23 @@ describe('MongoClientInterface::getDiskUsage', () => {
isLocationTransient: () => false,
shardCollections: false,
});

// Mock MongoDB stats response with missing properties
const mockStats = {
// No fsFreeSize or fsTotalSize
db: 'test',
collections: 5
};

// Mock the database and command
testClient.db = {
command: sinon.stub().resolves(mockStats)
};
testClient.client = {}; // Just to pass the initial check

testClient.getDiskUsage((err, result) => {
assert.strictEqual(err, null);
assert.deepStrictEqual(result, {
available: 0,
free: 0,
total: 0
});
assert(testClient.db.command.calledOnce);
assert(testClient.db.command.calledWith({ dbStats: 1, scale: 1 }));
assert.strictEqual(result, undefined);
assert(err);
assert(err.is.InternalError);
done();
});
});
Expand Down
Loading