diff --git a/dao-api/src/main/java/com/linkedin/metadata/dao/BaseLocalDAO.java b/dao-api/src/main/java/com/linkedin/metadata/dao/BaseLocalDAO.java index 07e76c7cb..c7d2b44b7 100644 --- a/dao-api/src/main/java/com/linkedin/metadata/dao/BaseLocalDAO.java +++ b/dao-api/src/main/java/com/linkedin/metadata/dao/BaseLocalDAO.java @@ -1,5 +1,6 @@ package com.linkedin.metadata.dao; +import com.google.common.base.Preconditions; import com.linkedin.common.AuditStamp; import com.linkedin.common.urn.Urn; import com.linkedin.data.schema.validation.CoercionMode; @@ -123,6 +124,9 @@ static class AddResult { // Always emit MAE on every update regardless if there's any actual change in value private boolean _alwaysEmitAuditEvent = false; + // Whether to emit the general MAE. This takes precedence over _alwaysEmitAuditEvent if general audit events are disabled. + private boolean _enableGeneralAuditEvent = true; + private boolean _emitAspectSpecificAuditEvent = false; private boolean _alwaysEmitAspectSpecificAuditEvent = false; @@ -250,6 +254,15 @@ public void enableModelValidationOnWrite(boolean enabled) { _modelValidationOnWrite = enabled; } + /** + * Enables or disables the emission of general audit events. + */ + public void enableGeneralAuditEvent(boolean generalAuditEventEnabled) { + Preconditions.checkState(generalAuditEventEnabled || _emitAspectSpecificAuditEvent, + "Emission of general audit events cannot be disabled without enabling aspect-specific audit events"); + _enableGeneralAuditEvent = generalAuditEventEnabled; + } + /** * Sets if MAE should be always emitted after each update even if there's no actual value change. */ @@ -381,7 +394,7 @@ public ASPECT add(@Nonnull URN urn, @Nonnull Cla final ASPECT newValue = result.getNewValue(); // Produce MAE after a successful update - if (_alwaysEmitAuditEvent || oldValue != newValue) { + if (_enableGeneralAuditEvent && (_alwaysEmitAuditEvent || oldValue != newValue)) { _producer.produceMetadataAuditEvent(urn, oldValue, newValue); } diff --git a/dao-api/src/test/java/com/linkedin/metadata/dao/BaseLocalDAOTest.java b/dao-api/src/test/java/com/linkedin/metadata/dao/BaseLocalDAOTest.java index 3ea55dec7..4f8863edf 100644 --- a/dao-api/src/test/java/com/linkedin/metadata/dao/BaseLocalDAOTest.java +++ b/dao-api/src/test/java/com/linkedin/metadata/dao/BaseLocalDAOTest.java @@ -259,6 +259,64 @@ public void testMAEWithNullValue() throws URISyntaxException { verifyNoMoreInteractions(_mockEventProducer); } + @Test + public void testGeneralMAEDisabled() throws URISyntaxException { + FooUrn urn = new FooUrn(1); + AspectFoo foo = new AspectFoo().setValue("foo"); + _dummyLocalDAO.setEmitAspectSpecificAuditEvent(true); + _dummyLocalDAO.enableGeneralAuditEvent(false); + + expectGetLatest(urn, AspectFoo.class, + Arrays.asList(makeAspectEntry(null, null), makeAspectEntry(foo, _dummyAuditStamp))); + + _dummyLocalDAO.add(urn, foo, _dummyAuditStamp); + _dummyLocalDAO.delete(urn, AspectFoo.class, _dummyAuditStamp); + verify(_mockEventProducer, times(0)).produceMetadataAuditEvent(urn, null, foo); + verify(_mockEventProducer, times(1)).produceAspectSpecificMetadataAuditEvent(urn, null, foo); + verifyNoMoreInteractions(_mockEventProducer); + } + + @Test + public void testGeneralMAEDefaultsEnabled() throws URISyntaxException { + FooUrn urn = new FooUrn(1); + AspectFoo foo = new AspectFoo().setValue("foo"); + + expectGetLatest(urn, AspectFoo.class, + Arrays.asList(makeAspectEntry(null, null), makeAspectEntry(foo, _dummyAuditStamp))); + + _dummyLocalDAO.add(urn, foo, _dummyAuditStamp); + _dummyLocalDAO.delete(urn, AspectFoo.class, _dummyAuditStamp); + verify(_mockEventProducer, times(1)).produceMetadataAuditEvent(urn, null, foo); + verify(_mockEventProducer, times(0)).produceAspectSpecificMetadataAuditEvent(urn, null, foo); + verifyNoMoreInteractions(_mockEventProducer); + } + + @Test(expectedExceptions = { + IllegalStateException.class}, + expectedExceptionsMessageRegExp = "Emission of general audit events cannot be disabled without enabling aspect-specific audit events") + public void testCannotDisableGeneralMAEWithoutEnablingAspectMAE() { + _dummyLocalDAO.setEmitAspectSpecificAuditEvent(false); + _dummyLocalDAO.enableGeneralAuditEvent(false); + } + + @Test + public void testMAEDualWrite() throws URISyntaxException { + FooUrn urn = new FooUrn(1); + AspectFoo foo = new AspectFoo().setValue("foo"); + + expectGetLatest(urn, AspectFoo.class, + Arrays.asList(makeAspectEntry(null, null), makeAspectEntry(foo, _dummyAuditStamp))); + + _dummyLocalDAO.setEmitAspectSpecificAuditEvent(true); + + _dummyLocalDAO.add(urn, foo, _dummyAuditStamp); + _dummyLocalDAO.delete(urn, AspectFoo.class, _dummyAuditStamp); + verify(_mockEventProducer, times(1)).produceMetadataAuditEvent(urn, null, foo); + verify(_mockEventProducer, times(1)).produceAspectSpecificMetadataAuditEvent(urn, null, foo); + verifyNoMoreInteractions(_mockEventProducer); + + } + @Test public void testAddSamePreUpdateHookTwice() { BiConsumer hook = (urn, foo) -> {