├── .github
└── workflows
│ └── maven.yml
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── pom.xml
└── src
├── etc
└── header.txt
├── main
└── java
│ └── com
│ └── nirmata
│ └── workflow
│ ├── WorkflowManager.java
│ ├── WorkflowManagerBuilder.java
│ ├── admin
│ ├── AutoCleaner.java
│ ├── RunInfo.java
│ ├── StandardAutoCleaner.java
│ ├── TaskDetails.java
│ ├── TaskInfo.java
│ ├── WorkflowAdmin.java
│ └── WorkflowManagerState.java
│ ├── details
│ ├── AutoCleanerHolder.java
│ ├── RunnableTaskDagBuilder.java
│ ├── Scheduler.java
│ ├── SchedulerSelector.java
│ ├── TaskExecutorSpec.java
│ ├── WorkflowListenerManagerImpl.java
│ ├── WorkflowManagerImpl.java
│ ├── ZooKeeperConstants.java
│ └── internalmodels
│ │ ├── RunnableTask.java
│ │ ├── RunnableTaskDag.java
│ │ └── StartedTask.java
│ ├── events
│ ├── WorkflowEvent.java
│ ├── WorkflowListener.java
│ └── WorkflowListenerManager.java
│ ├── executor
│ ├── TaskExecution.java
│ ├── TaskExecutionStatus.java
│ └── TaskExecutor.java
│ ├── models
│ ├── ExecutableTask.java
│ ├── Id.java
│ ├── RunId.java
│ ├── Task.java
│ ├── TaskExecutionResult.java
│ ├── TaskId.java
│ ├── TaskMode.java
│ └── TaskType.java
│ ├── queue
│ ├── Queue.java
│ ├── QueueConsumer.java
│ ├── QueueFactory.java
│ ├── TaskRunner.java
│ └── zookeeper
│ │ ├── SimpleQueue.java
│ │ ├── ZooKeeperSimpleQueue.java
│ │ └── ZooKeeperSimpleQueueFactory.java
│ └── serialization
│ ├── JsonSerializer.java
│ ├── JsonSerializerMapper.java
│ ├── Serializer.java
│ ├── StandardSerializer.java
│ └── TaskLoader.java
├── site
├── apt
│ ├── admin.apt
│ ├── example.apt
│ ├── index.apt
│ ├── limits.apt
│ ├── reference.apt
│ └── starting.apt
├── dag source.graffle
├── resources
│ ├── css
│ │ └── site.css
│ ├── dag.png
│ └── tasks.json
└── site.xml
└── test
├── java
└── com
│ └── nirmata
│ └── workflow
│ ├── BaseForTests.java
│ ├── ConcurrentTaskChecker.java
│ ├── TestAdmin.java
│ ├── TestAutoCleanerHolder.java
│ ├── TestNormal.java
│ ├── TestParentNodeDeletion.java
│ ├── TestTaskExecutor.java
│ ├── TestWorkflowListenerManager.java
│ ├── WorkflowManagerStateSampler.java
│ ├── details
│ ├── TestDelayPriorityTasks.java
│ ├── TestDisruptedScheduler.java
│ └── TestEdges.java
│ └── serialization
│ ├── JDKSerializer.java
│ └── TestSerializer.java
└── resources
├── log4j.properties
├── multi-tasks.json
└── tasks.json
/.github/workflows/maven.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3 |
4 | name: Java CI with Maven
5 |
6 | on:
7 | push:
8 | branches:
9 | - master
10 | pull_request:
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Set up JDK 8
20 | uses: actions/setup-java@v2
21 | with:
22 | java-version: '8'
23 | distribution: 'adopt'
24 | - name: Build with Maven
25 | run: mvn -B package --file pom.xml
26 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - openjdk8
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## [nirmata-workflow-0.9.0](https://github.com/Randgalt/workflow/tree/nirmata-workflow-0.9.0) (2017-02-08)
4 | [Full Changelog](https://github.com/Randgalt/workflow/compare/nirmata-workflow-0.8.1...nirmata-workflow-0.9.0)
5 |
6 | ## [nirmata-workflow-0.8.1](https://github.com/Randgalt/workflow/tree/nirmata-workflow-0.8.1) (2017-02-08)
7 | [Full Changelog](https://github.com/Randgalt/workflow/compare/nirmata-workflow-0.8.0...nirmata-workflow-0.8.1)
8 |
9 | ## [nirmata-workflow-0.8.0](https://github.com/Randgalt/workflow/tree/nirmata-workflow-0.8.0) (2016-07-27)
10 | [Full Changelog](https://github.com/Randgalt/workflow/compare/nirmata-workflow-0.7.2...nirmata-workflow-0.8.0)
11 |
12 | ## [nirmata-workflow-0.7.2](https://github.com/Randgalt/workflow/tree/nirmata-workflow-0.7.2) (2016-05-10)
13 | [Full Changelog](https://github.com/Randgalt/workflow/compare/nirmata-workflow-0.7.1...nirmata-workflow-0.7.2)
14 |
15 | ## [nirmata-workflow-0.7.1](https://github.com/Randgalt/workflow/tree/nirmata-workflow-0.7.1) (2016-03-12)
16 | [Full Changelog](https://github.com/Randgalt/workflow/compare/nirmata-workflow-0.7.0...nirmata-workflow-0.7.1)
17 |
18 | ## [nirmata-workflow-0.7.0](https://github.com/Randgalt/workflow/tree/nirmata-workflow-0.7.0) (2015-12-30)
19 | [Full Changelog](https://github.com/Randgalt/workflow/compare/nirmata-workflow-0.6.0...nirmata-workflow-0.7.0)
20 |
21 | ## [nirmata-workflow-0.6.0](https://github.com/Randgalt/workflow/tree/nirmata-workflow-0.6.0) (2015-07-17)
22 | [Full Changelog](https://github.com/Randgalt/workflow/compare/nirmata-workflow-0.5.0...nirmata-workflow-0.6.0)
23 |
24 | ## [nirmata-workflow-0.5.0](https://github.com/Randgalt/workflow/tree/nirmata-workflow-0.5.0) (2015-04-30)
25 | [Full Changelog](https://github.com/Randgalt/workflow/compare/nirmata-workflow-0.4.1...nirmata-workflow-0.5.0)
26 |
27 | ## [nirmata-workflow-0.4.1](https://github.com/Randgalt/workflow/tree/nirmata-workflow-0.4.1) (2015-04-27)
28 | [Full Changelog](https://github.com/Randgalt/workflow/compare/nirmata-workflow-0.4.0...nirmata-workflow-0.4.1)
29 |
30 | ## [nirmata-workflow-0.4.0](https://github.com/Randgalt/workflow/tree/nirmata-workflow-0.4.0) (2015-04-26)
31 | [Full Changelog](https://github.com/Randgalt/workflow/compare/nirmata-workflow-0.3.0...nirmata-workflow-0.4.0)
32 |
33 | ## [nirmata-workflow-0.3.0](https://github.com/Randgalt/workflow/tree/nirmata-workflow-0.3.0) (2015-03-18)
34 | [Full Changelog](https://github.com/Randgalt/workflow/compare/nirmata-workflow-0.2.0...nirmata-workflow-0.3.0)
35 |
36 | ## [nirmata-workflow-0.2.0](https://github.com/Randgalt/workflow/tree/nirmata-workflow-0.2.0) (2014-12-17)
37 | [Full Changelog](https://github.com/Randgalt/workflow/compare/nirmata-workflow-0.1.1...nirmata-workflow-0.2.0)
38 |
39 | ## [nirmata-workflow-0.1.1](https://github.com/Randgalt/workflow/tree/nirmata-workflow-0.1.1) (2014-10-15)
40 | [Full Changelog](https://github.com/Randgalt/workflow/compare/nirmata-workflow-0.1.0...nirmata-workflow-0.1.1)
41 |
42 | ## [nirmata-workflow-0.1.0](https://github.com/Randgalt/workflow/tree/nirmata-workflow-0.1.0) (2014-10-15)
43 |
44 |
45 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Nirmata Workflow
2 |
3 | If you would like to contribute code you can do so through GitHub by forking the repository and sending a pull request.
4 |
5 | When submitting code, please make every effort to follow existing conventions and style in order to keep the code as readable as possible.
6 |
7 | ## License
8 |
9 | By contributing your code, you agree to license your contribution under the terms of the APLv2: https://github.com/NirmataOSS/workflow/blob/master/LICENSE
10 |
11 | All files are released with the Apache 2.0 license.
12 |
13 | If you are adding a new file it should have a header like this:
14 |
15 | ```
16 | /**
17 | * Copyright 2014 Nirmata, Inc.
18 | *
19 | * Licensed under the Apache License, Version 2.0 (the "License");
20 | * you may not use this file except in compliance with the License.
21 | * You may obtain a copy of the License at
22 | *
23 | * http://www.apache.org/licenses/LICENSE-2.0
24 | *
25 | * Unless required by applicable law or agreed to in writing, software
26 | * distributed under the License is distributed on an "AS IS" BASIS,
27 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
28 | * See the License for the specific language governing permissions and
29 | * limitations under the License.
30 | */
31 | ```
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/nirmata/workflow)
2 | [](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.nirmata.workflow%22%20AND%20a%3A%22nirmata-workflow%22)
3 |
4 | Nirmata Workflow
5 | ========
6 |
7 | A ZooKeeper and Curator based distributed workflow management library that enables distributed task workflows.
8 |
9 | Full details here: http://nirmata.github.io/workflow
10 |
11 | Changes
12 | =========
13 |
14 | Change log here: https://github.com/Nirmata/workflow/blob/master/CHANGELOG.md
15 |
--------------------------------------------------------------------------------
/src/etc/header.txt:
--------------------------------------------------------------------------------
1 | Copyright 2014 Nirmata, Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/WorkflowManager.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow;
17 |
18 | import com.nirmata.workflow.admin.WorkflowAdmin;
19 | import com.nirmata.workflow.events.WorkflowListenerManager;
20 | import com.nirmata.workflow.executor.TaskExecutor;
21 | import com.nirmata.workflow.models.RunId;
22 | import com.nirmata.workflow.models.Task;
23 | import com.nirmata.workflow.models.TaskExecutionResult;
24 | import com.nirmata.workflow.models.TaskId;
25 | import org.apache.curator.framework.CuratorFramework;
26 | import java.io.Closeable;
27 | import java.util.Optional;
28 |
29 | /**
30 | * Main API - create via {@link WorkflowManagerBuilder}
31 | */
32 | public interface WorkflowManager extends Closeable
33 | {
34 | /**
35 | *
36 | * The manager must be started before use. Call {@link #close()} when done
37 | * with the manager. Every instance that starts a manager using
38 | * the same {@link WorkflowManagerBuilder#withCurator(CuratorFramework, String, String)} namespace
39 | * and version
will participate in the workflow.
40 | *
41 | *
42 | *
43 | * One instance will be nominated as the scheduler and will be
44 | * responsible for starting tasks and advancing the workflow.
45 | *
46 | *
47 | *
48 | * Task executors will be started based on the values in the {@link WorkflowManagerBuilder}.
49 | * Each WorkflowManager can declare a different combination of task executors
50 | * as needed.
51 | *
52 | */
53 | void start();
54 |
55 | /**
56 | * Submit a task for execution. The task will start nearly immediately. There's no
57 | * guarantee which instances will execute the various tasks. This method can be called
58 | * by any instance. i.e. it's not necessary that this be called from the currently
59 | * nominated scheduler instance.
60 | *
61 | * @param task task to execute
62 | * @return the Run ID for the task
63 | */
64 | RunId submitTask(Task task);
65 |
66 | /**
67 | * Submit a task for execution. The task will start nearly immediately. There's no
68 | * guarantee which instances will execute the various tasks. This method can be called
69 | * by any instance. i.e. it's not necessary that this be called from the currently
70 | * nominated scheduler instance.
71 | *
72 | * @param runId the RunId to use - MUST BE GLOBALLY UNIQUE
73 | * @param task task to execute
74 | * @return the Run ID for the task
75 | */
76 | RunId submitTask(RunId runId, Task task);
77 |
78 | /**
79 | * Same as {@link #submitTask(Task)} except that, when completed, the parent run will
80 | * be notified. This method is meant to be used inside of {@link TaskExecutor} for a task
81 | * that needs to initiate a sub-run and have the parent run wait for the sub-run to complete.
82 | *
83 | * @param parentRunId run id of the parent run
84 | * @param task task to execute
85 | * @return sub-Run ID
86 | */
87 | RunId submitSubTask(RunId parentRunId, Task task);
88 |
89 | /**
90 | * Same as {@link #submitTask(Task)} except that, when completed, the parent run will
91 | * be notified. This method is meant to be used inside of {@link TaskExecutor} for a task
92 | * that needs to initiate a sub-run and have the parent run wait for the sub-run to complete.
93 | *
94 | * @param runId the RunId to use - MUST BE GLOBALLY UNIQUE
95 | * @param parentRunId run id of the parent run
96 | * @param task task to execute
97 | * @return sub-Run ID
98 | */
99 | RunId submitSubTask(RunId runId, RunId parentRunId, Task task);
100 |
101 | /**
102 | * Update task progress info. This method is meant to be used inside of {@link TaskExecutor}
103 | * for a running task to update its execution progress(0-100).
104 | *
105 | * @param runId run id of the task
106 | * @param taskId the task
107 | * @param progress progress to be set
108 | */
109 | void updateTaskProgress(RunId runId, TaskId taskId, int progress);
110 |
111 | /**
112 | * Attempt to cancel the given run. NOTE: the cancellation is scheduled and does not
113 | * occur immediately. Currently executing tasks will continue. Only tasks that have not yet
114 | * executed can be canceled.
115 | *
116 | * @param runId the run to cancel
117 | * @return true if the run was found and the cancellation was scheduled
118 | */
119 | boolean cancelRun(RunId runId);
120 |
121 | /**
122 | * Return the result for a given task of a given run
123 | *
124 | * @param runId the run
125 | * @param taskId the task
126 | * @return if found, a loaded optional with the result. Otherwise, an empty optional.
127 | */
128 | Optional getTaskExecutionResult(RunId runId, TaskId taskId);
129 |
130 | /**
131 | * Return administration operations
132 | *
133 | * @return admin
134 | */
135 | WorkflowAdmin getAdmin();
136 |
137 | /**
138 | * Allocate a new WorkflowListenerManager
139 | *
140 | * @return new WorkflowListenerManager
141 | */
142 | WorkflowListenerManager newWorkflowListenerManager();
143 | }
144 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/WorkflowManagerBuilder.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow;
17 |
18 | import com.google.common.base.Preconditions;
19 | import com.google.common.collect.Lists;
20 | import com.google.common.util.concurrent.MoreExecutors;
21 | import com.nirmata.workflow.admin.AutoCleaner;
22 | import com.nirmata.workflow.details.AutoCleanerHolder;
23 | import com.nirmata.workflow.details.TaskExecutorSpec;
24 | import com.nirmata.workflow.details.WorkflowManagerImpl;
25 | import com.nirmata.workflow.executor.TaskExecutor;
26 | import com.nirmata.workflow.models.TaskType;
27 | import com.nirmata.workflow.queue.QueueFactory;
28 | import com.nirmata.workflow.queue.zookeeper.ZooKeeperSimpleQueueFactory;
29 | import com.nirmata.workflow.serialization.Serializer;
30 | import com.nirmata.workflow.serialization.StandardSerializer;
31 | import org.apache.curator.framework.CuratorFramework;
32 | import java.net.InetAddress;
33 | import java.net.UnknownHostException;
34 | import java.time.Duration;
35 | import java.util.List;
36 | import java.util.concurrent.Executor;
37 |
38 | /**
39 | * Builds {@link WorkflowManager} instances
40 | */
41 | public class WorkflowManagerBuilder
42 | {
43 | private QueueFactory queueFactory = new ZooKeeperSimpleQueueFactory();
44 | private String instanceName;
45 | private CuratorFramework curator;
46 | private AutoCleanerHolder autoCleanerHolder = newNullHolder();
47 | private Serializer serializer = new StandardSerializer();
48 | private Executor taskRunnerService = MoreExecutors.newDirectExecutorService();
49 |
50 | private final List specs = Lists.newArrayList();
51 |
52 | /**
53 | * Return a new builder
54 | *
55 | * @return new builder
56 | */
57 | public static WorkflowManagerBuilder builder()
58 | {
59 | return new WorkflowManagerBuilder();
60 | }
61 |
62 | /**
63 | * required
64 | * Set the Curator instance to use. In addition
65 | * to the Curator instance, specify a namespace for the workflow and a version. The namespace
66 | * and version combine to create a unique workflow. All instances using the same namespace and version
67 | * are logically part of the same workflow.
68 | *
69 | * @param curator Curator instance
70 | * @param namespace workflow namespace
71 | * @param version workflow version
72 | * @return this (for chaining)
73 | */
74 | public WorkflowManagerBuilder withCurator(CuratorFramework curator, String namespace, String version)
75 | {
76 | curator = Preconditions.checkNotNull(curator, "curator cannot be null");
77 | namespace = Preconditions.checkNotNull(namespace, "namespace cannot be null");
78 | version = Preconditions.checkNotNull(version, "version cannot be null");
79 | this.curator = curator.usingNamespace(namespace + "-" + version);
80 | return this;
81 | }
82 |
83 | /**
84 | *
85 | * Adds a pool of task executors for a given task type to this instance of
86 | * the workflow. The specified number of executors are allocated. Call this
87 | * method multiple times to allocate executors for the various types of tasks
88 | * that will be used in this workflow. You can choose to have all workflow
89 | * instances execute all task types or target certain task types to certain instances.
90 | *
91 | *
92 | *
93 | * qty
is the maximum concurrency for the given type of task for this instance.
94 | * The logical concurrency for a given task type is the total qty of all instances in the
95 | * workflow. e.g. if there are 3 instances in the workflow and instance A has 2 executors
96 | * for task type "a", instance B has 3 executors for task type "a" and instance C has no
97 | * executors for task type "a", the maximum concurrency for task type "a" is 5.
98 | *
99 | *
100 | *
101 | * IMPORTANT: every workflow cluster must have at least one instance that has task executor(s)
102 | * for each task type that will be submitted to the workflow. i.e workflows will stall
103 | * if there is no executor for a given task type.
104 | *
105 | *
106 | * @param taskExecutor the executor
107 | * @param qty the number of instances for this pool
108 | * @param taskType task type
109 | * @return this (for chaining)
110 | */
111 | public WorkflowManagerBuilder addingTaskExecutor(TaskExecutor taskExecutor, int qty, TaskType taskType)
112 | {
113 | specs.add(new TaskExecutorSpec(taskExecutor, qty, taskType));
114 | return this;
115 | }
116 |
117 | /**
118 | * optional
119 | *
120 | * Used in reporting. This will be the value recorded as tasks are executed. Via reporting,
121 | * you can determine which instance has executed a given task.
122 | *
123 | *
124 | *
125 | * Default is: InetAddress.getLocalHost().getHostName()
126 | *
127 | *
128 | * @param instanceName the name of this instance
129 | * @return this (for chaining)
130 | */
131 | public WorkflowManagerBuilder withInstanceName(String instanceName)
132 | {
133 | this.instanceName = Preconditions.checkNotNull(instanceName, "instanceName cannot be null");
134 | return this;
135 | }
136 |
137 | /**
138 | * Return a new WorkflowManager using the current builder values
139 | *
140 | * @return new WorkflowManager
141 | */
142 | public WorkflowManager build()
143 | {
144 | return new WorkflowManagerImpl(curator, queueFactory, instanceName, specs, autoCleanerHolder, serializer, taskRunnerService);
145 | }
146 |
147 | /**
148 | * optional
149 | * Pluggable queue factory. Default uses ZooKeeper for queuing.
150 | *
151 | * @param queueFactory new queue factory
152 | * @return this (for chaining)
153 | */
154 | public WorkflowManagerBuilder withQueueFactory(QueueFactory queueFactory)
155 | {
156 | this.queueFactory = Preconditions.checkNotNull(queueFactory, "queueFactory cannot be null");
157 | return this;
158 | }
159 |
160 | /**
161 | * optional
162 | * Sets an auto-cleaner that will run every given period. This is used to clean old runs.
163 | * IMPORTANT: the auto cleaner will only run on the instance that is the current scheduler.
164 | *
165 | * @param autoCleaner the auto cleaner to use
166 | * @param runPeriod how often to run
167 | * @return this (for chaining)
168 | */
169 | public WorkflowManagerBuilder withAutoCleaner(AutoCleaner autoCleaner, Duration runPeriod)
170 | {
171 | autoCleanerHolder = (autoCleaner == null) ? newNullHolder() : new AutoCleanerHolder(autoCleaner, runPeriod);
172 | return this;
173 | }
174 |
175 | /**
176 | * optional
177 | * By default, a JSON serializer is used to store data in ZooKeeper. Use this to specify an alternate serializer
178 | *
179 | * @param serializer serializer to use
180 | * @return this (for chaining)
181 | */
182 | public WorkflowManagerBuilder withSerializer(Serializer serializer)
183 | {
184 | this.serializer = Preconditions.checkNotNull(serializer, "serializer cannot be null");
185 | return this;
186 | }
187 |
188 | /**
189 | * optional
190 | * By default, tasks are run in an internal executor service. Use this to specify a custom executor service
191 | * for tasks. This executor does not add any async/concurrency benefit. It's purpose is to allow you to control
192 | * which thread executes your tasks.
193 | *
194 | * @param taskRunnerService custom executor service
195 | * @return this (for chaining)
196 | */
197 | public WorkflowManagerBuilder withTaskRunnerService(Executor taskRunnerService)
198 | {
199 | this.taskRunnerService = Preconditions.checkNotNull(taskRunnerService, "taskRunnerService cannot be null");
200 | return this;
201 | }
202 |
203 | private WorkflowManagerBuilder()
204 | {
205 | try
206 | {
207 | instanceName = InetAddress.getLocalHost().getHostName();
208 | }
209 | catch ( UnknownHostException e )
210 | {
211 | instanceName = "unknown";
212 | }
213 | }
214 |
215 | private AutoCleanerHolder newNullHolder()
216 | {
217 | return new AutoCleanerHolder(null, Duration.ofDays(Integer.MAX_VALUE));
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/admin/AutoCleaner.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.admin;
17 |
18 | @FunctionalInterface
19 | public interface AutoCleaner
20 | {
21 | /**
22 | * Called for each potential run to determine if it can be cleaned
23 | *
24 | * @param runInfo the run
25 | * @return true if it can be cleaned
26 | */
27 | boolean canBeCleaned(RunInfo runInfo);
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/admin/RunInfo.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.admin;
17 |
18 | import com.google.common.base.Preconditions;
19 | import com.nirmata.workflow.models.RunId;
20 | import java.time.LocalDateTime;
21 | import java.util.Optional;
22 |
23 | /**
24 | * Run information
25 | */
26 | public class RunInfo
27 | {
28 | private final RunId runId;
29 | private final LocalDateTime startTimeUtc;
30 | private final Optional completionTimeUtc;
31 |
32 | public RunInfo(RunId runId, LocalDateTime startTimeUtc)
33 | {
34 | this(runId, startTimeUtc, null);
35 | }
36 |
37 | public RunInfo(RunId runId, LocalDateTime startTimeUtc, LocalDateTime completionTimeUtc)
38 | {
39 | this.runId = Preconditions.checkNotNull(runId, "runId cannot be null");
40 | this.startTimeUtc = Preconditions.checkNotNull(startTimeUtc, "startTimeUtc cannot be null");
41 | this.completionTimeUtc = Optional.ofNullable(completionTimeUtc);
42 | }
43 |
44 | public RunId getRunId()
45 | {
46 | return runId;
47 | }
48 |
49 | public LocalDateTime getStartTimeUtc()
50 | {
51 | return startTimeUtc;
52 | }
53 |
54 | public LocalDateTime getCompletionTimeUtc()
55 | {
56 | //noinspection ConstantConditions
57 | return completionTimeUtc.get(); // exception if empty is desired
58 | }
59 |
60 | public boolean isComplete()
61 | {
62 | return completionTimeUtc.isPresent();
63 | }
64 |
65 | @Override
66 | public boolean equals(Object o)
67 | {
68 | if ( this == o )
69 | {
70 | return true;
71 | }
72 | if ( o == null || getClass() != o.getClass() )
73 | {
74 | return false;
75 | }
76 |
77 | RunInfo runInfo = (RunInfo)o;
78 |
79 | if ( !completionTimeUtc.equals(runInfo.completionTimeUtc) )
80 | {
81 | return false;
82 | }
83 | if ( !runId.equals(runInfo.runId) )
84 | {
85 | return false;
86 | }
87 | //noinspection RedundantIfStatement
88 | if ( !startTimeUtc.equals(runInfo.startTimeUtc) )
89 | {
90 | return false;
91 | }
92 |
93 | return true;
94 | }
95 |
96 | @Override
97 | public int hashCode()
98 | {
99 | int result = runId.hashCode();
100 | result = 31 * result + startTimeUtc.hashCode();
101 | result = 31 * result + completionTimeUtc.hashCode();
102 | return result;
103 | }
104 |
105 | @Override
106 | public String toString()
107 | {
108 | return "RunInfo{" +
109 | "runId=" + runId +
110 | ", startTime=" + startTimeUtc +
111 | ", completionTime=" + completionTimeUtc +
112 | '}';
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/admin/StandardAutoCleaner.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.admin;
17 |
18 | import java.time.Clock;
19 | import java.time.Duration;
20 | import java.time.LocalDateTime;
21 |
22 | /**
23 | * Default auto cleaner. Cleans if the run is completed and was completed past the given minimum age
24 | */
25 | public class StandardAutoCleaner implements AutoCleaner
26 | {
27 | private final Duration minAge;
28 |
29 | public StandardAutoCleaner(Duration minAge)
30 | {
31 | this.minAge = minAge;
32 | }
33 |
34 | @Override
35 | public boolean canBeCleaned(RunInfo runInfo)
36 | {
37 | if ( runInfo.isComplete() )
38 | {
39 | LocalDateTime nowUtc = LocalDateTime.now(Clock.systemUTC());
40 | Duration durationSinceCompletion = Duration.between(runInfo.getCompletionTimeUtc(), nowUtc);
41 | if ( durationSinceCompletion.compareTo(minAge) >= 0 )
42 | {
43 | return true;
44 | }
45 | }
46 | return false;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/admin/TaskDetails.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.admin;
17 |
18 | import com.google.common.base.Preconditions;
19 | import com.google.common.collect.ImmutableMap;
20 | import com.nirmata.workflow.models.Task;
21 | import com.nirmata.workflow.models.TaskId;
22 | import com.nirmata.workflow.models.TaskType;
23 | import java.util.Map;
24 | import java.util.Optional;
25 |
26 | /**
27 | * Contains the meta-data and type from a submitted {@link Task}
28 | */
29 | public class TaskDetails
30 | {
31 | private final TaskId taskId;
32 | private final Optional taskType;
33 | private final Map metaData;
34 |
35 | public TaskDetails(TaskId taskId, TaskType taskType, Map metaData)
36 | {
37 | this.taskId = Preconditions.checkNotNull(taskId, "taskId cannot be null");
38 | this.taskType = Optional.ofNullable(taskType);
39 | metaData = Preconditions.checkNotNull(metaData, "metaData cannot be null");
40 |
41 | this.metaData = ImmutableMap.copyOf(metaData);
42 | }
43 |
44 | public TaskId getTaskId()
45 | {
46 | return taskId;
47 | }
48 |
49 | public boolean isExecutable()
50 | {
51 | return taskType.isPresent();
52 | }
53 |
54 | public TaskType getTaskType()
55 | {
56 | //noinspection ConstantConditions
57 | return taskType.get(); // exception if empty is desired
58 | }
59 |
60 | public Map getMetaData()
61 | {
62 | return metaData;
63 | }
64 |
65 | public boolean matchesTask(Task task)
66 | {
67 | if ( task == null )
68 | {
69 | return false;
70 | }
71 | TaskDetails rhs = new TaskDetails(task.getTaskId(), task.isExecutable() ? task.getTaskType() : null, task.getMetaData());
72 | return this.equals(rhs);
73 | }
74 |
75 | @Override
76 | public boolean equals(Object o)
77 | {
78 | if ( this == o )
79 | {
80 | return true;
81 | }
82 | if ( o == null || getClass() != o.getClass() )
83 | {
84 | return false;
85 | }
86 |
87 | TaskDetails that = (TaskDetails)o;
88 |
89 | if ( !metaData.equals(that.metaData) )
90 | {
91 | return false;
92 | }
93 | if ( !taskId.equals(that.taskId) )
94 | {
95 | return false;
96 | }
97 | //noinspection RedundantIfStatement
98 | if ( !taskType.equals(that.taskType) )
99 | {
100 | return false;
101 | }
102 |
103 | return true;
104 | }
105 |
106 | @Override
107 | public int hashCode()
108 | {
109 | int result = taskId.hashCode();
110 | result = 31 * result + taskType.hashCode();
111 | result = 31 * result + metaData.hashCode();
112 | return result;
113 | }
114 |
115 | @Override
116 | public String toString()
117 | {
118 | return "TaskDetails{" +
119 | "taskId=" + taskId +
120 | ", taskType=" + taskType +
121 | ", metaData=" + metaData +
122 | '}';
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/admin/TaskInfo.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.admin;
17 |
18 | import com.google.common.base.Preconditions;
19 | import com.nirmata.workflow.models.TaskExecutionResult;
20 | import com.nirmata.workflow.models.TaskId;
21 | import java.time.LocalDateTime;
22 | import java.util.Optional;
23 |
24 | /**
25 | * Task information
26 | */
27 | @SuppressWarnings("ConstantConditions") // exception if empty is desired
28 | public class TaskInfo
29 | {
30 | private final TaskId taskId;
31 | private final Optional instanceName;
32 | private final Optional startDateUtc;
33 | private final Optional progress;
34 | private final Optional result;
35 |
36 | public TaskInfo(TaskId taskId)
37 | {
38 | this(taskId, null, null, 0, null);
39 | }
40 |
41 | public TaskInfo(TaskId taskId, String instanceName, LocalDateTime startDateUtc, int progress)
42 | {
43 | this(taskId, instanceName, startDateUtc, progress, null);
44 | }
45 |
46 | public TaskInfo(TaskId taskId, String instanceName, LocalDateTime startDateUtc, int progress, TaskExecutionResult result)
47 | {
48 | this.taskId = Preconditions.checkNotNull(taskId, "taskId cannot be null");
49 | this.instanceName = Optional.ofNullable(instanceName);
50 | this.startDateUtc = Optional.ofNullable(startDateUtc);
51 | this.result = Optional.ofNullable(result);
52 | this.progress = Optional.of(progress);
53 | }
54 |
55 | public TaskId getTaskId()
56 | {
57 | return taskId;
58 | }
59 |
60 | public String getInstanceName()
61 | {
62 | return instanceName.get();
63 | }
64 |
65 | public LocalDateTime getStartDateUtc()
66 | {
67 | return startDateUtc.get();
68 | }
69 |
70 | public TaskExecutionResult getResult()
71 | {
72 | return result.get();
73 | }
74 |
75 | public int getProgress()
76 | {
77 | return progress.get();
78 | }
79 |
80 | public boolean hasStarted()
81 | {
82 | return startDateUtc.isPresent() && instanceName.isPresent();
83 | }
84 |
85 | public boolean isComplete()
86 | {
87 | return result.isPresent();
88 | }
89 |
90 | @Override
91 | public boolean equals(Object o)
92 | {
93 | if ( this == o )
94 | {
95 | return true;
96 | }
97 | if ( o == null || getClass() != o.getClass() )
98 | {
99 | return false;
100 | }
101 |
102 | TaskInfo taskInfo = (TaskInfo)o;
103 |
104 | if ( !instanceName.equals(taskInfo.instanceName) )
105 | {
106 | return false;
107 | }
108 | if ( !result.equals(taskInfo.result) )
109 | {
110 | return false;
111 | }
112 | if ( !startDateUtc.equals(taskInfo.startDateUtc) )
113 | {
114 | return false;
115 | }
116 | //noinspection RedundantIfStatement
117 | if ( !taskId.equals(taskInfo.taskId) )
118 | {
119 | return false;
120 | }
121 |
122 | return true;
123 | }
124 |
125 | @Override
126 | public int hashCode()
127 | {
128 | int result1 = taskId.hashCode();
129 | result1 = 31 * result1 + instanceName.hashCode();
130 | result1 = 31 * result1 + startDateUtc.hashCode();
131 | result1 = 31 * result1 + result.hashCode();
132 | return result1;
133 | }
134 |
135 | @Override
136 | public String toString()
137 | {
138 | return "TaskInfo{" +
139 | "taskId=" + taskId +
140 | ", instanceName='" + instanceName + '\'' +
141 | ", startDateUtc=" + startDateUtc +
142 | ", progress=" + progress +
143 | ", result=" + result +
144 | '}';
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/admin/WorkflowAdmin.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.admin;
17 |
18 | import com.nirmata.workflow.models.RunId;
19 | import com.nirmata.workflow.models.TaskId;
20 | import java.util.List;
21 | import java.util.Map;
22 |
23 | /**
24 | * Admin operations
25 | */
26 | public interface WorkflowAdmin
27 | {
28 | /**
29 | * Return all run IDs completed or currently executing
30 | * in the workflow manager
31 | *
32 | * @return run infos
33 | */
34 | List getRunIds();
35 |
36 | /**
37 | * Return info about all runs completed or currently executing
38 | * in the workflow manager
39 | *
40 | * @return run infos
41 | */
42 | List getRunInfo();
43 |
44 | /**
45 | * Return info about the given run
46 | *
47 | * @param runId run
48 | * @return info
49 | */
50 | RunInfo getRunInfo(RunId runId);
51 |
52 | /**
53 | * Return info about all the tasks completed, started or waiting for
54 | * the given run
55 | *
56 | * @param runId run
57 | * @return task infos
58 | */
59 | List getTaskInfo(RunId runId);
60 |
61 | /**
62 | * Returns a map of all task details for the given run
63 | *
64 | * @param runId run
65 | * @return task details
66 | */
67 | Map getTaskDetails(RunId runId);
68 |
69 | /**
70 | * Delete all saved data for the given run.
71 | *
72 | * @param runId the run
73 | * @return true if the run was found
74 | */
75 | boolean clean(RunId runId);
76 |
77 | /**
78 | * Return information about the internal run/state of the workflow manager
79 | *
80 | * @return state
81 | */
82 | WorkflowManagerState getWorkflowManagerState();
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/admin/WorkflowManagerState.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.admin;
17 |
18 | import com.google.common.base.Preconditions;
19 | import com.google.common.collect.ImmutableList;
20 | import java.util.List;
21 |
22 | public class WorkflowManagerState
23 | {
24 | private final boolean isConnectedToZooKeeper;
25 | private final State schedulerState;
26 | private final List consumersState;
27 |
28 | public enum State
29 | {
30 | /**
31 | * Has not started
32 | */
33 | LATENT,
34 |
35 | /**
36 | * Is in a blocking method
37 | */
38 | SLEEPING,
39 |
40 | /**
41 | * Is doing work
42 | */
43 | PROCESSING,
44 |
45 | /**
46 | * Has been closed
47 | */
48 | CLOSED
49 | }
50 |
51 | public WorkflowManagerState(boolean isConnectedToZooKeeper, State schedulerState, List consumersState)
52 | {
53 | this.isConnectedToZooKeeper = isConnectedToZooKeeper;
54 | this.schedulerState = Preconditions.checkNotNull(schedulerState, "schedulerState cannot be null");
55 | this.consumersState = ImmutableList.copyOf(Preconditions.checkNotNull(consumersState, "consumersState cannot be null"));
56 | }
57 |
58 | /**
59 | * Returns true if the connection to ZooKeeper is active
60 | *
61 | * @return true/false
62 | */
63 | public boolean isConnectedToZooKeeper()
64 | {
65 | return isConnectedToZooKeeper;
66 | }
67 |
68 | /**
69 | * Return the state of the scheduler for this manager instance. If {@link State#LATENT} is returned
70 | * this instance is not currently the scheduler.
71 | *
72 | * @return state info
73 | */
74 | public State getSchedulerState()
75 | {
76 | return schedulerState;
77 | }
78 |
79 | /**
80 | * Return state info for each task executor in this manager
81 | *
82 | * @return task executors state
83 | */
84 | public List getExecutorsState()
85 | {
86 | return consumersState;
87 | }
88 |
89 | @Override
90 | public boolean equals(Object o)
91 | {
92 | if ( this == o )
93 | {
94 | return true;
95 | }
96 | if ( o == null || getClass() != o.getClass() )
97 | {
98 | return false;
99 | }
100 |
101 | WorkflowManagerState that = (WorkflowManagerState)o;
102 |
103 | if ( isConnectedToZooKeeper != that.isConnectedToZooKeeper )
104 | {
105 | return false;
106 | }
107 | //noinspection SimplifiableIfStatement
108 | if ( schedulerState != that.schedulerState )
109 | {
110 | return false;
111 | }
112 | return consumersState.equals(that.consumersState);
113 |
114 | }
115 |
116 | @Override
117 | public int hashCode()
118 | {
119 | int result = (isConnectedToZooKeeper ? 1 : 0);
120 | result = 31 * result + schedulerState.hashCode();
121 | result = 31 * result + consumersState.hashCode();
122 | return result;
123 | }
124 |
125 | @Override
126 | public String toString()
127 | {
128 | return "WorkflowManagerState{" +
129 | "isConnectedToZooKeeper=" + isConnectedToZooKeeper +
130 | ", schedulerState=" + schedulerState +
131 | ", consumersState=" + consumersState +
132 | '}';
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/details/AutoCleanerHolder.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.details;
17 |
18 | import com.google.common.base.Preconditions;
19 | import com.nirmata.workflow.admin.AutoCleaner;
20 | import com.nirmata.workflow.admin.WorkflowAdmin;
21 | import org.slf4j.Logger;
22 | import org.slf4j.LoggerFactory;
23 | import java.time.Duration;
24 | import java.time.Instant;
25 | import java.util.concurrent.atomic.AtomicReference;
26 |
27 | public class AutoCleanerHolder
28 | {
29 | private final Logger log = LoggerFactory.getLogger(getClass());
30 | private final AutoCleaner autoCleaner;
31 | private final Duration runPeriod;
32 | private final AtomicReference lastRun = new AtomicReference<>(Instant.now());
33 |
34 | public AutoCleanerHolder(AutoCleaner autoCleaner, Duration runPeriod)
35 | {
36 | this.autoCleaner = autoCleaner;
37 | this.runPeriod = Preconditions.checkNotNull(runPeriod, "runPeriod cannot be null");
38 | }
39 |
40 | public Duration getRunPeriod()
41 | {
42 | return runPeriod;
43 | }
44 |
45 | public void run(WorkflowAdmin admin)
46 | {
47 | Preconditions.checkNotNull(admin, "admin cannot be null");
48 | log.debug("Running");
49 | if ( autoCleaner != null )
50 | {
51 | admin.getRunInfo().stream().filter(autoCleaner::canBeCleaned).forEach(r -> {
52 | log.debug("Auto cleaning: " + r);
53 | admin.clean(r.getRunId());
54 | });
55 | }
56 | lastRun.set(Instant.now());
57 | }
58 |
59 | public boolean shouldRun()
60 | {
61 | if ( autoCleaner == null )
62 | {
63 | return false;
64 | }
65 | Duration periodSinceLast = Duration.between(lastRun.get(), Instant.now());
66 | return (periodSinceLast.compareTo(runPeriod) >= 0);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/details/RunnableTaskDagBuilder.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.details;
17 |
18 | import com.google.common.collect.ImmutableList;
19 | import com.google.common.collect.ImmutableMap;
20 | import com.google.common.collect.Sets;
21 | import com.nirmata.workflow.details.internalmodels.RunnableTaskDag;
22 | import com.nirmata.workflow.models.Task;
23 | import com.nirmata.workflow.models.TaskId;
24 | import org.jgrapht.alg.CycleDetector;
25 | import org.jgrapht.graph.DefaultDirectedGraph;
26 | import org.jgrapht.graph.DefaultEdge;
27 | import org.jgrapht.traverse.TopologicalOrderIterator;
28 | import java.util.List;
29 | import java.util.Map;
30 | import java.util.Set;
31 | import java.util.stream.Collectors;
32 |
33 | public class RunnableTaskDagBuilder
34 | {
35 | private final List entries;
36 | private final Map tasks;
37 |
38 | public RunnableTaskDagBuilder(Task task)
39 | {
40 | ImmutableList.Builder entriesBuilder = ImmutableList.builder();
41 | ImmutableMap.Builder tasksBuilder = ImmutableMap.builder();
42 | build(task, entriesBuilder, tasksBuilder);
43 |
44 | entries = entriesBuilder.build();
45 | tasks = tasksBuilder.build();
46 | }
47 |
48 | public List getEntries()
49 | {
50 | return entries;
51 | }
52 |
53 | public Map getTasks()
54 | {
55 | return tasks;
56 | }
57 |
58 | private void build(Task task, ImmutableList.Builder entriesBuilder, ImmutableMap.Builder tasksBuilder)
59 | {
60 | DefaultDirectedGraph graph = new DefaultDirectedGraph<>(DefaultEdge.class);
61 | worker(graph, task, null, tasksBuilder, Sets.newHashSet());
62 |
63 | CycleDetector cycleDetector = new CycleDetector<>(graph);
64 | if ( cycleDetector.detectCycles() )
65 | {
66 | throw new RuntimeException("The Task DAG contains cycles: " + task);
67 | }
68 |
69 | TopologicalOrderIterator orderIterator = new TopologicalOrderIterator<>(graph);
70 | while ( orderIterator.hasNext() )
71 | {
72 | TaskId taskId = orderIterator.next();
73 | Set taskIdEdges = graph.edgesOf(taskId);
74 | Set processed = taskIdEdges
75 | .stream()
76 | .map(graph::getEdgeSource)
77 | .filter(edge -> !edge.equals(taskId) && !edge.getId().equals(""))
78 | .collect(Collectors.toSet());
79 | entriesBuilder.add(new RunnableTaskDag(taskId, processed));
80 | }
81 | }
82 |
83 | private void worker(DefaultDirectedGraph graph, Task task, TaskId parentId, ImmutableMap.Builder tasksBuilder, Set usedTasksSet)
84 | {
85 | if ( usedTasksSet.add(task.getTaskId()) )
86 | {
87 | tasksBuilder.put(task.getTaskId(), task);
88 | }
89 |
90 | graph.addVertex(task.getTaskId());
91 | if ( parentId != null )
92 | {
93 | graph.addEdge(parentId, task.getTaskId());
94 | }
95 | task.getChildrenTasks().forEach(child -> worker(graph, child, task.getTaskId(), tasksBuilder, usedTasksSet));
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/details/SchedulerSelector.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.details;
17 |
18 | import com.google.common.annotations.VisibleForTesting;
19 | import com.google.common.base.Preconditions;
20 | import com.nirmata.workflow.admin.WorkflowManagerState;
21 | import com.nirmata.workflow.queue.QueueFactory;
22 | import org.apache.curator.framework.CuratorFramework;
23 | import org.apache.curator.framework.recipes.leader.LeaderSelector;
24 | import org.apache.curator.framework.recipes.leader.LeaderSelectorListener;
25 | import org.apache.curator.framework.recipes.leader.LeaderSelectorListenerAdapter;
26 | import org.apache.curator.utils.CloseableUtils;
27 | import org.slf4j.Logger;
28 | import org.slf4j.LoggerFactory;
29 | import java.io.Closeable;
30 | import java.util.concurrent.CountDownLatch;
31 | import java.util.concurrent.atomic.AtomicReference;
32 |
33 | public class SchedulerSelector implements Closeable
34 | {
35 | private final Logger log = LoggerFactory.getLogger(getClass());
36 | private final WorkflowManagerImpl workflowManager;
37 | private final QueueFactory queueFactory;
38 | private final AutoCleanerHolder autoCleanerHolder;
39 | private final LeaderSelector leaderSelector;
40 | private final AtomicReference scheduler = new AtomicReference<>();
41 |
42 | volatile AtomicReference debugLatch = new AtomicReference<>();
43 |
44 | public SchedulerSelector(WorkflowManagerImpl workflowManager, QueueFactory queueFactory, AutoCleanerHolder autoCleanerHolder)
45 | {
46 | this.workflowManager = workflowManager;
47 | this.queueFactory = queueFactory;
48 | this.autoCleanerHolder = autoCleanerHolder;
49 |
50 | LeaderSelectorListener listener = new LeaderSelectorListenerAdapter()
51 | {
52 | @Override
53 | public void takeLeadership(CuratorFramework client) throws Exception
54 | {
55 | SchedulerSelector.this.takeLeadership();
56 | }
57 | };
58 | leaderSelector = new LeaderSelector(workflowManager.getCurator(), ZooKeeperConstants.getSchedulerLeaderPath(), listener);
59 | leaderSelector.autoRequeue();
60 | }
61 |
62 | public WorkflowManagerState.State getState()
63 | {
64 | Scheduler localScheduler = scheduler.get();
65 | return (localScheduler != null) ? localScheduler.getState() : WorkflowManagerState.State.LATENT;
66 | }
67 |
68 | public void start()
69 | {
70 | leaderSelector.start();
71 | }
72 |
73 | @Override
74 | public void close()
75 | {
76 | CloseableUtils.closeQuietly(leaderSelector);
77 | }
78 |
79 | @VisibleForTesting
80 | LeaderSelector getLeaderSelector()
81 | {
82 | return leaderSelector;
83 | }
84 |
85 | @VisibleForTesting
86 | void debugValidateClosed()
87 | {
88 | Preconditions.checkState(!leaderSelector.hasLeadership());
89 | }
90 |
91 | private void takeLeadership()
92 | {
93 | log.info(workflowManager.getInstanceName() + " is now the scheduler");
94 | try
95 | {
96 | scheduler.set(new Scheduler(workflowManager, queueFactory, autoCleanerHolder));
97 | scheduler.get().run();
98 | }
99 | finally
100 | {
101 | log.info(workflowManager.getInstanceName() + " is no longer the scheduler");
102 | scheduler.set(null);
103 |
104 | CountDownLatch latch = debugLatch.getAndSet(null);
105 | if ( latch != null )
106 | {
107 | latch.countDown();
108 | }
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/details/TaskExecutorSpec.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.details;
17 |
18 | import com.google.common.base.Preconditions;
19 | import com.nirmata.workflow.executor.TaskExecutor;
20 | import com.nirmata.workflow.models.TaskType;
21 |
22 | public class TaskExecutorSpec
23 | {
24 | private final TaskExecutor taskExecutor;
25 | private final int qty;
26 | private final TaskType taskType;
27 |
28 | public TaskExecutorSpec(TaskExecutor taskExecutor, int qty, TaskType taskType)
29 | {
30 | this.taskType = Preconditions.checkNotNull(taskType, "taskType cannot be null");
31 | this.taskExecutor = Preconditions.checkNotNull(taskExecutor, "taskExecutor cannot be null");
32 | this.qty = qty;
33 | }
34 |
35 | TaskExecutor getTaskExecutor()
36 | {
37 | return taskExecutor;
38 | }
39 |
40 | int getQty()
41 | {
42 | return qty;
43 | }
44 |
45 | TaskType getTaskType()
46 | {
47 | return taskType;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/details/WorkflowListenerManagerImpl.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.details;
17 |
18 | import com.nirmata.workflow.events.WorkflowEvent;
19 | import com.nirmata.workflow.events.WorkflowListener;
20 | import com.nirmata.workflow.events.WorkflowListenerManager;
21 | import com.nirmata.workflow.models.RunId;
22 | import com.nirmata.workflow.models.TaskId;
23 | import org.apache.curator.framework.listen.Listenable;
24 | import org.apache.curator.framework.listen.StandardListenerManager;
25 | import org.apache.curator.framework.listen.UnaryListenerManager;
26 | import org.apache.curator.framework.recipes.cache.PathChildrenCache;
27 | import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
28 | import org.apache.curator.utils.CloseableUtils;
29 |
30 | import java.io.IOException;
31 |
32 | public class WorkflowListenerManagerImpl implements WorkflowListenerManager {
33 | private final PathChildrenCache completedTasksCache;
34 | private final PathChildrenCache startedTasksCache;
35 | private final PathChildrenCache runsCache;
36 | private final UnaryListenerManager listenerContainer = StandardListenerManager.standard();
37 |
38 | public WorkflowListenerManagerImpl(WorkflowManagerImpl workflowManager) {
39 | completedTasksCache = new PathChildrenCache(workflowManager.getCurator(), ZooKeeperConstants.getCompletedTaskParentPath(), false);
40 | startedTasksCache = new PathChildrenCache(workflowManager.getCurator(), ZooKeeperConstants.getStartedTasksParentPath(), false);
41 | runsCache = new PathChildrenCache(workflowManager.getCurator(), ZooKeeperConstants.getRunParentPath(), false);
42 | }
43 |
44 | @Override
45 | public void start() {
46 | try {
47 | runsCache.getListenable().addListener((client, event) -> {
48 | RunId runId = new RunId(ZooKeeperConstants.getRunIdFromRunPath(event.getData().getPath()));
49 | if (event.getType() == PathChildrenCacheEvent.Type.CHILD_ADDED) {
50 | postEvent(new WorkflowEvent(WorkflowEvent.EventType.RUN_STARTED, runId));
51 | } else if (event.getType() == PathChildrenCacheEvent.Type.CHILD_UPDATED) {
52 | postEvent(new WorkflowEvent(WorkflowEvent.EventType.RUN_UPDATED, runId));
53 | }
54 | });
55 |
56 | startedTasksCache.getListenable().addListener((client, event) -> {
57 | if (event.getType() == PathChildrenCacheEvent.Type.CHILD_ADDED) {
58 | RunId runId = new RunId(ZooKeeperConstants.getRunIdFromStartedTasksPath(event.getData().getPath()));
59 | TaskId taskId = new TaskId(ZooKeeperConstants.getTaskIdFromStartedTasksPath(event.getData().getPath()));
60 | postEvent(new WorkflowEvent(WorkflowEvent.EventType.TASK_STARTED, runId, taskId));
61 | }
62 | });
63 |
64 | completedTasksCache.getListenable().addListener((client, event) -> {
65 | if (event.getType() == PathChildrenCacheEvent.Type.CHILD_ADDED) {
66 | RunId runId = new RunId(ZooKeeperConstants.getRunIdFromCompletedTasksPath(event.getData().getPath()));
67 | TaskId taskId = new TaskId(ZooKeeperConstants.getTaskIdFromCompletedTasksPath(event.getData().getPath()));
68 | postEvent(new WorkflowEvent(WorkflowEvent.EventType.TASK_COMPLETED, runId, taskId));
69 | }
70 | });
71 |
72 | runsCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
73 | completedTasksCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
74 | startedTasksCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
75 | } catch (Exception e) {
76 | throw new RuntimeException(e);
77 | }
78 | }
79 |
80 | @Override
81 | public void close() throws IOException {
82 | CloseableUtils.closeQuietly(runsCache);
83 | CloseableUtils.closeQuietly(startedTasksCache);
84 | CloseableUtils.closeQuietly(completedTasksCache);
85 | }
86 |
87 | @Override
88 | public Listenable getListenable() {
89 | return listenerContainer;
90 | }
91 |
92 | private void postEvent(WorkflowEvent event) {
93 | listenerContainer.forEach(l -> {
94 | if (l != null) {
95 | l.receiveEvent(event);
96 | }
97 | });
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/details/ZooKeeperConstants.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.details;
17 |
18 | import com.google.common.base.Splitter;
19 | import com.nirmata.workflow.models.RunId;
20 | import com.nirmata.workflow.models.TaskId;
21 | import com.nirmata.workflow.models.TaskType;
22 | import org.apache.curator.utils.ZKPaths;
23 |
24 | public class ZooKeeperConstants
25 | {
26 | private static final String SCHEDULER_LEADER_PATH = "/scheduler-leader";
27 | private static final String RUN_PATH = "/runs";
28 | private static final String COMPLETED_TASKS_PATH = "/tasks-completed";
29 | private static final String STARTED_TASKS_PATH = "/tasks-started";
30 | private static final String QUEUE_PATH_BASE = "/tasks-queue";
31 |
32 | private static final String SEPARATOR = "|";
33 |
34 | public static final int MAX_PAYLOAD = 0xfffff; // see "jute.maxbuffer" at http://zookeeper.apache.org/doc/r3.3.1/zookeeperAdmin.html
35 |
36 | private ZooKeeperConstants()
37 | {
38 | }
39 |
40 | public static void main(String[] args)
41 | {
42 | TaskId taskId = new TaskId();
43 | RunId runId = new RunId();
44 | String runPath = getRunPath(runId);
45 | String completedTaskPath = getCompletedTaskPath(runId, taskId);
46 | String startedTaskPath = getStartedTaskPath(runId, taskId);
47 | TaskType taskType = new TaskType("a", "b", true);
48 |
49 | System.out.println("taskId = " + taskId.getId());
50 | System.out.println("runId = " + runId.getId());
51 | System.out.println();
52 |
53 | System.out.println("getRunIdFromRunPath:\t\t\t\t" + getRunIdFromRunPath(runPath));
54 | System.out.println("getRunIdFromCompletedTasksPath:\t\t" + getRunIdFromCompletedTasksPath(completedTaskPath));
55 | System.out.println("getTaskIdFromCompletedTasksPath:\t" + getTaskIdFromCompletedTasksPath(completedTaskPath));
56 | System.out.println("getTaskIdFromStartedTasksPath:\t\t" + getTaskIdFromStartedTasksPath(startedTaskPath));
57 | System.out.println();
58 |
59 | System.out.println("getSchedulerLeaderPath:\t\t\t\t" + getSchedulerLeaderPath());
60 | System.out.println("getRunParentPath:\t\t\t\t\t" + getRunParentPath());
61 | System.out.println("getRunPath:\t\t\t\t\t\t\t" + getRunPath(runId));
62 | System.out.println("getQueueBasePath:\t\t\t\t\t" + getQueueBasePath(taskType));
63 | System.out.println("getQueuePath:\t\t\t\t\t\t" + getQueuePath(taskType));
64 | System.out.println("getQueueLockPath:\t\t\t\t\t" + getQueueLockPath(taskType));
65 | System.out.println("getCompletedTasksParentPath:\t\t" + getCompletedTaskParentPath());
66 | System.out.println("getCompletedTaskPath:\t\t\t\t" + completedTaskPath);
67 | System.out.println("getStartedTasksParentPath:\t\t\t" + getStartedTasksParentPath());
68 | System.out.println("getStartedTaskPath:\t\t\t\t\t" + startedTaskPath);
69 | }
70 |
71 | public static String getSchedulerLeaderPath()
72 | {
73 | return SCHEDULER_LEADER_PATH;
74 | }
75 |
76 | public static String getRunParentPath()
77 | {
78 | return RUN_PATH;
79 | }
80 |
81 | public static String getQueuePathBase()
82 | {
83 | return QUEUE_PATH_BASE;
84 | }
85 |
86 | public static String getRunPath(RunId runId)
87 | {
88 | return ZKPaths.makePath(RUN_PATH, runId.getId());
89 | }
90 |
91 | public static String getRunIdFromRunPath(String path)
92 | {
93 | return ZKPaths.getPathAndNode(path).getNode();
94 | }
95 |
96 | public static String getQueuePath(TaskType taskType)
97 | {
98 | String path = getQueueBasePath(taskType);
99 | return ZKPaths.makePath(path, "queue");
100 | }
101 |
102 | public static String getQueueLockPath(TaskType taskType)
103 | {
104 | String path = getQueueBasePath(taskType);
105 | return ZKPaths.makePath(path, "queue-lock");
106 | }
107 |
108 | private static String getQueueBasePath(TaskType taskType)
109 | {
110 | String path = ZKPaths.makePath(QUEUE_PATH_BASE, taskType.getType());
111 | path = ZKPaths.makePath(path, taskType.isIdempotent() ? "i" : "ni");
112 | return ZKPaths.makePath(path, taskType.getVersion());
113 | }
114 |
115 | public static String getCompletedTaskParentPath()
116 | {
117 | return COMPLETED_TASKS_PATH;
118 | }
119 |
120 | public static String getRunIdFromCompletedTasksPath(String path)
121 | {
122 | String n = ZKPaths.getNodeFromPath(path);
123 | return Splitter.on(SEPARATOR).splitToList(n).get(0);
124 | }
125 |
126 | public static String getTaskIdFromStartedTasksPath(String path)
127 | {
128 | return getTaskIdFromCompletedTasksPath(path);
129 | }
130 |
131 | public static String getTaskIdFromCompletedTasksPath(String path)
132 | {
133 | String n = ZKPaths.getNodeFromPath(path);
134 | return Splitter.on(SEPARATOR).splitToList(n).get(1);
135 | }
136 |
137 | public static String getCompletedTaskPath(RunId runId, TaskId taskId)
138 | {
139 | return ZKPaths.makePath(getCompletedTaskParentPath(), makeRunTask(runId, taskId));
140 | }
141 |
142 | public static String getStartedTasksParentPath()
143 | {
144 | return STARTED_TASKS_PATH;
145 | }
146 |
147 | public static String getStartedTaskPath(RunId runId, TaskId taskId)
148 | {
149 | String parentPath = getStartedTasksParentPath();
150 | return ZKPaths.makePath(parentPath, makeRunTask(runId, taskId));
151 | }
152 |
153 | public static String getRunIdFromStartedTasksPath(String path)
154 | {
155 | return getRunIdFromCompletedTasksPath(path);
156 | }
157 |
158 | private static String makeRunTask(RunId runId, TaskId taskId)
159 | {
160 | return runId.getId() + SEPARATOR + taskId.getId();
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/details/internalmodels/RunnableTask.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.details.internalmodels;
17 |
18 | import com.google.common.base.Preconditions;
19 | import com.google.common.collect.ImmutableList;
20 | import com.google.common.collect.ImmutableMap;
21 | import com.nirmata.workflow.models.ExecutableTask;
22 | import com.nirmata.workflow.models.RunId;
23 | import com.nirmata.workflow.models.TaskId;
24 | import java.io.Serializable;
25 | import java.time.LocalDateTime;
26 | import java.util.List;
27 | import java.util.Map;
28 | import java.util.Optional;
29 |
30 | public class RunnableTask implements Serializable
31 | {
32 | private final Map tasks;
33 | private final List taskDags;
34 | private final LocalDateTime startTimeUtc;
35 | private final Optional completionTimeUtc;
36 | private final Optional parentRunId;
37 |
38 | public RunnableTask(Map tasks, List taskDags, LocalDateTime startTimeUtc)
39 | {
40 | this(tasks, taskDags, startTimeUtc, null, null);
41 | }
42 |
43 | public RunnableTask(Map tasks, List taskDags, LocalDateTime startTimeUtc, LocalDateTime completionTimeUtc)
44 | {
45 | this(tasks, taskDags, startTimeUtc, completionTimeUtc, null);
46 | }
47 |
48 | public RunnableTask(Map tasks, List taskDags, LocalDateTime startTimeUtc, LocalDateTime completionTimeUtc, RunId parentRunId)
49 | {
50 | this.startTimeUtc = Preconditions.checkNotNull(startTimeUtc, "startTimeUtc cannot be null");
51 | this.completionTimeUtc = Optional.ofNullable(completionTimeUtc);
52 | this.parentRunId = Optional.ofNullable(parentRunId);
53 | tasks = Preconditions.checkNotNull(tasks, "tasks cannot be null");
54 | taskDags = Preconditions.checkNotNull(taskDags, "taskDags cannot be null");
55 |
56 | this.tasks = ImmutableMap.copyOf(tasks);
57 | this.taskDags = ImmutableList.copyOf(taskDags);
58 | }
59 |
60 | public Map getTasks()
61 | {
62 | return tasks;
63 | }
64 |
65 | public List getTaskDags()
66 | {
67 | return taskDags;
68 | }
69 |
70 | public Optional getCompletionTimeUtc()
71 | {
72 | return completionTimeUtc;
73 | }
74 |
75 | public LocalDateTime getStartTimeUtc()
76 | {
77 | return startTimeUtc;
78 | }
79 |
80 | public Optional getParentRunId()
81 | {
82 | return parentRunId;
83 | }
84 |
85 | @Override
86 | public boolean equals(Object o)
87 | {
88 | if ( this == o )
89 | {
90 | return true;
91 | }
92 | if ( o == null || getClass() != o.getClass() )
93 | {
94 | return false;
95 | }
96 |
97 | RunnableTask that = (RunnableTask)o;
98 |
99 | if ( !completionTimeUtc.equals(that.completionTimeUtc) )
100 | {
101 | return false;
102 | }
103 | if ( !parentRunId.equals(that.parentRunId) )
104 | {
105 | return false;
106 | }
107 | if ( !startTimeUtc.equals(that.startTimeUtc) )
108 | {
109 | return false;
110 | }
111 | if ( !taskDags.equals(that.taskDags) )
112 | {
113 | return false;
114 | }
115 | //noinspection RedundantIfStatement
116 | if ( !tasks.equals(that.tasks) )
117 | {
118 | return false;
119 | }
120 |
121 | return true;
122 | }
123 |
124 | @Override
125 | public int hashCode()
126 | {
127 | int result = tasks.hashCode();
128 | result = 31 * result + taskDags.hashCode();
129 | result = 31 * result + startTimeUtc.hashCode();
130 | result = 31 * result + completionTimeUtc.hashCode();
131 | result = 31 * result + parentRunId.hashCode();
132 | return result;
133 | }
134 |
135 | @Override
136 | public String toString()
137 | {
138 | return "RunnableTask{" +
139 | "tasks=" + tasks +
140 | ", taskDags=" + taskDags +
141 | ", startTime=" + startTimeUtc +
142 | ", completionTime=" + completionTimeUtc +
143 | ", parentRunId=" + parentRunId +
144 | '}';
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/details/internalmodels/RunnableTaskDag.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.details.internalmodels;
17 |
18 | import com.google.common.base.Preconditions;
19 | import com.google.common.collect.ImmutableSet;
20 | import com.nirmata.workflow.models.TaskId;
21 | import java.io.Serializable;
22 | import java.util.Collection;
23 |
24 | public class RunnableTaskDag implements Serializable
25 | {
26 | private final TaskId taskId;
27 | private final Collection dependencies;
28 |
29 | public RunnableTaskDag(TaskId taskId, Collection dependencies)
30 | {
31 | this.taskId = Preconditions.checkNotNull(taskId, "taskId cannot be null");
32 | dependencies = Preconditions.checkNotNull(dependencies, "dependencies cannot be null");
33 | this.dependencies = ImmutableSet.copyOf(dependencies);
34 | }
35 |
36 | public TaskId getTaskId()
37 | {
38 | return taskId;
39 | }
40 |
41 | public Collection getDependencies()
42 | {
43 | return dependencies;
44 | }
45 |
46 | @Override
47 | public boolean equals(Object o)
48 | {
49 | if ( this == o )
50 | {
51 | return true;
52 | }
53 | if ( o == null || getClass() != o.getClass() )
54 | {
55 | return false;
56 | }
57 |
58 | RunnableTaskDag that = (RunnableTaskDag)o;
59 |
60 | if ( !dependencies.equals(that.dependencies) )
61 | {
62 | return false;
63 | }
64 | //noinspection RedundantIfStatement
65 | if ( !taskId.equals(that.taskId) )
66 | {
67 | return false;
68 | }
69 |
70 | return true;
71 | }
72 |
73 | @Override
74 | public int hashCode()
75 | {
76 | int result = taskId.hashCode();
77 | result = 31 * result + dependencies.hashCode();
78 | return result;
79 | }
80 |
81 | @Override
82 | public String toString()
83 | {
84 | return "RunnableTaskDagEntryModel{" +
85 | "taskId=" + taskId +
86 | ", dependencies=" + dependencies +
87 | '}';
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/details/internalmodels/StartedTask.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.details.internalmodels;
17 |
18 | import com.google.common.base.Preconditions;
19 | import java.io.Serializable;
20 | import java.time.LocalDateTime;
21 |
22 | public class StartedTask implements Serializable
23 | {
24 | private final String instanceName;
25 | private final LocalDateTime startDateUtc;
26 | private final int progress;
27 |
28 | public StartedTask(String instanceName, LocalDateTime startDateUtc, int progress)
29 | {
30 | this.instanceName = Preconditions.checkNotNull(instanceName, "instanceName cannot be null");
31 | this.startDateUtc = Preconditions.checkNotNull(startDateUtc, "startDateUtc cannot be null");
32 | this.progress = progress;
33 | }
34 |
35 | public String getInstanceName()
36 | {
37 | return instanceName;
38 | }
39 |
40 | public LocalDateTime getStartDateUtc()
41 | {
42 | return startDateUtc;
43 | }
44 |
45 | public int getProgress()
46 | {
47 | return progress;
48 | }
49 |
50 | @Override
51 | public boolean equals(Object o)
52 | {
53 | if ( this == o )
54 | {
55 | return true;
56 | }
57 | if ( o == null || getClass() != o.getClass() )
58 | {
59 | return false;
60 | }
61 |
62 | StartedTask that = (StartedTask)o;
63 |
64 | if ( !instanceName.equals(that.instanceName) )
65 | {
66 | return false;
67 | }
68 | //noinspection RedundantIfStatement
69 | if ( !startDateUtc.equals(that.startDateUtc) )
70 | {
71 | return false;
72 | }
73 |
74 | return true;
75 | }
76 |
77 | @Override
78 | public int hashCode()
79 | {
80 | int result = instanceName.hashCode();
81 | result = 31 * result + startDateUtc.hashCode();
82 | return result;
83 | }
84 |
85 | @Override
86 | public String toString()
87 | {
88 | return "StartedTaskModel{" +
89 | "instanceName='" + instanceName + '\'' +
90 | ", startDateUtc=" + startDateUtc +
91 | ", progress=" + progress +
92 | '}';
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/events/WorkflowEvent.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.events;
17 |
18 | import com.nirmata.workflow.models.RunId;
19 | import com.nirmata.workflow.models.TaskId;
20 | import java.util.Optional;
21 |
22 | /**
23 | * Models a workflow event
24 | */
25 | public class WorkflowEvent
26 | {
27 | public enum EventType
28 | {
29 | /**
30 | * A run has started. {@link WorkflowEvent#getRunId()} is the run id.
31 | */
32 | RUN_STARTED,
33 |
34 | /**
35 | * A run has been updated - usually meaning it has completed.
36 | * {@link WorkflowEvent#getRunId()} is the run id.
37 | */
38 | RUN_UPDATED,
39 |
40 | /**
41 | * A task has started. {@link WorkflowEvent#getRunId()} is the run id.
42 | * {@link WorkflowEvent#getTaskId()} is the task id.
43 | */
44 | TASK_STARTED,
45 |
46 | /**
47 | * A task has completed. {@link WorkflowEvent#getRunId()} is the run id.
48 | * {@link WorkflowEvent#getTaskId()} is the task id.
49 | */
50 | TASK_COMPLETED
51 | }
52 |
53 | private final EventType type;
54 | private final RunId runId;
55 | private final Optional taskId;
56 |
57 | public WorkflowEvent(EventType type, RunId runId)
58 | {
59 | this(type, runId, null);
60 | }
61 |
62 | public WorkflowEvent(EventType type, RunId runId, TaskId taskId)
63 | {
64 | this.type = type;
65 | this.runId = runId;
66 | this.taskId = Optional.ofNullable(taskId);
67 | }
68 |
69 | public EventType getType()
70 | {
71 | return type;
72 | }
73 |
74 | public RunId getRunId()
75 | {
76 | return runId;
77 | }
78 |
79 | public Optional getTaskId()
80 | {
81 | return taskId;
82 | }
83 |
84 | @Override
85 | public boolean equals(Object o)
86 | {
87 | if ( this == o )
88 | {
89 | return true;
90 | }
91 | if ( o == null || getClass() != o.getClass() )
92 | {
93 | return false;
94 | }
95 |
96 | WorkflowEvent that = (WorkflowEvent)o;
97 |
98 | if ( !runId.equals(that.runId) )
99 | {
100 | return false;
101 | }
102 | if ( !taskId.equals(that.taskId) )
103 | {
104 | return false;
105 | }
106 | //noinspection RedundantIfStatement
107 | if ( type != that.type )
108 | {
109 | return false;
110 | }
111 |
112 | return true;
113 | }
114 |
115 | @Override
116 | public int hashCode()
117 | {
118 | int result = type.hashCode();
119 | result = 31 * result + runId.hashCode();
120 | result = 31 * result + taskId.hashCode();
121 | return result;
122 | }
123 |
124 | @Override
125 | public String toString()
126 | {
127 | return "WorkflowEvent{" +
128 | "type=" + type +
129 | ", runId=" + runId +
130 | ", taskId=" + taskId +
131 | '}';
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/events/WorkflowListener.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.events;
17 |
18 | @FunctionalInterface
19 | public interface WorkflowListener
20 | {
21 | /**
22 | * Receive a workflow event
23 | *
24 | * @param event the event
25 | */
26 | void receiveEvent(WorkflowEvent event);
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/events/WorkflowListenerManager.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.events;
17 |
18 | import org.apache.curator.framework.listen.Listenable;
19 | import java.io.Closeable;
20 |
21 | /**
22 | * A listener manager for events. Once {@link #start()} is called, workflow
23 | * events will be generated as runs and tasks are started, completed, etc.
24 | * When through, call {@link #close()}.
25 | */
26 | public interface WorkflowListenerManager extends Closeable
27 | {
28 | /**
29 | * Start listening for events. NOTE: previous events are not reported.
30 | * i.e. already started runs/tasks are not reported.
31 | */
32 | void start();
33 |
34 | /**
35 | * Return the container to add/remove event listeners
36 | *
37 | * @return container
38 | */
39 | Listenable getListenable();
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/executor/TaskExecution.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.executor;
17 |
18 | import com.nirmata.workflow.models.TaskExecutionResult;
19 |
20 | /**
21 | * Represents a task execution. A new task execution is allocated for each run of a
22 | * task. The Workflow manager will call {@link #execute()} when the task should perform
23 | * its operation.
24 | */
25 | @FunctionalInterface
26 | public interface TaskExecution
27 | {
28 | /**
29 | * Execute the task and return the result when complete
30 | *
31 | * @return result
32 | */
33 | TaskExecutionResult execute();
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/executor/TaskExecutionStatus.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.executor;
17 |
18 | /**
19 | * Task execution status
20 | */
21 | public enum TaskExecutionStatus
22 | {
23 | /**
24 | * The task executed successfully
25 | */
26 | SUCCESS()
27 | {
28 | @Override
29 | public boolean isCancelingStatus()
30 | {
31 | return false;
32 | }
33 | },
34 |
35 | /**
36 | * The task failed, but the remaining tasks should still execute
37 | */
38 | FAILED_CONTINUE()
39 | {
40 | @Override
41 | public boolean isCancelingStatus()
42 | {
43 | return false;
44 | }
45 | },
46 |
47 | /**
48 | * The task failed and the remaining tasks in the run should be canceled
49 | */
50 | FAILED_STOP()
51 | {
52 | @Override
53 | public boolean isCancelingStatus()
54 | {
55 | return true;
56 | }
57 | }
58 | ;
59 |
60 | public abstract boolean isCancelingStatus();
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/executor/TaskExecutor.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.executor;
17 |
18 | import com.nirmata.workflow.WorkflowManager;
19 | import com.nirmata.workflow.models.ExecutableTask;
20 |
21 | /**
22 | * Factory for creating task executions
23 | */
24 | @FunctionalInterface
25 | public interface TaskExecutor
26 | {
27 | /**
28 | * Create a task execution for the given task
29 | *
30 | * @param workflowManager the manager
31 | * @param executableTask the task
32 | * @return the execution
33 | */
34 | TaskExecution newTaskExecution(WorkflowManager workflowManager, ExecutableTask executableTask);
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/models/ExecutableTask.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.models;
17 |
18 | import com.google.common.base.Preconditions;
19 | import com.google.common.collect.ImmutableMap;
20 | import java.io.Serializable;
21 | import java.util.Map;
22 |
23 | /**
24 | * Models a task that has been scheduled for execution
25 | * as part of a run
26 | */
27 | public class ExecutableTask implements Serializable
28 | {
29 | private final RunId runId;
30 | private final TaskId taskId;
31 | private final TaskType taskType;
32 | private final Map metaData;
33 | private final boolean isExecutable;
34 |
35 | /**
36 | * @param runId the run that this is part of
37 | * @param taskId the task
38 | * @param taskType task type
39 | * @param metaData meta data
40 | * @param isExecutable if false, this is merely a container for child tasks
41 | */
42 | public ExecutableTask(RunId runId, TaskId taskId, TaskType taskType, Map metaData, boolean isExecutable)
43 | {
44 | this.runId = Preconditions.checkNotNull(runId, "runId cannot be null");
45 | metaData = Preconditions.checkNotNull(metaData, "metaData cannot be null");
46 | this.isExecutable = isExecutable;
47 | this.taskId = Preconditions.checkNotNull(taskId, "taskId cannot be null");
48 | this.taskType = Preconditions.checkNotNull(taskType, "taskType cannot be null");
49 | this.metaData = ImmutableMap.copyOf(metaData);
50 | }
51 |
52 | public RunId getRunId()
53 | {
54 | return runId;
55 | }
56 |
57 | public boolean isExecutable()
58 | {
59 | return isExecutable;
60 | }
61 |
62 | public TaskId getTaskId()
63 | {
64 | return taskId;
65 | }
66 |
67 | public TaskType getTaskType()
68 | {
69 | return taskType;
70 | }
71 |
72 | public Map getMetaData()
73 | {
74 | return metaData;
75 | }
76 |
77 | @Override
78 | public boolean equals(Object o)
79 | {
80 | if ( this == o )
81 | {
82 | return true;
83 | }
84 | if ( o == null || getClass() != o.getClass() )
85 | {
86 | return false;
87 | }
88 |
89 | ExecutableTask that = (ExecutableTask)o;
90 |
91 | if ( isExecutable != that.isExecutable )
92 | {
93 | return false;
94 | }
95 | if ( !metaData.equals(that.metaData) )
96 | {
97 | return false;
98 | }
99 | if ( !runId.equals(that.runId) )
100 | {
101 | return false;
102 | }
103 | if ( !taskId.equals(that.taskId) )
104 | {
105 | return false;
106 | }
107 | //noinspection RedundantIfStatement
108 | if ( !taskType.equals(that.taskType) )
109 | {
110 | return false;
111 | }
112 |
113 | return true;
114 | }
115 |
116 | @Override
117 | public int hashCode()
118 | {
119 | int result = runId.hashCode();
120 | result = 31 * result + taskId.hashCode();
121 | result = 31 * result + taskType.hashCode();
122 | result = 31 * result + metaData.hashCode();
123 | result = 31 * result + (isExecutable ? 1 : 0);
124 | return result;
125 | }
126 |
127 | @Override
128 | public String toString()
129 | {
130 | return "ExecutableTask{" +
131 | "runId=" + runId +
132 | ", taskId=" + taskId +
133 | ", taskType=" + taskType +
134 | ", metaData=" + metaData +
135 | ", isExecutable=" + isExecutable +
136 | '}';
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/models/Id.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.models;
17 |
18 | import com.google.common.base.Preconditions;
19 | import java.io.Serializable;
20 | import java.util.UUID;
21 |
22 | /**
23 | * Base for IDs
24 | */
25 | public class Id implements Serializable
26 | {
27 | private final String id;
28 |
29 | protected Id()
30 | {
31 | id = newRandomId();
32 | }
33 |
34 | static String newRandomId()
35 | {
36 | return UUID.randomUUID().toString();
37 | }
38 |
39 | protected Id(String id)
40 | {
41 | this.id = Preconditions.checkNotNull(id, "id cannot be null");
42 | }
43 |
44 | public String getId()
45 | {
46 | return id;
47 | }
48 |
49 | public boolean isValid()
50 | {
51 | return (id.length() > 0);
52 | }
53 |
54 | @Override
55 | public boolean equals(Object o)
56 | {
57 | if ( this == o )
58 | {
59 | return true;
60 | }
61 | if ( o == null || getClass() != o.getClass() )
62 | {
63 | return false;
64 | }
65 |
66 | Id id1 = (Id)o;
67 |
68 | //noinspection RedundantIfStatement
69 | if ( !id.equals(id1.id) )
70 | {
71 | return false;
72 | }
73 |
74 | return true;
75 | }
76 |
77 | @Override
78 | public int hashCode()
79 | {
80 | return id.hashCode();
81 | }
82 |
83 | @Override
84 | public String toString()
85 | {
86 | return "Id{" +
87 | "id='" + id + '\'' +
88 | '}';
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/models/RunId.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.models;
17 |
18 | public class RunId extends Id
19 | {
20 | /**
21 | * Generate a new, globally unique ID that has the given prefix.
22 | * E.g. newRandomIdWithPrefix("test")
generates: test-828119e0-cd47-45a1-b120-94d284ecb7b3
23 | *
24 | * @param prefix ID's prefix
25 | * @return new ID
26 | */
27 | public static RunId newRandomIdWithPrefix(String prefix)
28 | {
29 | return new RunId(prefix + "-" + newRandomId());
30 | }
31 |
32 | public static void main(String[] args)
33 | {
34 | System.out.println(newRandomIdWithPrefix("hey"));
35 | System.out.println(newRandomIdWithPrefix("hey"));
36 | System.out.println(newRandomIdWithPrefix("hey"));
37 | System.out.println(newRandomIdWithPrefix("hey"));
38 | }
39 |
40 | public RunId()
41 | {
42 | }
43 |
44 | public RunId(String id)
45 | {
46 | super(id);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/models/Task.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.models;
17 |
18 | import com.google.common.base.Preconditions;
19 | import com.google.common.collect.ImmutableList;
20 | import com.google.common.collect.ImmutableMap;
21 | import com.google.common.collect.Lists;
22 | import com.google.common.collect.Maps;
23 | import java.io.Serializable;
24 | import java.util.List;
25 | import java.util.Map;
26 | import java.util.Optional;
27 |
28 | /**
29 | * Models a task
30 | */
31 | public class Task implements Serializable
32 | {
33 | private final TaskId taskId;
34 | private final Optional taskType;
35 | private final List childrenTasks;
36 | private final Map metaData;
37 |
38 | /**
39 | * Metadata value used by special purpose executors
40 | */
41 | public static final String META_TASK_SUBMIT_VALUE = "__submit_value";
42 |
43 | /**
44 | * Utility to create a new meta map with the given submit value
45 | *
46 | * @param value the submit value
47 | * @return new meta map
48 | */
49 | public static Map makeSpecialMeta(long value)
50 | {
51 | Map meta = Maps.newHashMap();
52 | meta.put(META_TASK_SUBMIT_VALUE, Long.toString(value));
53 | return meta;
54 | }
55 |
56 | /**
57 | * @param taskId this task's ID - must be unique
58 | * @param taskType the task type
59 | */
60 | public Task(TaskId taskId, TaskType taskType)
61 | {
62 | this(taskId, taskType, Lists.newArrayList(), Maps.newHashMap());
63 | }
64 |
65 | /**
66 | * This constructor makes a "container only" task. i.e. this task does
67 | * not execute anything. It exists solely to contain children tasks.
68 | *
69 | * @param taskId this task's ID - must be unique
70 | * @param childrenTasks child tasks - children are not executed until this task completes
71 | */
72 | public Task(TaskId taskId, List childrenTasks)
73 | {
74 | this(taskId, null, childrenTasks, Maps.newHashMap());
75 | }
76 |
77 | /**
78 | * @param taskId this task's ID - must be unique
79 | * @param taskType the task type
80 | * @param childrenTasks child tasks - children are not executed until this task completes
81 | */
82 | public Task(TaskId taskId, TaskType taskType, List childrenTasks)
83 | {
84 | this(taskId, taskType, childrenTasks, Maps.newHashMap());
85 | }
86 |
87 | /**
88 | * @param taskId this task's ID - must be unique
89 | * @param taskType the task type
90 | * @param childrenTasks child tasks - children are not executed until this task completes
91 | * @param metaData meta data for the task
92 | */
93 | public Task(TaskId taskId, TaskType taskType, List childrenTasks, Map metaData)
94 | {
95 | metaData = Preconditions.checkNotNull(metaData, "metaData cannot be null");
96 | childrenTasks = Preconditions.checkNotNull(childrenTasks, "childrenTasks cannot be null");
97 | this.taskId = Preconditions.checkNotNull(taskId, "taskId cannot be null");
98 | this.taskType = Optional.ofNullable(taskType);
99 |
100 | this.metaData = ImmutableMap.copyOf(metaData);
101 | this.childrenTasks = ImmutableList.copyOf(childrenTasks);
102 | }
103 |
104 | public List getChildrenTasks()
105 | {
106 | return childrenTasks;
107 | }
108 |
109 | public TaskId getTaskId()
110 | {
111 | return taskId;
112 | }
113 |
114 | public TaskType getTaskType()
115 | {
116 | //noinspection ConstantConditions
117 | return taskType.get(); // exception if empty is desired
118 | }
119 |
120 | public boolean isExecutable()
121 | {
122 | return taskType.isPresent();
123 | }
124 |
125 | public Map getMetaData()
126 | {
127 | return metaData;
128 | }
129 |
130 | @Override
131 | public boolean equals(Object o)
132 | {
133 | if ( this == o )
134 | {
135 | return true;
136 | }
137 | if ( o == null || getClass() != o.getClass() )
138 | {
139 | return false;
140 | }
141 |
142 | Task task = (Task)o;
143 |
144 | if ( !childrenTasks.equals(task.childrenTasks) )
145 | {
146 | return false;
147 | }
148 | if ( !taskId.equals(task.taskId) )
149 | {
150 | return false;
151 | }
152 | //noinspection RedundantIfStatement
153 | if ( !taskType.equals(task.taskType) )
154 | {
155 | return false;
156 | }
157 |
158 | return true;
159 | }
160 |
161 | @Override
162 | public int hashCode()
163 | {
164 | int result = taskId.hashCode();
165 | result = 31 * result + taskType.hashCode();
166 | result = 31 * result + childrenTasks.hashCode();
167 | return result;
168 | }
169 |
170 | @Override
171 | public String toString()
172 | {
173 | return "Task{" +
174 | "taskId=" + taskId +
175 | ", taskType=" + taskType +
176 | ", childrenTasks=" + childrenTasks +
177 | '}';
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/models/TaskExecutionResult.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.models;
17 |
18 | import com.google.common.base.Preconditions;
19 | import com.google.common.collect.ImmutableMap;
20 | import com.google.common.collect.Maps;
21 | import com.nirmata.workflow.WorkflowManager;
22 | import com.nirmata.workflow.executor.TaskExecutionStatus;
23 | import java.io.Serializable;
24 | import java.time.Clock;
25 | import java.time.LocalDateTime;
26 | import java.util.Map;
27 | import java.util.Optional;
28 |
29 | /**
30 | * Models an execution result.
31 | */
32 | public class TaskExecutionResult implements Serializable
33 | {
34 | private final TaskExecutionStatus status;
35 | private final String message;
36 | private final Map resultData;
37 | private final LocalDateTime completionTimeUtc;
38 | private final Optional subTaskRunId;
39 |
40 | /**
41 | * @param status the execution status
42 | * @param message any message that task
43 | */
44 | public TaskExecutionResult(TaskExecutionStatus status, String message)
45 | {
46 | this(status, message, Maps.newHashMap(), null, null);
47 | }
48 |
49 | /**
50 | * @param status the execution status
51 | * @param message any message that task
52 | * @param resultData result data (can be accessed via {@link WorkflowManager#getTaskExecutionResult(RunId, TaskId)})
53 | */
54 | public TaskExecutionResult(TaskExecutionStatus status, String message, Map resultData)
55 | {
56 | this(status, message, resultData, null, null);
57 | }
58 |
59 | /**
60 | * @param status the execution status
61 | * @param message any message that task
62 | * @param resultData result data (can be accessed via {@link WorkflowManager#getTaskExecutionResult(RunId, TaskId)})
63 | * @param subTaskRunId if not null, the value of a sub-task started via {@link WorkflowManager#submitSubTask(RunId, Task)}. If
64 | * a sub-task was started, it's vital that the run ID be passed here so that this run can pause until the sub-task
65 | * completes.
66 | */
67 | public TaskExecutionResult(TaskExecutionStatus status, String message, Map resultData, RunId subTaskRunId)
68 | {
69 | this(status, message, resultData, subTaskRunId, null);
70 | }
71 |
72 | /**
73 | * @param status the execution status
74 | * @param message any message that task
75 | * @param resultData result data (can be accessed via {@link WorkflowManager#getTaskExecutionResult(RunId, TaskId)})
76 | * @param subTaskRunId if not null, the value of a sub-task started via {@link WorkflowManager#submitSubTask(RunId, Task)}. If
77 | * a sub-task was started, it's vital that the run ID be passed here so that this run can pause until the sub-task
78 | * completes.
79 | * @param completionTimeUtc the completion time of the task. If null, LocalDateTime.now(Clock.systemUTC())
is used
80 | */
81 | public TaskExecutionResult(TaskExecutionStatus status, String message, Map resultData, RunId subTaskRunId, LocalDateTime completionTimeUtc)
82 | {
83 | this.message = Preconditions.checkNotNull(message, "message cannot be null");
84 | this.status = Preconditions.checkNotNull(status, "status cannot be null");
85 | this.subTaskRunId = Optional.ofNullable(subTaskRunId);
86 | this.completionTimeUtc = (completionTimeUtc != null) ? completionTimeUtc : LocalDateTime.now(Clock.systemUTC());
87 |
88 | resultData = Preconditions.checkNotNull(resultData, "resultData cannot be null");
89 | this.resultData = ImmutableMap.copyOf(resultData);
90 | }
91 |
92 | public String getMessage()
93 | {
94 | return message;
95 | }
96 |
97 | public TaskExecutionStatus getStatus()
98 | {
99 | return status;
100 | }
101 |
102 | public Map getResultData()
103 | {
104 | return resultData;
105 | }
106 |
107 | public Optional getSubTaskRunId()
108 | {
109 | return subTaskRunId;
110 | }
111 |
112 | public LocalDateTime getCompletionTimeUtc()
113 | {
114 | return completionTimeUtc;
115 | }
116 |
117 | @Override
118 | public boolean equals(Object o)
119 | {
120 | if ( this == o )
121 | {
122 | return true;
123 | }
124 | if ( o == null || getClass() != o.getClass() )
125 | {
126 | return false;
127 | }
128 |
129 | TaskExecutionResult that = (TaskExecutionResult)o;
130 |
131 | if ( !completionTimeUtc.equals(that.completionTimeUtc) )
132 | {
133 | return false;
134 | }
135 | if ( !message.equals(that.message) )
136 | {
137 | return false;
138 | }
139 | if ( !resultData.equals(that.resultData) )
140 | {
141 | return false;
142 | }
143 | if ( status != that.status )
144 | {
145 | return false;
146 | }
147 | //noinspection RedundantIfStatement
148 | if ( !subTaskRunId.equals(that.subTaskRunId) )
149 | {
150 | return false;
151 | }
152 |
153 | return true;
154 | }
155 |
156 | @Override
157 | public int hashCode()
158 | {
159 | int result = status.hashCode();
160 | result = 31 * result + message.hashCode();
161 | result = 31 * result + resultData.hashCode();
162 | result = 31 * result + completionTimeUtc.hashCode();
163 | result = 31 * result + subTaskRunId.hashCode();
164 | return result;
165 | }
166 |
167 | @Override
168 | public String toString()
169 | {
170 | return "TaskExecutionResult{" +
171 | "status=" + status +
172 | ", message='" + message + '\'' +
173 | ", resultData=" + resultData +
174 | ", completionTimeUtc=" + completionTimeUtc +
175 | ", subTaskRunId=" + subTaskRunId +
176 | '}';
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/models/TaskId.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.models;
17 |
18 | public class TaskId extends Id
19 | {
20 | public TaskId()
21 | {
22 | }
23 |
24 | public TaskId(String id)
25 | {
26 | super(id);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/models/TaskMode.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.models;
17 |
18 | public enum TaskMode
19 | {
20 | STANDARD(0),
21 | DELAY(1),
22 | PRIORITY(2)
23 | ;
24 |
25 | public static TaskMode fromCode(int code)
26 | {
27 | for ( TaskMode mode : values() )
28 | {
29 | if ( mode.code == code )
30 | {
31 | return mode;
32 | }
33 | }
34 | return null;
35 | }
36 |
37 | public int getCode()
38 | {
39 | return code;
40 | }
41 |
42 | private final int code;
43 |
44 | TaskMode(int code)
45 | {
46 | this.code = code;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/models/TaskType.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.models;
17 |
18 | import com.google.common.base.Preconditions;
19 | import java.io.Serializable;
20 |
21 | /**
22 | *
23 | * Models a task type
24 | *
25 | *
26 | *
27 | * Tasks can be idempotent or non-idempotent. Idempotent tasks
28 | * can be restarted/re-executed when a workflow instance crashes
29 | * or there is some other kind of error. non-idempotent tasks
30 | * will only be attempted once.
31 | *
32 | */
33 | public class TaskType implements Serializable
34 | {
35 | private final String type;
36 | private final String version;
37 | private final boolean isIdempotent;
38 | private final TaskMode mode;
39 |
40 | // for backward compatibility
41 | public TaskType(String type, String version, boolean isIdempotent)
42 | {
43 | this(type, version, isIdempotent, TaskMode.STANDARD);
44 | }
45 |
46 | /**
47 | * @param type any value to represent the task type
48 | * @param version the version of this task type
49 | * @param isIdempotent whether or not this task is idempotent (see class description for details)
50 | * @param mode the mode
51 | */
52 | public TaskType(String type, String version, boolean isIdempotent, TaskMode mode)
53 | {
54 | Preconditions.checkArgument(!type.contains("/"), "type cannot contain '/'");
55 | Preconditions.checkArgument(!version.contains("/"), "version cannot contain '/'");
56 | this.mode = Preconditions.checkNotNull(mode, "mode cannot be null");
57 |
58 | this.version = Preconditions.checkNotNull(version, "version cannot be null");
59 | this.type = Preconditions.checkNotNull(type, "type cannot be null");
60 | this.isIdempotent = isIdempotent;
61 | }
62 |
63 | public String getVersion()
64 | {
65 | return version;
66 | }
67 |
68 | public String getType()
69 | {
70 | return type;
71 | }
72 |
73 | public boolean isIdempotent()
74 | {
75 | return isIdempotent;
76 | }
77 |
78 | public TaskMode getMode()
79 | {
80 | return mode;
81 | }
82 |
83 | @SuppressWarnings("SimplifiableIfStatement")
84 | @Override
85 | public boolean equals(Object o)
86 | {
87 | if ( this == o )
88 | {
89 | return true;
90 | }
91 | if ( o == null || getClass() != o.getClass() )
92 | {
93 | return false;
94 | }
95 |
96 | TaskType taskType = (TaskType)o;
97 |
98 | if ( isIdempotent != taskType.isIdempotent )
99 | {
100 | return false;
101 | }
102 | if ( !type.equals(taskType.type) )
103 | {
104 | return false;
105 | }
106 | if ( !version.equals(taskType.version) )
107 | {
108 | return false;
109 | }
110 | return mode == taskType.mode;
111 |
112 | }
113 |
114 | @Override
115 | public int hashCode()
116 | {
117 | int result = type.hashCode();
118 | result = 31 * result + version.hashCode();
119 | result = 31 * result + (isIdempotent ? 1 : 0);
120 | result = 31 * result + mode.hashCode();
121 | return result;
122 | }
123 |
124 | @Override
125 | public String toString()
126 | {
127 | return "TaskType{" +
128 | "type='" + type + '\'' +
129 | ", version='" + version + '\'' +
130 | ", isIdempotent=" + isIdempotent +
131 | ", mode=" + mode +
132 | '}';
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/queue/Queue.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.queue;
17 |
18 | import com.nirmata.workflow.models.ExecutableTask;
19 | import java.io.Closeable;
20 |
21 | public interface Queue extends Closeable
22 | {
23 | void start();
24 |
25 | @Override
26 | void close();
27 |
28 | void put(ExecutableTask executableTask);
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/queue/QueueConsumer.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.queue;
17 |
18 | import com.google.common.annotations.VisibleForTesting;
19 | import com.nirmata.workflow.admin.WorkflowManagerState;
20 | import java.io.Closeable;
21 |
22 | public interface QueueConsumer extends Closeable
23 | {
24 | void start();
25 |
26 | @Override
27 | void close();
28 |
29 | WorkflowManagerState.State getState();
30 |
31 | @VisibleForTesting
32 | void debugValidateClosed();
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/queue/QueueFactory.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.queue;
17 |
18 | import com.nirmata.workflow.details.WorkflowManagerImpl;
19 | import com.nirmata.workflow.models.TaskType;
20 |
21 | public interface QueueFactory
22 | {
23 | Queue createQueue(WorkflowManagerImpl workflowManager, TaskType taskType);
24 | QueueConsumer createQueueConsumer(WorkflowManagerImpl workflowManager, TaskRunner taskRunner, TaskType taskType);
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/queue/TaskRunner.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.queue;
17 |
18 | import com.nirmata.workflow.models.ExecutableTask;
19 |
20 | @FunctionalInterface
21 | public interface TaskRunner
22 | {
23 | void executeTask(ExecutableTask executableTask);
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/queue/zookeeper/ZooKeeperSimpleQueue.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.queue.zookeeper;
17 |
18 | import com.google.common.base.Preconditions;
19 | import com.nirmata.workflow.details.ZooKeeperConstants;
20 | import com.nirmata.workflow.models.ExecutableTask;
21 | import com.nirmata.workflow.models.Task;
22 | import com.nirmata.workflow.models.TaskType;
23 | import com.nirmata.workflow.queue.Queue;
24 | import com.nirmata.workflow.queue.TaskRunner;
25 | import com.nirmata.workflow.serialization.Serializer;
26 | import org.apache.curator.framework.CuratorFramework;
27 | import org.slf4j.Logger;
28 | import org.slf4j.LoggerFactory;
29 |
30 | public class ZooKeeperSimpleQueue implements Queue
31 | {
32 | private final Logger log = LoggerFactory.getLogger(getClass());
33 | private final SimpleQueue queue;
34 | private final Serializer serializer;
35 |
36 | ZooKeeperSimpleQueue(TaskRunner taskRunner, Serializer serializer, CuratorFramework client, TaskType taskType)
37 | {
38 | this.serializer = Preconditions.checkNotNull(serializer, "serializer cannot be null");
39 | String path = ZooKeeperConstants.getQueuePath(taskType);
40 | String lockPath = ZooKeeperConstants.getQueueLockPath(taskType);
41 | queue = new SimpleQueue(client, taskRunner, serializer, path, lockPath, taskType.getMode(), taskType.isIdempotent());
42 | }
43 |
44 | @Override
45 | public void start()
46 | {
47 | // NOP
48 | }
49 |
50 | @Override
51 | public void close()
52 | {
53 | // NOP
54 | }
55 |
56 | @Override
57 | public void put(ExecutableTask executableTask)
58 | {
59 | long value = 0;
60 | try
61 | {
62 | String valueStr = executableTask.getMetaData().get(Task.META_TASK_SUBMIT_VALUE);
63 | if ( valueStr != null )
64 | {
65 | value = Long.parseLong(valueStr);
66 | }
67 | }
68 | catch ( NumberFormatException ignore )
69 | {
70 | // ignore
71 | }
72 |
73 | try
74 | {
75 | byte[] bytes = serializer.serialize(executableTask);
76 | queue.put(bytes, value);
77 | }
78 | catch ( Exception e )
79 | {
80 | log.error("Could not add to queue for: " + executableTask, e);
81 | throw new RuntimeException(e);
82 | }
83 | }
84 |
85 | SimpleQueue getQueue()
86 | {
87 | return queue;
88 | }
89 |
90 | Serializer getSerializer()
91 | {
92 | return serializer;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/queue/zookeeper/ZooKeeperSimpleQueueFactory.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.queue.zookeeper;
17 |
18 | import com.nirmata.workflow.details.WorkflowManagerImpl;
19 | import com.nirmata.workflow.models.TaskType;
20 | import com.nirmata.workflow.queue.Queue;
21 | import com.nirmata.workflow.queue.QueueConsumer;
22 | import com.nirmata.workflow.queue.QueueFactory;
23 | import com.nirmata.workflow.queue.TaskRunner;
24 |
25 | public class ZooKeeperSimpleQueueFactory implements QueueFactory
26 | {
27 | @Override
28 | public Queue createQueue(WorkflowManagerImpl workflowManager, TaskType taskType)
29 | {
30 | return internalCreateQueue(workflowManager, taskType, null);
31 | }
32 |
33 | @Override
34 | public QueueConsumer createQueueConsumer(WorkflowManagerImpl workflowManager, TaskRunner taskRunner, TaskType taskType)
35 | {
36 | ZooKeeperSimpleQueue queue = internalCreateQueue(workflowManager, taskType, taskRunner);
37 | return queue.getQueue();
38 | }
39 |
40 | private ZooKeeperSimpleQueue internalCreateQueue(WorkflowManagerImpl workflowManager, TaskType taskType, TaskRunner taskRunner)
41 | {
42 | return new ZooKeeperSimpleQueue(taskRunner, workflowManager.getSerializer(), workflowManager.getCurator(), taskType);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/serialization/JsonSerializerMapper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.serialization;
17 |
18 | import com.fasterxml.jackson.databind.JsonNode;
19 | import com.fasterxml.jackson.databind.ObjectMapper;
20 | import com.google.common.base.Preconditions;
21 | import com.google.common.collect.ImmutableMap;
22 | import java.lang.reflect.Method;
23 | import java.util.Map;
24 |
25 | public class JsonSerializerMapper
26 | {
27 | private final Map, Methods> methodsMap;
28 |
29 | private static final String NEW_PREFIX = "new";
30 | private static final String GET_PREFIX = "get";
31 |
32 | private static final String MODELS_PACKAGE = "com.nirmata.workflow.models.";
33 | private static final String INTERNAL_MODELS_PACKAGE = "com.nirmata.workflow.details.internalmodels.";
34 |
35 | private static class Methods
36 | {
37 | private final Method getter;
38 | private final Method newer;
39 |
40 | public Methods(Method getter, Method newer)
41 | {
42 | this.getter = getter;
43 | this.newer = newer;
44 | }
45 | }
46 |
47 | public JsonSerializerMapper()
48 | {
49 | ImmutableMap.Builder, Methods> builder = ImmutableMap.builder();
50 | for ( Method method : JsonSerializer.class.getDeclaredMethods() )
51 | {
52 | if ( method.getName().startsWith(NEW_PREFIX) && (method.getReturnType() == JsonNode.class) && (method.getParameterCount() == 1) )
53 | {
54 | String className = method.getName().substring(NEW_PREFIX.length());
55 | Method getter = getGetter(className);
56 | if ( getter != null )
57 | {
58 | Class> clazz = getClazz(className);
59 | if ( (clazz != null) && (method.getParameterTypes()[0] == clazz) )
60 | {
61 | builder.put(clazz, new Methods(getter, method));
62 | }
63 | }
64 | }
65 | }
66 | methodsMap = builder.build();
67 | }
68 |
69 | public JsonNode make(T obj)
70 | {
71 | Methods methods = methodsMap.get(obj.getClass());
72 | methods = Preconditions.checkNotNull(methods, "No serializer found for: " + obj.getClass());
73 | try
74 | {
75 | return (JsonNode)methods.newer.invoke(null, obj);
76 | }
77 | catch ( Exception e )
78 | {
79 | throw new RuntimeException("Could not invoke newer() for: " + obj.getClass(), e);
80 | }
81 | }
82 |
83 | public ObjectMapper getMapper()
84 | {
85 | return JsonSerializer.getMapper();
86 | }
87 |
88 | public T get(JsonNode node, Class clazz)
89 | {
90 | Methods methods = methodsMap.get(clazz);
91 | methods = Preconditions.checkNotNull(methods, "No serializer found for: " + clazz);
92 | try
93 | {
94 | return clazz.cast(methods.getter.invoke(null, node));
95 | }
96 | catch ( Exception e )
97 | {
98 | throw new RuntimeException("Could not invoke getter() for: " + clazz, e);
99 | }
100 | }
101 |
102 | private Method getGetter(String className)
103 | {
104 | try
105 | {
106 | return JsonSerializer.class.getDeclaredMethod(GET_PREFIX + className, JsonNode.class);
107 | }
108 | catch ( NoSuchMethodException ignore )
109 | {
110 | // ignore
111 | }
112 | return null;
113 | }
114 |
115 | private Class> getClazz(String className)
116 | {
117 | Class> clazz = null;
118 | try
119 | {
120 | clazz = Class.forName(MODELS_PACKAGE + className);
121 | }
122 | catch ( ClassNotFoundException ignore )
123 | {
124 | try
125 | {
126 | clazz = Class.forName(INTERNAL_MODELS_PACKAGE + className);
127 | }
128 | catch ( ClassNotFoundException ignore2 )
129 | {
130 | // ignore
131 | }
132 | }
133 | return clazz;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/serialization/Serializer.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.serialization;
17 |
18 | public interface Serializer
19 | {
20 | /**
21 | * Serialize the given object into bytes
22 | *
23 | * @param obj the object
24 | * @return byte representation
25 | */
26 | byte[] serialize(T obj);
27 |
28 | /**
29 | * Deserialize the given bytes into an object
30 | *
31 | * @param data bytes
32 | * @param clazz type of object
33 | * @return object
34 | */
35 | T deserialize(byte[] data, Class clazz);
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/serialization/StandardSerializer.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.serialization;
17 |
18 | import com.fasterxml.jackson.core.JsonProcessingException;
19 | import com.fasterxml.jackson.databind.JsonNode;
20 | import org.slf4j.Logger;
21 | import org.slf4j.LoggerFactory;
22 | import java.io.IOException;
23 | import java.util.Arrays;
24 |
25 | public class StandardSerializer implements Serializer
26 | {
27 | private final Logger log = LoggerFactory.getLogger(getClass());
28 | private final JsonSerializerMapper mapper = new JsonSerializerMapper();
29 |
30 | @Override
31 | public byte[] serialize(T obj)
32 | {
33 | try
34 | {
35 | JsonNode node = mapper.make(obj);
36 | return JsonSerializer.getMapper().writeValueAsBytes(node);
37 | }
38 | catch ( JsonProcessingException e )
39 | {
40 | log.error("Could not serialize: " + obj.getClass(), e);
41 | throw new RuntimeException(e);
42 | }
43 | }
44 |
45 | @Override
46 | public T deserialize(byte[] data, Class clazz)
47 | {
48 | try
49 | {
50 | JsonNode node = JsonSerializer.getMapper().readTree(data);
51 | return mapper.get(node, clazz);
52 | }
53 | catch ( IOException e )
54 | {
55 | log.error("Could not deserialize: " + Arrays.toString(data), e);
56 | throw new RuntimeException(e);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/com/nirmata/workflow/serialization/TaskLoader.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.serialization;
17 |
18 | import com.nirmata.workflow.models.Task;
19 | import java.io.IOException;
20 | import java.io.Reader;
21 | import java.io.StringReader;
22 |
23 | /**
24 | * Utility for loading Tasks from a file/stream
25 | */
26 | public class TaskLoader
27 | {
28 | /**
29 | * Load a complete task with children from a JSON stream
30 | *
31 | * @param jsonStream the JSON stream
32 | * @return task
33 | */
34 | public static Task load(Reader jsonStream)
35 | {
36 | try
37 | {
38 | return JsonSerializer.getTask(JsonSerializer.getMapper().readTree(jsonStream));
39 | }
40 | catch ( IOException e )
41 | {
42 | throw new RuntimeException(e);
43 | }
44 | }
45 |
46 | /**
47 | * Load a complete task with children from a JSON string
48 | *
49 | * @param json the JSON
50 | * @return task
51 | */
52 | public static Task load(String json)
53 | {
54 | try ( StringReader reader = new StringReader(json) )
55 | {
56 | return load(reader);
57 | }
58 | }
59 |
60 | private TaskLoader()
61 | {
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/site/apt/admin.apt:
--------------------------------------------------------------------------------
1 | Administration APIs
2 |
3 | * WorkflowAdmin
4 |
5 | When a workflow run is executed, various data is written to ZooKeeper to keep track of the task runs and results. The WorkflowAdmin
6 | APIs allow you to get run info as well as clean up old runs. NOTE: the admin APIs can be called from any Workflow server.
7 |
8 | * << getRunInfo();>>>
9 |
10 | Info about all runs completed or currently executing in the workflow manager.
11 |
12 | * <<>>
13 |
14 | Info about the given run.
15 |
16 | * << getTaskInfo(RunId runId);>>>
17 |
18 | Info about all the tasks completed, started or waiting for the given run.
19 |
20 | * << getTaskDetails(RunId runId);>>>
21 |
22 | A map of all task details for the given run.
23 |
24 | * <<>>
25 |
26 | Delete all saved data for the given run. IMPORTANT: you should clean up old run data periodically so that
27 | your ZooKeeper instance doesn't get burdened by old data. Also, each time the WorkflowManager starts, it cycles
28 | through existing run data. Old run data will slow startup performance.
29 |
30 | * RunInfo
31 |
32 | Information about a specific task run.
33 |
34 | * <<>>
35 |
36 | The RunId.
37 |
38 | * <<>>
39 |
40 | The start time (in UTC) of the run.
41 |
42 | * <<>>
43 |
44 | Returns true if the run has completed.
45 |
46 | * <<>>
47 |
48 | The completion time (in UTC) of the run.
49 |
50 | * TaskInfo
51 |
52 | * <<>>
53 |
54 | The TaskId.
55 |
56 | * <<>>
57 |
58 | Returns true if the task has started. Otherwise, it is still waiting on dependent parent tasks.
59 |
60 | * <<>>
61 |
62 | The instance that ran the task.
63 |
64 | * <<>>
65 |
66 | The start time (in UTC) of the task.
67 |
68 | * <<>>
69 |
70 | Returns true if the task has completed.
71 |
72 | * <<>>
73 |
74 | For completed tasks, the result data.
75 |
76 | * TaskDetails
77 |
78 | * <<>>
79 |
80 | The TaskId.
81 |
82 | * <<>>
83 |
84 | Returns true if this is an executable task.
85 |
86 | * <<>>
87 |
88 | The task type (if executable).
89 |
90 | * << getMetaData();>>>
91 |
92 | The task's metadata.
93 |
--------------------------------------------------------------------------------
/src/site/apt/example.apt:
--------------------------------------------------------------------------------
1 | Example
2 |
3 | Here is a simple example that shows a Workflow Manager being started and a workflow of three tasks being submitted:
4 |
5 | * Example.java
6 |
7 | +---------------------------------------
8 | public class Example implements Closeable
9 | {
10 | private final TestingServer testingServer;
11 | private final CuratorFramework curator;
12 |
13 | public static void main(String[] args) throws Exception
14 | {
15 | Example example = new Example();
16 | example.runExample();
17 | example.close();
18 | }
19 |
20 | public Example() throws Exception
21 | {
22 | // for testing purposes, start an in-memory test ZooKeeper instance
23 | testingServer = new TestingServer();
24 |
25 | // allocate the Curator instance
26 | curator = CuratorFrameworkFactory.builder()
27 | .connectString(testingServer.getConnectString())
28 | .retryPolicy(new ExponentialBackoffRetry(100, 3))
29 | .build();
30 | }
31 |
32 | public void runExample()
33 | {
34 | curator.start();
35 |
36 | // for our example, we'll just have one task type
37 | TaskType taskType = new TaskType("my type", "1", true);
38 |
39 | // a task which will have two parents
40 | Task childTask = new Task(new TaskId("child task"), taskType);
41 |
42 | // parent #1
43 | Task parentTask1 = new Task(new TaskId("parent 1"), taskType, Lists.newArrayList(childTask));
44 |
45 | // parent #2
46 | Task parentTask2 = new Task(new TaskId("parent 2"), taskType, Lists.newArrayList(childTask));
47 |
48 | // a root container-only for the parent tasks
49 | Task rootTask = new Task(new TaskId(), Lists.newArrayList(parentTask1, parentTask2));
50 |
51 | // an executor that just logs a message and returns
52 | ExampleTaskExecutor taskExecutor = new ExampleTaskExecutor();
53 |
54 | // allocate the workflow manager with some executors for our type
55 | WorkflowManager workflowManager = WorkflowManagerBuilder.builder()
56 | .addingTaskExecutor(taskExecutor, 10, taskType)
57 | .withCurator(curator, "test", "1")
58 | .build();
59 |
60 | WorkflowListenerManager workflowListenerManager = workflowManager.newWorkflowListenerManager();
61 | try
62 | {
63 | // listen for run completion and count down a latch when it happens
64 | final CountDownLatch doneLatch = new CountDownLatch(1);
65 | WorkflowListener listener = new WorkflowListener()
66 | {
67 | @Override
68 | public void receiveEvent(WorkflowEvent event)
69 | {
70 | if ( event.getType() == WorkflowEvent.EventType.RUN_UPDATED )
71 | {
72 | // note: the run could have had an error. RUN_UPDATED does not guarantee successful completion
73 | doneLatch.countDown();
74 | }
75 | }
76 | };
77 | workflowListenerManager.getListenable().addListener(listener);
78 |
79 | // start the manager and the listeners
80 | workflowManager.start();
81 | workflowListenerManager.start();
82 |
83 | // submit our task
84 | workflowManager.submitTask(rootTask);
85 |
86 | // you should see these messages in the console:
87 | // Executing task: Id{id='parent 1'}
88 | // Executing task: Id{id='parent 2'}
89 | // then
90 | // Executing task: Id{id='child task'}
91 |
92 | // wait for completion
93 | doneLatch.await();
94 | }
95 | catch ( InterruptedException e )
96 | {
97 | Thread.currentThread().interrupt();
98 | }
99 | finally
100 | {
101 | CloseableUtils.closeQuietly(workflowListenerManager);
102 | CloseableUtils.closeQuietly(workflowManager);
103 | }
104 | }
105 |
106 | @Override
107 | public void close()
108 | {
109 | CloseableUtils.closeQuietly(curator);
110 | CloseableUtils.closeQuietly(testingServer);
111 | }
112 | }
113 | +---------------------------------------
114 |
115 | * ExampleTaskExecutor.java
116 |
117 | +---------------------------------------
118 | public class ExampleTaskExecutor implements TaskExecutor
119 | {
120 | @Override
121 | public TaskExecution newTaskExecution(WorkflowManager workflowManager, ExecutableTask executableTask)
122 | {
123 | return new TaskExecution()
124 | {
125 | @Override
126 | public TaskExecutionResult execute()
127 | {
128 | System.out.println("Executing task: " + executableTask.getTaskId());
129 | return new TaskExecutionResult(TaskExecutionStatus.SUCCESS, "My message");
130 | }
131 | };
132 | }
133 | }
134 | +---------------------------------------
135 |
--------------------------------------------------------------------------------
/src/site/apt/index.apt:
--------------------------------------------------------------------------------
1 | Nirmata Workflow
2 |
3 | Nirmata Workflow is a Java {{{http://zookeeper.apache.org/}Apache ZooKeeper}} and {{{http://curator.apache.org/}Apache Curator}}
4 | based library that enables distributed task workflows. You can build a
5 | cluster of servers as large as needed to execute tasks. You can limit servers to certain tasks types and/or allow servers to execute any task.
6 |
7 | A workflow is a {{{https://en.wikipedia.org/wiki/Directed_acyclic_graph}DAG}} of tasks
8 | whereby a group of tasks executes concurrently and when complete a set of children tasks execute and so on.
9 |
10 | [dag.png]
11 |
12 | In the DAG described in the diagram above, Tasks 1, 2 and 3 execute concurrently. When they complete, Tasks 4 and 5 execute. Then, Tasks 6, 7 and 8 execute.
13 | When Task 6 completes, Task 9 executes. When Tasks 6 and 8 complete, Task 10 executes.
14 |
15 | * Features
16 |
17 | * Can model simple to complex task relationships
18 |
19 | * Manages task relationships and distributed scheduling
20 |
21 | * Idempotent and non-idempotent tasks supported
22 |
23 | * Custom task-types can be defined and targeted to specific servers
24 |
25 | * Simple API
26 |
27 | * Supports runtime cluster changes
28 |
29 | * No single point of failure
30 |
31 |
--------------------------------------------------------------------------------
/src/site/apt/limits.apt:
--------------------------------------------------------------------------------
1 | Limits
2 |
3 | * Nirmata Workflow uses Apache ZooKeeper which has certain built-in limits. For example, the entire ZooKeeper database is kept in memory.
4 | So, huge data sets are not supported.
5 |
6 | * A given Nirmata Workflow run should be able to support several thousand tasks.
7 |
8 | * Each task should limit the amount of result data it produces. Result data is meant for a few dozen
9 | values at most.
10 |
11 | * It's vital that you clean up old runs periodically. See the {{{./admin.html}Administration APIs}} for details.
12 |
13 | * For proper high availability and failover, you should run a cluster of ZooKeeper instances (as opposed to just one).
14 |
15 |
--------------------------------------------------------------------------------
/src/site/apt/starting.apt:
--------------------------------------------------------------------------------
1 | Getting Started
2 |
3 | * Background
4 |
5 | The basic unit is a {{{./apidocs/com/nirmata/workflow/models/Task.html}Task}}. Tasks have an id, {{{./apidocs/com/nirmata/workflow/models/TaskType.html}type}},
6 | optional metadata, and zero or more children.
7 | Tasks are submitted to be executed through a {{{./apidocs/com/nirmata/workflow/WorkflowManager.html}WorkflowManager}} instance. WorkflowManager instances
8 | are created using a {{{./apidocs/com/nirmata/workflow/WorkflowManagerBuilder.html}WorkflowManagerBuilder}}. The work of a task is executed by a
9 | {{{./apidocs/com/nirmata/workflow/executor/TaskExecutor.html}TaskExecutor}} instance which you must provide an implementation for.
10 |
11 | * Prerequisites
12 |
13 | Nirmata Workflow uses {{{http://zookeeper.apache.org/}Apache ZooKeeper}} and {{{http://curator.apache.org/}Apache Curator}}. You will need to run
14 | an Apache ZooKeeper cluster in order to use the Workflow Manager.
15 | You will also need to create a Curator client instance for the Workflow Manager to use. Refer to the documentation of each of these projects
16 | for details.
17 |
18 | NOTE: Nirmata Workflow is a Java 8 Library
19 |
20 | * Using in Your Project
21 |
22 | Nirmata Workflow is available from Maven Central:
23 |
24 | *------------*-----------------------------*
25 | | GroupId | com.nirmata.workflow |
26 | *------------*-----------------------------*
27 | | ArtifactId | nirmata-workflow |
28 | *------------*-----------------------------*
29 |
30 | * From Maven
31 |
32 | +---------------------------------------
33 |
34 | com.nirmata.workflow
35 | nirmata-workflow
36 | ${nirmata-workflow-version}
37 |
38 | +---------------------------------------
39 |
40 | * From Gradle
41 |
42 | +---------------------------------------
43 | dependencies {
44 | compile('com.nirmata.workflow:nirmata-workflow:$nirmataWorkflowVersion')
45 | }
46 | +---------------------------------------
47 |
48 |
49 | * Tasks
50 |
51 | You can create task objects programmatically or via a file/stream. To load via file/stream, you can use the utility <<>>.
52 | Here is an {{{./tasks.json}example tasks JSON file}}. It defines 4 tasks. The first task, "root" is a
53 | container only task - i.e. there's nothing to execute; it just has children. Container only tasks don't have a "task type". Then, there is "task1" and "task2"
54 | both of which have a child task, "task3". This means that "task3" will not execute until both "task1" and "task2" complete.
55 |
56 | * Task Types
57 |
58 | Every task has a TaskType. The TaskType is used by your TaskExecutor implementation to do the work of the task. TaskType's have three fields:
59 | "type": a string that contains any value you define; "version": a version string used to version the tasks; "isIdempotent": whether or not
60 | the task is idempotent. TaskType also allows setting a priority or delay before a task executes.
61 |
62 | * TaskExecutor
63 |
64 | The TaskExecutor is a factory that you provide which creates TaskExecution instances. Every time a task needs to be executed, the newTaskExecution()
65 | method of TaskExecutor is called to get a new TaskExecution instance. The TaskExecution's execute() method is then called to do the work of the task
66 | and return a result. A task is executed randomly on one of the servers that is configured to handle tasks with that task type. Nirmata Workflow
67 | runs TaskExecutions in internally managed thread pools.
68 |
69 | * WorkflowManagerBuilder
70 |
71 | WorkflowManagerBuilder is used to build WorkflowManager instances. For most uses, you will have one WorkflowManager per server/JVM in your cluster.
72 | However, you can create as many WorkflowManager instances as you need. The builder has the following methods:
73 |
74 | *--------------------*------------------------------------------------------------------------*
75 | | withCurator | Required to set the {{{http://curator.apache.org/}Curator}} instance. |
76 | | | You also specify a namespace and a version. The namespace and version |
77 | | | combine to create a unique workflow. All instances using the same |
78 | | | namespace and version are logically part of the same workflow. |
79 | *--------------------*------------------------------------------------------------------------*
80 | | addingTaskExecutor | Adds a pool of task executors for a given task type to this instance |
81 | | | of the workflow. The specified number of executors are allocated. Call |
82 | | | this method multiple times to allocate executors for the various types |
83 | | | of tasks that will be used in this workflow. You can choose to have |
84 | | | all workflow instances execute all task types or target certain task |
85 | | | types to certain instances.\ |
86 | | | \ |
87 | | | qty is the maximum concurrency for the given type of task for this |
88 | | | instance. The logical concurrency for a given task type is the total |
89 | | | qty of all instances in the workflow. e.g. if there are 3 instances |
90 | | | in the workflow and instance A has 2 executors for task type "a", |
91 | | | instance B has 3 executors for task type "a" and instance C has no |
92 | | | executors for task type "a", the maximum concurrency for task type "a" |
93 | | | is 5.\ |
94 | | | \ |
95 | | | IMPORTANT: every workflow cluster must have at least one instance that |
96 | | | has task executor(s) for each task type that will be submitted to the |
97 | | | workflow. i.e workflows will stall if there is no executor for a given |
98 | | | task type. |
99 | *--------------------*------------------------------------------------------------------------*
100 | | withInstanceName | optional - used in reporting. This will be the value recorded as tasks |
101 | | | are executed. Via reporting, you can determine which instance has |
102 | | | executed a given task. the Default is: |
103 | | | <<>> |
104 | *--------------------*------------------------------------------------------------------------*
105 | | withAutoCleaner | optional - Sets an auto-cleaner that will run every given period. This |
106 | | | is used to clean old runs. IMPORTANT: the auto cleaner will only run |
107 | | | on the instance that is the current scheduler. |
108 | *--------------------*------------------------------------------------------------------------*
109 | | withSerializer | optional - By default, a JSON serializer is used to store data in |
110 | | | ZooKeeper. Use this to specify an alternate serializer. |
111 | *--------------------*------------------------------------------------------------------------*
112 | | withQueueFactory | optional - Pluggable queue factory. Default uses ZooKeeper for |
113 | | | queuing. |
114 | *--------------------*------------------------------------------------------------------------*
115 |
116 | * WorkflowManager
117 |
118 | WorkflowManager is the main API. Through it, you submit tasks, etc. Once created with WorkflowManagerBuilder, call the <<>> method and
119 | the workflow system will operate asynchronously. You can <<>> tasks at any time and from any instance.
120 |
--------------------------------------------------------------------------------
/src/site/dag source.graffle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nirmata/workflow/b92f8b36dd000682e217a77f12b6be16a0558f23/src/site/dag source.graffle
--------------------------------------------------------------------------------
/src/site/resources/css/site.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | tt {
17 | background-color: transparent;
18 | }
--------------------------------------------------------------------------------
/src/site/resources/dag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nirmata/workflow/b92f8b36dd000682e217a77f12b6be16a0558f23/src/site/resources/dag.png
--------------------------------------------------------------------------------
/src/site/resources/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "rootTaskId": "root",
3 | "tasks": [
4 | {
5 | "taskId": "root",
6 | "childrenTaskIds": ["task1", "task2"]
7 | },
8 |
9 | {
10 | "taskId": "task1",
11 | "taskType": {
12 | "type": "test",
13 | "version": "1",
14 | "isIdempotent": true
15 | },
16 | "metaData": [
17 | {
18 | "field1": "value1",
19 | "field2": "value2"
20 | }
21 | ],
22 | "childrenTaskIds": ["task3"]
23 | },
24 |
25 | {
26 | "taskId": "task2",
27 | "taskType": {
28 | "type": "test",
29 | "version": "1",
30 | "isIdempotent": true
31 | },
32 | "metaData": [],
33 | "childrenTaskIds": ["task3"]
34 | },
35 |
36 | {
37 | "taskId": "task3",
38 | "taskType": {
39 | "type": "test",
40 | "version": "1",
41 | "isIdempotent": true
42 | },
43 | "metaData": [],
44 | "childrenTaskIds": []
45 | }
46 | ]
47 | }
--------------------------------------------------------------------------------
/src/site/site.xml:
--------------------------------------------------------------------------------
1 |
18 |
20 |
21 | org.apache.maven.skins
22 | maven-fluido-skin
23 | 1.7
24 |
25 |
26 |
27 | Nirmata Workflow
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | NirmataOSS/workflow
36 | right
37 | black
38 |
39 |
40 |
41 |
42 |
43 |
51 |
52 |
58 |
59 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/test/java/com/nirmata/workflow/BaseForTests.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow;
17 |
18 | import org.apache.curator.framework.CuratorFramework;
19 | import org.apache.curator.framework.CuratorFrameworkFactory;
20 | import org.apache.curator.retry.RetryOneTime;
21 | import org.apache.curator.test.TestingServer;
22 | import org.apache.curator.test.Timing;
23 | import org.apache.curator.utils.CloseableUtils;
24 | import org.testng.annotations.AfterMethod;
25 | import org.testng.annotations.BeforeMethod;
26 |
27 | public abstract class BaseForTests
28 | {
29 | protected TestingServer server;
30 | protected CuratorFramework curator;
31 | protected final Timing timing = new Timing();
32 |
33 | @BeforeMethod
34 | public void setup() throws Exception
35 | {
36 | server = new TestingServer();
37 |
38 | curator = CuratorFrameworkFactory.builder().connectString(server.getConnectString()).retryPolicy(new RetryOneTime(1)).build();
39 | curator.start();
40 | }
41 |
42 | @AfterMethod
43 | public void teardown() throws Exception
44 | {
45 | CloseableUtils.closeQuietly(curator);
46 | CloseableUtils.closeQuietly(server);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/test/java/com/nirmata/workflow/ConcurrentTaskChecker.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow;
17 |
18 | import com.google.common.collect.Lists;
19 | import com.google.common.collect.Sets;
20 | import com.nirmata.workflow.models.TaskId;
21 | import org.testng.Assert;
22 | import java.util.HashSet;
23 | import java.util.List;
24 | import java.util.Set;
25 |
26 | class ConcurrentTaskChecker
27 | {
28 | private final Set currentSet = Sets.newHashSet();
29 | private final List all = Lists.newArrayList();
30 | private int count = 0;
31 | private final List> sets = Lists.newArrayList();
32 |
33 | synchronized void reset()
34 | {
35 | currentSet.clear();
36 | all.clear();
37 | sets.clear();
38 | count = 0;
39 | }
40 |
41 | synchronized void add(TaskId taskId)
42 | {
43 | all.add(taskId);
44 | currentSet.add(taskId);
45 | ++count;
46 | }
47 |
48 | synchronized void decrement()
49 | {
50 | if ( --count == 0 )
51 | {
52 | HashSet copy = Sets.newHashSet(currentSet);
53 | currentSet.clear();
54 | count = 0;
55 | sets.add(copy);
56 | }
57 | }
58 |
59 | synchronized List> getSets()
60 | {
61 | return Lists.newArrayList(sets);
62 | }
63 |
64 | synchronized List getAll()
65 | {
66 | return Lists.newArrayList(all);
67 | }
68 |
69 | synchronized void assertNoDuplicates()
70 | {
71 | Assert.assertEquals(all.size(), Sets.newHashSet(all).size()); // no dups
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/test/java/com/nirmata/workflow/TestAutoCleanerHolder.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow;
17 |
18 | import com.google.common.collect.Lists;
19 | import com.nirmata.workflow.admin.RunInfo;
20 | import com.nirmata.workflow.admin.StandardAutoCleaner;
21 | import com.nirmata.workflow.admin.TaskDetails;
22 | import com.nirmata.workflow.admin.TaskInfo;
23 | import com.nirmata.workflow.admin.WorkflowAdmin;
24 | import com.nirmata.workflow.admin.WorkflowManagerState;
25 | import com.nirmata.workflow.details.AutoCleanerHolder;
26 | import com.nirmata.workflow.models.RunId;
27 | import com.nirmata.workflow.models.TaskId;
28 | import org.apache.curator.test.Timing;
29 | import org.testng.Assert;
30 | import org.testng.annotations.Test;
31 | import java.time.Clock;
32 | import java.time.Duration;
33 | import java.time.LocalDateTime;
34 | import java.util.List;
35 | import java.util.Map;
36 | import java.util.concurrent.TimeUnit;
37 |
38 | public class TestAutoCleanerHolder
39 | {
40 | @Test
41 | public void testNull() throws InterruptedException
42 | {
43 | Timing timing = new Timing();
44 | AutoCleanerHolder holder = new AutoCleanerHolder(null, Duration.ofDays(10));
45 | Assert.assertFalse(holder.shouldRun());
46 | Assert.assertFalse(holder.shouldRun());
47 | timing.sleepABit();
48 | Assert.assertFalse(holder.shouldRun());
49 | Assert.assertFalse(holder.shouldRun());
50 |
51 | Assert.assertNotNull(holder.getRunPeriod());
52 | }
53 |
54 | @Test
55 | public void testPeriod() throws InterruptedException
56 | {
57 | AutoCleanerHolder holder = new AutoCleanerHolder(new StandardAutoCleaner(Duration.ofSeconds(2)), Duration.ofSeconds(1));
58 | Assert.assertFalse(holder.shouldRun());
59 | TimeUnit.SECONDS.sleep(2);
60 | Assert.assertTrue(holder.shouldRun());
61 |
62 | RunId runningId = new RunId();
63 | RunId completedId = new RunId();
64 | RunId completedButRecentId = new RunId();
65 | List runs = Lists.newArrayList(
66 | new RunInfo(runningId, LocalDateTime.now(Clock.systemUTC())),
67 | new RunInfo(completedButRecentId, LocalDateTime.now(Clock.systemUTC()), LocalDateTime.now(Clock.systemUTC())),
68 | new RunInfo(completedId, LocalDateTime.now(Clock.systemUTC()), LocalDateTime.now(Clock.systemUTC()).minusSeconds(3))
69 | );
70 |
71 | List cleaned = Lists.newArrayList();
72 | WorkflowAdmin admin = new WorkflowAdmin()
73 | {
74 | @Override
75 | public List getRunInfo()
76 | {
77 | return runs;
78 | }
79 |
80 | @Override
81 | public List getRunIds()
82 | {
83 | throw new UnsupportedOperationException();
84 | }
85 |
86 | @Override
87 | public RunInfo getRunInfo(RunId runId)
88 | {
89 | throw new UnsupportedOperationException();
90 | }
91 |
92 | @Override
93 | public List getTaskInfo(RunId runId)
94 | {
95 | throw new UnsupportedOperationException();
96 | }
97 |
98 | @Override
99 | public WorkflowManagerState getWorkflowManagerState()
100 | {
101 | return new WorkflowManagerState(true, WorkflowManagerState.State.PROCESSING, Lists.newArrayList());
102 | }
103 |
104 | @Override
105 | public boolean clean(RunId runId)
106 | {
107 | cleaned.add(runId);
108 | return true;
109 | }
110 |
111 | @Override
112 | public Map getTaskDetails(RunId runId)
113 | {
114 | throw new UnsupportedOperationException();
115 | }
116 | };
117 |
118 | holder.run(admin);
119 | Assert.assertEquals(cleaned.size(), 1);
120 | Assert.assertEquals(cleaned.get(0), completedId);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/test/java/com/nirmata/workflow/TestParentNodeDeletion.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow;
17 |
18 | import com.nirmata.workflow.admin.AutoCleaner;
19 | import com.nirmata.workflow.admin.StandardAutoCleaner;
20 | import com.nirmata.workflow.details.ZooKeeperConstants;
21 | import com.nirmata.workflow.executor.TaskExecutionStatus;
22 | import com.nirmata.workflow.executor.TaskExecutor;
23 | import com.nirmata.workflow.models.RunId;
24 | import com.nirmata.workflow.models.Task;
25 | import com.nirmata.workflow.models.TaskExecutionResult;
26 | import com.nirmata.workflow.models.TaskId;
27 | import com.nirmata.workflow.models.TaskType;
28 | import org.apache.curator.framework.CuratorFramework;
29 | import org.apache.curator.framework.CuratorFrameworkFactory;
30 | import org.apache.curator.retry.ExponentialBackoffRetry;
31 | import org.apache.curator.utils.ZKPaths;
32 | import org.apache.zookeeper.KeeperException;
33 | import org.slf4j.Logger;
34 | import org.slf4j.LoggerFactory;
35 | import org.testng.Assert;
36 | import org.testng.annotations.Test;
37 | import java.time.Duration;
38 | import java.util.concurrent.TimeUnit;
39 |
40 | public class TestParentNodeDeletion extends BaseForTests
41 | {
42 | private static final String TASK_NAME = "TestTask";
43 | private static final String VERSION = "v1";
44 | private static final TaskType TASK_TYPE = new TaskType(TASK_NAME, VERSION, true);
45 | private static final int CONCURRENT_TASKS = 5;
46 |
47 | private static final Logger log = LoggerFactory.getLogger(TestParentNodeDeletion.class);
48 |
49 | @Test
50 | public void testParentNodeDeletion() throws Exception
51 | {
52 | final String namespace = "test";
53 | final String workflowPath = "/" + namespace + "-" + VERSION;
54 |
55 | try ( CuratorFramework curator = newCurator() )
56 | {
57 | WorkflowManager workflowManager = buildWorkflow(curator, namespace);
58 | RunId runId = submitTask(workflowManager);
59 | assertTask(workflowManager, runId);
60 |
61 | String runPath = ZKPaths.makePath(workflowPath, ZooKeeperConstants.getRunParentPath());
62 | String startedPath = ZKPaths.makePath(workflowPath, ZooKeeperConstants.getStartedTasksParentPath());
63 | String completedPath = ZKPaths.makePath(workflowPath, ZooKeeperConstants.getCompletedTaskParentPath());
64 | String queuePath = ZKPaths.makePath(workflowPath, ZooKeeperConstants.getQueuePathBase());
65 |
66 | deletePath(curator, runPath);
67 | deletePath(curator, startedPath);
68 | deletePath(curator, completedPath);
69 |
70 | timing.sleepABit();
71 |
72 | runId = submitTask(workflowManager);
73 | assertTask(workflowManager, runId);
74 | }
75 | }
76 |
77 | private void assertTask(WorkflowManager workflowManager, RunId runId) throws Exception
78 | {
79 | Assert.assertTrue(workflowManager.getAdmin().getRunIds().contains(runId));
80 | long start = System.nanoTime();
81 | for(;;)
82 | {
83 | long elapsed = System.nanoTime() - start;
84 | if ( TimeUnit.NANOSECONDS.toMillis(elapsed) > timing.forWaiting().milliseconds() )
85 | {
86 | Assert.fail("Task did not execute within timeout");
87 | }
88 | Assert.assertTrue(workflowManager.getAdmin().getRunIds().contains(runId));
89 | if ( workflowManager.getAdmin().getRunInfo(runId).isComplete() )
90 | {
91 | break;
92 | }
93 | timing.sleepABit();
94 | }
95 | }
96 |
97 | private void deletePath(CuratorFramework curator, String path) throws Exception
98 | {
99 | try
100 | {
101 | curator.delete().deletingChildrenIfNeeded().forPath(path);
102 | }
103 | catch ( KeeperException ignore )
104 | {
105 | // ignore
106 | }
107 | }
108 |
109 | private CuratorFramework newCurator()
110 | {
111 | final CuratorFramework curator = CuratorFrameworkFactory.newClient(server.getConnectString(), timing.session(), timing.connection(), new ExponentialBackoffRetry(1000, 10));
112 | curator.start();
113 | return curator;
114 | }
115 |
116 | private WorkflowManager buildWorkflow(CuratorFramework curator, String namespace)
117 | {
118 | Duration runPeriod = Duration.ofSeconds(5);
119 | AutoCleaner cleaner = new StandardAutoCleaner(Duration.ofSeconds(5));
120 |
121 | final WorkflowManagerBuilder workflowManagerBuilder = WorkflowManagerBuilder.builder().withCurator(curator, namespace, VERSION).withAutoCleaner(cleaner, runPeriod);
122 |
123 | final TaskExecutor taskExecutor = (workflowManager, executableTask) -> () -> {
124 | final String runId = executableTask.getRunId().getId();
125 | final String taskId = executableTask.getTaskId().getId();
126 | log.debug("execute task {} - {}", runId, taskId);
127 |
128 | return new TaskExecutionResult(TaskExecutionStatus.SUCCESS, "");
129 | };
130 |
131 | workflowManagerBuilder.addingTaskExecutor(taskExecutor, CONCURRENT_TASKS, TASK_TYPE);
132 |
133 | final WorkflowManager workflowManager = workflowManagerBuilder.build();
134 | workflowManager.start();
135 |
136 | return workflowManager;
137 | }
138 |
139 | private RunId submitTask(WorkflowManager workflowManager)
140 | {
141 | TaskId taskId = new TaskId();
142 | Task task = new Task(taskId, TASK_TYPE);
143 | RunId runId = workflowManager.submitTask(task);
144 | log.debug("submit task done - runId {}", runId.getId());
145 | return runId;
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/test/java/com/nirmata/workflow/TestTaskExecutor.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow;
17 |
18 | import com.google.common.collect.Maps;
19 | import com.nirmata.workflow.executor.TaskExecution;
20 | import com.nirmata.workflow.executor.TaskExecutionStatus;
21 | import com.nirmata.workflow.executor.TaskExecutor;
22 | import com.nirmata.workflow.models.ExecutableTask;
23 | import com.nirmata.workflow.models.TaskExecutionResult;
24 | import java.util.concurrent.CountDownLatch;
25 |
26 | public class TestTaskExecutor implements TaskExecutor
27 | {
28 | private final ConcurrentTaskChecker checker = new ConcurrentTaskChecker();
29 | private final int latchQty;
30 | private volatile CountDownLatch latch;
31 |
32 | public TestTaskExecutor()
33 | {
34 | this(1);
35 | }
36 |
37 | public TestTaskExecutor(int latchQty)
38 | {
39 | this.latchQty = latchQty;
40 | latch = new CountDownLatch(latchQty);
41 | }
42 |
43 | public CountDownLatch getLatch()
44 | {
45 | return latch;
46 | }
47 |
48 | public ConcurrentTaskChecker getChecker()
49 | {
50 | return checker;
51 | }
52 |
53 | public void reset()
54 | {
55 | checker.reset();
56 | latch = new CountDownLatch(latchQty);
57 | }
58 |
59 | @Override
60 | public TaskExecution newTaskExecution(WorkflowManager workflowManager, ExecutableTask task)
61 | {
62 | return () -> {
63 | try
64 | {
65 | checker.add(task.getTaskId());
66 | doRun(task);
67 | }
68 | catch ( InterruptedException e )
69 | {
70 | Thread.currentThread().interrupt();
71 | throw new RuntimeException(e);
72 | }
73 | finally
74 | {
75 | checker.decrement();
76 | latch.countDown();
77 | }
78 | return new TaskExecutionResult(TaskExecutionStatus.SUCCESS, "hey", Maps.newHashMap());
79 | };
80 | }
81 |
82 | @SuppressWarnings("UnusedParameters")
83 | protected void doRun(ExecutableTask task) throws InterruptedException
84 | {
85 | Thread.sleep(1000);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/test/java/com/nirmata/workflow/TestWorkflowListenerManager.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow;
17 |
18 | import com.google.common.collect.Queues;
19 | import com.nirmata.workflow.events.WorkflowEvent;
20 | import com.nirmata.workflow.events.WorkflowListenerManager;
21 | import com.nirmata.workflow.models.RunId;
22 | import com.nirmata.workflow.models.Task;
23 | import com.nirmata.workflow.models.TaskId;
24 | import com.nirmata.workflow.models.TaskType;
25 | import org.apache.curator.test.Timing;
26 | import org.apache.curator.utils.CloseableUtils;
27 | import org.testng.Assert;
28 | import org.testng.annotations.Test;
29 | import java.util.concurrent.BlockingQueue;
30 | import java.util.concurrent.TimeUnit;
31 |
32 | public class TestWorkflowListenerManager extends BaseForTests
33 | {
34 | @Test
35 | public void testBasic() throws Exception
36 | {
37 | WorkflowListenerManager workflowListenerManager = null;
38 | TestTaskExecutor taskExecutor = new TestTaskExecutor(6);
39 | TaskType taskType = new TaskType("test", "1", true);
40 | WorkflowManager workflowManager = WorkflowManagerBuilder.builder()
41 | .addingTaskExecutor(taskExecutor, 10, taskType)
42 | .withCurator(curator, "test", "1")
43 | .build();
44 | try
45 | {
46 | Task task = new Task(new TaskId(), taskType);
47 |
48 | BlockingQueue eventQueue = Queues.newLinkedBlockingQueue();
49 | workflowListenerManager = workflowManager.newWorkflowListenerManager();
50 | workflowListenerManager.getListenable().addListener(eventQueue::add);
51 |
52 | workflowManager.start();
53 | workflowListenerManager.start();
54 |
55 | RunId runId = workflowManager.submitTask(task);
56 |
57 | timing.sleepABit();
58 |
59 | WorkflowEvent runStarted = new WorkflowEvent(WorkflowEvent.EventType.RUN_STARTED, runId);
60 | WorkflowEvent taskStarted = new WorkflowEvent(WorkflowEvent.EventType.TASK_STARTED, runId, task.getTaskId());
61 | WorkflowEvent event1 = eventQueue.poll(timing.milliseconds(), TimeUnit.MILLISECONDS);
62 | WorkflowEvent event2 = eventQueue.poll(timing.milliseconds(), TimeUnit.MILLISECONDS);
63 | // due to timing, task start might come first
64 | Assert.assertTrue((event1.equals(runStarted) && event2.equals(taskStarted)) || (event2.equals(runStarted) && event1.equals(taskStarted)));
65 |
66 | Assert.assertEquals(eventQueue.poll(timing.milliseconds(), TimeUnit.MILLISECONDS), new WorkflowEvent(WorkflowEvent.EventType.TASK_COMPLETED, runId, task.getTaskId()));
67 | Assert.assertEquals(eventQueue.poll(timing.milliseconds(), TimeUnit.MILLISECONDS), new WorkflowEvent(WorkflowEvent.EventType.RUN_UPDATED, runId));
68 | }
69 | finally
70 | {
71 | CloseableUtils.closeQuietly(workflowListenerManager);
72 | CloseableUtils.closeQuietly(workflowManager);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/test/java/com/nirmata/workflow/WorkflowManagerStateSampler.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow;
17 |
18 | import com.google.common.base.Preconditions;
19 | import com.google.common.collect.Lists;
20 | import com.nirmata.workflow.admin.WorkflowAdmin;
21 | import com.nirmata.workflow.admin.WorkflowManagerState;
22 | import org.apache.curator.utils.ThreadUtils;
23 | import java.io.Closeable;
24 | import java.time.Duration;
25 | import java.util.LinkedList;
26 | import java.util.List;
27 | import java.util.concurrent.ScheduledExecutorService;
28 | import java.util.concurrent.TimeUnit;
29 | import java.util.concurrent.atomic.AtomicReference;
30 |
31 | public class WorkflowManagerStateSampler implements Closeable
32 | {
33 | private final WorkflowAdmin workflowAdmin;
34 | private final int windowSize;
35 | private final Duration samplePeriod;
36 | private final LinkedList samples; // guarded by sync
37 | private final ScheduledExecutorService service;
38 | private final AtomicReference state = new AtomicReference<>(State.LATENT);
39 |
40 | private enum State
41 | {
42 | LATENT,
43 | STARTED,
44 | CLOSED
45 | }
46 |
47 | public WorkflowManagerStateSampler(WorkflowAdmin workflowAdmin, int windowSize, Duration samplePeriod)
48 | {
49 | Preconditions.checkArgument(windowSize > 0, "windowSize must be greater than 0");
50 | this.workflowAdmin = workflowAdmin;
51 | this.windowSize = windowSize;
52 | this.samplePeriod = samplePeriod;
53 | samples = Lists.newLinkedList();
54 | service = ThreadUtils.newSingleThreadScheduledExecutor(WorkflowManagerStateSampler.class.getName());
55 | }
56 |
57 | public void start()
58 | {
59 | Preconditions.checkState(state.compareAndSet(State.LATENT, State.STARTED), "Already started");
60 | service.scheduleAtFixedRate(this::runLoop, samplePeriod.toMillis(), samplePeriod.toMillis(), TimeUnit.MILLISECONDS);
61 | }
62 |
63 | @Override
64 | public void close()
65 | {
66 | if ( state.compareAndSet(State.STARTED, State.CLOSED) )
67 | {
68 | service.shutdownNow();
69 | }
70 | }
71 |
72 | public List getSamples()
73 | {
74 | synchronized(samples)
75 | {
76 | return Lists.newArrayList(samples);
77 | }
78 | }
79 |
80 | private void runLoop()
81 | {
82 | WorkflowManagerState workflowManagerState = workflowAdmin.getWorkflowManagerState();
83 | synchronized(samples)
84 | {
85 | while ( samples.size() >= windowSize )
86 | {
87 | samples.removeFirst();
88 | }
89 | samples.add(workflowManagerState);
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/test/java/com/nirmata/workflow/details/TestDelayPriorityTasks.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.details;
17 |
18 | import com.google.common.collect.Lists;
19 | import com.nirmata.workflow.BaseForTests;
20 | import com.nirmata.workflow.WorkflowManager;
21 | import com.nirmata.workflow.WorkflowManagerBuilder;
22 | import com.nirmata.workflow.executor.TaskExecutionStatus;
23 | import com.nirmata.workflow.executor.TaskExecutor;
24 | import com.nirmata.workflow.models.Task;
25 | import com.nirmata.workflow.models.TaskExecutionResult;
26 | import com.nirmata.workflow.models.TaskId;
27 | import com.nirmata.workflow.models.TaskMode;
28 | import com.nirmata.workflow.models.TaskType;
29 | import com.nirmata.workflow.queue.zookeeper.SimpleQueue;
30 | import org.testng.Assert;
31 | import org.testng.annotations.Test;
32 | import java.util.concurrent.BlockingQueue;
33 | import java.util.concurrent.LinkedBlockingQueue;
34 | import java.util.concurrent.Semaphore;
35 | import java.util.concurrent.TimeUnit;
36 |
37 | public class TestDelayPriorityTasks extends BaseForTests
38 | {
39 | @Test
40 | public void testDelay() throws Exception
41 | {
42 | final long delayMs = TimeUnit.SECONDS.toMillis(5);
43 |
44 | BlockingQueue queue = new LinkedBlockingQueue<>();
45 | TaskExecutor taskExecutor = (workflowManager, executableTask) -> () ->
46 | {
47 | queue.add(System.currentTimeMillis());
48 | return new TaskExecutionResult(TaskExecutionStatus.SUCCESS, "");
49 | };
50 | TaskType taskType = new TaskType("test", "1", true, TaskMode.DELAY);
51 | try ( WorkflowManager workflowManager = WorkflowManagerBuilder.builder()
52 | .addingTaskExecutor(taskExecutor, 10, taskType)
53 | .withCurator(curator, "test", "1")
54 | .build() )
55 | {
56 | workflowManager.start();
57 |
58 | Task task = new Task(new TaskId(), taskType);
59 | workflowManager.submitTask(task);
60 |
61 | Long ticksMs = queue.poll(1, TimeUnit.SECONDS);
62 | Assert.assertNotNull(ticksMs);
63 | Assert.assertTrue((ticksMs - ((WorkflowManagerImpl)workflowManager).debugLastSubmittedTimeMs) < 1000); // should have executed immediately
64 |
65 | task = new Task(new TaskId(), taskType, Lists.newArrayList(), Task.makeSpecialMeta(System.currentTimeMillis() + delayMs));
66 | long startTicks = System.currentTimeMillis();
67 | workflowManager.submitTask(task);
68 | ticksMs = queue.poll(delayMs * 2, TimeUnit.MILLISECONDS);
69 | Assert.assertNotNull(ticksMs);
70 | long elapsed = ticksMs - startTicks;
71 | Assert.assertTrue(elapsed >= delayMs, String.format("Bad timing. Elapsed: %d, delay: %d ", elapsed, delayMs));
72 | }
73 | }
74 |
75 | @Test
76 | public void testPriority() throws Exception
77 | {
78 | BlockingQueue queue = new LinkedBlockingQueue<>();
79 | TaskExecutor taskExecutor = (workflowManager, executableTask) -> () ->
80 | {
81 | queue.add(executableTask.getTaskId().getId());
82 | try
83 | {
84 | Thread.sleep(1);
85 | }
86 | catch ( InterruptedException e )
87 | {
88 | Thread.currentThread().interrupt();
89 | }
90 | return new TaskExecutionResult(TaskExecutionStatus.SUCCESS, "");
91 | };
92 | TaskType taskType = new TaskType("test", "1", true, TaskMode.PRIORITY);
93 | try ( WorkflowManager workflowManager = WorkflowManagerBuilder.builder()
94 | .addingTaskExecutor(taskExecutor, 1, taskType)
95 | .withCurator(curator, "test", "1")
96 | .build() )
97 | {
98 | SimpleQueue.debugQueuedTasks = new Semaphore(0);
99 | ((WorkflowManagerImpl)workflowManager).debugDontStartConsumers = true; // make sure all tasks are added to ZK before they start getting consumed
100 | workflowManager.start();
101 |
102 | Task task1 = new Task(new TaskId("1"), taskType, Lists.newArrayList(), Task.makeSpecialMeta(1));
103 | Task task2 = new Task(new TaskId("2"), taskType, Lists.newArrayList(), Task.makeSpecialMeta(100));
104 | Task task3 = new Task(new TaskId("3"), taskType, Lists.newArrayList(), Task.makeSpecialMeta(50));
105 | Task task4 = new Task(new TaskId("4"), taskType, Lists.newArrayList(), Task.makeSpecialMeta(300));
106 | Task task5 = new Task(new TaskId("5"), taskType, Lists.newArrayList(), Task.makeSpecialMeta(200));
107 | workflowManager.submitTask(task1);
108 | workflowManager.submitTask(task2);
109 | workflowManager.submitTask(task3);
110 | workflowManager.submitTask(task4);
111 | workflowManager.submitTask(task5);
112 |
113 | Assert.assertTrue(SimpleQueue.debugQueuedTasks.tryAcquire(5, 5, TimeUnit.SECONDS));
114 | ((WorkflowManagerImpl)workflowManager).startQueueConsumers();
115 |
116 | Assert.assertEquals(queue.poll(1, TimeUnit.SECONDS), "1");
117 | Assert.assertEquals(queue.poll(1, TimeUnit.SECONDS), "3");
118 | Assert.assertEquals(queue.poll(1, TimeUnit.SECONDS), "2");
119 | Assert.assertEquals(queue.poll(1, TimeUnit.SECONDS), "5");
120 | Assert.assertEquals(queue.poll(1, TimeUnit.SECONDS), "4");
121 | }
122 | finally
123 | {
124 | SimpleQueue.debugQueuedTasks = null;
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/test/java/com/nirmata/workflow/details/TestDisruptedScheduler.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.details;
17 |
18 | import com.google.common.collect.Lists;
19 | import com.google.common.collect.Sets;
20 | import com.google.common.io.Resources;
21 | import com.nirmata.workflow.TestTaskExecutor;
22 | import com.nirmata.workflow.WorkflowManager;
23 | import com.nirmata.workflow.WorkflowManagerBuilder;
24 | import com.nirmata.workflow.models.ExecutableTask;
25 | import com.nirmata.workflow.models.Task;
26 | import com.nirmata.workflow.models.TaskId;
27 | import com.nirmata.workflow.models.TaskType;
28 | import com.nirmata.workflow.serialization.JsonSerializerMapper;
29 | import org.apache.curator.framework.CuratorFramework;
30 | import org.apache.curator.framework.CuratorFrameworkFactory;
31 | import org.apache.curator.retry.ExponentialBackoffRetry;
32 | import org.apache.curator.test.TestingCluster;
33 | import org.apache.curator.test.Timing;
34 | import org.apache.curator.utils.CloseableUtils;
35 | import org.testng.Assert;
36 | import org.testng.annotations.AfterMethod;
37 | import org.testng.annotations.BeforeMethod;
38 | import org.testng.annotations.Test;
39 | import java.io.Closeable;
40 | import java.io.IOException;
41 | import java.nio.charset.Charset;
42 | import java.util.List;
43 | import java.util.Optional;
44 | import java.util.Set;
45 | import java.util.concurrent.CountDownLatch;
46 | import java.util.stream.IntStream;
47 |
48 | public class TestDisruptedScheduler
49 | {
50 | private static final TaskType taskType = new TaskType("test", "1", true);
51 | private static final Timing timing = new Timing(1, 2);
52 |
53 | private TestingCluster cluster;
54 | private List clients;
55 | private Set executedTasks;
56 | private CountDownLatch executedTasksLatch;
57 |
58 | @BeforeMethod
59 | public void setup() throws Exception
60 | {
61 | cluster = new TestingCluster(3);
62 | cluster.start();
63 |
64 | clients = Lists.newArrayList();
65 | executedTasks = Sets.newConcurrentHashSet();
66 | executedTasksLatch = new CountDownLatch(6);
67 | }
68 |
69 | @AfterMethod
70 | public void teardown() throws Exception
71 | {
72 | clients.forEach(CloseableUtils::closeQuietly);
73 | clients = null;
74 | CloseableUtils.closeQuietly(cluster);
75 | }
76 |
77 | private static class Client implements Closeable
78 | {
79 | final CuratorFramework curator;
80 | final WorkflowManager workflowManager;
81 |
82 | private Client(int id, TestingCluster cluster, Set executedTasks, CountDownLatch executedTasksLatch)
83 | {
84 | curator = CuratorFrameworkFactory.builder().connectString(cluster.getConnectString()).retryPolicy(new ExponentialBackoffRetry(10, 3)).build();
85 | curator.start();
86 |
87 | TestTaskExecutor taskExecutor = new TestTaskExecutor(6) {
88 | @Override
89 | protected void doRun(ExecutableTask task) throws InterruptedException
90 | {
91 | executedTasks.add(task.getTaskId());
92 | timing.forWaiting().sleepABit();
93 | executedTasksLatch.countDown();
94 | }
95 | };
96 |
97 | workflowManager = WorkflowManagerBuilder.builder()
98 | .addingTaskExecutor(taskExecutor, 10, taskType)
99 | .withCurator(curator, "test", "1")
100 | .withInstanceName("i-" + id)
101 | .build();
102 | workflowManager.start();
103 | }
104 |
105 | @Override
106 | public void close() throws IOException
107 | {
108 | CloseableUtils.closeQuietly(workflowManager);
109 | CloseableUtils.closeQuietly(curator);
110 | }
111 | }
112 |
113 | @Test
114 | public void testDisruptedScheduler() throws Exception
115 | {
116 | final int QTY = 3;
117 | IntStream.range(0, QTY).forEach(i -> clients.add(new Client(i, cluster, executedTasks, executedTasksLatch)));
118 |
119 | Optional clientOptional = Optional.empty();
120 | for ( int i = 0; !clientOptional.isPresent() && (i < 3); ++i )
121 | {
122 | timing.sleepABit();
123 | clientOptional = clients
124 | .stream()
125 | .filter(client -> ((WorkflowManagerImpl)client.workflowManager).getSchedulerSelector().getLeaderSelector().hasLeadership())
126 | .findFirst();
127 | }
128 | Assert.assertTrue(clientOptional.isPresent());
129 | Client scheduler = clientOptional.get();
130 | Client nonScheduler = clients.get(0).equals(scheduler) ? clients.get(1) : clients.get(0);
131 |
132 | String json = Resources.toString(Resources.getResource("tasks.json"), Charset.defaultCharset());
133 | JsonSerializerMapper jsonSerializerMapper = new JsonSerializerMapper();
134 | Task task = jsonSerializerMapper.get(jsonSerializerMapper.getMapper().readTree(json), Task.class);
135 | nonScheduler.workflowManager.submitTask(task); // additional test - submit to the non-scheduler
136 |
137 | while ( executedTasks.size() == 0 ) // wait until some tasks have started
138 | {
139 | Thread.sleep(100);
140 | }
141 |
142 | CountDownLatch latch = new CountDownLatch(1);
143 | ((WorkflowManagerImpl)scheduler.workflowManager).getSchedulerSelector().debugLatch.set(latch);
144 | ((WorkflowManagerImpl)scheduler.workflowManager).getSchedulerSelector().getLeaderSelector().interruptLeadership(); // interrupt the scheduler wherever it is
145 | Assert.assertTrue(executedTasks.size() < 6);
146 | Assert.assertTrue(timing.awaitLatch(latch)); // wait for the scheduler method to exit
147 |
148 | Assert.assertTrue(timing.awaitLatch(executedTasksLatch)); // all tasks should complete
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/test/java/com/nirmata/workflow/details/TestEdges.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.details;
17 |
18 | import com.google.common.collect.Lists;
19 | import com.google.common.collect.Queues;
20 | import com.google.common.collect.Sets;
21 | import com.nirmata.workflow.BaseForTests;
22 | import com.nirmata.workflow.WorkflowManager;
23 | import com.nirmata.workflow.WorkflowManagerBuilder;
24 | import com.nirmata.workflow.executor.TaskExecutionStatus;
25 | import com.nirmata.workflow.executor.TaskExecutor;
26 | import com.nirmata.workflow.models.Task;
27 | import com.nirmata.workflow.models.TaskExecutionResult;
28 | import com.nirmata.workflow.models.TaskId;
29 | import com.nirmata.workflow.models.TaskType;
30 | import org.apache.curator.test.Timing;
31 | import org.apache.curator.utils.CloseableUtils;
32 | import org.testng.Assert;
33 | import org.testng.annotations.Test;
34 | import java.util.List;
35 | import java.util.Queue;
36 | import java.util.Set;
37 | import java.util.concurrent.Semaphore;
38 | import java.util.concurrent.atomic.AtomicInteger;
39 | import java.util.stream.Collectors;
40 | import java.util.stream.IntStream;
41 |
42 | public class TestEdges extends BaseForTests
43 | {
44 | @Test
45 | public void testMultiConcurrent() throws Exception
46 | {
47 | final int RETRIES = 3;
48 |
49 | try
50 | {
51 | IntStream.range(0, RETRIES).forEach(i -> {
52 | System.out.println("Retry " + i);
53 | multiConcurrentRetry();
54 | });
55 | }
56 | finally
57 | {
58 | Scheduler.debugBadRunIdCount = null;
59 | }
60 | }
61 |
62 | private void multiConcurrentRetry()
63 | {
64 | final int WORKFLOW_QTY = 3;
65 | final int ITERATIONS = 10;
66 |
67 | Scheduler.debugBadRunIdCount = new AtomicInteger(0);
68 |
69 | TaskType type1 = new TaskType("type1", "1", true);
70 | TaskType type2 = new TaskType("type2", "1", true);
71 |
72 | Semaphore semaphore = new Semaphore(0);
73 | TaskExecutor taskExecutor = (m, t) -> () -> {
74 | try
75 | {
76 | Thread.sleep(578);
77 | }
78 | catch ( InterruptedException e )
79 | {
80 | Thread.currentThread().interrupt();
81 | }
82 | semaphore.release();
83 | return new TaskExecutionResult(TaskExecutionStatus.SUCCESS, "");
84 | };
85 |
86 | List workflowManagers = IntStream.range(0, WORKFLOW_QTY).mapToObj(i -> {
87 | TaskType type = ((i & 1) != 0) ? type1 : type2;
88 | return WorkflowManagerBuilder.builder()
89 | .addingTaskExecutor(taskExecutor, 10, type)
90 | .withCurator(curator, "test-" + i, "1")
91 | .build();
92 | }).collect(Collectors.toList());
93 | try
94 | {
95 | workflowManagers.stream().forEach(WorkflowManager::start);
96 |
97 | for ( int i = 0; i < ITERATIONS; ++i )
98 | {
99 | Assert.assertEquals(Scheduler.debugBadRunIdCount.get(), 0);
100 | System.out.println("Iteration " + i);
101 | for ( int j = 0; j < WORKFLOW_QTY; ++j )
102 | {
103 | TaskType type = ((j & 1) != 0) ? type1 : type2;
104 | workflowManagers.get(j).submitTask(new Task(new TaskId(), type));
105 | }
106 | Assert.assertTrue(timing.acquireSemaphore(semaphore, WORKFLOW_QTY));
107 | }
108 | }
109 | finally
110 | {
111 | workflowManagers.stream().forEach(CloseableUtils::closeQuietly);
112 | }
113 | }
114 |
115 | @Test
116 | public void testIdempotency() throws Exception
117 | {
118 | TaskType idempotentType = new TaskType("yes", "1", true);
119 | TaskType nonIdempotentType = new TaskType("no", "1", false);
120 |
121 | Task idempotentTask = new Task(new TaskId(), idempotentType);
122 | Task nonIdempotentTask = new Task(new TaskId(), nonIdempotentType);
123 | Task root = new Task(new TaskId(), Lists.newArrayList(idempotentTask, nonIdempotentTask));
124 |
125 | Set thrownTasks = Sets.newConcurrentHashSet();
126 | Queue tasks = Queues.newConcurrentLinkedQueue();
127 | TaskExecutor taskExecutor = (m, t) -> () -> {
128 | if ( thrownTasks.add(t.getTaskId()) )
129 | {
130 | throw new RuntimeException();
131 | }
132 | tasks.add(t.getTaskId());
133 | return new TaskExecutionResult(TaskExecutionStatus.SUCCESS, "");
134 | };
135 | WorkflowManager workflowManager = WorkflowManagerBuilder.builder()
136 | .addingTaskExecutor(taskExecutor, 10, idempotentType)
137 | .addingTaskExecutor(taskExecutor, 10, nonIdempotentType)
138 | .withCurator(curator, "test", "1")
139 | .build();
140 | try
141 | {
142 | workflowManager.start();
143 | workflowManager.submitTask(root);
144 |
145 | timing.sleepABit();
146 |
147 | Assert.assertEquals(tasks.size(), 1);
148 | Assert.assertEquals(tasks.poll(), idempotentTask.getTaskId());
149 | }
150 | finally
151 | {
152 | CloseableUtils.closeQuietly(workflowManager);
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/test/java/com/nirmata/workflow/serialization/JDKSerializer.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.serialization;
17 |
18 | import java.io.ByteArrayInputStream;
19 | import java.io.ByteArrayOutputStream;
20 | import java.io.IOException;
21 | import java.io.ObjectInputStream;
22 | import java.io.ObjectOutputStream;
23 |
24 | public class JDKSerializer implements Serializer
25 | {
26 | @Override
27 | public byte[] serialize(T obj)
28 | {
29 | ByteArrayOutputStream bytes = null;
30 | try
31 | {
32 | bytes = new ByteArrayOutputStream();
33 | ObjectOutputStream out = new ObjectOutputStream(bytes);
34 | out.writeObject(obj);
35 | out.close();
36 | }
37 | catch ( IOException e )
38 | {
39 | throw new RuntimeException(e);
40 | }
41 | return bytes.toByteArray();
42 | }
43 |
44 | @Override
45 | public T deserialize(byte[] data, Class clazz)
46 | {
47 | try
48 | {
49 | ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(data));
50 | return clazz.cast(in.readObject());
51 | }
52 | catch ( Exception e )
53 | {
54 | throw new RuntimeException(e);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/test/java/com/nirmata/workflow/serialization/TestSerializer.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Nirmata, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.nirmata.workflow.serialization;
17 |
18 | import com.fasterxml.jackson.databind.JsonNode;
19 | import com.google.common.collect.Lists;
20 | import com.google.common.collect.Maps;
21 | import com.google.common.io.Resources;
22 | import com.nirmata.workflow.details.internalmodels.RunnableTask;
23 | import com.nirmata.workflow.details.internalmodels.RunnableTaskDag;
24 | import com.nirmata.workflow.details.internalmodels.StartedTask;
25 | import com.nirmata.workflow.executor.TaskExecutionStatus;
26 | import com.nirmata.workflow.models.ExecutableTask;
27 | import com.nirmata.workflow.models.RunId;
28 | import com.nirmata.workflow.models.Task;
29 | import com.nirmata.workflow.models.TaskExecutionResult;
30 | import com.nirmata.workflow.models.TaskId;
31 | import com.nirmata.workflow.models.TaskType;
32 | import org.testng.Assert;
33 | import org.testng.annotations.Test;
34 | import java.io.IOException;
35 | import java.nio.charset.Charset;
36 | import java.time.Clock;
37 | import java.time.LocalDateTime;
38 | import java.util.Collection;
39 | import java.util.List;
40 | import java.util.Map;
41 | import java.util.Random;
42 | import java.util.stream.Collectors;
43 | import java.util.stream.IntStream;
44 | import java.util.stream.Stream;
45 |
46 | import static com.nirmata.workflow.serialization.JsonSerializer.*;
47 |
48 | public class TestSerializer
49 | {
50 | private static final Random random = new Random();
51 |
52 | @Test
53 | public void testRunnableTaskDag()
54 | {
55 | RunnableTaskDag runnableTaskDag = new RunnableTaskDag(new TaskId(), randomTasks());
56 | JsonNode node = newRunnableTaskDag(runnableTaskDag);
57 | String str = nodeToString(node);
58 | System.out.println(str);
59 |
60 | RunnableTaskDag unRunnableTaskDag = getRunnableTaskDag(fromString(str));
61 | Assert.assertEquals(runnableTaskDag, unRunnableTaskDag);
62 | }
63 |
64 | @Test
65 | public void testExecutableTask()
66 | {
67 | ExecutableTask executableTask = new ExecutableTask(new RunId(), new TaskId(), randomTaskType(), randomMap(), random.nextBoolean());
68 | JsonNode node = newExecutableTask(executableTask);
69 | String str = nodeToString(node);
70 | System.out.println(str);
71 |
72 | ExecutableTask unExecutableTask = getExecutableTask(fromString(str));
73 | Assert.assertEquals(executableTask, unExecutableTask);
74 | }
75 |
76 | @Test
77 | public void testRunnableTask()
78 | {
79 | Map tasks = Stream.generate(() -> "")
80 | .limit(random.nextInt(3) + 1)
81 | .collect(Collectors.toMap(s -> new TaskId(), s -> new ExecutableTask(new RunId(), new TaskId(), randomTaskType(), randomMap(), random.nextBoolean())))
82 | ;
83 | List taskDags = Stream.generate(() -> new RunnableTaskDag(new TaskId(), randomTasks()))
84 | .limit(random.nextInt(3) + 1)
85 | .collect(Collectors.toList())
86 | ;
87 | LocalDateTime completionTime = random.nextBoolean() ? LocalDateTime.now() : null;
88 | RunId parentRunId = random.nextBoolean() ? new RunId() : null;
89 | RunnableTask runnableTask = new RunnableTask(tasks, taskDags, LocalDateTime.now(), completionTime, parentRunId);
90 | JsonNode node = newRunnableTask(runnableTask);
91 | String str = nodeToString(node);
92 | System.out.println(str);
93 |
94 | RunnableTask unRunnableTask = getRunnableTask(fromString(str));
95 | Assert.assertEquals(runnableTask, unRunnableTask);
96 | }
97 |
98 | @Test
99 | public void testTaskExecutionResult()
100 | {
101 | TaskExecutionResult taskExecutionResult = new TaskExecutionResult(TaskExecutionStatus.SUCCESS, Integer.toString(random.nextInt()), randomMap());
102 | JsonNode node = newTaskExecutionResult(taskExecutionResult);
103 | String str = nodeToString(node);
104 | System.out.println(str);
105 |
106 | TaskExecutionResult unTaskExecutionResult = getTaskExecutionResult(fromString(str));
107 | Assert.assertEquals(taskExecutionResult, unTaskExecutionResult);
108 | }
109 |
110 | @Test
111 | public void testStartedTask()
112 | {
113 | StartedTask startedTask = new StartedTask(Integer.toString(random.nextInt()), LocalDateTime.now(Clock.systemUTC()), 0);
114 | JsonNode node = newStartedTask(startedTask);
115 | String str = nodeToString(node);
116 | System.out.println(str);
117 |
118 | StartedTask unStartedTask = getStartedTask(fromString(str));
119 | Assert.assertEquals(startedTask, unStartedTask);
120 | }
121 |
122 | @Test
123 | public void testTaskType()
124 | {
125 | TaskType taskType = randomTaskType();
126 | JsonNode node = newTaskType(taskType);
127 | String str = nodeToString(node);
128 | System.out.println(str);
129 |
130 | TaskType unTaskType = getTaskType(fromString(str));
131 | Assert.assertEquals(taskType, unTaskType);
132 | }
133 |
134 | @Test
135 | public void testTask()
136 | {
137 | Task task = randomTask(0);
138 | JsonNode node = newTask(task);
139 | String str = nodeToString(node);
140 | System.out.println(str);
141 |
142 | Task unTask = TaskLoader.load(str);
143 | Assert.assertEquals(task, unTask);
144 | }
145 |
146 | @Test
147 | public void testLoadedTask() throws IOException
148 | {
149 | TaskType taskType = new TaskType("test", "1", true);
150 | Task task6 = new Task(new TaskId("task6"), taskType);
151 | Task task5 = new Task(new TaskId("task5"), taskType, Lists.newArrayList(task6), Maps.newHashMap());
152 | Task task4 = new Task(new TaskId("task4"), taskType, Lists.newArrayList(task6), Maps.newHashMap());
153 | Task task3 = new Task(new TaskId("task3"), taskType, Lists.newArrayList(task6), Maps.newHashMap());
154 | Task task2 = new Task(new TaskId("task2"), taskType, Lists.newArrayList(task3, task4, task5), Maps.newHashMap());
155 | Task task1 = new Task(new TaskId("task1"), taskType, Lists.newArrayList(task3, task4, task5), Maps.newHashMap());
156 | Task task = new Task(new TaskId("root"), Lists.newArrayList(task1, task2));
157 |
158 | String json = Resources.toString(Resources.getResource("tasks.json"), Charset.defaultCharset());
159 | Task unTask = TaskLoader.load(json);
160 |
161 | Assert.assertEquals(task, unTask);
162 | }
163 |
164 | @Test
165 | public void testJsonSerializerMapper()
166 | {
167 | JsonSerializerMapper mapper = new JsonSerializerMapper();
168 | Task task = randomTask(0);
169 | JsonNode node = mapper.make(task);
170 | String str = nodeToString(node);
171 | System.out.println(str);
172 |
173 | Task unTask = mapper.get(fromString(str), Task.class);
174 | Assert.assertEquals(task, unTask);
175 | }
176 |
177 | @Test
178 | public void testAlternateSerializers()
179 | {
180 | JDKSerializer serializer = new JDKSerializer();
181 |
182 | StartedTask startedTask = new StartedTask(Integer.toString(random.nextInt()), LocalDateTime.now(Clock.systemUTC()), 0);
183 | byte[] bytes = serializer.serialize(startedTask);
184 | StartedTask unStartedTask = serializer.deserialize(bytes, StartedTask.class);
185 | Assert.assertEquals(startedTask, unStartedTask);
186 | }
187 |
188 | private Task randomTask(int index)
189 | {
190 | List childrenTasks = Lists.newArrayList();
191 | boolean shouldHaveChildren = (index == 0) || ((index < 2) && random.nextBoolean());
192 | int childrenQty = shouldHaveChildren ? random.nextInt(5) : 0;
193 | IntStream.range(0, childrenQty).forEach(i -> childrenTasks.add(randomTask(index + 1)));
194 | return new Task(new TaskId(), randomTaskType(), childrenTasks, randomMap());
195 | }
196 |
197 | private TaskType randomTaskType()
198 | {
199 | return new TaskType(Integer.toString(random.nextInt()), Integer.toHexString(random.nextInt()), random.nextBoolean());
200 | }
201 |
202 | private Map randomMap()
203 | {
204 | return Stream.generate(random::nextInt)
205 | .limit(random.nextInt(3) + 1)
206 | .collect(Collectors.toMap(n -> Integer.toString(n * 4), n -> Integer.toString(n * 2)));
207 | }
208 |
209 | private Collection randomTasks()
210 | {
211 | return Stream.generate(TaskId::new)
212 | .limit(random.nextInt(10) + 1)
213 | .collect(Collectors.toList());
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/src/test/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2014 Nirmata, Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | log4j.rootLogger=WARN, console
18 | log4j.logger.com.nirmata.workflow=DEBUG
19 |
20 | log4j.appender.console=org.apache.log4j.ConsoleAppender
21 | log4j.appender.console.layout=org.apache.log4j.PatternLayout
22 | log4j.appender.console.layout.ConversionPattern=%-5p %c %x %m [%t]%n
23 |
--------------------------------------------------------------------------------
/src/test/resources/multi-tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "rootTaskId": "root",
3 | "tasks": [
4 | {
5 | "taskId": "root",
6 | "childrenTaskIds": ["task1", "task2"]
7 | },
8 |
9 | {
10 | "taskId": "task1",
11 | "taskType": {
12 | "type": "type1",
13 | "version": "1",
14 | "isIdempotent": true
15 | },
16 | "metaData": [],
17 | "childrenTaskIds": ["task3", "task4", "task5"]
18 | },
19 |
20 | {
21 | "taskId": "task2",
22 | "taskType": {
23 | "type": "type1",
24 | "version": "1",
25 | "isIdempotent": true
26 | },
27 | "metaData": [],
28 | "childrenTaskIds": ["task3", "task4", "task5"]
29 | },
30 |
31 | {
32 | "taskId": "task3",
33 | "taskType": {
34 | "type": "type2",
35 | "version": "1",
36 | "isIdempotent": true
37 | },
38 | "metaData": [],
39 | "childrenTaskIds": ["task6"]
40 | },
41 |
42 | {
43 | "taskId": "task4",
44 | "taskType": {
45 | "type": "type2",
46 | "version": "1",
47 | "isIdempotent": true
48 | },
49 | "metaData": [],
50 | "childrenTaskIds": ["task6"]
51 | },
52 |
53 | {
54 | "taskId": "task5",
55 | "taskType": {
56 | "type": "type3",
57 | "version": "1",
58 | "isIdempotent": true
59 | },
60 | "metaData": [],
61 | "childrenTaskIds": ["task6"]
62 | },
63 |
64 | {
65 | "taskId": "task6",
66 | "taskType": {
67 | "type": "type3",
68 | "version": "1",
69 | "isIdempotent": true
70 | },
71 | "metaData": [],
72 | "childrenTaskIds": []
73 | }
74 | ]
75 | }
--------------------------------------------------------------------------------
/src/test/resources/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "rootTaskId": "root",
3 | "tasks": [
4 | {
5 | "taskId": "root",
6 | "childrenTaskIds": ["task1", "task2"]
7 | },
8 |
9 | {
10 | "taskId": "task1",
11 | "taskType": {
12 | "type": "test",
13 | "version": "1",
14 | "isIdempotent": true
15 | },
16 | "metaData": [],
17 | "childrenTaskIds": ["task3", "task4", "task5"]
18 | },
19 |
20 | {
21 | "taskId": "task2",
22 | "taskType": {
23 | "type": "test",
24 | "version": "1",
25 | "isIdempotent": true
26 | },
27 | "metaData": [],
28 | "childrenTaskIds": ["task3", "task4", "task5"]
29 | },
30 |
31 | {
32 | "taskId": "task3",
33 | "taskType": {
34 | "type": "test",
35 | "version": "1",
36 | "isIdempotent": true
37 | },
38 | "metaData": [],
39 | "childrenTaskIds": ["task6"]
40 | },
41 |
42 | {
43 | "taskId": "task4",
44 | "taskType": {
45 | "type": "test",
46 | "version": "1",
47 | "isIdempotent": true
48 | },
49 | "metaData": [],
50 | "childrenTaskIds": ["task6"]
51 | },
52 |
53 | {
54 | "taskId": "task5",
55 | "taskType": {
56 | "type": "test",
57 | "version": "1",
58 | "isIdempotent": true
59 | },
60 | "metaData": [],
61 | "childrenTaskIds": ["task6"]
62 | },
63 |
64 | {
65 | "taskId": "task6",
66 | "taskType": {
67 | "type": "test",
68 | "version": "1",
69 | "isIdempotent": true
70 | },
71 | "metaData": [],
72 | "childrenTaskIds": []
73 | }
74 | ]
75 | }
--------------------------------------------------------------------------------