-
Notifications
You must be signed in to change notification settings - Fork 749
Fix FlowSpec Updating Function #3823
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
73cf30a
c88da7a
24cba18
3001cd6
906a65d
5a3e735
3d80b3e
36162db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,7 +26,6 @@ | |
| import com.linkedin.data.template.StringMap; | ||
| import com.typesafe.config.Config; | ||
| import com.typesafe.config.ConfigFactory; | ||
| import com.typesafe.config.ConfigValueFactory; | ||
| import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; | ||
| import java.net.URI; | ||
| import java.net.URISyntaxException; | ||
|
|
@@ -76,10 +75,11 @@ public class FlowSpec implements Configurable, Spec { | |
| /** Human-readable description of the flow spec */ | ||
| final String description; | ||
|
|
||
| /** Flow config as a typesafe config object*/ | ||
| /** Flow config as a typesafe config object */ | ||
| final Config config; | ||
|
|
||
| /** Flow config as a properties collection for backwards compatibility */ | ||
| /** Flow config as a properties collection for backwards compatibility | ||
| * It can be updated to store properties in addition to ones in the immutable Config object */ | ||
| // Note that this property is not strictly necessary as it can be generated from the typesafe | ||
| // config. We use it as a cache until typesafe config is more widely adopted in Gobblin. | ||
| final Properties configAsProperties; | ||
|
|
@@ -125,6 +125,18 @@ public static FlowSpec.Builder builder(URI catalogURI, Properties flowProps) { | |
| throw new RuntimeException("Unable to create a FlowSpec URI: " + e, e); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Add new property at the specified path to the configAsProperties objects. | ||
| * Note: this does NOT update the Config so any property added through this function must be retrieved through the | ||
| * ConfigAsProperties field | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. may be error prone... why not instead create a new do be sure to synchronize an update of |
||
| * @param path | ||
| * @param value | ||
| */ | ||
| public void addPropertyToConfigAsProperties(String path, String value) { | ||
| this.configAsProperties.setProperty(path, value); | ||
| } | ||
|
|
||
| public void addCompilationError(String src, String dst, String errorMessage, int numberOfHops) { | ||
| this.compilationErrors.add(new CompilationError(getConfig(), src, dst, errorMessage, numberOfHops)); | ||
| } | ||
|
|
@@ -518,15 +530,5 @@ public static int maxFlowSpecUriLength() { | |
| return URI_SCHEME.length() + ":".length() // URI separator | ||
| + URI_PATH_SEPARATOR.length() + ServiceConfigKeys.MAX_FLOW_NAME_LENGTH + URI_PATH_SEPARATOR.length() + ServiceConfigKeys.MAX_FLOW_GROUP_LENGTH; | ||
| } | ||
|
|
||
| /** | ||
| * Create a new FlowSpec object with the added property defined by path and value parameters | ||
| * @param path key for new property | ||
| * @param value | ||
| */ | ||
| public static FlowSpec createFlowSpecWithProperty(FlowSpec flowSpec, String path, String value) { | ||
| Config updatedConfig = flowSpec.getConfig().withValue(path, ConfigValueFactory.fromAnyRef(value)); | ||
| return new Builder(flowSpec.getUri()).withConfig(updatedConfig).build(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. my hunch is that this |
||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -484,7 +484,9 @@ public synchronized void setActive(boolean active) { | |||
| log.error("Exception encountered when shutting down DagManager threads.", e); | ||||
| } | ||||
| } | ||||
| } catch (IOException e) { | ||||
| } catch (Throwable e) { | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are not catching Line 435 in 9e30c6c
this.active = true beforehand so it remains as leader.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree w/ the spirit here, but
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am updating it to catch
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Catching and throwing the exception will fall the service health check so the service deploy/startup will fail if we encounter NPE. Previously it would silently fail initialization is the issue. |
||||
| // All exceptions should fail leader transition obviously to avoid case where transition to active fails to | ||||
| // complete but is not apparent | ||||
| log.error("Exception encountered when activating the new DagManager", e); | ||||
| throw new RuntimeException(e); | ||||
| } | ||||
|
|
@@ -507,6 +509,9 @@ public void handleLaunchFlowEvent(DagActionStore.DagAction launchAction) { | |||
| this.flowCompilationValidationHelper.createExecutionPlanIfValid(spec); | ||||
| if (optionalJobExecutionPlanDag.isPresent()) { | ||||
| addDag(optionalJobExecutionPlanDag.get(), true, true); | ||||
| } else { | ||||
| log.warn("Failed flow compilation of spec causing launch flow event to be skipped on startup. Flow {}", flowId); | ||||
| this.dagManagerMetrics.incrementFailedLaunchCount(); | ||||
|
Comment on lines
+511
to
+512
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. doesn't actually say what/how it failed flow compilation... is that because such a message would have already been logged?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, we don't have that information available here yet. We emit a flow compilation failed event that we can check Line 89 in 9e30c6c
|
||||
| } | ||||
| // Upon handling the action, delete it so on leadership change this is not duplicated | ||||
| this.dagActionStore.get().deleteDagAction(launchAction); | ||||
|
|
||||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -203,8 +203,8 @@ protected void submitFlowToDagManagerHelper(String flowGroup, String flowName, S | |||
| URI flowUri = FlowSpec.Utils.createFlowSpecUri(flowId); | ||||
| spec = (FlowSpec) flowCatalog.getSpecs(flowUri); | ||||
| // Adds flowExecutionId to config to ensure they are consistent across hosts | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this brief comment may not draw enough attention to how critical this is! |
||||
| FlowSpec updatedSpec = FlowSpec.Utils.createFlowSpecWithProperty(spec, ConfigurationKeys.FLOW_EXECUTION_ID_KEY, flowExecutionId); | ||||
| this.orchestrator.submitFlowToDagManager(updatedSpec); | ||||
| spec.addPropertyToConfigAsProperties(ConfigurationKeys.FLOW_EXECUTION_ID_KEY, flowExecutionId); | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you point me to the code where we query this flow execution id?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes we query here from Line 186 in 9e30c6c
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is where we add flow.execution.id.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to be careful here for adhoc flows that already save a flow execution ID in the flowspec configs, for those do we know if the flow execution ID in their config is consistent if they don't have a trigger time, or should we check if the property already exists in properties to not override them? |
||||
| this.orchestrator.submitFlowToDagManager(spec); | ||||
| } catch (URISyntaxException e) { | ||||
| log.warn("Could not create URI object for flowId {}. Exception {}", flowId, e.getMessage()); | ||||
| this.failedFlowLaunchSubmissions.mark(); | ||||
|
|
||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| /* | ||
| * 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.gobblin.service.modules.utils; | ||
|
|
||
| import com.google.common.base.Optional; | ||
| import java.io.IOException; | ||
| import java.util.Properties; | ||
| import org.apache.gobblin.configuration.ConfigurationKeys; | ||
| import org.apache.gobblin.metrics.event.EventSubmitter; | ||
| import org.apache.gobblin.runtime.api.FlowSpec; | ||
| import org.apache.gobblin.service.modules.core.IdentityFlowToJobSpecCompilerTest; | ||
| import org.apache.gobblin.service.modules.flow.IdentityFlowToJobSpecCompiler; | ||
| import org.apache.gobblin.service.modules.flow.SpecCompiler; | ||
| import org.apache.gobblin.service.modules.flowgraph.Dag; | ||
| import org.apache.gobblin.service.modules.orchestration.UserQuotaManager; | ||
| import org.apache.gobblin.service.modules.spec.JobExecutionPlan; | ||
| import org.apache.gobblin.service.monitoring.FlowStatusGenerator; | ||
| import org.apache.gobblin.util.ConfigUtils; | ||
| import org.junit.Assert; | ||
| import org.testng.annotations.Test; | ||
|
|
||
| import static org.mockito.Mockito.*; | ||
|
|
||
|
|
||
| /** | ||
| * Test functionality provided by the helper class re-used between the DagManager and Orchestrator for flow compilation. | ||
| */ | ||
| public class FlowCompilationValidationHelperTest { | ||
|
|
||
| class MockFlowCompilationValidationHelper extends FlowCompilationValidationHelper { | ||
|
|
||
| public MockFlowCompilationValidationHelper(SharedFlowMetricsSingleton sharedFlowMetricsSingleton, | ||
| SpecCompiler specCompiler, UserQuotaManager quotaManager, Optional<EventSubmitter> eventSubmitter, | ||
| FlowStatusGenerator flowStatusGenerator, boolean isFlowConcurrencyEnabled) { | ||
| super(sharedFlowMetricsSingleton, specCompiler, quotaManager, eventSubmitter, flowStatusGenerator, | ||
| isFlowConcurrencyEnabled); | ||
| } | ||
|
|
||
| /* | ||
| In overriden function simply return if concurrent execution is allowed or not because flowStatusGenerator will be | ||
| mocked | ||
| */ | ||
| @Override | ||
| protected boolean isExecutionPermitted(FlowStatusGenerator flowStatusGenerator, String flowName, String flowGroup, | ||
| boolean allowConcurrentExecution) { | ||
| return allowConcurrentExecution; | ||
| } | ||
|
|
||
| } | ||
|
|
||
| /* | ||
| Creates a mock {@link FlowCompilationValidationHelper} which has a valid {@link SpecCompiler} but mocks other | ||
| components. | ||
| */ | ||
| MockFlowCompilationValidationHelper createMockFlowCompilationValidationHelper(boolean isFlowConcurrencyEnabled) { | ||
| SpecCompiler specCompiler = new IdentityFlowToJobSpecCompiler(ConfigUtils.propertiesToConfig(new Properties())); | ||
| specCompiler.onAddSpec(IdentityFlowToJobSpecCompilerTest.initTopologySpec()); | ||
| return new MockFlowCompilationValidationHelper(mock(SharedFlowMetricsSingleton.class), specCompiler, | ||
| mock(UserQuotaManager.class), Optional.of(mock(EventSubmitter.class)), mock(FlowStatusGenerator.class), | ||
| isFlowConcurrencyEnabled); | ||
| } | ||
|
|
||
| /** | ||
| * Verifies that a flow spec that compiles to create a valid job execution plan dag will also generate one after | ||
| * having a flow execution id key-value pair added to its config | ||
| * @throws IOException | ||
| * @throws InterruptedException | ||
| */ | ||
| @Test | ||
| public void compileFlowSpec() throws IOException, InterruptedException { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to extend this test to also test for serialization/deserialization of the jobexecutionplan that is stored in the dag just because that was the area that caused the fatal issues
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to extend this test for |
||
| MockFlowCompilationValidationHelper mockFlowCompilationValidationHelper = createMockFlowCompilationValidationHelper(true); | ||
| FlowSpec flowSpec = IdentityFlowToJobSpecCompilerTest.initFlowSpec(); | ||
| Optional<Dag<JobExecutionPlan>> dagOptional = mockFlowCompilationValidationHelper.createExecutionPlanIfValid(flowSpec); | ||
| // Assert FlowSpec compilation results in non-null or empty dag | ||
| Assert.assertTrue(dagOptional.isPresent()); | ||
| Assert.assertNotNull(dagOptional.get()); | ||
| Assert.assertTrue(dagOptional.get().getNodes().size() == 1); | ||
| Assert.assertEquals(dagOptional.get().getStartNodes().size(), 1); | ||
|
|
||
| // Update flow spec and check if still compiles and passes checks | ||
| flowSpec.addPropertyToConfigAsProperties(ConfigurationKeys.FLOW_EXECUTION_ID_KEY, "54321"); | ||
| dagOptional = mockFlowCompilationValidationHelper.createExecutionPlanIfValid(flowSpec); | ||
| // Assert FlowSpec compilation results in non-null or empty dag | ||
| Assert.assertTrue(dagOptional.isPresent()); | ||
| Assert.assertNotNull(dagOptional.get()); | ||
| Assert.assertTrue(dagOptional.get().getNodes().size() == 1); | ||
| Assert.assertEquals(dagOptional.get().getStartNodes().size(), 1); | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer if we name this function something more semantic, such as
addMutablePropertyand a reader for it asgetMutablePropertyso that the user will not attempt to read from this.confgs.