forked from Netflix/genie
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Managed script that determines if a given job should execute in agent or embedded mode. This allows whitelisting or blacklisting of jobs that are not ready to migrate while allowing the rest to proceed without special handling.
- Loading branch information
Showing
9 changed files
with
352 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
132 changes: 132 additions & 0 deletions
132
...ntegTest/java/com/netflix/genie/web/scripts/ExecutionModeFilterScriptIntegrationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
/* | ||
* | ||
* Copyright 2019 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.genie.web.scripts; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.google.common.collect.Lists; | ||
import com.google.common.collect.Sets; | ||
import com.netflix.genie.common.dto.ClusterCriteria; | ||
import com.netflix.genie.common.dto.JobRequest; | ||
import com.netflix.genie.common.util.GenieObjectMapper; | ||
import com.netflix.genie.web.exceptions.checked.ScriptExecutionException; | ||
import com.netflix.genie.web.properties.ExecutionModeFilterScriptProperties; | ||
import com.netflix.genie.web.properties.ScriptManagerProperties; | ||
import io.micrometer.core.instrument.MeterRegistry; | ||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry; | ||
import org.assertj.core.api.Assertions; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.Arguments; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
import org.springframework.core.io.DefaultResourceLoader; | ||
import org.springframework.core.io.ResourceLoader; | ||
import org.springframework.scheduling.TaskScheduler; | ||
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler; | ||
|
||
import javax.script.ScriptEngineManager; | ||
import java.util.Optional; | ||
import java.util.UUID; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.Executors; | ||
import java.util.stream.Stream; | ||
|
||
class ExecutionModeFilterScriptIntegrationTest { | ||
|
||
private static final String TEST_SCRIPT_NAME = "execution-mode-filter.groovy"; | ||
|
||
private ExecutionModeFilterScriptProperties scriptProperties; | ||
private ExecutionModeFilterScript executionModeFilterScript; | ||
|
||
@BeforeEach | ||
void setUp() { | ||
final MeterRegistry meterRegistry = new SimpleMeterRegistry(); | ||
final ScriptManagerProperties scriptManagerProperties = new ScriptManagerProperties(); | ||
final TaskScheduler taskScheduler = new ConcurrentTaskScheduler(); | ||
final ExecutorService executorService = Executors.newCachedThreadPool(); | ||
final ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); | ||
final ResourceLoader resourceLoader = new DefaultResourceLoader(); | ||
final ObjectMapper objectMapper = GenieObjectMapper.getMapper(); | ||
final ScriptManager scriptManager = new ScriptManager( | ||
scriptManagerProperties, | ||
taskScheduler, | ||
executorService, | ||
scriptEngineManager, | ||
resourceLoader, | ||
meterRegistry | ||
); | ||
this.scriptProperties = new ExecutionModeFilterScriptProperties(); | ||
this.executionModeFilterScript = new ExecutionModeFilterScript( | ||
scriptManager, | ||
scriptProperties, | ||
objectMapper, | ||
meterRegistry | ||
); | ||
} | ||
|
||
private static Stream<Arguments> getEvaluateTestArguments() { | ||
return Stream.of( | ||
Arguments.of(Optional.of(true)), | ||
Arguments.of(Optional.of(false)), | ||
Arguments.of(Optional.empty()) | ||
); | ||
} | ||
|
||
@ParameterizedTest(name = "Script returns: {0}") | ||
@MethodSource("getEvaluateTestArguments") | ||
void evaluateTest( | ||
final Optional<Boolean> expected | ||
) throws Exception { | ||
ManagedScriptIntegrationTest.loadScript(TEST_SCRIPT_NAME, executionModeFilterScript, scriptProperties); | ||
|
||
final JobRequest jobRequest = new JobRequest.Builder( | ||
"jobName", | ||
"jobUser", | ||
"jobVersion", | ||
Lists.newArrayList( | ||
new ClusterCriteria(Sets.newHashSet(UUID.randomUUID().toString(), UUID.randomUUID().toString())) | ||
), | ||
Sets.newHashSet(UUID.randomUUID().toString()) | ||
) | ||
.withDescription("Script should return: " + expected.orElse(null)) | ||
.build(); | ||
|
||
Assertions.assertThat( | ||
this.executionModeFilterScript.forceAgentExecution(jobRequest) | ||
).isEqualTo(expected); | ||
} | ||
|
||
@Test | ||
void evaluateErrorTest() throws Exception { | ||
final JobRequest jobRequest = new JobRequest.Builder( | ||
"jobName", | ||
"jobUser", | ||
"jobVersion", | ||
Lists.newArrayList( | ||
new ClusterCriteria(Sets.newHashSet(UUID.randomUUID().toString(), UUID.randomUUID().toString())) | ||
), | ||
Sets.newHashSet(UUID.randomUUID().toString()) | ||
) | ||
.build(); | ||
|
||
ManagedScriptIntegrationTest.loadScript(TEST_SCRIPT_NAME, executionModeFilterScript, scriptProperties); | ||
Assertions | ||
.assertThatExceptionOfType(ScriptExecutionException.class) | ||
.isThrownBy(() -> this.executionModeFilterScript.forceAgentExecution(jobRequest)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
...b/src/main/java/com/netflix/genie/web/properties/ExecutionModeFilterScriptProperties.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/* | ||
* | ||
* Copyright 2019 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.genie.web.properties; | ||
|
||
import com.netflix.genie.web.scripts.ExecutionModeFilterScript; | ||
import com.netflix.genie.web.scripts.ManagedScriptBaseProperties; | ||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
|
||
/** | ||
* Properties for {@link ExecutionModeFilterScript}. | ||
* | ||
* @author mprimi | ||
* @since 4.0.0 | ||
*/ | ||
@ConfigurationProperties(prefix = ExecutionModeFilterScriptProperties.PREFIX) | ||
public class ExecutionModeFilterScriptProperties extends ManagedScriptBaseProperties { | ||
/** | ||
* Prefix for this properties class. | ||
*/ | ||
public static final String PREFIX = ManagedScriptBaseProperties.SCRIPTS_PREFIX + ".execution-mode-filter"; | ||
/** | ||
* Name of script source property. | ||
*/ | ||
public static final String SOURCE_PROPERTY = PREFIX + ManagedScriptBaseProperties.SOURCE_PROPERTY_SUFFIX; | ||
} |
91 changes: 91 additions & 0 deletions
91
genie-web/src/main/java/com/netflix/genie/web/scripts/ExecutionModeFilterScript.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
* | ||
* Copyright 2019 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.genie.web.scripts; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.google.common.collect.ImmutableMap; | ||
import com.netflix.genie.common.dto.JobRequest; | ||
import com.netflix.genie.web.exceptions.checked.ScriptExecutionException; | ||
import com.netflix.genie.web.exceptions.checked.ScriptNotConfiguredException; | ||
import com.netflix.genie.web.properties.ExecutionModeFilterScriptProperties; | ||
import io.micrometer.core.instrument.MeterRegistry; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
/** | ||
* Implementation of {@link ManagedScript} that delegates selection of a job's execution mode to a script, | ||
* thus allowing fine-grained blacklisting/whitelisting when it comes to execution mode. | ||
* See also: {@link com.netflix.genie.web.util.JobExecutionModeSelector}. | ||
* <p> | ||
* The contract between the script and the Java code is that the script will be supplied global variables | ||
* {@code jobRequest} which will be JSON strings representing job request that kicked off this evaluation. | ||
* The code expects the script to either return the true (to force agent execution), false (to force legacy/embedded | ||
* execution) or null (for no preference). | ||
* | ||
* @author mprimi | ||
* @since 4.0.0 | ||
*/ | ||
@Slf4j | ||
public class ExecutionModeFilterScript extends ManagedScript { | ||
private static final String JOB_REQUEST_BINDING = "jobRequest"; | ||
|
||
/** | ||
* Constructor. | ||
* | ||
* @param scriptManager script manager | ||
* @param properties script properties | ||
* @param mapper object mapper | ||
* @param registry meter registry | ||
*/ | ||
public ExecutionModeFilterScript( | ||
final ScriptManager scriptManager, | ||
final ExecutionModeFilterScriptProperties properties, | ||
final ObjectMapper mapper, | ||
final MeterRegistry registry | ||
) { | ||
super(scriptManager, properties, mapper, registry); | ||
} | ||
|
||
/** | ||
* Evaluate the script and return true if this job should be forced to execute via agent, false if it should be | ||
* forced to execute in embedded mode, null if the script decides not explicitly flag this job for one or the other | ||
* execution mode. | ||
* | ||
* @param jobRequest the job request | ||
* @return An optional boolean value | ||
* @throws ScriptNotConfiguredException if the script is notyet successfully loaded and compiled | ||
* @throws ScriptExecutionException if the script evaluation produces an error | ||
*/ | ||
public Optional<Boolean> forceAgentExecution( | ||
final JobRequest jobRequest | ||
) throws ScriptNotConfiguredException, ScriptExecutionException { | ||
final Map<String, Object> scriptParameters = ImmutableMap.of(JOB_REQUEST_BINDING, jobRequest); | ||
|
||
final Object scriptOutput = this.evaluateScript(scriptParameters); | ||
log.debug("Execution mode selector returned: {} for job request: {}", scriptOutput, jobRequest); | ||
|
||
if (scriptOutput == null) { | ||
return Optional.empty(); | ||
} else if (scriptOutput instanceof Boolean) { | ||
return Optional.of((Boolean) scriptOutput); | ||
} | ||
throw new ScriptExecutionException("Script returned unexpected value: " + scriptOutput); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.