From 64a488ba8f1ab408a0099891145efc7e0abe1da3 Mon Sep 17 00:00:00 2001 From: lkancode Date: Fri, 4 Aug 2017 17:09:44 -0700 Subject: [PATCH 1/4] Add record count variance validator and test --- .../RecordCountVarianceValidator.java | 69 +++++++++ .../RecordCountVarianceValidatorTests.java | 133 ++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 hollow/src/main/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidator.java create mode 100644 hollow/src/test/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidatorTests.java diff --git a/hollow/src/main/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidator.java b/hollow/src/main/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidator.java new file mode 100644 index 0000000000..42663ca0f7 --- /dev/null +++ b/hollow/src/main/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidator.java @@ -0,0 +1,69 @@ +/* + * + * Copyright 2017 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.netflix.hollow.api.producer.validation; + +import com.netflix.hollow.api.producer.HollowProducer.ReadState; +import com.netflix.hollow.api.producer.HollowProducer.Validator; +import com.netflix.hollow.core.read.engine.HollowTypeReadState; + +/** + * @author lkanchanapalli + * + */ +public class RecordCountVarianceValidator implements Validator { + private final String typeName; + private final float allowableVariancePercent; + + /** + * + * @param typeName + * @param allowableVariancePercent: Used to validate if the cardinality change in current cycle is with in the allowed percent. + * Ex: 0% allowableVariancePercent ensures type cardinality does not vary at all for cycle to cycle. Ex: Number of state in United States. + * 10% allowableVariancePercent: from previous cycle any addition or removal within 10% cardinality is valid. Anything more results in failure of validation. + */ + public RecordCountVarianceValidator(String typeName, float allowableVariancePercent) { + this.typeName = typeName; + if(allowableVariancePercent < 0) + throw new IllegalArgumentException("RecordCountVarianceValidator for type "+typeName+": cannot have allowableVariancePercent less than 0. Value provided: "+allowableVariancePercent); + this.allowableVariancePercent = allowableVariancePercent; + } + + /* (non-Javadoc) + * @see com.netflix.hollow.api.producer.HollowProducer.Validator#validate(com.netflix.hollow.api.producer.HollowProducer.ReadState) + */ + @Override + public void validate(ReadState readState) { + HollowTypeReadState typeState = readState.getStateEngine().getTypeState(typeName); + int latestCardinality = typeState.getPopulatedOrdinals().cardinality(); + int previousCardinality = typeState.getPreviousOrdinals().cardinality(); + + // TODO: log message indicating previous state is 0. Can happen for new name space. And also can happen + // when a type gets to zero count then the validation will be skipped. + if(previousCardinality == 0) + return; + + float actualChangePercent = (float)(100*Math.abs(latestCardinality - previousCardinality))/previousCardinality; + if (Float.compare(actualChangePercent, allowableVariancePercent) > 0) { + throw new ValidationException("RecordCountVarianceValidator for type " + typeName + + " failed. Actual change percent: " + actualChangePercent + "; allowableVariancePercent: " + + allowableVariancePercent + "; current cycle record count: " + latestCardinality + + "; previous cycle record count: " + previousCardinality); + } + } +} + diff --git a/hollow/src/test/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidatorTests.java b/hollow/src/test/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidatorTests.java new file mode 100644 index 0000000000..a3594cfe5b --- /dev/null +++ b/hollow/src/test/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidatorTests.java @@ -0,0 +1,133 @@ +/* + * + * Copyright 2017 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.netflix.hollow.api.producer.validation; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.netflix.hollow.api.consumer.HollowConsumer; +import com.netflix.hollow.api.consumer.InMemoryBlobStore; +import com.netflix.hollow.api.producer.HollowProducer; +import com.netflix.hollow.api.producer.HollowProducer.Populator; +import com.netflix.hollow.api.producer.HollowProducer.Validator.ValidationException; +import com.netflix.hollow.api.producer.HollowProducer.WriteState; +import com.netflix.hollow.api.producer.fs.HollowInMemoryBlobStager; +import com.netflix.hollow.api.producer.validation.ProducerValidationTests.TypeWithPrimaryKey; + +public class RecordCountVarianceValidatorTests { + private InMemoryBlobStore blobStore; + + @Before + public void setUp() { + blobStore = new InMemoryBlobStore(); + } + + @Test + public void failTestTooManyAdded() { + try { + HollowProducer producer = HollowProducer.withPublisher(blobStore) + .withBlobStager(new HollowInMemoryBlobStager()) + .withValidator(new RecordCountVarianceValidator("TypeWithPrimaryKey", 1f)).build(); + + producer.runCycle(new Populator() { + public void populate(WriteState newState) throws Exception { + newState.add(new TypeWithPrimaryKey(1, "Brad Pitt", "klsdjfla;sdjkf")); + newState.add(new TypeWithPrimaryKey(1, "Angelina Jolie", "as;dlkfjasd;l")); + } + }); + + producer.runCycle(new Populator() { + public void populate(WriteState newState) throws Exception { + newState.add(new TypeWithPrimaryKey(1, "Brad Pitt", "klsdjfla;sdjkf")); + newState.add(new TypeWithPrimaryKey(2, "Angelina Jolie", "as;dlkfjasd;l")); + newState.add(new TypeWithPrimaryKey(3, "Angelina Jolie1", "as;dlkfjasd;l")); + newState.add(new TypeWithPrimaryKey(4, "Angelina Jolie2", "as;dlkfjasd;l")); + newState.add(new TypeWithPrimaryKey(5, "Angelina Jolie3", "as;dlkfjasd;l")); + newState.add(new TypeWithPrimaryKey(6, "Angelina Jolie4", "as;dlkfjasd;l")); + newState.add(new TypeWithPrimaryKey(7, "Angelina Jolie5", "as;dlkfjasd;l")); + } + }); + Assert.fail(); + } catch (ValidationException expected) { + Assert.assertEquals(1, expected.getIndividualFailures().size()); + // System.out.println("Message: + // "+expected.getIndividualFailures().get(0).getMessage()); + Assert.assertTrue(expected.getIndividualFailures().get(0).getMessage() + .startsWith("RecordCountVarianceValidator for type TypeWithPrimaryKey failed.")); + } + } + + @Test + public void failTestTooManyRemoved() { + try { + HollowProducer producer = HollowProducer.withPublisher(blobStore) + .withBlobStager(new HollowInMemoryBlobStager()) + .withValidator(new RecordCountVarianceValidator("TypeWithPrimaryKey", 1f)).build(); + + producer.runCycle(new Populator() { + public void populate(WriteState newState) throws Exception { + newState.add(new TypeWithPrimaryKey(1, "Brad Pitt", "klsdjfla;sdjkf")); + newState.add(new TypeWithPrimaryKey(1, "Angelina Jolie", "as;dlkfjasd;l")); + } + }); + + producer.runCycle(new Populator() { + public void populate(WriteState newState) throws Exception { + newState.add(new TypeWithPrimaryKey(1, "Brad Pitt", "klsdjfla;sdjkf")); + newState.add(new TypeWithPrimaryKey(1, "Angelina Jolie", "as;dlkfjasd;l")); + newState.add(new TypeWithPrimaryKey(1, "Bruce Willis", "as;dlkfjasd;l")); + } + }); + Assert.fail(); + } catch (ValidationException expected) { + Assert.assertEquals(1, expected.getIndividualFailures().size()); + // System.out.println("Message: + // "+expected.getIndividualFailures().get(0).getMessage()); + Assert.assertTrue(expected.getIndividualFailures().get(0).getMessage() + .startsWith("RecordCountVarianceValidator for type TypeWithPrimaryKey failed.")); + } + } + + @Test + public void passTestNoMoreChangeThanExpected() { + HollowProducer producer = HollowProducer.withPublisher(blobStore).withBlobStager(new HollowInMemoryBlobStager()) + .withValidator(new RecordCountVarianceValidator("TypeWithPrimaryKey", 50f)).build(); + + // runCycle(producer, 1); + producer.runCycle(new Populator() { + + public void populate(WriteState newState) throws Exception { + newState.add(new TypeWithPrimaryKey(1, "Brad Pitt", "klsdjfla;sdjkf")); + newState.add(new TypeWithPrimaryKey(1, "Angelina Jolie", "as;dlkfjasd;l")); + } + }); + + producer.runCycle(new Populator() { + + public void populate(WriteState newState) throws Exception { + newState.add(new TypeWithPrimaryKey(1, "Brad Pitt", "klsdjfla;sdjkf")); + newState.add(new TypeWithPrimaryKey(2, "Angelina Jolie", "as;dlkfjasd;l")); + newState.add(new TypeWithPrimaryKey(7, "Bruce Willis", "as;dlkfjasd;l")); + } + }); + HollowConsumer consumer = HollowConsumer.withBlobRetriever(blobStore).build(); + consumer.triggerRefresh(); + Assert.assertEquals(3,consumer.getStateEngine().getTypeState("TypeWithPrimaryKey").getPopulatedOrdinals().cardinality()); + } +} From aa3254bda72f67c8d7068812660cb84bcf3c64f0 Mon Sep 17 00:00:00 2001 From: lkancode Date: Fri, 4 Aug 2017 17:19:45 -0700 Subject: [PATCH 2/4] Minor: error message changes suggested by Tim --- .../api/producer/validation/RecordCountVarianceValidator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hollow/src/main/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidator.java b/hollow/src/main/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidator.java index 42663ca0f7..e1426d4542 100644 --- a/hollow/src/main/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidator.java +++ b/hollow/src/main/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidator.java @@ -60,8 +60,8 @@ public void validate(ReadState readState) { float actualChangePercent = (float)(100*Math.abs(latestCardinality - previousCardinality))/previousCardinality; if (Float.compare(actualChangePercent, allowableVariancePercent) > 0) { throw new ValidationException("RecordCountVarianceValidator for type " + typeName - + " failed. Actual change percent: " + actualChangePercent + "; allowableVariancePercent: " - + allowableVariancePercent + "; current cycle record count: " + latestCardinality + + " failed. Actual variance: " + actualChangePercent + "%; Allowed variance: " + + allowableVariancePercent + "%; current cycle record count: " + latestCardinality + "; previous cycle record count: " + previousCardinality); } } From 561f783edb11b1e69cd353c2221d1074f5eb068f Mon Sep 17 00:00:00 2001 From: lkancode Date: Mon, 7 Aug 2017 11:18:59 -0700 Subject: [PATCH 3/4] Minor: change error message --- .../producer/validation/RecordCountVarianceValidator.java | 5 +++-- .../validation/RecordCountVarianceValidatorTests.java | 6 ++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/hollow/src/main/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidator.java b/hollow/src/main/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidator.java index e1426d4542..6f5fb0f530 100644 --- a/hollow/src/main/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidator.java +++ b/hollow/src/main/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidator.java @@ -61,8 +61,9 @@ public void validate(ReadState readState) { if (Float.compare(actualChangePercent, allowableVariancePercent) > 0) { throw new ValidationException("RecordCountVarianceValidator for type " + typeName + " failed. Actual variance: " + actualChangePercent + "%; Allowed variance: " - + allowableVariancePercent + "%; current cycle record count: " + latestCardinality - + "; previous cycle record count: " + previousCardinality); + + allowableVariancePercent + "%; previous cycle record count: " + previousCardinality + + "; current cycle record count: " + latestCardinality); + } } } diff --git a/hollow/src/test/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidatorTests.java b/hollow/src/test/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidatorTests.java index a3594cfe5b..9cb7d6d024 100644 --- a/hollow/src/test/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidatorTests.java +++ b/hollow/src/test/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidatorTests.java @@ -66,8 +66,7 @@ public void populate(WriteState newState) throws Exception { Assert.fail(); } catch (ValidationException expected) { Assert.assertEquals(1, expected.getIndividualFailures().size()); - // System.out.println("Message: - // "+expected.getIndividualFailures().get(0).getMessage()); + System.out.println("Message:"+expected.getIndividualFailures().get(0).getMessage()); Assert.assertTrue(expected.getIndividualFailures().get(0).getMessage() .startsWith("RecordCountVarianceValidator for type TypeWithPrimaryKey failed.")); } @@ -96,9 +95,8 @@ public void populate(WriteState newState) throws Exception { }); Assert.fail(); } catch (ValidationException expected) { + System.out.println("Message:"+expected.getIndividualFailures().get(0).getMessage()); Assert.assertEquals(1, expected.getIndividualFailures().size()); - // System.out.println("Message: - // "+expected.getIndividualFailures().get(0).getMessage()); Assert.assertTrue(expected.getIndividualFailures().get(0).getMessage() .startsWith("RecordCountVarianceValidator for type TypeWithPrimaryKey failed.")); } From bad85cad6f70a369fc7deaa8fc4f8459459dd9ac Mon Sep 17 00:00:00 2001 From: lkancode Date: Mon, 7 Aug 2017 11:23:31 -0700 Subject: [PATCH 4/4] Minor: don't print error messages in unit test --- .../validation/RecordCountVarianceValidatorTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hollow/src/test/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidatorTests.java b/hollow/src/test/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidatorTests.java index 9cb7d6d024..152263b55f 100644 --- a/hollow/src/test/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidatorTests.java +++ b/hollow/src/test/java/com/netflix/hollow/api/producer/validation/RecordCountVarianceValidatorTests.java @@ -66,7 +66,7 @@ public void populate(WriteState newState) throws Exception { Assert.fail(); } catch (ValidationException expected) { Assert.assertEquals(1, expected.getIndividualFailures().size()); - System.out.println("Message:"+expected.getIndividualFailures().get(0).getMessage()); + //System.out.println("Message:"+expected.getIndividualFailures().get(0).getMessage()); Assert.assertTrue(expected.getIndividualFailures().get(0).getMessage() .startsWith("RecordCountVarianceValidator for type TypeWithPrimaryKey failed.")); } @@ -95,7 +95,7 @@ public void populate(WriteState newState) throws Exception { }); Assert.fail(); } catch (ValidationException expected) { - System.out.println("Message:"+expected.getIndividualFailures().get(0).getMessage()); + //System.out.println("Message:"+expected.getIndividualFailures().get(0).getMessage()); Assert.assertEquals(1, expected.getIndividualFailures().size()); Assert.assertTrue(expected.getIndividualFailures().get(0).getMessage() .startsWith("RecordCountVarianceValidator for type TypeWithPrimaryKey failed."));