Skip to content
Open
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
78 changes: 54 additions & 24 deletions lib/models/ObjectMD.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as crypto from 'crypto';
import * as constants from '../constants';
import * as VersionIDUtils from '../versioning/VersionID';
import { VersioningConstants } from '../versioning/constants';
Expand Down Expand Up @@ -32,6 +31,7 @@ export type ReplicationInfo = {
storageType: string;
dataStoreVersionId: string;
isNFS?: boolean;
isReplica?: boolean;
Comment thread
maeldonn marked this conversation as resolved.
};

export type ObjectMDTag = {
Expand Down Expand Up @@ -243,6 +243,7 @@ export default class ObjectMD {
storageType: '',
dataStoreVersionId: '',
isNFS: undefined,
isReplica: undefined,
Comment thread
maeldonn marked this conversation as resolved.
},
dataStoreName: '',
originOp: '',
Expand Down Expand Up @@ -1102,9 +1103,20 @@ export default class ObjectMD {
storageType?: string;
dataStoreVersionId?: string;
isNFS?: boolean;
isReplica?: boolean;
}) {
const { status, backends, content, destination, storageClass, role, storageType, dataStoreVersionId, isNFS } =
replicationInfo;
const {
status,
backends,
content,
destination,
storageClass,
role,
storageType,
dataStoreVersionId,
isNFS,
isReplica,
} = replicationInfo;
this._data.replicationInfo = {
status,
backends,
Expand All @@ -1115,6 +1127,7 @@ export default class ObjectMD {
storageType: storageType || '',
dataStoreVersionId: dataStoreVersionId || '',
isNFS,
isReplica: isReplica !== undefined ? isReplica : (status === 'REPLICA' ? true : undefined),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
isReplica: isReplica !== undefined ? isReplica : (status === 'REPLICA' ? true : undefined),
isReplica: isReplica !== undefined ? isReplica : (status === 'REPLICA' ? true : false),

not sure if undefined or false when status not REPLICA

Copy link
Copy Markdown
Contributor Author

@maeldonn maeldonn Jun 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it's a boolean i would say false. But if we can save some storage in database, we should use undefined.

};
return this;
}
Expand All @@ -1130,6 +1143,9 @@ export default class ObjectMD {

setReplicationStatus(status: string) {
this._data.replicationInfo.status = status;
if (status === 'REPLICA') {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is useless since we are not going anymore to set status to 'REPLICA'

this.setReplicationIsReplica(true);
}
return this;
}

Expand All @@ -1151,6 +1167,27 @@ export default class ObjectMD {
return !!this._data.replicationInfo.isNFS;
}

/**
* Mark this object as the result of a replication write (replica),
* as opposed to a write originating from a user request.
*
* @param isReplica - true if this object was written by replication
* @return itself
*/
setReplicationIsReplica(isReplica: boolean) {
Comment thread
maeldonn marked this conversation as resolved.
this._data.replicationInfo.isReplica = isReplica;
return this;
}

/**
* Get whether this object was written by replication (replica).
* @return true if this object is a replica
*/
getReplicationIsReplica(): boolean {
return this._data.replicationInfo.isReplica === true
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if need logic in this getter. Is it used ?

|| this._data.replicationInfo.status === 'REPLICA';
}

setReplicationSiteStatus(site: string, status: string) {
const backend = this._data.replicationInfo.backends.find(o => o.site === site);
if (backend) {
Expand Down Expand Up @@ -1327,35 +1364,28 @@ export default class ObjectMD {
}

/**
* Create or update the microVersionId field
*
* This field can be used to force an update in MongoDB. This can
* be needed in the following cases:
*
* - in case no other metadata field changes
*
* - to detect a change when fields change but object version does
* not change e.g. when ingesting a putObjectTagging coming from
* S3C to Zenko
*
* - to manage conflicts during concurrent updates, using
* conditions on the microVersionId field.
*
* It's a field of 16 hexadecimal characters randomly generated
*
* Update the microVersionId
*
* This field can be used to force an update in MongoDB when no other
* metadata field changes, to detect a change for CRR,
* and to manage conflicts during concurrent updates using conditions on this field.
*
* @param instanceId - instance identifier (e.g. config.instanceId)
* @param replicationGroupId - replication group ID (e.g. config.replicationGroupId)
* @return itself
*/
updateMicroVersionId() {
this._data.microVersionId = crypto.randomBytes(8).toString('hex');
updateMicroVersionId(instanceId: string, replicationGroupId: string) {
this._data.microVersionId = VersionIDUtils.generateVersionId(instanceId, replicationGroupId);
return this;
}
Comment thread
SylvainSenechal marked this conversation as resolved.
Comment thread
SylvainSenechal marked this conversation as resolved.
Comment thread
SylvainSenechal marked this conversation as resolved.

/**
* Get the microVersionId field, or null if not set
* Get the microVersionId field, or undefined if not set
*
* @return the microVersionId field if exists, or {null} if it does not exist
* @return the microVersionId field if set, or undefined if not
*/
getMicroVersionId() {
return this._data.microVersionId || null;
return this._data.microVersionId || undefined;
Comment thread
maeldonn marked this conversation as resolved.
Comment thread
SylvainSenechal marked this conversation as resolved.
}

/**
Expand Down
37 changes: 37 additions & 0 deletions lib/versioning/VersionID.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,3 +369,40 @@ export function decode(str: string): string | Error {
}
return new Error(`cannot decode str ${str.length}`);
}

/**
* Returns true if a stored (raw) microVersionId is time-ordered and comparable.
*
* Returns false for:
* - null / undefined (field absent)
* - the legacy 16-char random-hex value written by old ObjectMD code
* - any other string shorter than the minimum valid raw length (27 chars)
*/
export function isMicroVersionIdComparable(id: string | null | undefined): boolean {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export function isMicroVersionIdComparable(id: string | null | undefined): boolean {
export function isComparable(id?: string | null): boolean {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the field really be null ? 🤔

return typeof id === 'string' && id.length >= LEGACY_BASE62_DECODED_LENGTH;
}

/**
* Compare an incoming (raw) microVersionId against the one stored in object
* metadata for CRR cascade loop/stale detection.
*
* Returns 'proceed' whenever either ID is non-comparable (absent, legacy
* 16-char hex, or otherwise too short) : a legacy incoming ID must never
* produce a false 'stale' or 'loop' event
*/
export function checkCrrCascadeEvent(
Comment thread
SylvainSenechal marked this conversation as resolved.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export function checkCrrCascadeEvent(
export function compare(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'loop' | 'stale' | 'proceed' are specific to CRR not to versionID format. We should just expose a simple compare function in this file

incomingRaw: string,
existingRaw: string | null | undefined,
): 'loop' | 'stale' | 'proceed' {
if (!isMicroVersionIdComparable(incomingRaw) || !isMicroVersionIdComparable(existingRaw)) {
return 'proceed';
}
const existing = existingRaw as string;
if (incomingRaw === existing) {
return 'loop';
}
if (incomingRaw > existing) {
return 'stale';
}
return 'proceed';
}
Comment thread
SylvainSenechal marked this conversation as resolved.
Loading
Loading