function getObjMetadataAndDelete(authInfo, canonicalID, request,
        bucketName, bucket, quietSetting, errorResults, inPlay, log, next) {
    const successfullyDeleted = [];
    let totalContentLengthDeleted = 0;
    let numOfObjectsRemoved = 0;
    const skipError = new Error('skip');
    const objectLockedError = new Error('object locked');
    let deleteFromStorage = [];

    return async.waterfall([
        callback => initializeMultiObjectDeleteWithBatchingSupport(bucketName, inPlay, log, callback),
        (cache, callback) => async.forEachLimit(inPlay, config.multiObjectDeleteConcurrency, (entry, moveOn) => {
            async.waterfall([
                callback => callback(...decodeObjectVersion(entry, bucketName)),
                // for obj deletes, no need to check acl's at object level
                // (authority is at the bucket level for obj deletes)
                (versionId, callback) => metadataUtils.metadataGetObject(bucketName, entry.key,
                    versionId, cache, log, (err, objMD) => callback(err, objMD, versionId)),
                (objMD, versionId, callback) => {
                    if (!objMD) {
                        const verCfg = bucket.getVersioningConfiguration();
                        // To adhere to AWS behavior, create a delete marker
                        // if trying to delete an object that does not exist
                        // when versioning has been configured
                        if (verCfg && !entry.versionId) {
                            log.debug('trying to delete specific version ' +
                                'that does not exist');
                            return callback(null, objMD, versionId);
                        }
                        // otherwise if particular key does not exist, AWS
                        // returns success for key so add to successfullyDeleted
                        // list and move on
                        successfullyDeleted.push({ entry });
                        return callback(skipError);
                    }
                    if (versionId && objMD.location &&
                        Array.isArray(objMD.location) && objMD.location[0]) {
                        // we need this information for data deletes to AWS
                        // eslint-disable-next-line no-param-reassign
                        objMD.location[0].deleteVersion = true;
                    }
                    return callback(null, objMD, versionId);
                },
                (objMD, versionId, callback) => {
                    // AWS only returns an object lock error if a version id
                    // is specified, else continue to create a delete marker
                    if (!versionId || !bucket.isObjectLockEnabled()) {
                        return callback(null, null, objMD, versionId);
                    }
                    const hasGovernanceBypass = hasGovernanceBypassHeader(request.headers);
                    if (hasGovernanceBypass && isRequesterNonAccountUser(authInfo)) {
                        return checkUserGovernanceBypass(request, authInfo, bucket, entry.key, log, error => {
                            if (error && error.is.AccessDenied) {
                                log.debug('user does not have BypassGovernanceRetention and object is locked',
                                    { error });
                                return callback(objectLockedError);
                            }
                            if (error) {
                                return callback(error);
                            }
                            return callback(null, hasGovernanceBypass, objMD, versionId);
                        });
                    }
                    return callback(null, hasGovernanceBypass, objMD, versionId);
                },
                (hasGovernanceBypass, objMD, versionId, callback) => {
                    // AWS only returns an object lock error if a version id
                    // is specified, else continue to create a delete marker
                    if (!versionId || !bucket.isObjectLockEnabled()) {
                        return callback(null, objMD, versionId);
                    }
                    const objLockInfo = new ObjectLockInfo({
                        mode: objMD.retentionMode,
                        date: objMD.retentionDate,
                        legalHold: objMD.legalHold || false,
                    });

                    // If the object can not be deleted raise an error
                    if (!objLockInfo.canModifyObject(hasGovernanceBypass)) {
                        log.debug('trying to delete locked object');
                        return callback(objectLockedError);
                    }

                    return callback(null, objMD, versionId);
                },
                (objMD, versionId, callback) => {
                    const bytes = processBytesToWrite('objectDelete', bucket, versionId, 0, objMD);
                    return validateQuotas(request, bucket, request.accountQuotas, ['objectDelete'],
                        'objectDelete', bytes, false, log, err => callback(err, objMD, versionId));
                },
                (objMD, versionId, callback) => {
                    const options = preprocessingVersioningDelete(
                        bucketName, bucket, objMD, versionId, config.nullVersionCompatMode);
                    const deleteInfo = {};
                    if (options && options.deleteData) {
                        options.overheadField = overheadField;
                        deleteInfo.deleted = true;
                        if (!_deleteRequiresOplogUpdate(objMD, bucket)) {
                            options.doesNotNeedOpogUpdate = true;
                        }
                        if (objMD.uploadId) {
                            options.replayId = objMD.uploadId;
                        }
                        return services.deleteObject(bucketName, objMD,
                            entry.key, options, config.multiObjectDeleteEnableOptimizations, log,
                            's3:ObjectRemoved:Delete', (err, toDelete) => {
                                if (err) {
                                    return callback(err);
                                }
                                if (toDelete) {
                                    deleteFromStorage = deleteFromStorage.concat(toDelete);
                                }
                                return callback(null, objMD, deleteInfo);
                            });
                    }
                    deleteInfo.newDeleteMarker = true;
                    // This call will create a delete-marker
                    return createAndStoreObject(bucketName, bucket, entry.key,
                        objMD, authInfo, canonicalID, null, request,
                        deleteInfo.newDeleteMarker, null, overheadField, log,
                        's3:ObjectRemoved:DeleteMarkerCreated', (err, result) =>
                            callback(err, objMD, deleteInfo, result.versionId));
                },
            ], (err, objMD, deleteInfo, versionId) => {
                if (err === skipError) {
                    return moveOn();
                } else if (err === objectLockedError) {
                    errorResults.push({ entry, error: errors.AccessDenied, objectLocked: true });
                    return moveOn();
                } else if (err) {
                    log.error('error deleting object', { error: err, entry });
                    errorResults.push({ entry, error: err });
                    return moveOn();
                }
                if (deleteInfo.deleted && objMD['content-length']) {
                    numOfObjectsRemoved++;
                    totalContentLengthDeleted += objMD['content-length'];
                }
                let isDeleteMarker;
                let deleteMarkerVersionId;
                // - If trying to delete an object that does not exist (if a new
                // delete marker was created)
                // - Or if an object exists but no version was specified
                // return DeleteMarkerVersionId equals the versionID of the marker
                // you just generated and DeleteMarker tag equals true
                if (deleteInfo.newDeleteMarker) {
                    isDeleteMarker = true;
                    deleteMarkerVersionId = versionIdUtils.encode(versionId);
                    // In this case we are putting a new object (i.e., the delete
                    // marker), so we decrement the numOfObjectsRemoved value.
                    numOfObjectsRemoved--;
                    // If trying to delete a delete marker, DeleteMarkerVersionId equals
                    // deleteMarker's versionID and DeleteMarker equals true
                } else if (objMD && objMD.isDeleteMarker) {
                    isDeleteMarker = true;
                    deleteMarkerVersionId = entry.versionId;
                }
                successfullyDeleted.push({
                    entry, isDeleteMarker,
                    deleteMarkerVersionId,
                });
                return moveOn();
            });
        },
            // end of forEach func
            err => {
                // Batch delete all objects
                const onDone = () => callback(err, quietSetting, errorResults, numOfObjectsRemoved,
                    successfullyDeleted, totalContentLengthDeleted, bucket);

                if (err && deleteFromStorage.length === 0) {
                    log.trace('no objects to delete from data backend');
                    return onDone();
                }
                // If error but we have objects in the list, delete them to ensure
                // consistent state.
                log.trace('deleting objects from data backend');

                // Split the array into chunks
                const chunks = [];
                while (deleteFromStorage.length > 0) {
                    chunks.push(deleteFromStorage.splice(0, config.multiObjectDeleteConcurrency));
                }

                return async.each(chunks, (chunk, done) => data.batchDelete(chunk, null, null,
                    logger.newRequestLoggerFromSerializedUids(log.getSerializedUids()), done),
                    err => {
                        if (err) {
                            log.error('error deleting objects from data backend', { error: err });
                            return onDone(err);
                        }
                        return onDone();
                    });
            }),
    ], (err, ...results) => {
        // if general error from metadata return error
        if (err) {
            monitoring.promMetrics('DELETE', bucketName, err.code,
                'multiObjectDelete');
            return next(err);
        }
        return next(null, ...results);
    });
}
gets object metadata and deletes object
@param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info
@param {string} canonicalID - canonicalId of requester
@param {object} request - http request
@param {string} bucketName - bucketName
@param {BucketInfo} bucket - bucket
@param {boolean} quietSetting - true if xml should just include error list
and false if should include deleted list and error list
@param {object []} errorResults - list of error result objects with each
object containing -- key: objectName, error: arsenal error
@param {string []} inPlay - list of object keys still in play
@param {object} log - logger object
@param {function} next - callback to next step in waterfall
@return {undefined}
@callback called with (err, quietSetting, errorResults, numOfObjects,
successfullyDeleted, totalContentLengthDeleted)