FileSpan makePermanent(MockId id, boolean forcePut, short lifeVersion) throws StoreException {
    IndexValue value = getExpectedValue(id, false);
    boolean hasLifeVersion = IndexValue.hasLifeVersion(lifeVersion);
    if (!forcePut && value == null) {
      throw new IllegalArgumentException(id + " does not exist in the index");
    } else if (expiredKeys.contains(id) || (value != null && value.isDelete())) {
      throw new IllegalArgumentException(id + " is deleted or expired");
    } else if (value != null && value.isTtlUpdate()) {
      throw new IllegalArgumentException(id + " ttl has already been updated");
    }

    byte[] dataWritten = appendToLog(CuratedLogIndexState.TTL_UPDATE_RECORD_SIZE);
    Offset endOffsetOfPrevMsg = index.getCurrentEndOffset();
    FileSpan fileSpan = log.getFileSpanForMessage(endOffsetOfPrevMsg, CuratedLogIndexState.TTL_UPDATE_RECORD_SIZE);
    Offset startOffset = fileSpan.getStartOffset();
    Offset indexSegmentStartOffset = generateReferenceIndexSegmentStartOffset(startOffset);
    if (!referenceIndex.containsKey(indexSegmentStartOffset)) {
      // rollover will occur
      advanceTime(DELAY_BETWEEN_LAST_MODIFIED_TIMES_MS);
      referenceIndex.put(indexSegmentStartOffset, new TreeMap<>());
    }
    IndexValue newValue;
    if (value != null) {
      short ttlUpdateLifeVersion = hasLifeVersion ? lifeVersion : value.getLifeVersion();
      newValue =
          new IndexValue(value.getSize(), value.getOffset(), value.getFlags(), Utils.Infinite_Time, time.milliseconds(),
              value.getAccountId(), value.getContainerId(), ttlUpdateLifeVersion);
      newValue.setNewOffset(startOffset);
      newValue.setNewSize(CuratedLogIndexState.TTL_UPDATE_RECORD_SIZE);
    } else {
      short ttlUpdateLifeVersion = hasLifeVersion ? lifeVersion : 0;
      newValue =
          new IndexValue(CuratedLogIndexState.TTL_UPDATE_RECORD_SIZE, startOffset, IndexValue.FLAGS_DEFAULT_VALUE,
              Utils.Infinite_Time, time.milliseconds(), id.getAccountId(), id.getContainerId(), ttlUpdateLifeVersion);
      newValue.clearOriginalMessageOffset();
    }
    newValue.clearFlag(IndexValue.Flags.Undelete_Index);
    newValue.setFlag(IndexValue.Flags.Ttl_Update_Index);
    if (forcePut) {
      index.addToIndex(new IndexEntry(id, newValue), fileSpan);
    } else {
      index.markAsPermanent(id, fileSpan, newValue.getOperationTimeInMs());
    }
    ttlUpdatedKeys.add(id);
    logOrder.put(startOffset, new Pair<>(id, new LogEntry(dataWritten, newValue)));
    allKeys.computeIfAbsent(id, k -> new TreeSet<>()).add(newValue);
    referenceIndex.get(indexSegmentStartOffset).computeIfAbsent(id, k -> new TreeSet<>()).add(newValue);
    lastModifiedTimesInSecs.put(indexSegmentStartOffset,
        TimeUnit.MILLISECONDS.toSeconds(newValue.getOperationTimeInMs()));
    endOffsetOfPrevMsg = fileSpan.getEndOffset();
    assertEquals("End Offset of index not as expected", endOffsetOfPrevMsg, index.getCurrentEndOffset());
    assertEquals("Journal's last offset not as expected", startOffset, index.journal.getLastOffset());
    return fileSpan;
  }
Makes the TTL of {@code id} infinite.
@param id the {@link MockId} whose TTL needs to be made infinite.
@param forcePut if {@code true}, forces a ttl update record to be created even if a PUT isn't present. Does NOT
                force if a ttl update or delete is present.
@param lifeVersion the life version of the ttl update
@return the {@link FileSpan} at which the update record was added
@throws StoreException
ambry-store/src/test/java/com/github/ambry/store/CuratedLogIndexState.java