2121import org .elasticsearch .gradle .DistributionDownloadPlugin ;
2222import org .elasticsearch .gradle .ReaperPlugin ;
2323import org .elasticsearch .gradle .ReaperService ;
24+ import org .elasticsearch .gradle .tool .Boilerplate ;
2425import org .gradle .api .NamedDomainObjectContainer ;
2526import org .gradle .api .Plugin ;
2627import org .gradle .api .Project ;
3031import org .gradle .api .invocation .Gradle ;
3132import org .gradle .api .logging .Logger ;
3233import org .gradle .api .logging .Logging ;
34+ import org .gradle .api .provider .Provider ;
3335import org .gradle .api .tasks .TaskState ;
3436
3537import java .io .File ;
3638
3739public class TestClustersPlugin implements Plugin <Project > {
3840
39- private static final String LIST_TASK_NAME = "listTestClusters" ;
4041 public static final String EXTENSION_NAME = "testClusters" ;
41- private static final String REGISTRY_EXTENSION_NAME = "testClustersRegistry " ;
42+ public static final String THROTTLE_SERVICE_NAME = "testClustersThrottle " ;
4243
44+ private static final String LIST_TASK_NAME = "listTestClusters" ;
45+ private static final String REGISTRY_SERVICE_NAME = "testClustersRegistry" ;
4346 private static final Logger logger = Logging .getLogger (TestClustersPlugin .class );
4447
45- private ReaperService reaper ;
46-
4748 @ Override
4849 public void apply (Project project ) {
4950 project .getPlugins ().apply (DistributionDownloadPlugin .class );
50-
5151 project .getRootProject ().getPluginManager ().apply (ReaperPlugin .class );
52- reaper = project .getRootProject ().getExtensions ().getByType (ReaperService .class );
52+
53+ ReaperService reaper = project .getRootProject ().getExtensions ().getByType (ReaperService .class );
5354
5455 // enable the DSL to describe clusters
55- NamedDomainObjectContainer <ElasticsearchCluster > container = createTestClustersContainerExtension (project );
56+ NamedDomainObjectContainer <ElasticsearchCluster > container = createTestClustersContainerExtension (project , reaper );
5657
5758 // provide a task to be able to list defined clusters.
5859 createListClustersTask (project , container );
5960
60- if (project .getRootProject ().getExtensions ().findByName (REGISTRY_EXTENSION_NAME ) == null ) {
61- TestClustersRegistry registry = project .getRootProject ()
62- .getExtensions ()
63- .create (REGISTRY_EXTENSION_NAME , TestClustersRegistry .class );
64-
65- // When we know what tasks will run, we claim the clusters of those task to differentiate between clusters
66- // that are defined in the build script and the ones that will actually be used in this invocation of gradle
67- // we use this information to determine when the last task that required the cluster executed so that we can
68- // terminate the cluster right away and free up resources.
69- configureClaimClustersHook (project .getGradle (), registry );
61+ // register cluster registry as a global build service
62+ project .getGradle ().getSharedServices ().registerIfAbsent (REGISTRY_SERVICE_NAME , TestClustersRegistry .class , spec -> {});
7063
71- // Before each task, we determine if a cluster needs to be started for that task.
72- configureStartClustersHook (project .getGradle (), registry );
64+ // register throttle so we only run at most max-workers/2 nodes concurrently
65+ project .getGradle ()
66+ .getSharedServices ()
67+ .registerIfAbsent (
68+ THROTTLE_SERVICE_NAME ,
69+ TestClustersThrottle .class ,
70+ spec -> spec .getMaxParallelUsages ().set (project .getGradle ().getStartParameter ().getMaxWorkerCount () / 2 )
71+ );
7372
74- // After each task we determine if there are clusters that are no longer needed.
75- configureStopClustersHook (project .getGradle (), registry );
76- }
73+ // register cluster hooks
74+ project .getRootProject ().getPluginManager ().apply (TestClustersHookPlugin .class );
7775 }
7876
79- private NamedDomainObjectContainer <ElasticsearchCluster > createTestClustersContainerExtension (Project project ) {
77+ private NamedDomainObjectContainer <ElasticsearchCluster > createTestClustersContainerExtension (Project project , ReaperService reaper ) {
8078 // Create an extensions that allows describing clusters
8179 NamedDomainObjectContainer <ElasticsearchCluster > container = project .container (
8280 ElasticsearchCluster .class ,
@@ -95,52 +93,78 @@ private void createListClustersTask(Project project, NamedDomainObjectContainer<
9593 );
9694 }
9795
98- private static void configureClaimClustersHook (Gradle gradle , TestClustersRegistry registry ) {
99- // Once we know all the tasks that need to execute, we claim all the clusters that belong to those and count the
100- // claims so we'll know when it's safe to stop them.
101- gradle .getTaskGraph ().whenReady (taskExecutionGraph -> {
102- taskExecutionGraph .getAllTasks ()
103- .stream ()
104- .filter (task -> task instanceof TestClustersAware )
105- .map (task -> (TestClustersAware ) task )
106- .flatMap (task -> task .getClusters ().stream ())
107- .forEach (registry ::claimCluster );
108- });
109- }
96+ static class TestClustersHookPlugin implements Plugin <Project > {
97+ @ Override
98+ public void apply (Project project ) {
99+ if (project != project .getRootProject ()) {
100+ throw new IllegalStateException (this .getClass ().getName () + " can only be applied to the root project." );
101+ }
102+
103+ Provider <TestClustersRegistry > registryProvider = Boilerplate .getBuildService (
104+ project .getGradle ().getSharedServices (),
105+ REGISTRY_SERVICE_NAME
106+ );
107+ TestClustersRegistry registry = registryProvider .get ();
108+
109+ // When we know what tasks will run, we claim the clusters of those task to differentiate between clusters
110+ // that are defined in the build script and the ones that will actually be used in this invocation of gradle
111+ // we use this information to determine when the last task that required the cluster executed so that we can
112+ // terminate the cluster right away and free up resources.
113+ configureClaimClustersHook (project .getGradle (), registry );
114+
115+ // Before each task, we determine if a cluster needs to be started for that task.
116+ configureStartClustersHook (project .getGradle (), registry );
117+
118+ // After each task we determine if there are clusters that are no longer needed.
119+ configureStopClustersHook (project .getGradle (), registry );
120+ }
110121
111- private static void configureStartClustersHook (Gradle gradle , TestClustersRegistry registry ) {
112- gradle .addListener (new TaskActionListener () {
113- @ Override
114- public void beforeActions (Task task ) {
115- if (task instanceof TestClustersAware == false ) {
116- return ;
122+ private static void configureClaimClustersHook (Gradle gradle , TestClustersRegistry registry ) {
123+ // Once we know all the tasks that need to execute, we claim all the clusters that belong to those and count the
124+ // claims so we'll know when it's safe to stop them.
125+ gradle .getTaskGraph ().whenReady (taskExecutionGraph -> {
126+ taskExecutionGraph .getAllTasks ()
127+ .stream ()
128+ .filter (task -> task instanceof TestClustersAware )
129+ .map (task -> (TestClustersAware ) task )
130+ .flatMap (task -> task .getClusters ().stream ())
131+ .forEach (registry ::claimCluster );
132+ });
133+ }
134+
135+ private static void configureStartClustersHook (Gradle gradle , TestClustersRegistry registry ) {
136+ gradle .addListener (new TaskActionListener () {
137+ @ Override
138+ public void beforeActions (Task task ) {
139+ if (task instanceof TestClustersAware == false ) {
140+ return ;
141+ }
142+ // we only start the cluster before the actions, so we'll not start it if the task is up-to-date
143+ TestClustersAware awareTask = (TestClustersAware ) task ;
144+ awareTask .beforeStart ();
145+ awareTask .getClusters ().forEach (registry ::maybeStartCluster );
117146 }
118- // we only start the cluster before the actions, so we'll not start it if the task is up-to-date
119- TestClustersAware awareTask = (TestClustersAware ) task ;
120- awareTask .beforeStart ();
121- awareTask .getClusters ().forEach (registry ::maybeStartCluster );
122- }
123147
124- @ Override
125- public void afterActions (Task task ) {}
126- });
127- }
148+ @ Override
149+ public void afterActions (Task task ) {}
150+ });
151+ }
128152
129- private static void configureStopClustersHook (Gradle gradle , TestClustersRegistry registry ) {
130- gradle .addListener (new TaskExecutionListener () {
131- @ Override
132- public void afterExecute (Task task , TaskState state ) {
133- if (task instanceof TestClustersAware == false ) {
134- return ;
153+ private static void configureStopClustersHook (Gradle gradle , TestClustersRegistry registry ) {
154+ gradle .addListener (new TaskExecutionListener () {
155+ @ Override
156+ public void afterExecute (Task task , TaskState state ) {
157+ if (task instanceof TestClustersAware == false ) {
158+ return ;
159+ }
160+ // always unclaim the cluster, even if _this_ task is up-to-date, as others might not have been
161+ // and caused the cluster to start.
162+ ((TestClustersAware ) task ).getClusters ().forEach (cluster -> registry .stopCluster (cluster , state .getFailure () != null ));
135163 }
136- // always unclaim the cluster, even if _this_ task is up-to-date, as others might not have been
137- // and caused the cluster to start.
138- ((TestClustersAware ) task ).getClusters ().forEach (cluster -> registry .stopCluster (cluster , state .getFailure () != null ));
139- }
140164
141- @ Override
142- public void beforeExecute (Task task ) {}
143- });
165+ @ Override
166+ public void beforeExecute (Task task ) {}
167+ });
168+ }
144169 }
145-
146170}
0 commit comments