http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/0eb6645e/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/TestUpdateForwarder.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/TestUpdateForwarder.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/TestUpdateForwarder.java new file mode 100644 index 0000000..3bf32f8 --- /dev/null +++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/TestUpdateForwarder.java @@ -0,0 +1,277 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.sentry.provider.db.service; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.locks.ReadWriteLock; + +import junit.framework.Assert; + +import org.apache.sentry.hdfs.Updateable; +import org.apache.sentry.hdfs.Updateable.Update; +import org.apache.sentry.provider.db.service.UpdateForwarder.ExternalImageRetriever; +import org.junit.Test; + +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; + +public class TestUpdateForwarder { + + static class DummyUpdate implements Update { + private long seqNum = 0; + private boolean hasFullUpdate = false; + private String stuff; + public DummyUpdate(long seqNum, boolean hasFullUpdate) { + this.seqNum = seqNum; + this.hasFullUpdate = hasFullUpdate; + } + public String getStuff() { + return stuff; + } + public DummyUpdate setStuff(String stuff) { + this.stuff = stuff; + return this; + } + @Override + public boolean hasFullImage() { + return hasFullUpdate; + } + @Override + public long getSeqNum() { + return seqNum; + } + @Override + public void setSeqNum(long seqNum) { + this.seqNum = seqNum; + } + } + + static class DummyUpdatable implements Updateable<DummyUpdate> { + + private List<String> state = new LinkedList<String>(); + private long lastUpdatedSeqNum = 0; + + @Override + public void updatePartial(Iterable<DummyUpdate> update, ReadWriteLock lock) { + for (DummyUpdate u : update) { + state.add(u.getStuff()); + lastUpdatedSeqNum = u.seqNum; + } + } + + @Override + public Updateable<DummyUpdate> updateFull(DummyUpdate update) { + DummyUpdatable retVal = new DummyUpdatable(); + retVal.lastUpdatedSeqNum = update.seqNum; + retVal.state = Lists.newArrayList(update.stuff.split(",")); + return retVal; + } + + @Override + public long getLastUpdatedSeqNum() { + return lastUpdatedSeqNum; + } + + @Override + public DummyUpdate createFullImageUpdate(long currSeqNum) { + DummyUpdate retVal = new DummyUpdate(currSeqNum, true); + retVal.stuff = Joiner.on(",").join(state); + return retVal; + } + + public String getState() { + return Joiner.on(",").join(state); + } + } + + static class DummyImageRetreiver implements ExternalImageRetriever<DummyUpdate> { + + private String state; + public void setState(String state) { + this.state = state; + } + @Override + public DummyUpdate retrieveFullImage(long currSeqNum) { + DummyUpdate retVal = new DummyUpdate(currSeqNum, true); + retVal.stuff = state; + return retVal; + } + } + + @Test + public void testInit() { + DummyImageRetreiver imageRetreiver = new DummyImageRetreiver(); + imageRetreiver.setState("a,b,c"); + UpdateForwarder<DummyUpdate> updateForwarder = new UpdateForwarder<DummyUpdate>( + new DummyUpdatable(), imageRetreiver, 10); + Assert.assertEquals(-2, updateForwarder.getLastUpdatedSeqNum()); + List<DummyUpdate> allUpdates = updateForwarder.getAllUpdatesFrom(0); + Assert.assertTrue(allUpdates.size() == 1); + Assert.assertEquals("a,b,c", allUpdates.get(0).getStuff()); + + // If the current process has restarted the input seqNum will be > currSeq + allUpdates = updateForwarder.getAllUpdatesFrom(100); + Assert.assertTrue(allUpdates.size() == 1); + Assert.assertEquals("a,b,c", allUpdates.get(0).getStuff()); + Assert.assertEquals(-2, allUpdates.get(0).getSeqNum()); + allUpdates = updateForwarder.getAllUpdatesFrom(-1); + Assert.assertEquals(0, allUpdates.size()); + } + + @Test + public void testUpdateReceive() throws Exception { + DummyImageRetreiver imageRetreiver = new DummyImageRetreiver(); + imageRetreiver.setState("a,b,c"); + UpdateForwarder<DummyUpdate> updateForwarder = new UpdateForwarder<DummyUpdate>( + new DummyUpdatable(), imageRetreiver, 5); + updateForwarder.handleUpdateNotification(new DummyUpdate(5, false).setStuff("d")); + while(!updateForwarder.areAllUpdatesCommited()) { + Thread.sleep(100); + } + Assert.assertEquals(5, updateForwarder.getLastUpdatedSeqNum()); + List<DummyUpdate> allUpdates = updateForwarder.getAllUpdatesFrom(0); + Assert.assertEquals(2, allUpdates.size()); + Assert.assertEquals("a,b,c", allUpdates.get(0).getStuff()); + Assert.assertEquals("d", allUpdates.get(1).getStuff()); + } + + @Test + public void testGetUpdates() throws Exception { + DummyImageRetreiver imageRetreiver = new DummyImageRetreiver(); + imageRetreiver.setState("a,b,c"); + UpdateForwarder<DummyUpdate> updateForwarder = new UpdateForwarder<DummyUpdate>( + new DummyUpdatable(), imageRetreiver, 5); + updateForwarder.handleUpdateNotification(new DummyUpdate(5, false).setStuff("d")); + while(!updateForwarder.areAllUpdatesCommited()) { + Thread.sleep(100); + } + Assert.assertEquals(5, updateForwarder.getLastUpdatedSeqNum()); + List<DummyUpdate> allUpdates = updateForwarder.getAllUpdatesFrom(0); + Assert.assertEquals(2, allUpdates.size()); + + updateForwarder.handleUpdateNotification(new DummyUpdate(6, false).setStuff("e")); + updateForwarder.handleUpdateNotification(new DummyUpdate(7, false).setStuff("f")); + + while(!updateForwarder.areAllUpdatesCommited()) { + Thread.sleep(100); + } + Assert.assertEquals(7, updateForwarder.getLastUpdatedSeqNum()); + allUpdates = updateForwarder.getAllUpdatesFrom(0); + Assert.assertEquals(4, allUpdates.size()); + Assert.assertEquals("a,b,c", allUpdates.get(0).getStuff()); + Assert.assertEquals(4, allUpdates.get(0).getSeqNum()); + Assert.assertEquals("d", allUpdates.get(1).getStuff()); + Assert.assertEquals(5, allUpdates.get(1).getSeqNum()); + Assert.assertEquals("e", allUpdates.get(2).getStuff()); + Assert.assertEquals(6, allUpdates.get(2).getSeqNum()); + Assert.assertEquals("f", allUpdates.get(3).getStuff()); + Assert.assertEquals(7, allUpdates.get(3).getSeqNum()); + + updateForwarder.handleUpdateNotification(new DummyUpdate(8, false).setStuff("g")); + while(!updateForwarder.areAllUpdatesCommited()) { + Thread.sleep(100); + } + Assert.assertEquals(8, updateForwarder.getLastUpdatedSeqNum()); + allUpdates = updateForwarder.getAllUpdatesFrom(8); + Assert.assertEquals(1, allUpdates.size()); + Assert.assertEquals("g", allUpdates.get(0).getStuff()); + } + + @Test + public void testGetUpdatesAfterExternalEntityReset() throws Exception { + DummyImageRetreiver imageRetreiver = new DummyImageRetreiver(); + imageRetreiver.setState("a,b,c"); + UpdateForwarder<DummyUpdate> updateForwarder = new UpdateForwarder<DummyUpdate>( + new DummyUpdatable(), imageRetreiver, 5); + updateForwarder.handleUpdateNotification(new DummyUpdate(5, false).setStuff("d")); + while(!updateForwarder.areAllUpdatesCommited()) { + Thread.sleep(100); + } + + updateForwarder.handleUpdateNotification(new DummyUpdate(6, false).setStuff("e")); + updateForwarder.handleUpdateNotification(new DummyUpdate(7, false).setStuff("f")); + + while(!updateForwarder.areAllUpdatesCommited()) { + Thread.sleep(100); + } + Assert.assertEquals(7, updateForwarder.getLastUpdatedSeqNum()); + List<DummyUpdate> allUpdates = updateForwarder.getAllUpdatesFrom(0); + Assert.assertEquals(4, allUpdates.size()); + Assert.assertEquals("f", allUpdates.get(3).getStuff()); + Assert.assertEquals(7, allUpdates.get(3).getSeqNum()); + + updateForwarder.handleUpdateNotification(new DummyUpdate(8, false).setStuff("g")); + while(!updateForwarder.areAllUpdatesCommited()) { + Thread.sleep(100); + } + Assert.assertEquals(8, updateForwarder.getLastUpdatedSeqNum()); + allUpdates = updateForwarder.getAllUpdatesFrom(8); + Assert.assertEquals(1, allUpdates.size()); + Assert.assertEquals("g", allUpdates.get(0).getStuff()); + + imageRetreiver.setState("a,b,c,d,e,f,g,h"); + + // New update comes with SeqNum = 1 + updateForwarder.handleUpdateNotification(new DummyUpdate(1, false).setStuff("h")); + while(!updateForwarder.areAllUpdatesCommited()) { + Thread.sleep(100); + } + // NN plugin asks for next update + allUpdates = updateForwarder.getAllUpdatesFrom(9); + Assert.assertEquals(1, allUpdates.size()); + Assert.assertEquals("a,b,c,d,e,f,g,h", allUpdates.get(0).getStuff()); + Assert.assertEquals(1, allUpdates.get(0).getSeqNum()); + } + + @Test + public void testUpdateLogCompression() throws Exception { + DummyImageRetreiver imageRetreiver = new DummyImageRetreiver(); + imageRetreiver.setState("a,b,c"); + UpdateForwarder<DummyUpdate> updateForwarder = new UpdateForwarder<DummyUpdate>( + new DummyUpdatable(), imageRetreiver, 5); + updateForwarder.handleUpdateNotification(new DummyUpdate(5, false).setStuff("d")); + while(!updateForwarder.areAllUpdatesCommited()) { + Thread.sleep(100); + } + Assert.assertEquals(5, updateForwarder.getLastUpdatedSeqNum()); + List<DummyUpdate> allUpdates = updateForwarder.getAllUpdatesFrom(0); + Assert.assertEquals(2, allUpdates.size()); + + updateForwarder.handleUpdateNotification(new DummyUpdate(6, false).setStuff("e")); + updateForwarder.handleUpdateNotification(new DummyUpdate(7, false).setStuff("f")); + updateForwarder.handleUpdateNotification(new DummyUpdate(8, false).setStuff("g")); + updateForwarder.handleUpdateNotification(new DummyUpdate(9, false).setStuff("h")); + updateForwarder.handleUpdateNotification(new DummyUpdate(10, false).setStuff("i")); + updateForwarder.handleUpdateNotification(new DummyUpdate(11, false).setStuff("j")); + + while(!updateForwarder.areAllUpdatesCommited()) { + Thread.sleep(100); + } + Assert.assertEquals(11, updateForwarder.getLastUpdatedSeqNum()); + allUpdates = updateForwarder.getAllUpdatesFrom(0); + Assert.assertEquals(3, allUpdates.size()); + Assert.assertEquals("a,b,c,d,e,f,g,h", allUpdates.get(0).getStuff()); + Assert.assertEquals(9, allUpdates.get(0).getSeqNum()); + Assert.assertEquals("i", allUpdates.get(1).getStuff()); + Assert.assertEquals(10, allUpdates.get(1).getSeqNum()); + Assert.assertEquals("j", allUpdates.get(2).getStuff()); + Assert.assertEquals(11, allUpdates.get(2).getSeqNum()); + } +}
http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/0eb6645e/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryPolicyStoreProcessor.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryPolicyStoreProcessor.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryPolicyStoreProcessor.java index 46f8fb8..14207de 100644 --- a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryPolicyStoreProcessor.java +++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryPolicyStoreProcessor.java @@ -21,6 +21,7 @@ import junit.framework.Assert; import org.apache.hadoop.conf.Configuration; import org.apache.sentry.provider.db.service.thrift.PolicyStoreConstants.PolicyStoreServerConfig; +import org.apache.sentry.service.thrift.ServiceConstants.ServerConfig; import org.junit.Before; import org.junit.Test; @@ -31,6 +32,7 @@ public class TestSentryPolicyStoreProcessor { @Before public void setup() { conf = new Configuration(false); + conf.setBoolean(ServerConfig.SENTRY_HDFS_INTEGRATION_ENABLE, true); } @Test(expected=SentryConfigurationException.class) public void testConfigNotNotificationHandler() throws Exception { http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/0eb6645e/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryServerWithoutKerberos.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryServerWithoutKerberos.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryServerWithoutKerberos.java index e5238a6..b3ad2c9 100644 --- a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryServerWithoutKerberos.java +++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryServerWithoutKerberos.java @@ -18,18 +18,27 @@ package org.apache.sentry.provider.db.service.thrift; import static junit.framework.Assert.assertEquals; -import static org.junit.Assert.assertEquals; +import java.io.IOException; import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import junit.framework.Assert; +import org.apache.hadoop.fs.permission.AclEntry; import org.apache.sentry.core.common.ActiveRoleSet; import org.apache.sentry.core.model.db.AccessConstants; import org.apache.sentry.core.model.db.Database; import org.apache.sentry.core.model.db.Server; import org.apache.sentry.core.model.db.Table; +import org.apache.sentry.hdfs.PermissionsUpdate; +import org.apache.sentry.hdfs.SentryServiceClient; +import org.apache.sentry.hdfs.SentryServiceClient.SentryAuthzUpdate; +import org.apache.sentry.hdfs.UpdateableAuthzPermissions; import org.apache.sentry.service.thrift.SentryServiceIntegrationBase; import org.junit.Test; @@ -38,6 +47,30 @@ import com.google.common.collect.Sets; public class TestSentryServerWithoutKerberos extends SentryServiceIntegrationBase { + public class SentryAdapter { + + private SentryServiceClient sentryClient; + private UpdateableAuthzPermissions perms; + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + public SentryAdapter(UpdateableAuthzPermissions perms, SentryPolicyServiceClient sentryClient) throws Exception { + this.perms = perms; + this.sentryClient = new SentryServiceClient(conf); + } + + public void pullUpdates() throws IOException { + SentryAuthzUpdate sentryUpdates = sentryClient.getAllUpdatesFrom( + perms.getLastUpdatedSeqNum() + 1, 0); + for (PermissionsUpdate update : sentryUpdates.getPermUpdates()) { + if (update.hasFullImage()) { + perms = perms.updateFull(update); + } + perms.updatePartial(Lists.newArrayList(update), lock); + } + } + + } + @Override public void beforeSetup() throws Exception { this.kerberos = false; @@ -61,6 +94,8 @@ public class TestSentryServerWithoutKerberos extends SentryServiceIntegrationBas Set<String> requestorUserGroupNames = Sets.newHashSet(ADMIN_GROUP); setLocalGroupMapping(requestorUserName, requestorUserGroupNames); writePolicyFile(); + + UpdateableAuthzPermissions authzPerms = new UpdateableAuthzPermissions(); String roleName1 = "admin_r1"; String roleName2 = "admin_r2"; @@ -77,6 +112,12 @@ public class TestSentryServerWithoutKerberos extends SentryServiceIntegrationBas client.grantTablePrivilege(requestorUserName, roleName1, "server", "db2", "table3", "ALL"); client.grantTablePrivilege(requestorUserName, roleName1, "server", "db2", "table4", "ALL"); + SentryAdapter adapter = new SentryAdapter(authzPerms, client); + adapter.pullUpdates(); +// waitToCommit(authzPerms); + + List<AclEntry> sentryAcls = authzPerms.getAcls("db1.table1"); + System.out.println("1 : " + sentryAcls); client.dropRoleIfExists(requestorUserName, roleName2); client.createRole(requestorUserName, roleName2); @@ -89,6 +130,12 @@ public class TestSentryServerWithoutKerberos extends SentryServiceIntegrationBas client.grantTablePrivilege(requestorUserName, roleName2, "server", "db2", "table4", "ALL"); client.grantTablePrivilege(requestorUserName, roleName2, "server", "db3", "table5", "ALL"); + adapter.pullUpdates(); +// waitToCommit(authzPermCache); + sentryAcls = authzPerms.getAcls("db1.table1"); + System.out.println("2 : " + sentryAcls); + + Set<TSentryPrivilege> listPrivilegesByRoleName = client.listPrivilegesByRoleName(requestorUserName, roleName2, Lists.newArrayList(new Server("server"), new Database("db1"))); assertEquals("Privilege not assigned to role2 !!", 2, listPrivilegesByRoleName.size()); @@ -162,4 +209,15 @@ public class TestSentryServerWithoutKerberos extends SentryServiceIntegrationBas assertEquals(0, client.listPrivilegesForProvider(requestorUserGroupNames, ActiveRoleSet.ALL).size()); } + +// private void waitToCommit(Update hmsCache) throws InterruptedException { +// int counter = 0; +// while(!hmsCache.areAllUpdatesCommited()) { +// Thread.sleep(200); +// counter++; +// if (counter > 10000) { +// fail("Updates taking too long to commit !!"); +// } +// } +// } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/0eb6645e/sentry-service-client/pom.xml ---------------------------------------------------------------------- diff --git a/sentry-service-client/pom.xml b/sentry-service-client/pom.xml new file mode 100644 index 0000000..9c158aa --- /dev/null +++ b/sentry-service-client/pom.xml @@ -0,0 +1,164 @@ +<?xml version="1.0"?> +<!-- +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You 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. +--> +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.sentry</groupId> + <artifactId>sentry</artifactId> + <version>1.5.0-incubating-SNAPSHOT</version> + <relativePath>..</relativePath> + </parent> + + <artifactId>sentry-service-client</artifactId> + <name>Sentry Thrift client</name> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </dependency> + <dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + </dependency> + <dependency> + <groupId>org.apache.shiro</groupId> + <artifactId>shiro-core</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + </dependency> + <dependency> + <groupId>org.apache.thrift</groupId> + <artifactId>libfb303</artifactId> + </dependency> + <dependency> + <groupId>org.apache.thrift</groupId> + <artifactId>libthrift</artifactId> + </dependency> + <dependency> + <groupId>ant-contrib</groupId> + <artifactId>ant-contrib</artifactId> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-all</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <sourceDirectory>${basedir}/src/main/java</sourceDirectory> + <testSourceDirectory>${basedir}/src/test/java</testSourceDirectory> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <executions> + <execution> + <id>add-source</id> + <phase>generate-sources</phase> + <goals> + <goal>add-source</goal> + </goals> + <configuration> + <sources> + <source>src/gen/thrift/gen-javabean</source> + </sources> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + <profiles> + <profile> + <id>thriftif</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-antrun-plugin</artifactId> + <executions> + <execution> + <id>generate-thrift-sources</id> + <phase>generate-sources</phase> + <configuration> + <target> + <taskdef name="for" classname="net.sf.antcontrib.logic.ForTask" + classpathref="maven.plugin.classpath" /> + <property name="thrift.args" value="-I ${thrift.home} --gen java:beans,hashcode"/> + <property name="thrift.gen.dir" value="${basedir}/src/gen/thrift"/> + <delete dir="${thrift.gen.dir}"/> + <mkdir dir="${thrift.gen.dir}"/> + <for param="thrift.file"> + <path> + <fileset dir="${basedir}/src/main/resources/" includes="**/*.thrift" /> + </path> + <sequential> + <echo message="Generating Thrift code for @{thrift.file}"/> + <exec executable="${thrift.home}/bin/thrift" failonerror="true" dir="."> + <arg line="${thrift.args} -I ${basedir}/src/main/resources/ -o ${thrift.gen.dir} @{thrift.file} " /> + </exec> + </sequential> + </for> + </target> + </configuration> + <goals> + <goal>run</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-enforcer-plugin</artifactId> + <executions> + <execution> + <id>enforce-property</id> + <goals> + <goal>enforce</goal> + </goals> + <configuration> + <rules> + <requireProperty> + <property>thrift.home</property> + </requireProperty> + </rules> + <fail>true</fail> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> + +</project>
