Repository: Netflix/conductor Branch: main Commit: 548f386c2c6c Files: 1030 Total size: 5.8 MB Directory structure: gitextract_cp2gulur/ ├── .dockerignore ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ ├── documentation.md │ │ └── feature_request.md │ ├── dependabot.yml │ ├── pull_request_template.md │ ├── release-drafter.yml │ └── workflows/ │ ├── ci.yml │ ├── publish.yml │ ├── release_draft.yml │ ├── stale.yml │ └── update-gradle-wrapper.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── OSSMETADATA ├── README.md ├── RELATED.md ├── SECURITY.md ├── USERS.md ├── annotations/ │ ├── README.md │ ├── build.gradle │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── netflix/ │ └── conductor/ │ └── annotations/ │ └── protogen/ │ ├── ProtoEnum.java │ ├── ProtoField.java │ └── ProtoMessage.java ├── annotations-processor/ │ ├── README.md │ ├── build.gradle │ └── src/ │ ├── example/ │ │ └── java/ │ │ └── com/ │ │ └── example/ │ │ └── Example.java │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── netflix/ │ │ │ └── conductor/ │ │ │ └── annotationsprocessor/ │ │ │ └── protogen/ │ │ │ ├── AbstractMessage.java │ │ │ ├── Enum.java │ │ │ ├── Message.java │ │ │ ├── ProtoFile.java │ │ │ ├── ProtoGen.java │ │ │ ├── ProtoGenTask.java │ │ │ └── types/ │ │ │ ├── AbstractType.java │ │ │ ├── ExternMessageType.java │ │ │ ├── GenericType.java │ │ │ ├── ListType.java │ │ │ ├── MapType.java │ │ │ ├── MessageType.java │ │ │ ├── ScalarType.java │ │ │ ├── TypeMapper.java │ │ │ └── WrappedType.java │ │ └── resources/ │ │ └── templates/ │ │ ├── file.proto │ │ └── message.proto │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ └── annotationsprocessor/ │ │ └── protogen/ │ │ └── ProtoGenTest.java │ └── resources/ │ └── example.proto.txt ├── awss3-storage/ │ ├── README.md │ ├── build.gradle │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ └── s3/ │ │ ├── config/ │ │ │ ├── S3Configuration.java │ │ │ └── S3Properties.java │ │ └── storage/ │ │ └── S3PayloadStorage.java │ └── resources/ │ └── META-INF/ │ └── additional-spring-configuration-metadata.json ├── awssqs-event-queue/ │ ├── README.md │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── netflix/ │ │ │ └── conductor/ │ │ │ └── sqs/ │ │ │ ├── config/ │ │ │ │ ├── SQSEventQueueConfiguration.java │ │ │ │ ├── SQSEventQueueProperties.java │ │ │ │ └── SQSEventQueueProvider.java │ │ │ └── eventqueue/ │ │ │ └── SQSObservableQueue.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── additional-spring-configuration-metadata.json │ └── test/ │ └── java/ │ └── com/ │ └── netflix/ │ └── conductor/ │ └── sqs/ │ └── eventqueue/ │ ├── DefaultEventQueueProcessorTest.java │ └── SQSObservableQueueTest.java ├── build.gradle ├── cassandra-persistence/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── netflix/ │ │ │ └── conductor/ │ │ │ └── cassandra/ │ │ │ ├── config/ │ │ │ │ ├── CassandraConfiguration.java │ │ │ │ ├── CassandraProperties.java │ │ │ │ └── cache/ │ │ │ │ ├── CacheableEventHandlerDAO.java │ │ │ │ ├── CacheableMetadataDAO.java │ │ │ │ └── CachingConfig.java │ │ │ ├── dao/ │ │ │ │ ├── CassandraBaseDAO.java │ │ │ │ ├── CassandraEventHandlerDAO.java │ │ │ │ ├── CassandraExecutionDAO.java │ │ │ │ ├── CassandraMetadataDAO.java │ │ │ │ └── CassandraPollDataDAO.java │ │ │ └── util/ │ │ │ ├── Constants.java │ │ │ └── Statements.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── additional-spring-configuration-metadata.json │ └── test/ │ └── groovy/ │ └── com/ │ └── netflix/ │ └── conductor/ │ └── cassandra/ │ ├── dao/ │ │ ├── CassandraEventHandlerDAOSpec.groovy │ │ ├── CassandraExecutionDAOSpec.groovy │ │ ├── CassandraMetadataDAOSpec.groovy │ │ └── CassandraSpec.groovy │ └── util/ │ └── StatementsSpec.groovy ├── client/ │ ├── build.gradle │ ├── spotbugsExclude.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ └── client/ │ │ ├── automator/ │ │ │ ├── PollingSemaphore.java │ │ │ ├── TaskPollExecutor.java │ │ │ └── TaskRunnerConfigurer.java │ │ ├── config/ │ │ │ ├── ConductorClientConfiguration.java │ │ │ ├── DefaultConductorClientConfiguration.java │ │ │ └── PropertyFactory.java │ │ ├── exception/ │ │ │ └── ConductorClientException.java │ │ ├── http/ │ │ │ ├── ClientBase.java │ │ │ ├── ClientRequestHandler.java │ │ │ ├── EventClient.java │ │ │ ├── MetadataClient.java │ │ │ ├── PayloadStorage.java │ │ │ ├── TaskClient.java │ │ │ └── WorkflowClient.java │ │ ├── telemetry/ │ │ │ └── MetricsContainer.java │ │ └── worker/ │ │ └── Worker.java │ └── test/ │ ├── groovy/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ └── client/ │ │ └── http/ │ │ ├── ClientSpecification.groovy │ │ ├── EventClientSpec.groovy │ │ ├── MetadataClientSpec.groovy │ │ ├── TaskClientSpec.groovy │ │ └── WorkflowClientSpec.groovy │ ├── java/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ └── client/ │ │ ├── automator/ │ │ │ ├── PollingSemaphoreTest.java │ │ │ ├── TaskPollExecutorTest.java │ │ │ └── TaskRunnerConfigurerTest.java │ │ ├── config/ │ │ │ └── TestPropertyFactory.java │ │ ├── sample/ │ │ │ ├── Main.java │ │ │ └── SampleWorker.java │ │ ├── testing/ │ │ │ ├── AbstractWorkflowTests.java │ │ │ ├── LoanWorkflowInput.java │ │ │ ├── LoanWorkflowTest.java │ │ │ ├── RegressionTest.java │ │ │ └── SubWorkflowTest.java │ │ └── worker/ │ │ └── TestWorkflowTask.java │ └── resources/ │ ├── config.properties │ ├── tasks.json │ ├── test_data/ │ │ ├── loan_workflow_input.json │ │ └── workflow1_run.json │ └── workflows/ │ ├── PopulationMinMax.json │ ├── calculate_loan_workflow.json │ ├── kitchensink.json │ └── workflow1.json ├── client-spring/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── netflix/ │ │ │ └── conductor/ │ │ │ └── client/ │ │ │ └── spring/ │ │ │ ├── ClientProperties.java │ │ │ ├── ConductorClientAutoConfiguration.java │ │ │ ├── ConductorWorkerAutoConfiguration.java │ │ │ └── SpringWorkerConfiguration.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring.factories │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ └── client/ │ │ └── spring/ │ │ ├── ExampleClient.java │ │ └── Workers.java │ └── resources/ │ └── application.properties ├── common/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ └── common/ │ │ ├── config/ │ │ │ ├── ObjectMapperBuilderConfiguration.java │ │ │ ├── ObjectMapperConfiguration.java │ │ │ └── ObjectMapperProvider.java │ │ ├── constraints/ │ │ │ ├── NoSemiColonConstraint.java │ │ │ ├── OwnerEmailMandatoryConstraint.java │ │ │ ├── TaskReferenceNameUniqueConstraint.java │ │ │ └── TaskTimeoutConstraint.java │ │ ├── jackson/ │ │ │ └── JsonProtoModule.java │ │ ├── metadata/ │ │ │ ├── Auditable.java │ │ │ ├── BaseDef.java │ │ │ ├── acl/ │ │ │ │ └── Permission.java │ │ │ ├── events/ │ │ │ │ ├── EventExecution.java │ │ │ │ └── EventHandler.java │ │ │ ├── tasks/ │ │ │ │ ├── PollData.java │ │ │ │ ├── Task.java │ │ │ │ ├── TaskDef.java │ │ │ │ ├── TaskExecLog.java │ │ │ │ ├── TaskResult.java │ │ │ │ └── TaskType.java │ │ │ └── workflow/ │ │ │ ├── DynamicForkJoinTask.java │ │ │ ├── DynamicForkJoinTaskList.java │ │ │ ├── RerunWorkflowRequest.java │ │ │ ├── SkipTaskRequest.java │ │ │ ├── StartWorkflowRequest.java │ │ │ ├── SubWorkflowParams.java │ │ │ ├── WorkflowDef.java │ │ │ ├── WorkflowDefSummary.java │ │ │ └── WorkflowTask.java │ │ ├── model/ │ │ │ └── BulkResponse.java │ │ ├── run/ │ │ │ ├── ExternalStorageLocation.java │ │ │ ├── SearchResult.java │ │ │ ├── TaskSummary.java │ │ │ ├── Workflow.java │ │ │ ├── WorkflowSummary.java │ │ │ └── WorkflowTestRequest.java │ │ ├── utils/ │ │ │ ├── ConstraintParamUtil.java │ │ │ ├── EnvUtils.java │ │ │ ├── ExternalPayloadStorage.java │ │ │ ├── SummaryUtil.java │ │ │ └── TaskUtils.java │ │ └── validation/ │ │ ├── ErrorResponse.java │ │ └── ValidationError.java │ └── test/ │ └── java/ │ └── com/ │ └── netflix/ │ └── conductor/ │ └── common/ │ ├── config/ │ │ └── TestObjectMapperConfiguration.java │ ├── events/ │ │ └── EventHandlerTest.java │ ├── run/ │ │ └── TaskSummaryTest.java │ ├── tasks/ │ │ ├── TaskDefTest.java │ │ ├── TaskResultTest.java │ │ └── TaskTest.java │ ├── utils/ │ │ ├── ConstraintParamUtilTest.java │ │ └── SummaryUtilTest.java │ └── workflow/ │ ├── SubWorkflowParamsTest.java │ ├── WorkflowDefValidatorTest.java │ └── WorkflowTaskTest.java ├── core/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── netflix/ │ │ │ └── conductor/ │ │ │ ├── annotations/ │ │ │ │ ├── Audit.java │ │ │ │ ├── Trace.java │ │ │ │ └── VisibleForTesting.java │ │ │ ├── core/ │ │ │ │ ├── LifecycleAwareComponent.java │ │ │ │ ├── WorkflowContext.java │ │ │ │ ├── config/ │ │ │ │ │ ├── ConductorCoreConfiguration.java │ │ │ │ │ ├── ConductorProperties.java │ │ │ │ │ └── SchedulerConfiguration.java │ │ │ │ ├── dal/ │ │ │ │ │ └── ExecutionDAOFacade.java │ │ │ │ ├── event/ │ │ │ │ │ ├── WorkflowCreationEvent.java │ │ │ │ │ └── WorkflowEvaluationEvent.java │ │ │ │ ├── events/ │ │ │ │ │ ├── ActionProcessor.java │ │ │ │ │ ├── DefaultEventProcessor.java │ │ │ │ │ ├── DefaultEventQueueManager.java │ │ │ │ │ ├── EventQueueManager.java │ │ │ │ │ ├── EventQueueProvider.java │ │ │ │ │ ├── EventQueues.java │ │ │ │ │ ├── ScriptEvaluator.java │ │ │ │ │ ├── SimpleActionProcessor.java │ │ │ │ │ └── queue/ │ │ │ │ │ ├── ConductorEventQueueProvider.java │ │ │ │ │ ├── ConductorObservableQueue.java │ │ │ │ │ ├── DefaultEventQueueProcessor.java │ │ │ │ │ ├── Message.java │ │ │ │ │ └── ObservableQueue.java │ │ │ │ ├── exception/ │ │ │ │ │ ├── ConflictException.java │ │ │ │ │ ├── NonTransientException.java │ │ │ │ │ ├── NotFoundException.java │ │ │ │ │ ├── TerminateWorkflowException.java │ │ │ │ │ └── TransientException.java │ │ │ │ ├── execution/ │ │ │ │ │ ├── AsyncSystemTaskExecutor.java │ │ │ │ │ ├── DeciderService.java │ │ │ │ │ ├── StartWorkflowInput.java │ │ │ │ │ ├── WorkflowExecutor.java │ │ │ │ │ ├── evaluators/ │ │ │ │ │ │ ├── Evaluator.java │ │ │ │ │ │ ├── JavascriptEvaluator.java │ │ │ │ │ │ └── ValueParamEvaluator.java │ │ │ │ │ ├── mapper/ │ │ │ │ │ │ ├── DecisionTaskMapper.java │ │ │ │ │ │ ├── DoWhileTaskMapper.java │ │ │ │ │ │ ├── DynamicTaskMapper.java │ │ │ │ │ │ ├── EventTaskMapper.java │ │ │ │ │ │ ├── ExclusiveJoinTaskMapper.java │ │ │ │ │ │ ├── ForkJoinDynamicTaskMapper.java │ │ │ │ │ │ ├── ForkJoinTaskMapper.java │ │ │ │ │ │ ├── HTTPTaskMapper.java │ │ │ │ │ │ ├── HumanTaskMapper.java │ │ │ │ │ │ ├── InlineTaskMapper.java │ │ │ │ │ │ ├── JoinTaskMapper.java │ │ │ │ │ │ ├── JsonJQTransformTaskMapper.java │ │ │ │ │ │ ├── KafkaPublishTaskMapper.java │ │ │ │ │ │ ├── LambdaTaskMapper.java │ │ │ │ │ │ ├── NoopTaskMapper.java │ │ │ │ │ │ ├── SetVariableTaskMapper.java │ │ │ │ │ │ ├── SimpleTaskMapper.java │ │ │ │ │ │ ├── StartWorkflowTaskMapper.java │ │ │ │ │ │ ├── SubWorkflowTaskMapper.java │ │ │ │ │ │ ├── SwitchTaskMapper.java │ │ │ │ │ │ ├── TaskMapper.java │ │ │ │ │ │ ├── TaskMapperContext.java │ │ │ │ │ │ ├── TerminateTaskMapper.java │ │ │ │ │ │ ├── UserDefinedTaskMapper.java │ │ │ │ │ │ └── WaitTaskMapper.java │ │ │ │ │ └── tasks/ │ │ │ │ │ ├── Decision.java │ │ │ │ │ ├── DoWhile.java │ │ │ │ │ ├── Event.java │ │ │ │ │ ├── ExclusiveJoin.java │ │ │ │ │ ├── ExecutionConfig.java │ │ │ │ │ ├── Fork.java │ │ │ │ │ ├── Human.java │ │ │ │ │ ├── Inline.java │ │ │ │ │ ├── IsolatedTaskQueueProducer.java │ │ │ │ │ ├── Join.java │ │ │ │ │ ├── Lambda.java │ │ │ │ │ ├── Noop.java │ │ │ │ │ ├── SetVariable.java │ │ │ │ │ ├── StartWorkflow.java │ │ │ │ │ ├── SubWorkflow.java │ │ │ │ │ ├── Switch.java │ │ │ │ │ ├── SystemTaskRegistry.java │ │ │ │ │ ├── SystemTaskWorker.java │ │ │ │ │ ├── SystemTaskWorkerCoordinator.java │ │ │ │ │ ├── Terminate.java │ │ │ │ │ ├── Wait.java │ │ │ │ │ └── WorkflowSystemTask.java │ │ │ │ ├── index/ │ │ │ │ │ ├── NoopIndexDAO.java │ │ │ │ │ └── NoopIndexDAOConfiguration.java │ │ │ │ ├── listener/ │ │ │ │ │ ├── TaskStatusListener.java │ │ │ │ │ ├── TaskStatusListenerStub.java │ │ │ │ │ ├── WorkflowStatusListener.java │ │ │ │ │ └── WorkflowStatusListenerStub.java │ │ │ │ ├── metadata/ │ │ │ │ │ └── MetadataMapperService.java │ │ │ │ ├── operation/ │ │ │ │ │ ├── StartWorkflowOperation.java │ │ │ │ │ └── WorkflowOperation.java │ │ │ │ ├── reconciliation/ │ │ │ │ │ ├── WorkflowReconciler.java │ │ │ │ │ ├── WorkflowRepairService.java │ │ │ │ │ └── WorkflowSweeper.java │ │ │ │ ├── storage/ │ │ │ │ │ └── DummyPayloadStorage.java │ │ │ │ ├── sync/ │ │ │ │ │ ├── Lock.java │ │ │ │ │ ├── local/ │ │ │ │ │ │ ├── LocalOnlyLock.java │ │ │ │ │ │ └── LocalOnlyLockConfiguration.java │ │ │ │ │ └── noop/ │ │ │ │ │ └── NoopLock.java │ │ │ │ └── utils/ │ │ │ │ ├── DateTimeUtils.java │ │ │ │ ├── ExternalPayloadStorageUtils.java │ │ │ │ ├── IDGenerator.java │ │ │ │ ├── JsonUtils.java │ │ │ │ ├── ParametersUtils.java │ │ │ │ ├── QueueUtils.java │ │ │ │ ├── SemaphoreUtil.java │ │ │ │ └── Utils.java │ │ │ ├── dao/ │ │ │ │ ├── ConcurrentExecutionLimitDAO.java │ │ │ │ ├── EventHandlerDAO.java │ │ │ │ ├── ExecutionDAO.java │ │ │ │ ├── IndexDAO.java │ │ │ │ ├── MetadataDAO.java │ │ │ │ ├── PollDataDAO.java │ │ │ │ ├── QueueDAO.java │ │ │ │ └── RateLimitingDAO.java │ │ │ ├── metrics/ │ │ │ │ ├── Monitors.java │ │ │ │ └── WorkflowMonitor.java │ │ │ ├── model/ │ │ │ │ ├── TaskModel.java │ │ │ │ └── WorkflowModel.java │ │ │ ├── service/ │ │ │ │ ├── AdminService.java │ │ │ │ ├── AdminServiceImpl.java │ │ │ │ ├── EventService.java │ │ │ │ ├── EventServiceImpl.java │ │ │ │ ├── ExecutionLockService.java │ │ │ │ ├── ExecutionService.java │ │ │ │ ├── MetadataService.java │ │ │ │ ├── MetadataServiceImpl.java │ │ │ │ ├── TaskService.java │ │ │ │ ├── TaskServiceImpl.java │ │ │ │ ├── WorkflowBulkService.java │ │ │ │ ├── WorkflowBulkServiceImpl.java │ │ │ │ ├── WorkflowService.java │ │ │ │ ├── WorkflowServiceImpl.java │ │ │ │ └── WorkflowTestService.java │ │ │ └── validations/ │ │ │ ├── ValidationContext.java │ │ │ └── WorkflowTaskTypeConstraint.java │ │ └── resources/ │ │ └── META-INF/ │ │ ├── additional-spring-configuration-metadata.json │ │ ├── validation/ │ │ │ └── constraints.xml │ │ └── validation.xml │ └── test/ │ ├── groovy/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ ├── core/ │ │ │ ├── execution/ │ │ │ │ ├── AsyncSystemTaskExecutorTest.groovy │ │ │ │ └── tasks/ │ │ │ │ ├── DoWhileSpec.groovy │ │ │ │ ├── EventSpec.groovy │ │ │ │ ├── IsolatedTaskQueueProducerSpec.groovy │ │ │ │ └── StartWorkflowSpec.groovy │ │ │ └── operation/ │ │ │ └── StartWorkflowOperationSpec.groovy │ │ └── model/ │ │ ├── TaskModelSpec.groovy │ │ └── WorkflowModelSpec.groovy │ ├── java/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ ├── TestUtils.java │ │ ├── core/ │ │ │ ├── dal/ │ │ │ │ └── ExecutionDAOFacadeTest.java │ │ │ ├── events/ │ │ │ │ ├── MockObservableQueue.java │ │ │ │ ├── MockQueueProvider.java │ │ │ │ ├── TestDefaultEventProcessor.java │ │ │ │ ├── TestScriptEval.java │ │ │ │ └── TestSimpleActionProcessor.java │ │ │ ├── execution/ │ │ │ │ ├── TestDeciderOutcomes.java │ │ │ │ ├── TestDeciderService.java │ │ │ │ ├── TestWorkflowDef.java │ │ │ │ ├── TestWorkflowExecutor.java │ │ │ │ ├── WorkflowSystemTaskStub.java │ │ │ │ ├── mapper/ │ │ │ │ │ ├── DecisionTaskMapperTest.java │ │ │ │ │ ├── DoWhileTaskMapperTest.java │ │ │ │ │ ├── DynamicTaskMapperTest.java │ │ │ │ │ ├── EventTaskMapperTest.java │ │ │ │ │ ├── ForkJoinDynamicTaskMapperTest.java │ │ │ │ │ ├── ForkJoinTaskMapperTest.java │ │ │ │ │ ├── HTTPTaskMapperTest.java │ │ │ │ │ ├── HumanTaskMapperTest.java │ │ │ │ │ ├── InlineTaskMapperTest.java │ │ │ │ │ ├── JoinTaskMapperTest.java │ │ │ │ │ ├── JsonJQTransformTaskMapperTest.java │ │ │ │ │ ├── KafkaPublishTaskMapperTest.java │ │ │ │ │ ├── LambdaTaskMapperTest.java │ │ │ │ │ ├── NoopTaskMapperTest.java │ │ │ │ │ ├── SetVariableTaskMapperTest.java │ │ │ │ │ ├── SimpleTaskMapperTest.java │ │ │ │ │ ├── SubWorkflowTaskMapperTest.java │ │ │ │ │ ├── SwitchTaskMapperTest.java │ │ │ │ │ ├── TerminateTaskMapperTest.java │ │ │ │ │ ├── UserDefinedTaskMapperTest.java │ │ │ │ │ └── WaitTaskMapperTest.java │ │ │ │ └── tasks/ │ │ │ │ ├── EventQueueResolutionTest.java │ │ │ │ ├── InlineTest.java │ │ │ │ ├── TestLambda.java │ │ │ │ ├── TestNoop.java │ │ │ │ ├── TestSubWorkflow.java │ │ │ │ ├── TestSystemTaskWorker.java │ │ │ │ ├── TestSystemTaskWorkerCoordinator.java │ │ │ │ └── TestTerminate.java │ │ │ ├── metadata/ │ │ │ │ └── MetadataMapperServiceTest.java │ │ │ ├── reconciliation/ │ │ │ │ ├── TestWorkflowRepairService.java │ │ │ │ └── TestWorkflowSweeper.java │ │ │ ├── storage/ │ │ │ │ └── DummyPayloadStorageTest.java │ │ │ ├── sync/ │ │ │ │ └── local/ │ │ │ │ └── LocalOnlyLockTest.java │ │ │ └── utils/ │ │ │ ├── ExternalPayloadStorageUtilsTest.java │ │ │ ├── JsonUtilsTest.java │ │ │ ├── ParametersUtilsTest.java │ │ │ ├── QueueUtilsTest.java │ │ │ └── SemaphoreUtilTest.java │ │ ├── dao/ │ │ │ ├── ExecutionDAOTest.java │ │ │ └── PollDataDAOTest.java │ │ ├── metrics/ │ │ │ └── WorkflowMonitorTest.java │ │ ├── service/ │ │ │ ├── EventServiceTest.java │ │ │ ├── ExecutionServiceTest.java │ │ │ ├── MetadataServiceTest.java │ │ │ ├── TaskServiceTest.java │ │ │ ├── WorkflowBulkServiceTest.java │ │ │ └── WorkflowServiceTest.java │ │ └── validations/ │ │ ├── WorkflowDefConstraintTest.java │ │ └── WorkflowTaskTypeConstraintTest.java │ └── resources/ │ ├── completed.json │ ├── conditional_flow.json │ ├── conditional_flow_with_switch.json │ ├── payload.json │ └── test.json ├── dependencies.gradle ├── docker/ │ ├── README.md │ ├── ci/ │ │ └── Dockerfile │ ├── docker-compose-mysql.yaml │ ├── docker-compose-postgres.yaml │ ├── docker-compose.yaml │ ├── server/ │ │ ├── Dockerfile │ │ ├── config/ │ │ │ ├── config-mysql.properties │ │ │ ├── config-postgres.properties │ │ │ ├── config-redis.properties │ │ │ ├── config.properties │ │ │ ├── log4j-file-appender.properties │ │ │ ├── log4j.properties │ │ │ └── redis.conf │ │ └── nginx/ │ │ └── nginx.conf │ └── ui/ │ ├── Dockerfile │ └── README.md ├── docs/ │ ├── docs/ │ │ ├── apispec.md │ │ ├── architecture/ │ │ │ ├── overview.md │ │ │ └── tasklifecycle.md │ │ ├── bestpractices.md │ │ ├── configuration/ │ │ │ ├── eventhandlers.md │ │ │ ├── isolationgroups.md │ │ │ ├── sysoperator.md │ │ │ ├── systask.md │ │ │ ├── taskdef.md │ │ │ ├── taskdomains.md │ │ │ ├── workerdef.md │ │ │ └── workflowdef.md │ │ ├── css/ │ │ │ └── custom.css │ │ ├── extend.md │ │ ├── externalpayloadstorage.md │ │ ├── faq.md │ │ ├── gettingstarted/ │ │ │ ├── basicconcepts.md │ │ │ ├── client.md │ │ │ ├── docker.md │ │ │ ├── hosted.md │ │ │ ├── intro.md │ │ │ ├── source.md │ │ │ ├── startworkflow.md │ │ │ └── steps.md │ │ ├── googleba55068fa3e0e553.html │ │ ├── how-tos/ │ │ │ ├── Monitoring/ │ │ │ │ └── Conductor-LogLevel.md │ │ │ ├── Tasks/ │ │ │ │ ├── creating-tasks.md │ │ │ │ ├── dynamic-vs-switch-tasks.md │ │ │ │ ├── extending-system-tasks.md │ │ │ │ ├── monitoring-task-queues.md │ │ │ │ ├── reusing-tasks.md │ │ │ │ ├── task-configurations.md │ │ │ │ ├── task-inputs.md │ │ │ │ ├── task-timeouts.md │ │ │ │ └── updating-tasks.md │ │ │ ├── Test/ │ │ │ │ └── testing-workflows.md │ │ │ ├── Workers/ │ │ │ │ ├── build-a-golang-task-worker.md │ │ │ │ ├── build-a-java-task-worker.md │ │ │ │ └── build-a-python-task-worker.md │ │ │ ├── Workflows/ │ │ │ │ ├── debugging-workflows.md │ │ │ │ ├── handling-errors.md │ │ │ │ ├── searching-workflows.md │ │ │ │ ├── starting-workflows.md │ │ │ │ ├── updating-workflows.md │ │ │ │ ├── versioning-workflows.md │ │ │ │ └── view-workflow-executions.md │ │ │ ├── clojure-sdk.md │ │ │ ├── csharp-sdk.md │ │ │ ├── go-sdk.md │ │ │ ├── java-sdk.md │ │ │ └── python-sdk.md │ │ ├── index.md │ │ ├── labs/ │ │ │ ├── beginner.md │ │ │ ├── eventhandlers.md │ │ │ ├── kitchensink.md │ │ │ └── running-first-workflow.md │ │ ├── metrics/ │ │ │ ├── client.md │ │ │ └── server.md │ │ ├── reference-docs/ │ │ │ ├── annotation-processor.md │ │ │ ├── archival-of-workflows.md │ │ │ ├── azureblob-storage.md │ │ │ ├── directed-acyclic-graph.md │ │ │ ├── do-while-task.md │ │ │ ├── dynamic-fork-task.md │ │ │ ├── dynamic-task.md │ │ │ ├── event-task.md │ │ │ ├── fork-task.md │ │ │ ├── http-task.md │ │ │ ├── human-task.md │ │ │ ├── inline-task.md │ │ │ ├── join-task.md │ │ │ ├── json-jq-transform-task.md │ │ │ ├── kafka-publish-task.md │ │ │ ├── redis.md │ │ │ ├── set-variable-task.md │ │ │ ├── start-workflow-task.md │ │ │ ├── sub-workflow-task.md │ │ │ ├── switch-task.md │ │ │ ├── terminate-task.md │ │ │ └── wait-task.md │ │ ├── resources/ │ │ │ ├── code-of-conduct.md │ │ │ ├── contributing.md │ │ │ ├── license.md │ │ │ └── related.md │ │ └── technicaldetails.md │ ├── kitchensink.json │ ├── mkdocs.yml │ └── theme/ │ ├── main.html │ ├── toc-sub.html │ └── toc.html ├── es6-persistence/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── netflix/ │ │ │ └── conductor/ │ │ │ └── es6/ │ │ │ ├── config/ │ │ │ │ ├── ElasticSearchConditions.java │ │ │ │ ├── ElasticSearchProperties.java │ │ │ │ ├── ElasticSearchV6Configuration.java │ │ │ │ ├── IsHttpProtocol.java │ │ │ │ └── IsTcpProtocol.java │ │ │ └── dao/ │ │ │ ├── index/ │ │ │ │ ├── BulkRequestBuilderWrapper.java │ │ │ │ ├── BulkRequestWrapper.java │ │ │ │ ├── ElasticSearchBaseDAO.java │ │ │ │ ├── ElasticSearchDAOV6.java │ │ │ │ └── ElasticSearchRestDAOV6.java │ │ │ └── query/ │ │ │ └── parser/ │ │ │ ├── Expression.java │ │ │ ├── FilterProvider.java │ │ │ ├── GroupedExpression.java │ │ │ ├── NameValue.java │ │ │ └── internal/ │ │ │ ├── AbstractNode.java │ │ │ ├── BooleanOp.java │ │ │ ├── ComparisonOp.java │ │ │ ├── ConstValue.java │ │ │ ├── FunctionThrowingException.java │ │ │ ├── ListConst.java │ │ │ ├── Name.java │ │ │ ├── ParserException.java │ │ │ └── Range.java │ │ └── resources/ │ │ ├── mappings_docType_task.json │ │ ├── mappings_docType_workflow.json │ │ ├── template_event.json │ │ ├── template_message.json │ │ └── template_task_log.json │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ └── es6/ │ │ ├── dao/ │ │ │ ├── index/ │ │ │ │ ├── ElasticSearchDaoBaseTest.java │ │ │ │ ├── ElasticSearchRestDaoBaseTest.java │ │ │ │ ├── ElasticSearchTest.java │ │ │ │ ├── TestElasticSearchDAOV6.java │ │ │ │ ├── TestElasticSearchDAOV6Batch.java │ │ │ │ ├── TestElasticSearchRestDAOV6.java │ │ │ │ └── TestElasticSearchRestDAOV6Batch.java │ │ │ └── query/ │ │ │ └── parser/ │ │ │ ├── TestExpression.java │ │ │ └── internal/ │ │ │ ├── TestAbstractParser.java │ │ │ ├── TestBooleanOp.java │ │ │ ├── TestComparisonOp.java │ │ │ ├── TestConstValue.java │ │ │ └── TestName.java │ │ └── utils/ │ │ └── TestUtils.java │ └── resources/ │ ├── expected_template_task_log.json │ ├── task_summary.json │ └── workflow_summary.json ├── family.properties ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── grpc/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── netflix/ │ │ │ └── conductor/ │ │ │ └── grpc/ │ │ │ ├── AbstractProtoMapper.java │ │ │ └── ProtoMapper.java │ │ └── proto/ │ │ ├── grpc/ │ │ │ ├── event_service.proto │ │ │ ├── metadata_service.proto │ │ │ ├── search.proto │ │ │ ├── task_service.proto │ │ │ └── workflow_service.proto │ │ └── model/ │ │ ├── dynamicforkjointask.proto │ │ ├── dynamicforkjointasklist.proto │ │ ├── eventexecution.proto │ │ ├── eventhandler.proto │ │ ├── polldata.proto │ │ ├── rerunworkflowrequest.proto │ │ ├── skiptaskrequest.proto │ │ ├── startworkflowrequest.proto │ │ ├── subworkflowparams.proto │ │ ├── task.proto │ │ ├── taskdef.proto │ │ ├── taskexeclog.proto │ │ ├── taskresult.proto │ │ ├── tasksummary.proto │ │ ├── workflow.proto │ │ ├── workflowdef.proto │ │ ├── workflowdefsummary.proto │ │ ├── workflowsummary.proto │ │ └── workflowtask.proto │ └── test/ │ └── java/ │ └── com/ │ └── netflix/ │ └── conductor/ │ └── grpc/ │ └── TestProtoMapper.java ├── grpc-client/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ └── client/ │ │ └── grpc/ │ │ ├── ClientBase.java │ │ ├── EventClient.java │ │ ├── MetadataClient.java │ │ ├── TaskClient.java │ │ └── WorkflowClient.java │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ └── client/ │ │ └── grpc/ │ │ ├── EventClientTest.java │ │ ├── TaskClientTest.java │ │ └── WorkflowClientTest.java │ └── resources/ │ └── mockito-extensions/ │ └── org.mockito.plugins.MockMaker ├── grpc-server/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ └── grpc/ │ │ └── server/ │ │ ├── GRPCServer.java │ │ ├── GRPCServerProperties.java │ │ ├── GrpcConfiguration.java │ │ └── service/ │ │ ├── EventServiceImpl.java │ │ ├── GRPCHelper.java │ │ ├── HealthServiceImpl.java │ │ ├── MetadataServiceImpl.java │ │ ├── TaskServiceImpl.java │ │ └── WorkflowServiceImpl.java │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ └── grpc/ │ │ └── server/ │ │ └── service/ │ │ ├── HealthServiceImplTest.java │ │ ├── TaskServiceImplTest.java │ │ └── WorkflowServiceImplTest.java │ └── resources/ │ └── log4j.properties ├── http-task/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── netflix/ │ │ │ └── conductor/ │ │ │ └── tasks/ │ │ │ └── http/ │ │ │ ├── HttpTask.java │ │ │ └── providers/ │ │ │ ├── DefaultRestTemplateProvider.java │ │ │ └── RestTemplateProvider.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── additional-spring-configuration-metadata.json │ └── test/ │ └── java/ │ └── com/ │ └── netflix/ │ └── conductor/ │ └── tasks/ │ └── http/ │ ├── HttpTaskTest.java │ └── providers/ │ └── DefaultRestTemplateProviderTest.java ├── java-sdk/ │ ├── README.md │ ├── build.gradle │ ├── example/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── netflix/ │ │ │ └── conductor/ │ │ │ └── sdk/ │ │ │ └── example/ │ │ │ └── shipment/ │ │ │ ├── Order.java │ │ │ ├── Shipment.java │ │ │ ├── ShipmentState.java │ │ │ ├── ShipmentWorkers.java │ │ │ ├── ShipmentWorkflow.java │ │ │ └── User.java │ │ └── resources/ │ │ └── script.js │ ├── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── netflix/ │ │ │ │ └── conductor/ │ │ │ │ └── sdk/ │ │ │ │ ├── healthcheck/ │ │ │ │ │ └── HealthCheckClient.java │ │ │ │ ├── testing/ │ │ │ │ │ ├── LocalServerRunner.java │ │ │ │ │ └── WorkflowTestRunner.java │ │ │ │ └── workflow/ │ │ │ │ ├── def/ │ │ │ │ │ ├── ConductorWorkflow.java │ │ │ │ │ ├── ValidationError.java │ │ │ │ │ ├── WorkflowBuilder.java │ │ │ │ │ └── tasks/ │ │ │ │ │ ├── DoWhile.java │ │ │ │ │ ├── Dynamic.java │ │ │ │ │ ├── DynamicFork.java │ │ │ │ │ ├── DynamicForkInput.java │ │ │ │ │ ├── Event.java │ │ │ │ │ ├── ForkJoin.java │ │ │ │ │ ├── Http.java │ │ │ │ │ ├── JQ.java │ │ │ │ │ ├── Javascript.java │ │ │ │ │ ├── Join.java │ │ │ │ │ ├── SetVariable.java │ │ │ │ │ ├── SimpleTask.java │ │ │ │ │ ├── SubWorkflow.java │ │ │ │ │ ├── Switch.java │ │ │ │ │ ├── Task.java │ │ │ │ │ ├── TaskRegistry.java │ │ │ │ │ ├── Terminate.java │ │ │ │ │ └── Wait.java │ │ │ │ ├── executor/ │ │ │ │ │ ├── WorkflowExecutor.java │ │ │ │ │ └── task/ │ │ │ │ │ ├── AnnotatedWorker.java │ │ │ │ │ ├── AnnotatedWorkerExecutor.java │ │ │ │ │ ├── DynamicForkWorker.java │ │ │ │ │ ├── NonRetryableException.java │ │ │ │ │ ├── TaskContext.java │ │ │ │ │ └── WorkerConfiguration.java │ │ │ │ ├── task/ │ │ │ │ │ ├── InputParam.java │ │ │ │ │ ├── OutputParam.java │ │ │ │ │ └── WorkerTask.java │ │ │ │ └── utils/ │ │ │ │ ├── InputOutputGetter.java │ │ │ │ ├── MapBuilder.java │ │ │ │ └── ObjectMapperProvider.java │ │ │ └── resources/ │ │ │ └── test-server.properties │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── netflix/ │ │ │ └── conductor/ │ │ │ └── sdk/ │ │ │ └── workflow/ │ │ │ ├── def/ │ │ │ │ ├── TaskConversionsTests.java │ │ │ │ ├── WorkflowCreationTests.java │ │ │ │ ├── WorkflowDefTaskTests.java │ │ │ │ └── WorkflowState.java │ │ │ ├── executor/ │ │ │ │ └── task/ │ │ │ │ ├── AnnotatedWorkerTests.java │ │ │ │ └── TestWorkerConfig.java │ │ │ └── testing/ │ │ │ ├── Task1Input.java │ │ │ ├── TestWorkflowInput.java │ │ │ └── WorkflowTestFrameworkTests.java │ │ └── resources/ │ │ ├── application-integrationtest.properties │ │ ├── log4j2.xml │ │ ├── script.js │ │ ├── simple_workflow.json │ │ └── tasks.json │ ├── testing_framework.md │ ├── worker_sdk.md │ └── workflow_sdk.md ├── json-jq-task/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ └── tasks/ │ │ └── json/ │ │ └── JsonJqTransform.java │ └── test/ │ └── java/ │ └── com/ │ └── netflix/ │ └── conductor/ │ └── tasks/ │ └── json/ │ └── JsonJqTransformTest.java ├── licenseheader.txt ├── polyglot-clients/ │ └── README.md ├── redis-concurrency-limit/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ └── redis/ │ │ └── limit/ │ │ ├── RedisConcurrentExecutionLimitDAO.java │ │ └── config/ │ │ ├── RedisConcurrentExecutionLimitConfiguration.java │ │ └── RedisConcurrentExecutionLimitProperties.java │ └── test/ │ └── groovy/ │ └── com/ │ └── netflix/ │ └── conductor/ │ └── redis/ │ └── limit/ │ └── RedisConcurrentExecutionLimitDAOSpec.groovy ├── redis-lock/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── netflix/ │ │ │ └── conductor/ │ │ │ └── redislock/ │ │ │ ├── config/ │ │ │ │ ├── RedisLockConfiguration.java │ │ │ │ └── RedisLockProperties.java │ │ │ └── lock/ │ │ │ └── RedisLock.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── additional-spring-configuration-metadata.json │ └── test/ │ └── java/ │ └── com/ │ └── netflix/ │ └── conductor/ │ └── redis/ │ └── lock/ │ └── RedisLockTest.java ├── redis-persistence/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ └── redis/ │ │ ├── config/ │ │ │ ├── AnyRedisCondition.java │ │ │ ├── DynomiteClusterConfiguration.java │ │ │ ├── InMemoryRedisConfiguration.java │ │ │ ├── JedisCommandsConfigurer.java │ │ │ ├── RedisClusterConfiguration.java │ │ │ ├── RedisCommonConfiguration.java │ │ │ ├── RedisProperties.java │ │ │ ├── RedisSentinelConfiguration.java │ │ │ └── RedisStandaloneConfiguration.java │ │ ├── dao/ │ │ │ ├── BaseDynoDAO.java │ │ │ ├── DynoQueueDAO.java │ │ │ ├── RedisEventHandlerDAO.java │ │ │ ├── RedisExecutionDAO.java │ │ │ ├── RedisMetadataDAO.java │ │ │ ├── RedisPollDataDAO.java │ │ │ └── RedisRateLimitingDAO.java │ │ ├── dynoqueue/ │ │ │ ├── ConfigurationHostSupplier.java │ │ │ ├── LocalhostHostSupplier.java │ │ │ └── RedisQueuesShardingStrategyProvider.java │ │ └── jedis/ │ │ ├── JedisCluster.java │ │ ├── JedisMock.java │ │ ├── JedisProxy.java │ │ ├── JedisSentinel.java │ │ └── JedisStandalone.java │ └── test/ │ └── java/ │ └── com/ │ └── netflix/ │ └── conductor/ │ └── redis/ │ ├── config/ │ │ └── utils/ │ │ └── RedisQueuesShardingStrategyProviderTest.java │ ├── dao/ │ │ ├── BaseDynoDAOTest.java │ │ ├── DynoQueueDAOTest.java │ │ ├── RedisEventHandlerDAOTest.java │ │ ├── RedisExecutionDAOTest.java │ │ ├── RedisMetadataDAOTest.java │ │ ├── RedisPollDataDAOTest.java │ │ └── RedisRateLimitDAOTest.java │ └── jedis/ │ ├── ConfigurationHostSupplierTest.java │ ├── JedisClusterTest.java │ └── JedisSentinelTest.java ├── rest/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── netflix/ │ │ │ └── conductor/ │ │ │ └── rest/ │ │ │ ├── config/ │ │ │ │ ├── RequestMappingConstants.java │ │ │ │ └── RestConfiguration.java │ │ │ ├── controllers/ │ │ │ │ ├── AdminResource.java │ │ │ │ ├── ApplicationExceptionMapper.java │ │ │ │ ├── EventResource.java │ │ │ │ ├── HealthCheckResource.java │ │ │ │ ├── MetadataResource.java │ │ │ │ ├── QueueAdminResource.java │ │ │ │ ├── TaskResource.java │ │ │ │ ├── ValidationExceptionMapper.java │ │ │ │ ├── WorkflowBulkResource.java │ │ │ │ └── WorkflowResource.java │ │ │ └── startup/ │ │ │ └── KitchenSinkInitializer.java │ │ └── resources/ │ │ ├── kitchensink/ │ │ │ ├── kitchenSink-ephemeralWorkflowWithEphemeralTasks.json │ │ │ ├── kitchenSink-ephemeralWorkflowWithStoredTasks.json │ │ │ ├── kitchensink.json │ │ │ ├── sub_flow_1.json │ │ │ ├── wf1.json │ │ │ └── wf2.json │ │ └── static/ │ │ └── index.html │ └── test/ │ └── java/ │ └── com/ │ └── netflix/ │ └── conductor/ │ └── rest/ │ └── controllers/ │ ├── AdminResourceTest.java │ ├── EventResourceTest.java │ ├── MetadataResourceTest.java │ ├── TaskResourceTest.java │ └── WorkflowResourceTest.java ├── server/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── netflix/ │ │ │ └── conductor/ │ │ │ └── Conductor.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── additional-spring-configuration-metadata.json │ │ ├── application.properties │ │ ├── banner.txt │ │ └── log4j2.xml │ └── test/ │ └── java/ │ └── com/ │ └── netflix/ │ └── conductor/ │ └── common/ │ └── config/ │ └── ConductorObjectMapperTest.java ├── settings.gradle ├── springboot-bom-overrides.gradle ├── test-harness/ │ ├── build.gradle │ └── src/ │ └── test/ │ ├── groovy/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ └── test/ │ │ ├── base/ │ │ │ ├── AbstractResiliencySpecification.groovy │ │ │ └── AbstractSpecification.groovy │ │ ├── integration/ │ │ │ ├── DecisionTaskSpec.groovy │ │ │ ├── DoWhileSpec.groovy │ │ │ ├── DynamicForkJoinSpec.groovy │ │ │ ├── EventTaskSpec.groovy │ │ │ ├── ExclusiveJoinSpec.groovy │ │ │ ├── ExternalPayloadStorageSpec.groovy │ │ │ ├── FailureWorkflowSpec.groovy │ │ │ ├── ForkJoinSpec.groovy │ │ │ ├── HierarchicalForkJoinSubworkflowRerunSpec.groovy │ │ │ ├── HierarchicalForkJoinSubworkflowRestartSpec.groovy │ │ │ ├── HierarchicalForkJoinSubworkflowRetrySpec.groovy │ │ │ ├── JsonJQTransformSpec.groovy │ │ │ ├── LambdaAndTerminateTaskSpec.groovy │ │ │ ├── NestedForkJoinSubWorkflowSpec.groovy │ │ │ ├── SetVariableTaskSpec.groovy │ │ │ ├── SimpleWorkflowSpec.groovy │ │ │ ├── StartWorkflowSpec.groovy │ │ │ ├── SubWorkflowRerunSpec.groovy │ │ │ ├── SubWorkflowRestartSpec.groovy │ │ │ ├── SubWorkflowRetrySpec.groovy │ │ │ ├── SubWorkflowSpec.groovy │ │ │ ├── SwitchTaskSpec.groovy │ │ │ ├── SystemTaskSpec.groovy │ │ │ ├── TaskLimitsWorkflowSpec.groovy │ │ │ ├── TestWorkflowSpec.groovy │ │ │ ├── WaitTaskSpec.groovy │ │ │ └── WorkflowAndTaskConfigurationSpec.groovy │ │ ├── resiliency/ │ │ │ ├── QueueResiliencySpec.groovy │ │ │ └── TaskResiliencySpec.groovy │ │ └── util/ │ │ └── WorkflowTestUtil.groovy │ ├── java/ │ │ └── com/ │ │ └── netflix/ │ │ └── conductor/ │ │ ├── ConductorTestApp.java │ │ └── test/ │ │ ├── integration/ │ │ │ ├── AbstractEndToEndTest.java │ │ │ ├── grpc/ │ │ │ │ ├── AbstractGrpcEndToEndTest.java │ │ │ │ └── GrpcEndToEndTest.java │ │ │ └── http/ │ │ │ ├── AbstractHttpEndToEndTest.java │ │ │ └── HttpEndToEndTest.java │ │ └── utils/ │ │ ├── MockExternalPayloadStorage.java │ │ └── UserTask.java │ └── resources/ │ ├── application-integrationtest.properties │ ├── concurrency_limited_task_workflow_integration_test.json │ ├── conditional_switch_task_workflow_integration_test.json │ ├── conditional_system_task_workflow_integration_test.json │ ├── conditional_task_workflow_integration_test.json │ ├── decision_and_fork_join_integration_test.json │ ├── decision_and_terminate_integration_test.json │ ├── do_while_as_subtask_integration_test.json │ ├── do_while_five_loop_over_integration_test.json │ ├── do_while_integration_test.json │ ├── do_while_iteration_fix_test.json │ ├── do_while_multiple_integration_test.json │ ├── do_while_set_variable_fix.json │ ├── do_while_sub_workflow_integration_test.json │ ├── do_while_system_tasks.json │ ├── do_while_with_decision_task.json │ ├── dynamic_fork_join_integration_test.json │ ├── event_workflow_integration_test.json │ ├── exclusive_join_integration_test.json │ ├── failure_workflow_for_terminate_task_workflow.json │ ├── fork_join_integration_test.json │ ├── fork_join_sub_workflow.json │ ├── fork_join_with_no_task_retry_integration_test.json │ ├── fork_join_with_optional_sub_workflow_forks_integration_test.json │ ├── hierarchical_fork_join_swf.json │ ├── input.json │ ├── json_jq_transform_result_integration_test.json │ ├── nested_fork_join_integration_test.json │ ├── nested_fork_join_swf.json │ ├── nested_fork_join_with_sub_workflow_integration_test.json │ ├── output.json │ ├── rate_limited_simple_task_workflow_integration_test.json │ ├── rate_limited_system_task_workflow_integration_test.json │ ├── sequential_json_jq_transform_integration_test.json │ ├── set_variable_workflow_integration_test.json │ ├── simple_decision_task_integration_test.json │ ├── simple_json_jq_transform_integration_test.json │ ├── simple_lambda_workflow_integration_test.json │ ├── simple_one_task_sub_workflow_integration_test.json │ ├── simple_set_variable_workflow_integration_test.json │ ├── simple_switch_task_integration_test.json │ ├── simple_wait_task_workflow_integration_test.json │ ├── simple_workflow_1_input_template_integration_test.json │ ├── simple_workflow_1_integration_test.json │ ├── simple_workflow_3_integration_test.json │ ├── simple_workflow_with_async_complete_system_task_integration_test.json │ ├── simple_workflow_with_optional_task_integration_test.json │ ├── simple_workflow_with_resp_time_out_integration_test.json │ ├── simple_workflow_with_sub_workflow_inline_def_integration_test.json │ ├── start_workflow_input.json │ ├── switch_and_fork_join_integration_test.json │ ├── switch_and_terminate_integration_test.json │ ├── switch_with_no_default_case_integration_test.json │ ├── terminate_task_completed_workflow_integration_test.json │ ├── terminate_task_failed_workflow_integration.json │ ├── terminate_task_parent_workflow.json │ ├── terminate_task_sub_workflow.json │ ├── test_task_failed_parent_workflow.json │ ├── test_task_failed_sub_workflow.json │ ├── wait_workflow_integration_test.json │ ├── workflow_that_starts_another_workflow.json │ ├── workflow_with_sub_workflow_1_integration_test.json │ └── workflow_with_synchronous_system_task.json └── ui/ ├── .eslintrc ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── README.md ├── cypress/ │ ├── e2e/ │ │ └── spec.cy.js │ ├── fixtures/ │ │ ├── doWhile/ │ │ │ └── doWhileSwitch.json │ │ ├── dynamicFork/ │ │ │ ├── externalizedInput.json │ │ │ ├── noneSpawned.json │ │ │ ├── notExecuted.json │ │ │ ├── oneFailed.json │ │ │ └── success.json │ │ ├── dynamicFork.json │ │ ├── metadataTasks.json │ │ ├── metadataWorkflow.json │ │ ├── taskSearch.json │ │ └── workflowSearch.json │ └── support/ │ ├── commands.ts │ ├── component-index.html │ ├── component.ts │ └── e2e.ts ├── cypress.config.ts ├── package.json ├── public/ │ ├── index.html │ └── robots.txt ├── src/ │ ├── App.jsx │ ├── components/ │ │ ├── Banner.jsx │ │ ├── Button.jsx │ │ ├── ButtonGroup.jsx │ │ ├── ConfirmChoiceDialog.jsx │ │ ├── CustomButtons.jsx │ │ ├── DataTable.jsx │ │ ├── DateRangePicker.jsx │ │ ├── Dropdown.jsx │ │ ├── DropdownButton.jsx │ │ ├── Heading.jsx │ │ ├── Input.jsx │ │ ├── KeyValueTable.jsx │ │ ├── LinearProgress.jsx │ │ ├── NavLink.jsx │ │ ├── Paper.jsx │ │ ├── Pill.jsx │ │ ├── PrimaryButton.jsx │ │ ├── ReactJson.jsx │ │ ├── SecondaryButton.jsx │ │ ├── Select.jsx │ │ ├── SplitButton.jsx │ │ ├── StatusBadge.jsx │ │ ├── Tabs.jsx │ │ ├── TaskLink.jsx │ │ ├── TaskNameInput.jsx │ │ ├── TertiaryButton.jsx │ │ ├── Text.jsx │ │ ├── WorkflowNameInput.jsx │ │ ├── definitionList/ │ │ │ └── DefinitionList.jsx │ │ ├── diagram/ │ │ │ ├── TaskPointer.d.ts │ │ │ ├── TaskResult.d.ts │ │ │ ├── WorkflowDAG.js │ │ │ ├── WorkflowGraph.jsx │ │ │ ├── WorkflowGraph.test.cy.js │ │ │ └── diagram.scss │ │ ├── formik/ │ │ │ ├── FormikCronEditor.jsx │ │ │ ├── FormikDropdown.jsx │ │ │ ├── FormikInput.jsx │ │ │ ├── FormikJsonInput.jsx │ │ │ ├── FormikSwitch.jsx │ │ │ ├── FormikVersionDropdown.jsx │ │ │ ├── FormikWorkflowNameInput.jsx │ │ │ └── cron.css │ │ └── index.js │ ├── data/ │ │ ├── actions.js │ │ ├── bulkactions.js │ │ ├── common.js │ │ ├── misc.js │ │ ├── task.js │ │ └── workflow.js │ ├── hooks/ │ │ └── useTime.js │ ├── index.css │ ├── index.js │ ├── pages/ │ │ ├── definition/ │ │ │ ├── EventHandler.jsx │ │ │ ├── ResetConfirmationDialog.jsx │ │ │ ├── SaveTaskDialog.jsx │ │ │ ├── SaveWorkflowDialog.jsx │ │ │ ├── TaskDefinition.jsx │ │ │ └── WorkflowDefinition.jsx │ │ ├── definitions/ │ │ │ ├── EventHandler.jsx │ │ │ ├── Header.jsx │ │ │ ├── Task.jsx │ │ │ └── Workflow.jsx │ │ ├── execution/ │ │ │ ├── ActionModule.jsx │ │ │ ├── Execution.jsx │ │ │ ├── ExecutionInputOutput.jsx │ │ │ ├── ExecutionJson.jsx │ │ │ ├── ExecutionSummary.jsx │ │ │ ├── Legend.jsx │ │ │ ├── RightPanel.jsx │ │ │ ├── TaskDetails.jsx │ │ │ ├── TaskList.jsx │ │ │ ├── TaskLogs.jsx │ │ │ ├── TaskPollData.jsx │ │ │ ├── TaskSummary.jsx │ │ │ ├── Timeline.jsx │ │ │ └── timeline.scss │ │ ├── executions/ │ │ │ ├── BulkActionModule.jsx │ │ │ ├── ResultsTable.jsx │ │ │ ├── SearchTabs.jsx │ │ │ ├── TaskResultsTable.jsx │ │ │ ├── TaskSearch.jsx │ │ │ ├── WorkflowSearch.jsx │ │ │ └── executionsStyles.js │ │ ├── kitchensink/ │ │ │ ├── DataTableDemo.jsx │ │ │ ├── DiagramTest.jsx │ │ │ ├── Dropdown.jsx │ │ │ ├── EnhancedTable.jsx │ │ │ ├── Examples.jsx │ │ │ ├── Gantt.jsx │ │ │ ├── KitchenSink.jsx │ │ │ └── sampleMovieData.js │ │ ├── misc/ │ │ │ └── TaskQueue.jsx │ │ ├── styles.js │ │ └── workbench/ │ │ ├── ExecutionHistory.jsx │ │ ├── RunHistory.tsx │ │ ├── Workbench.jsx │ │ └── WorkbenchForm.jsx │ ├── plugins/ │ │ ├── AppBarModules.jsx │ │ ├── AppLogo.jsx │ │ ├── CustomAppBarButtons.jsx │ │ ├── CustomRoutes.jsx │ │ ├── constants.js │ │ ├── customTypeRenderers.jsx │ │ ├── env.js │ │ └── fetch.js │ ├── react-app-env.d.ts │ ├── schema/ │ │ ├── task.js │ │ └── workflow.js │ ├── serviceWorker.js │ ├── setupProxy.js │ ├── setupTests.js │ ├── theme/ │ │ ├── colorOverrides.js │ │ ├── colors.js │ │ ├── index.js │ │ ├── provider.jsx │ │ ├── theme.js │ │ └── variables.js │ └── utils/ │ ├── constants.js │ ├── helpers.js │ ├── localstorage.ts │ └── path.js ├── test-karbon.sh └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ # General # Backend server/build/libs # UI **/node_modules ui/build ================================================ FILE: .gitattributes ================================================ gradlew eol=lf *.gradle eol=lf *.java eol=lf *.groovy eol=lf spring.factories eol=lf *.sh eol=lf docs/* linguist-documentation server/src/main/resources/swagger-ui/* linguist-vendored ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: "" labels: 'type: bug' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **Details** Conductor version: Persistence implementation: Cassandra, Postgres, MySQL, Dynomite etc Queue implementation: Postgres, MySQL, Dynoqueues etc Lock: Redis or Zookeeper? Workflow definition: Task definition: Event handler definition: **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false ================================================ FILE: .github/ISSUE_TEMPLATE/documentation.md ================================================ --- name: Documentation about: Something in the documentation that needs improvement title: "[DOC]: " labels: 'type: docs' assignees: '' --- ## What are you missing in the docs ## Proposed text ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Propose a new feature title: "[FEATURE]: " labels: 'type: feature' assignees: '' --- Please read our [contributor guide](https://github.com/Netflix/conductor/blob/main/CONTRIBUTING.md) before creating an issue. Also consider discussing your idea on the [discussion forum](https://github.com/Netflix/conductor/discussions) first. ## Describe the Feature Request _A clear and concise description of what the feature request is._ ## Describe Preferred Solution _A clear and concise description of what you want to happen._ ## Describe Alternatives _A clear and concise description of any alternative solutions or features you've considered._ ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "gradle" directory: "/" schedule: interval: "weekly" reviewers: - "aravindanr" - "jxu-nflx" - "apanicker-nflx" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" ================================================ FILE: .github/pull_request_template.md ================================================ Pull Request type ---- - [ ] Bugfix - [ ] Feature - [ ] Refactoring (no functional changes, no api changes) - [ ] Build related changes (Please run `./gradlew generateLock saveLock` to refresh dependencies) - [ ] WHOSUSING.md - [ ] Other (please describe): **NOTE**: Please remember to run `./gradlew spotlessApply` to fix any format violations. Changes in this PR ---- _Describe the new behavior from this PR, and why it's needed_ Issue # Alternatives considered ---- _Describe alternative implementation you have considered_ ================================================ FILE: .github/release-drafter.yml ================================================ template: | ## What’s Changed $CHANGES name-template: 'v$RESOLVED_VERSION' tag-template: 'v$RESOLVED_VERSION' categories: - title: 'IMPORTANT' label: 'type: important' - title: 'New' label: 'type: feature' - title: 'Bug Fixes' label: 'type: bug' - title: 'Refactor' label: 'type: maintenance' - title: 'Documentation' label: 'type: docs' - title: 'Dependency Updates' label: 'type: dependencies' version-resolver: minor: labels: - 'type: important' patch: labels: - 'type: bug' - 'type: maintenance' - 'type: docs' - 'type: dependencies' - 'type: feature' exclude-labels: - 'skip-changelog' - 'gradle-wrapper' - 'github_actions' ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: [ push, pull_request ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 - name: Gradle wrapper validation uses: gradle/wrapper-validation-action@v1 - name: Set up Zulu JDK 17 uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: '17' - name: Cache SonarCloud packages uses: actions/cache@v3 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - name: Cache Gradle packages uses: actions/cache@v3 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: ${{ runner.os }}-gradle- - name: Build with Gradle if: github.ref != 'refs/heads/main' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | ./gradlew build --scan - name: Build and Publish snapshot if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' run: | echo "Running build for commit ${{ github.sha }}" ./gradlew build snapshot --scan env: NETFLIX_OSS_SIGNING_KEY: ${{ secrets.ORG_SIGNING_KEY }} NETFLIX_OSS_SIGNING_PASSWORD: ${{ secrets.ORG_SIGNING_PASSWORD }} NETFLIX_OSS_REPO_USERNAME: ${{ secrets.ORG_NETFLIXOSS_USERNAME }} NETFLIX_OSS_REPO_PASSWORD: ${{ secrets.ORG_NETFLIXOSS_PASSWORD }} - name: Publish Test Report uses: mikepenz/action-junit-report@v3 if: always() with: report_paths: '**/build/test-results/test/TEST-*.xml' - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: build-artifacts path: '**/build/reports' - name: Store Buildscan URL uses: actions/upload-artifact@v3 with: name: build-scan path: 'buildscan.log' build-ui: runs-on: ubuntu-latest container: cypress/browsers:node14.17.6-chrome100-ff98 defaults: run: working-directory: ui steps: - uses: actions/checkout@v3 - name: Install Dependencies run: yarn install - name: Build UI run: yarn run build - name: Run E2E Tests uses: cypress-io/github-action@v4 with: working-directory: ui install: false start: yarn run serve-build wait-on: 'http://localhost:5000' - name: Run Component Tests uses: cypress-io/github-action@v4 with: working-directory: ui install: false component: true - name: Archive test screenshots uses: actions/upload-artifact@v2 if: failure() with: name: cypress-screenshots path: ui/cypress/screenshots - name: Archive test videos uses: actions/upload-artifact@v2 if: always() with: name: cypress-videos path: ui/cypress/videos ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish to NetflixOSS and Maven Central on: release: types: - released - prereleased permissions: contents: read jobs: publish: runs-on: ubuntu-latest name: Gradle Build and Publish steps: - uses: actions/checkout@v3 - name: Set up Zulu JDK 17 uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: '17' - name: Cache Gradle packages uses: actions/cache@v3 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | ${{ runner.os }}-gradle- - name: Publish candidate if: startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '-rc.') run: ./gradlew -Prelease.useLastTag=true candidate --scan env: NETFLIX_OSS_SONATYPE_USERNAME: ${{ secrets.ORG_SONATYPE_USERNAME }} NETFLIX_OSS_SONATYPE_PASSWORD: ${{ secrets.ORG_SONATYPE_PASSWORD }} NETFLIX_OSS_SIGNING_KEY: ${{ secrets.ORG_SIGNING_KEY }} NETFLIX_OSS_SIGNING_PASSWORD: ${{ secrets.ORG_SIGNING_PASSWORD }} NETFLIX_OSS_REPO_USERNAME: ${{ secrets.ORG_NETFLIXOSS_USERNAME }} NETFLIX_OSS_REPO_PASSWORD: ${{ secrets.ORG_NETFLIXOSS_PASSWORD }} - name: Publish release if: startsWith(github.ref, 'refs/tags/v') && (!contains(github.ref, '-rc.')) run: ./gradlew -Prelease.useLastTag=true final --scan env: NETFLIX_OSS_SONATYPE_USERNAME: ${{ secrets.ORG_SONATYPE_USERNAME }} NETFLIX_OSS_SONATYPE_PASSWORD: ${{ secrets.ORG_SONATYPE_PASSWORD }} NETFLIX_OSS_SIGNING_KEY: ${{ secrets.ORG_SIGNING_KEY }} NETFLIX_OSS_SIGNING_PASSWORD: ${{ secrets.ORG_SIGNING_PASSWORD }} NETFLIX_OSS_REPO_USERNAME: ${{ secrets.ORG_NETFLIXOSS_USERNAME }} NETFLIX_OSS_REPO_PASSWORD: ${{ secrets.ORG_NETFLIXOSS_PASSWORD }} - name: Publish tag to community repo if: startsWith(github.ref, 'refs/tags/v') run: | export TAG=$(git describe --tags --abbrev=0) echo "Current release version is $TAG" echo "Triggering community build" curl \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: Bearer ${{ secrets.COMMUNITY_REPO_TRIGGER }}" \ -X POST https://api.github.com/repos/Netflix/conductor-community/dispatches \ -d '{"event_type": "publish_build","client_payload": {"tag":"'"$TAG"'"}}' - name: Publish Test Report uses: mikepenz/action-junit-report@v3 if: always() # always run even if the previous step fails with: report_paths: '**/build/test-results/test/TEST-*.xml' ================================================ FILE: .github/workflows/release_draft.yml ================================================ name: Release Drafter on: push: branches: - main permissions: contents: read jobs: update_release_draft: permissions: contents: write # for release-drafter/release-drafter to create a github release pull-requests: write # for release-drafter/release-drafter to add label to PR runs-on: ubuntu-latest steps: - uses: release-drafter/release-drafter@v5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/stale.yml ================================================ name: Close stale issues and pull requests on: schedule: - cron: "0 0 * * *" permissions: contents: read jobs: stale: permissions: issues: write # for actions/stale to close stale issues pull-requests: write # for actions/stale to close stale PRs runs-on: ubuntu-latest steps: - uses: actions/stale@v6 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue is stale, because it has been open for 45 days with no activity. Remove the stale label or comment, or this will be closed in 7 days.' close-issue-message: 'This issue was closed, because it has been stalled for 7 days with no activity.' stale-pr-message: 'This PR is stale, because it has been open for 45 days with no activity. Remove the stale label or comment, or this will be closed in 7 days.' close-pr-message: 'This PR was closed, because it has been stalled for 7 days with no activity.' days-before-issue-stale: 45 days-before-issue-close: 7 days-before-pr-stale: 45 days-before-pr-close: 7 exempt-issue-labels: 'type: bug,enhancement,work_in_progress,help_wanted' ================================================ FILE: .github/workflows/update-gradle-wrapper.yml ================================================ name: Update Gradle Wrapper on: schedule: - cron: "0 0 * * *" workflow_dispatch: jobs: update-gradle-wrapper: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Zulu JDK 17 uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: '17' - name: Update Gradle Wrapper uses: gradle-update/update-gradle-wrapper-action@v1 ================================================ FILE: .gitignore ================================================ # Java Build .gradle .classpath dump.rdb out bin target buildscan.log /docs/site # Python /polyglot-clients/python/conductor.egg-info *.pyc # OS & IDE .DS_Store .settings .vscode .idea .project *.iml # JS & UI Related node_modules /ui/build /ui/public/monaco-editor # publishing secrets secrets/signing-key # local builds lib/ build/ */build/ # asdf version file .tool-versions ================================================ FILE: CHANGELOG.md ================================================ Conductor has been upgraded to use the SpringBoot framework and requires Java11 or above. #### NOTE: The java clients (conductor-client, conductor-client-spring, conductor-grpc-client) are still compiled using Java8 to ensure backward compatibility and smoother migration. ## Removals/Deprecations - Removed support for EmbeddedElasticSearch - Removed deprecated constructors in DynoQueueDAO - Removed deprecated methods in the Worker interface - Removed OAuth Support in HTTP task (Looking for contributions for OAuth/OAuth2.0) - Removed deprecated fields and methods in the Workflow object - Removed deprecated fields and methods in the Task object - Removed deprecated fields and methods in the WorkflowTask object Removed unused methods from QueueDAO: - List pop(String, int, int, long) - List pollMessages(String, int, int, long) Removed APIs: - GET /tasks/in_progress/{tasktype} - GET /tasks/in_progress/{workflowId}/{taskRefName} - POST /tasks/{taskId}/ack - POST /tasks/queue/requeue - DELETE /queue/{taskType}/{taskId} - GET /event/queues - GET /event/queues/providers - void restart(String) in workflow client - List getPendingTasksByType(String, String, Integer) in task client - Task getPendingTaskForWorkflow(String, String) in task client - boolean preAck(Task) in Worker - int getPollCount() in Worker ## What's changed Changes to configurations: ### `azureblob-storage` module: | Old | New | Default | | --- | --- | --- | | workflow.external.payload.storage.azure_blob.connection_string | conductor.external-payload-storage.azureblob.connectionString | null | | workflow.external.payload.storage.azure_blob.container_name | conductor.external-payload-storage.azureblob.containerName | conductor-payloads | | workflow.external.payload.storage.azure_blob.endpoint | conductor.external-payload-storage.azureblob.endpoint | null | | workflow.external.payload.storage.azure_blob.sas_token | conductor.external-payload-storage.azureblob.sasToken | null | | workflow.external.payload.storage.azure_blob.signedurlexpirationseconds | conductor.external-payload-storage.azureblob.signedUrlExpirationDuration | 5s | | workflow.external.payload.storage.azure_blob.workflow_input_path | conductor.external-payload-storage.azureblob.workflowInputPath | workflow/input/ | | workflow.external.payload.storage.azure_blob.workflow_output_path | conductor.external-payload-storage.azureblob.workflowOutputPath | workflow/output/ | | workflow.external.payload.storage.azure_blob.task_input_path | conductor.external-payload-storage.azureblob.taskInputPath | task/input/ | | workflow.external.payload.storage.azure_blob.task_output_path | conductor.external-payload-storage.azureblob.taskOutputPath | task/output/ | ### `cassandra-persistence` module: | Old | New | Default | | --- | --- | --- | | workflow.cassandra.host | conductor.cassandra.hostAddress | 127.0.0.1 | | workflow.cassandra.port | conductor.cassandra.port | 9142 | | workflow.cassandra.cluster | conductor.cassandra.cluster | "" | | workflow.cassandra.keyspace | conductor.cassandra.keyspace | conductor | | workflow.cassandra.shard.size | conductor.cassandra.shardSize | 100 | | workflow.cassandra.replication.strategy | conductor.cassandra.replicationStrategy | SimpleStrategy | | workflow.cassandra.replication.factor.key | conductor.cassandra.replicationFactorKey | replication_factor | | workflow.cassandra.replication.factor.value | conductor.cassandra.replicationFactorValue | 3 | | workflow.cassandra.read.consistency.level | conductor.cassandra.readConsistencyLevel | LOCAL_QUORUM | | workflow.cassandra.write.consistency.level | conductor.cassandra.writeConsistencyLevel | LOCAL_QUORUM | | conductor.taskdef.cache.refresh.time.seconds | conductor.cassandra.taskDefCacheRefreshInterval | 60s | | conductor.eventhandler.cache.refresh.time.seconds | conductor.cassandra.eventHandlerCacheRefreshInterval | 60s | | workflow.event.execution.persistence.ttl.seconds | conductor.cassandra.eventExecutionPersistenceTTL | 0s | ### `contribs` module: | Old | New | Default | | --- | --- | --- | | workflow.archival.ttl.seconds | conductor.workflow-status-listener.archival.ttlDuration | 0s | | workflow.archival.delay.queue.worker.thread.count | conductor.workflow-status-listener.archival.delayQueueWorkerThreadCount | 5 | | workflow.archival.delay.seconds | conductor.workflow-status-listener.archival.delaySeconds | 60 | | | | | workflowstatuslistener.publisher.success.queue | conductor.workflow-status-listener.queue-publisher.successQueue | _callbackSuccessQueue | | workflowstatuslistener.publisher.failure.queue | conductor.workflow-status-listener.queue-publisher.failureQueue | _callbackFailureQueue | | | | | | com.netflix.conductor.contribs.metrics.LoggingMetricsModule.reportPeriodSeconds | conductor.metrics-logger.reportInterval | 30s | | | | | | workflow.event.queues.amqp.batchSize | conductor.event-queues.amqp.batchSize | 1 | | workflow.event.queues.amqp.pollTimeInMs | conductor.event-queues.amqp.pollTimeDuration | 100ms | | workflow.event.queues.amqp.hosts | conductor.event-queues.amqp.hosts | localhost | | workflow.event.queues.amqp.username | conductor.event-queues.amqp.username | guest | | workflow.event.queues.amqp.password | conductor.event-queues.amqp.password | guest | | workflow.event.queues.amqp.virtualHost | conductor.event-queues.amqp.virtualHost | / | | workflow.event.queues.amqp.port | conductor.event-queues.amqp.port.port | 5672 | | workflow.event.queues.amqp.connectionTimeout | conductor.event-queues.amqp.connectionTimeout | 60000ms | | workflow.event.queues.amqp.useNio | conductor.event-queues.amqp.useNio | false | | workflow.event.queues.amqp.durable | conductor.event-queues.amqp.durable | true | | workflow.event.queues.amqp.exclusive | conductor.event-queues.amqp.exclusive | false | | workflow.event.queues.amqp.autoDelete | conductor.event-queues.amqp.autoDelete | false | | workflow.event.queues.amqp.contentType | conductor.event-queues.amqp.contentType | application/json | | workflow.event.queues.amqp.contentEncoding | conductor.event-queues.amqp.contentEncoding | UTF-8 | | workflow.event.queues.amqp.amqp_exchange | conductor.event-queues.amqp.exchangeType | topic | | workflow.event.queues.amqp.deliveryMode | conductor.event-queues.amqp.deliveryMode | 2 | | workflow.listener.queue.useExchange | conductor.event-queues.amqp.useExchange | true | | workflow.listener.queue.prefix | conductor.event-queues.amqp.listenerQueuePrefix | "" | | | | | | io.nats.streaming.clusterId | conductor.event-queues.nats-stream.clusterId | test-cluster | | io.nats.streaming.durableName | conductor.event-queues.nats-stream.durableName | null | | io.nats.streaming.url | conductor.event-queues.nats-stream.url | nats://localhost:4222 | | | | | | workflow.event.queues.sqs.batchSize | conductor.event-queues.sqs.batchSize | 1 | | workflow.event.queues.sqs.pollTimeInMS | conductor.event-queues.sqs.pollTimeDuration | 100ms | | workflow.event.queues.sqs.visibilityTimeoutInSeconds | conductor.event-queues.sqs.visibilityTimeout | 60s | | workflow.listener.queue.prefix | conductor.event-queues.sqs.listenerQueuePrefix | "" | | workflow.listener.queue.authorizedAccounts | conductor.event-queues.sqs.authorizedAccounts | "" | | | | | | workflow.external.payload.storage.s3.bucket | conductor.external-payload-storage.s3.bucketName | conductor_payloads | | workflow.external.payload.storage.s3.signedurlexpirationseconds | conductor.external-payload-storage.s3.signedUrlExpirationDuration | 5s | | workflow.external.payload.storage.s3.region | conductor.external-payload-storage.s3.region | us-east-1 | | | | | | http.task.read.timeout | conductor.tasks.http.readTimeout | 150ms | | http.task.connect.timeout | conductor.tasks.http.connectTimeout | 100ms | | | | | | kafka.publish.request.timeout.ms | conductor.tasks.kafka-publish.requestTimeout | 100ms | | kafka.publish.max.block.ms | conductor.tasks.kafka-publish.maxBlock | 500ms | | kafka.publish.producer.cache.size | conductor.tasks.kafka-publish.cacheSize | 10 | | kafka.publish.producer.cache.time.ms | conductor.tasks.kafka-publish.cacheTime | 120000ms | ### `core` module: | Old | New | Default | | --- | --- | --- | | environment | _removed_ | | | STACK | conductor.app.stack | test | | APP_ID | conductor.app.appId | conductor | | workflow.executor.service.max.threads | conductor.app.executorServiceMaxThreadCount | 50 | | decider.sweep.frequency.seconds | conductor.app.sweepFrequency | 30s | | workflow.sweeper.thread.count | conductor.app.sweeperThreadCount | 5 | | - | conductor.app.sweeperWorkflowPollTimeout | 2000ms | | workflow.event.processor.thread.count | conductor.app.eventProcessorThreadCount | 2 | | workflow.event.message.indexing.enabled | conductor.app.eventMessageIndexingEnabled | true | | workflow.event.execution.indexing.enabled | conductor.app.eventExecutionIndexingEnabled | true | | workflow.decider.locking.enabled | conductor.app.workflowExecutionLockEnabled | false | | workflow.locking.lease.time.ms | conductor.app.lockLeaseTime | 60000ms | | workflow.locking.time.to.try.ms | conductor.app.lockTimeToTry | 500ms | | tasks.active.worker.lastpoll | conductor.app.activeWorkerLastPollTimeout | 10s | | task.queue.message.postponeSeconds | conductor.app.taskExecutionPostponeDuration | 60s | | workflow.taskExecLog.indexing.enabled | conductor.app.taskExecLogIndexingEnabled | true | | async.indexing.enabled | conductor.app.asyncIndexingEnabled | false | | workflow.system.task.worker.thread.count | conductor.app.systemTaskWorkerThreadCount | # available processors * 2 | | workflow.system.task.worker.callback.seconds | conductor.app.systemTaskWorkerCallbackDuration | 30s | | workflow.system.task.worker.poll.interval | conductor.app.systemTaskWorkerPollInterval | 50s | | workflow.system.task.worker.executionNameSpace | conductor.app.systemTaskWorkerExecutionNamespace | "" | | workflow.isolated.system.task.worker.thread.count | conductor.app.isolatedSystemTaskWorkerThreadCount | 1 | | workflow.system.task.queue.pollCount | conductor.app.systemTaskMaxPollCount | 1 | | async.update.short.workflow.duration.seconds | conductor.app.asyncUpdateShortRunningWorkflowDuration | 30s | | async.update.delay.seconds | conductor.app.asyncUpdateDelay | 60s | | summary.input.output.json.serialization.enabled | conductor.app.summary-input-output-json-serialization.enabled | false | | workflow.owner.email.mandatory | conductor.app.ownerEmailMandatory | true | | workflow.repairservice.enabled | conductor.app.workflowRepairServiceEnabled | false | | workflow.event.queue.scheduler.poll.thread.count | conductor.app.eventSchedulerPollThreadCount | # CPU cores | | workflow.dyno.queues.pollingInterval | conductor.app.eventQueuePollInterval | 100ms | | workflow.dyno.queues.pollCount | conductor.app.eventQueuePollCount | 10 | | workflow.dyno.queues.longPollTimeout | conductor.app.eventQueueLongPollTimeout | 1000ms | | conductor.workflow.input.payload.threshold.kb | conductor.app.workflowInputPayloadSizeThreshold | 5120KB | | conductor.max.workflow.input.payload.threshold.kb | conductor.app.maxWorkflowInputPayloadSizeThreshold | 10240KB | | conductor.workflow.output.payload.threshold.kb | conductor.app.workflowOutputPayloadSizeThreshold | 5120KB | | conductor.max.workflow.output.payload.threshold.kb | conductor.app.maxWorkflowOutputPayloadSizeThreshold | 10240KB | | conductor.task.input.payload.threshold.kb | conductor.app.taskInputPayloadSizeThreshold | 3072KB | | conductor.max.task.input.payload.threshold.kb | conductor.app.maxTaskInputPayloadSizeThreshold | 10240KB | | conductor.task.output.payload.threshold.kb | conductor.app.taskOutputPayloadSizeThreshold | 3072KB | | conductor.max.task.output.payload.threshold.kb | conductor.app.maxTaskOutputPayloadSizeThreshold | 10240KB | | conductor.max.workflow.variables.payload.threshold.kb | conductor.app.maxWorkflowVariablesPayloadSizeThreshold | 256KB | | | | | | workflow.isolated.system.task.enable | conductor.app.isolatedSystemTaskEnabled | false | | workflow.isolated.system.task.poll.time.secs | conductor.app.isolatedSystemTaskQueuePollInterval | 10s | | | | | | workflow.task.pending.time.threshold.minutes | conductor.app.taskPendingTimeThreshold | 60m | | | | | | workflow.monitor.metadata.refresh.counter | conductor.workflow-monitor.metadataRefreshInterval | 10 | | workflow.monitor.stats.freq.seconds | conductor.workflow-monitor.statsFrequency | 60s | ### `es6-persistence` module: | Old | New | Default | | --- | --- | --- | | workflow.elasticsearch.version | conductor.elasticsearch.version | 6 | | workflow.elasticsearch.url | conductor.elasticsearch.url | localhost:9300 | | workflow.elasticsearch.index.name | conductor.elasticsearch.indexPrefix | conductor | | workflow.elasticsearch.tasklog.index.name | _removed_ | | | workflow.elasticsearch.cluster.health.color | conductor.elasticsearch.clusterHealthColor | green | | workflow.elasticsearch.archive.search.batchSize | _removed_ | | | workflow.elasticsearch.index.batchSize | conductor.elasticsearch.indexBatchSize | 1 | | workflow.elasticsearch.async.dao.worker.queue.size | conductor.elasticsearch.asyncWorkerQueueSize | 100 | | workflow.elasticsearch.async.dao.max.pool.size | conductor.elasticsearch.asyncMaxPoolSize | 12 | | workflow.elasticsearch.async.buffer.flush.timeout.seconds | conductor.elasticsearch.asyncBufferFlushTimeout | 10s | | workflow.elasticsearch.index.shard.count | conductor.elasticsearch.indexShardCount | 5 | | workflow.elasticsearch.index.replicas.count | conductor.elasticsearch.indexReplicasCount | 1 | | tasklog.elasticsearch.query.size | conductor.elasticsearch.taskLogResultLimit | 10 | | workflow.elasticsearch.rest.client.connectionRequestTimeout.milliseconds | conductor.elasticsearch.restClientConnectionRequestTimeout | -1 | | workflow.elasticsearch.auto.index.management.enabled | conductor.elasticsearch.autoIndexManagementEnabled | true | | workflow.elasticsearch.document.type.override | conductor.elasticsearch.documentTypeOverride | "" | ### `es7-persistence` module: | Old | New | Default | | --- | --- | --- | | workflow.elasticsearch.version | conductor.elasticsearch.version | 7 | | workflow.elasticsearch.url | conductor.elasticsearch.url | localhost:9300 | | workflow.elasticsearch.index.name | conductor.elasticsearch.indexPrefix | conductor | | workflow.elasticsearch.tasklog.index.name | _removed_ | | | workflow.elasticsearch.cluster.health.color | conductor.elasticsearch.clusterHealthColor | green | | workflow.elasticsearch.archive.search.batchSize | _removed_ | | | workflow.elasticsearch.index.batchSize | conductor.elasticsearch.indexBatchSize | 1 | | workflow.elasticsearch.async.dao.worker.queue.size | conductor.elasticsearch.asyncWorkerQueueSize | 100 | | workflow.elasticsearch.async.dao.max.pool.size | conductor.elasticsearch.asyncMaxPoolSize | 12 | | workflow.elasticsearch.async.buffer.flush.timeout.seconds | conductor.elasticsearch.asyncBufferFlushTimeout | 10s | | workflow.elasticsearch.index.shard.count | conductor.elasticsearch.indexShardCount | 5 | | workflow.elasticsearch.index.replicas.count | conductor.elasticsearch.indexReplicasCount | 1 | | tasklog.elasticsearch.query.size | conductor.elasticsearch.taskLogResultLimit | 10 | | workflow.elasticsearch.rest.client.connectionRequestTimeout.milliseconds | conductor.elasticsearch.restClientConnectionRequestTimeout | -1 | | workflow.elasticsearch.auto.index.management.enabled | conductor.elasticsearch.autoIndexManagementEnabled | true | | workflow.elasticsearch.document.type.override | conductor.elasticsearch.documentTypeOverride | "" | | workflow.elasticsearch.basic.auth.username | conductor.elasticsearch.username | "" | | workflow.elasticsearch.basic.auth.password | conductor.elasticsearch.password | "" | ### `grpc-server` module: | Old | New | Default | | --- | --- | --- | | conductor.grpc.server.port | conductor.grpc-server.port | 8090 | | conductor.grpc.server.reflectionEnabled | conductor.grpc-server.reflectionEnabled | true | ### `mysql-persistence` module (v3.0.0 - v3.0.5): | Old | New | Default | | --- | --- | --- | | jdbc.url | conductor.mysql.jdbcUrl | jdbc:mysql://localhost:3306/conductor | | jdbc.username | conductor.mysql.jdbcUsername | conductor | | jdbc.password | conductor.mysql.jdbcPassword | password | | flyway.enabled | conductor.mysql.flywayEnabled | true | | flyway.table | conductor.mysql.flywayTable | null | | conductor.mysql.connection.pool.size.max | conductor.mysql.connectionPoolMaxSize | -1 | | conductor.mysql.connection.pool.idle.min | conductor.mysql.connectionPoolMinIdle | -1 | | conductor.mysql.connection.lifetime.max | conductor.mysql.connectionMaxLifetime | 30m | | conductor.mysql.connection.idle.timeout | conductor.mysql.connectionIdleTimeout | 10m | | conductor.mysql.connection.timeout | conductor.mysql.connectionTimeout | 30s | | conductor.mysql.transaction.isolation.level | conductor.mysql.transactionIsolationLevel | "" | | conductor.mysql.autocommit | conductor.mysql.autoCommit | false | | conductor.taskdef.cache.refresh.time.seconds | conductor.mysql.taskDefCacheRefreshInterval | 60s | ### `mysql-persistence` module (v3.0.5+): | Old | New | | --- | --- | | jdbc.url | spring.datasource.url | | jdbc.username | spring.datasource.username | | jdbc.password | spring.datasource.password | | flyway.enabled | spring.flyway.enabled | | flyway.table | spring.flyway.table | | conductor.mysql.connection.pool.size.max | spring.datasource.hikari.maximum-pool-size | | conductor.mysql.connection.pool.idle.min | spring.datasource.hikari.minimum-idle | | conductor.mysql.connection.lifetime.max | spring.datasource.hikari.max-lifetime | | conductor.mysql.connection.idle.timeout | spring.datasource.hikari.idle-timeout | | conductor.mysql.connection.timeout | spring.datasource.hikari.connection-timeout | | conductor.mysql.transaction.isolation.level | spring.datasource.hikari.transaction-isolation | | conductor.mysql.autocommit | spring.datasource.hikari.auto-commit | | conductor.taskdef.cache.refresh.time.seconds | conductor.mysql.taskDefCacheRefreshInterval | * for more properties and default values: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#application-properties.data.spring.datasource.hikari ### `postgres-persistence` module (v3.0.0 - v3.0.5): | Old | New | Default | | --- | --- | --- | | jdbc.url | conductor.postgres.jdbcUrl | jdbc:postgresql://localhost:5432/conductor | | jdbc.username | conductor.postgres.jdbcUsername | conductor | | jdbc.password | conductor.postgres.jdbcPassword | password | | flyway.enabled | conductor.postgres.flywayEnabled | true | | flyway.table | conductor.postgres.flywayTable | null | | conductor.postgres.connection.pool.size.max | conductor.postgres.connectionPoolMaxSize | -1 | | conductor.postgres.connection.pool.idle.min | conductor.postgres.connectionPoolMinIdle | -1 | | conductor.postgres.connection.lifetime.max | conductor.postgres.connectionMaxLifetime | 30m | | conductor.postgres.connection.idle.timeout | conductor.postgres.connectionIdleTimeout | 10m | | conductor.postgres.connection.timeout | conductor.postgres.connectionTimeout | 30s | | conductor.postgres.transaction.isolation.level | conductor.postgres.transactionIsolationLevel | "" | | conductor.postgres.autocommit | conductor.postgres.autoCommit | false | | conductor.taskdef.cache.refresh.time.seconds | conductor.postgres.taskDefCacheRefreshInterval | 60s | ### `postgres-persistence` module (v3.0.5+): | Old | New | | --- | --- | | jdbc.url | spring.datasource.url | | jdbc.username | spring.datasource.username | | jdbc.password | spring.datasource.password | | flyway.enabled | spring.flyway.enabled | | flyway.table | spring.flyway.table | | conductor.postgres.connection.pool.size.max | spring.datasource.hikari.maximum-pool-size | | conductor.postgres.connection.pool.idle.min | spring.datasource.hikari.minimum-idle | | conductor.postgres.connection.lifetime.max | spring.datasource.hikari.max-lifetime | | conductor.postgres.connection.idle.timeout | spring.datasource.hikari.idle-timeout | | conductor.postgres.connection.timeout | spring.datasource.hikari.connection-timeout | | conductor.postgres.transaction.isolation.level | spring.datasource.hikari.transaction-isolation | | conductor.postgres.autocommit | spring.datasource.hikari.auto-commit | | conductor.taskdef.cache.refresh.time.seconds | conductor.postgres.taskDefCacheRefreshInterval | * for more properties and default values: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#application-properties.data.spring.datasource.hikari ### `redis-lock` module: | Old | New | Default | | --- | --- | --- | | workflow.redis.locking.server.type | conductor.redis-lock.serverType | single | | workflow.redis.locking.server.address | conductor.redis-lock.serverAddress | redis://127.0.0.1:6379 | | workflow.redis.locking.server.password | conductor.redis-lock.serverPassword | null | | workflow.redis.locking.server.master.name | conductor.redis-lock.serverMasterName | master | | workflow.decider.locking.namespace | conductor.redis-lock.namespace | "" | | workflow.decider.locking.exceptions.ignore | conductor.redis-lock.ignoreLockingExceptions | false | ### `redis-persistence` module: | Old | New | Default | | --- | --- | --- | | EC2_REGION | conductor.redis.dataCenterRegion | us-east-1 | | EC2_AVAILABILITY_ZONE | conductor.redis.availabilityZone | us-east-1c | | workflow.dynomite.cluster | _removed_ | | workflow.dynomite.cluster.name | conductor.redis.clusterName | "" | | workflow.dynomite.cluster.hosts | conductor.redis.hosts | null | | workflow.namespace.prefix | conductor.redis.workflowNamespacePrefix | null | | workflow.namespace.queue.prefix | conductor.redis.queueNamespacePrefix | null | | workflow.dyno.keyspace.domain | conductor.redis.keyspaceDomain | null | | workflow.dynomite.connection.maxConnsPerHost | conductor.redis.maxConnectionsPerHost | 10 | | workflow.dynomite.connection.max.retry.attempt | conductor.redis.maxRetryAttempts | 0 | | workflow.dynomite.connection.max.timeout.exhausted.ms | conductor.redis.maxTimeoutWhenExhausted | 800ms | | queues.dynomite.nonQuorum.port | conductor.redis.queuesNonQuorumPort | 22122 | | workflow.dyno.queue.sharding.strategy | conductor.redis.queueShardingStrategy | roundRobin | | conductor.taskdef.cache.refresh.time.seconds | conductor.redis.taskDefCacheRefreshInterval | 60s | | workflow.event.execution.persistence.ttl.seconds | conductor.redis.eventExecutionPersistenceTTL | 60s | ### `zookeeper-lock` module: | Old | New | Default | | --- | --- | --- | | workflow.zookeeper.lock.connection | conductor.zookeeper-lock.connectionString | localhost:2181 | | workflow.zookeeper.lock.sessionTimeoutMs | conductor.zookeeper-lock.sessionTimeout | 60000ms | | workflow.zookeeper.lock.connectionTimeoutMs | conductor.zookeeper-lock.connectionTimeout | 15000ms | | workflow.decider.locking.namespace | conductor.zookeeper-lock.namespace | "" | ### Component configuration: | Old | New | Default | | --- | --- | --- | | db | conductor.db.type | "" | | workflow.indexing.enabled | conductor.indexing.enabled | true | | conductor.disable.async.workers | conductor.system-task-workers.enabled | true | | decider.sweep.disable | conductor.workflow-reconciler.enabled | true | | conductor.grpc.server.enabled | conductor.grpc-server.enabled | false | | workflow.external.payload.storage | conductor.external-payload-storage.type | dummy | | workflow.default.event.processor.enabled | conductor.default-event-processor.enabled | true | | workflow.events.default.queue.type | conductor.default-event-queue.type | sqs | | workflow.status.listener.type | conductor.workflow-status-listener.type | stub | | - | conductor.task-status-listener.type | stub | | workflow.decider.locking.server | conductor.workflow-execution-lock.type | noop_lock | | | | | | workflow.default.event.queue.enabled | conductor.event-queues.default.enabled | true | | workflow.sqs.event.queue.enabled | conductor.event-queues.sqs.enabled | false | | workflow.amqp.event.queue.enabled | conductor.event-queues.amqp.enabled | false | | workflow.nats.event.queue.enabled | conductor.event-queues.nats.enabled | false | | workflow.nats_stream.event.queue.enabled | conductor.event-queues.nats-stream.enabled | false | | | | | | - | conductor.metrics-logger.enabled | false | | - | conductor.metrics-prometheus.enabled | false | | - | conductor.metrics-datadog.enable | false | | - | conductor.metrics-datadog.api-key | | ================================================ FILE: CODE_OF_CONDUCT.md ================================================ [Code of Conduct](docs/docs/resources/code-of-conduct.md) ================================================ FILE: CONTRIBUTING.md ================================================ [Contributing](docs/docs/resources/contributing.md) ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} Netflix, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: OSSMETADATA ================================================ osslifecycle=active ================================================ FILE: README.md ================================================  ## Announcement > Effective **December 13, 2023**, Netflix will discontinue maintenance of Conductor OSS on GitHub. This strategic decision, while difficult, is essential for realigning our resources to better serve our business objectives with our internal Conductor fork. > > We are *deeply grateful* for your support and contributions over the years. While Netflix will no longer be maintaining this repo, members of the Conductor community have been active in promoting alternative forks of this project, we’ll leave the code as is and trust that the health of the community will remain strong and continue to develop moving forward. # Conductor []() [](https://GitHub.com/Netflix/conductor/releases) [](http://www.apache.org/licenses/LICENSE-2.0) [](https://GitHub.com/Netflix/conductor/stargazers/) [](https://GitHub.com/Netflix/conductor/network/) Conductor is a platform created by Netflix to orchestrate workflows that span across microservices. ## Releases The final release is [](https://GitHub.com/Netflix/conductor/releases) ## Workflow Creation in Code Conductor supports creating workflows using JSON and Code. SDK support for creating workflows using code is available in multiple languages and can be found at https://github.com/conductor-sdk ## Community Contributions The modules contributed by the community are housed at [conductor-community](https://github.com/Netflix/conductor-community). Compatible versions of the community modules are released simultaneously with releases of the main modules. [Discussion Forum](https://github.com/Netflix/conductor/discussions): Please use the forum for questions and discussing ideas and join the community. [List of Conductor community projects](/docs/docs/resources/related.md): Backup tool, Cron like workflow starter, Docker containers and more. ## Getting Started - Building & Running Conductor ### Using Docker: The easiest way to get started is with Docker containers. Please follow the instructions [here](https://conductor.netflix.com/devguide/running/docker.html). ### From Source: Conductor Server is a [Spring Boot](https://spring.io/projects/spring-boot) project and follows all applicable conventions. See instructions [here](https://conductor.netflix.com/devguide/running/source.html). ## Published Artifacts Binaries are available from the [Maven Central Repository](https://search.maven.org/search?q=g:com.netflix.conductor). | Artifact | Description | |---------------------------------|-------------------------------------------------------------------------------------------------| | conductor-common | Common models used by various conductor modules | | conductor-core | Core Conductor module | | conductor-redis-persistence | Persistence and queue using Redis/Dynomite | | conductor-cassandra-persistence | Persistence using Cassandra | | conductor-es6-persistence | Indexing using Elasticsearch 6.X | | conductor-rest | Spring MVC resources for the core services | | conductor-ui | node.js based UI for Conductor | | conductor-client | Java client for Conductor that includes helpers for running worker tasks | | conductor-client-spring | Client starter kit for Spring | | conductor-java-sdk | SDK for writing workflows in code | | conductor-server | Spring Boot Web Application | | conductor-redis-lock | Workflow execution lock implementation using Redis | | conductor-awss3-storage | External payload storage implementation using AWS S3 | | conductor-awssqs-event-queue | Event queue implementation using AWS SQS | | conductor-http-task | Workflow system task implementation to send make requests | | conductor-json-jq-task | Workflow system task implementation to evaluate JSON using [jq](https://stedolan.github.io/jq/) | | conductor-grpc | Protobuf models used by the server and client | | conductor-grpc-client | gRPC client to interact with the gRPC server | | conductor-grpc-server | gRPC server Application | | conductor-test-harness | Integration and regression tests | ## Database Requirements * The default persistence used is Redis * The indexing backend is [Elasticsearch](https://www.elastic.co/) (6.x) ## Other Requirements * JDK 17+ * UI requires Node 14 to build. Earlier Node versions may work but is untested. ## Get Support There are several ways to get in touch with us: * [Slack Community](https://join.slack.com/t/orkes-conductor/shared_invite/zt-xyxqyseb-YZ3hwwAgHJH97bsrYRnSZg) * [GitHub Discussion Forum](https://github.com/Netflix/conductor/discussions) ## Contributions Whether it is a small documentation correction, bug fix or a new feature, contributions are highly appreciated. We just ask you to follow standard OSS guidelines. The [Discussion Forum](https://github.com/Netflix/conductor/discussions) is a good place to ask questions, discuss new features and explore ideas. Please check with us before spending too much time, only to find out later that someone else is already working on a similar feature. `main` branch is the current working branch. Please send your PR's to `main` branch, making sure that it builds on your local system successfully. Also, please make sure all the conflicts are resolved. ## License Copyright 2022 Netflix, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: RELATED.md ================================================ [Related Projects](docs/docs/resources/related.md) ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Supported Versions | Version | Supported | | ------- | ------------------ | | 3.x.x | :white_check_mark: | | 2.x.x | :x: | | 1.x.x | :x: | ## Reporting a Vulnerability Please email conductor@netflix.com to report vulnerabilities. ================================================ FILE: USERS.md ================================================ ## Who uses Conductor? We would like to keep track of whose using Conductor. Please send a pull request with your company name and Github handle. * [Netflix](https://www.netflix.com/) [[@aravindanr](https://github.com/aravindanr)] * [Florida Blue](http://bcbsfl.com/) [[@rickfish](https://github.com/rickfish)] * [UWM](https://www.uwm.com/) [[@zergrushjoe](https://github.com/ZergRushJoe)] * [Deutsche Telekom Digital Labs](https://dtdl.in) [[@jas34](https://github.com/jas34)] [[@deoramanas](https://github.com/deoramanas)] * [VMware](https://www.vmware.com/) [[@taojwmware](https://github.com/taojwmware)] [[@venkag](https://github.com/venkag)] * [JP Morgan Chase](https://www.chase.com/) [[@maheshyaddanapudi](https://github.com/maheshyaddanapudi)] * [Orkes](https://orkes.io/) [[@CherishSantoshi](https://github.com/CherishSantoshi)] * [313X](https://313x.com.br) [[@dalmoveras](https://github.com/dalmoveras)] * [Supercharge](https://supercharge.io) [[@team-supercharge](https://github.com/team-supercharge)] * [GE Healthcare](https://www.gehealthcare.com/) [[@flavioschuindt](https://github.com/flavioschuindt)] * [ReliaQuest](https://www.reliaquest.com/) [[@rq-dbrady](https://github.com/rq-dbrady)] [[@alexmay48](https://github.com/alexmay48)] * [Clari](https://www.clari.com/) [[@TeamJOF](https://github.com/clari)] * [Atlassian](https://www.atlassian.com/) [[@LuisLainez](https://github.com/LuisLainez)] [[@aradu](https://github.com/aradu-atlassian)] ================================================ FILE: annotations/README.md ================================================ # Annotations - `protogen` Annotations - Original Author: Vicent Martí - https://github.com/vmg - Original Repo: https://github.com/vmg/protogen ================================================ FILE: annotations/build.gradle ================================================ dependencies { } ================================================ FILE: annotations/src/main/java/com/netflix/conductor/annotations/protogen/ProtoEnum.java ================================================ /* * Copyright 2022 Netflix, Inc. *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at *
* http://www.apache.org/licenses/LICENSE-2.0 *
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package com.netflix.conductor.annotations.protogen; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * ProtoEnum annotates an enum type that will be exposed via the GRPC API as a native Protocol * Buffers enum. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ProtoEnum {} ================================================ FILE: annotations/src/main/java/com/netflix/conductor/annotations/protogen/ProtoField.java ================================================ /* * Copyright 2022 Netflix, Inc. *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at *
* http://www.apache.org/licenses/LICENSE-2.0 *
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package com.netflix.conductor.annotations.protogen; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * ProtoField annotates a field inside an struct with metadata on how to expose it on its * corresponding Protocol Buffers struct. For a field to be exposed in a ProtoBuf struct, the * containing struct must also be annotated with a {@link ProtoMessage} or {@link ProtoEnum} tag. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ProtoField { /** * Mandatory. Sets the Protocol Buffer ID for this specific field. Once a field has been * annotated with a given ID, the ID can never change to a different value or the resulting * Protocol Buffer struct will not be backwards compatible. * * @return the numeric ID for the field */ int id(); } ================================================ FILE: annotations/src/main/java/com/netflix/conductor/annotations/protogen/ProtoMessage.java ================================================ /* * Copyright 2022 Netflix, Inc. *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at *
* http://www.apache.org/licenses/LICENSE-2.0 *
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package com.netflix.conductor.annotations.protogen; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * ProtoMessage annotates a given Java class so it becomes exposed via the GRPC API as a native * Protocol Buffers struct. The annotated class must be a POJO. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ProtoMessage { /** * Sets whether the generated mapping code will contain a helper to translate the POJO for this * class into the equivalent ProtoBuf object. * * @return whether this class will generate a mapper to ProtoBuf objects */ boolean toProto() default true; /** * Sets whether the generated mapping code will contain a helper to translate the ProtoBuf * object for this class into the equivalent POJO. * * @return whether this class will generate a mapper from ProtoBuf objects */ boolean fromProto() default true; /** * Sets whether this is a wrapper class that will be used to encapsulate complex nested type * interfaces. Wrapper classes are not directly exposed by the ProtoBuf API and must be mapped * manually. * * @return whether this is a wrapper class */ boolean wrapper() default false; } ================================================ FILE: annotations-processor/README.md ================================================ [Annotations Processor](docs/docs/reference-docs/annotations-processor.md) ================================================ FILE: annotations-processor/build.gradle ================================================ sourceSets { example } dependencies { implementation project(':conductor-annotations') api 'com.google.guava:guava:32.1.2-jre' api 'com.squareup:javapoet:1.13.+' api 'com.github.jknack:handlebars:4.3.+' api 'com.google.protobuf:protobuf-java:3.21.12' api 'javax.annotation:javax.annotation-api:1.3.2' api gradleApi() exampleImplementation sourceSets.main.output exampleImplementation project(':conductor-annotations') } task exampleJar(type: Jar) { archiveFileName = 'example.jar' from sourceSets.example.output.classesDirs } testClasses.finalizedBy(exampleJar) ================================================ FILE: annotations-processor/src/example/java/com/example/Example.java ================================================ /* * Copyright 2022 Netflix, Inc. *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at *
* http://www.apache.org/licenses/LICENSE-2.0 *
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package com.example; import com.netflix.conductor.annotations.protogen.ProtoField; import com.netflix.conductor.annotations.protogen.ProtoMessage; @ProtoMessage public class Example { @ProtoField(id = 1) public String name; @ProtoField(id = 2) public Long count; } ================================================ FILE: annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/AbstractMessage.java ================================================ /* * Copyright 2022 Netflix, Inc. *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at *
* http://www.apache.org/licenses/LICENSE-2.0 *
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.netflix.conductor.annotations.protogen.ProtoEnum;
import com.netflix.conductor.annotations.protogen.ProtoMessage;
import com.netflix.conductor.annotationsprocessor.protogen.types.MessageType;
import com.netflix.conductor.annotationsprocessor.protogen.types.TypeMapper;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
public abstract class AbstractMessage {
protected Class> clazz;
protected MessageType type;
protected List
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen;
import javax.lang.model.element.Modifier;
import com.netflix.conductor.annotationsprocessor.protogen.types.MessageType;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
public class Enum extends AbstractMessage {
public enum MapType {
FROM_PROTO("fromProto"),
TO_PROTO("toProto");
private final String methodName;
MapType(String m) {
methodName = m;
}
public String getMethodName() {
return methodName;
}
}
public Enum(Class cls, MessageType parent) {
super(cls, parent);
int protoIndex = 0;
for (java.lang.reflect.Field field : cls.getDeclaredFields()) {
if (field.isEnumConstant()) fields.add(new EnumField(protoIndex++, field));
}
}
@Override
public String getProtoClass() {
return "enum";
}
private MethodSpec javaMap(MapType mt, TypeName from, TypeName to) {
MethodSpec.Builder method = MethodSpec.methodBuilder(mt.getMethodName());
method.addModifiers(Modifier.PUBLIC);
method.returns(to);
method.addParameter(from, "from");
method.addStatement("$T to", to);
method.beginControlFlow("switch (from)");
for (Field field : fields) {
String fromName = (mt == MapType.TO_PROTO) ? field.getName() : field.getProtoName();
String toName = (mt == MapType.TO_PROTO) ? field.getProtoName() : field.getName();
method.addStatement("case $L: to = $T.$L; break", fromName, to, toName);
}
method.addStatement(
"default: throw new $T(\"Unexpected enum constant: \" + from)",
IllegalArgumentException.class);
method.endControlFlow();
method.addStatement("return to");
return method.build();
}
@Override
protected void javaMapFromProto(TypeSpec.Builder type) {
type.addMethod(
javaMap(
MapType.FROM_PROTO,
this.type.getJavaProtoType(),
TypeName.get(this.clazz)));
}
@Override
protected void javaMapToProto(TypeSpec.Builder type) {
type.addMethod(
javaMap(MapType.TO_PROTO, TypeName.get(this.clazz), this.type.getJavaProtoType()));
}
public class EnumField extends Field {
protected EnumField(int index, java.lang.reflect.Field field) {
super(index, field);
}
@Override
public String getProtoTypeDeclaration() {
return String.format("%s = %d", getProtoName(), getProtoIndex());
}
}
}
================================================
FILE: annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/Message.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.lang.model.element.Modifier;
import com.netflix.conductor.annotations.protogen.ProtoField;
import com.netflix.conductor.annotations.protogen.ProtoMessage;
import com.netflix.conductor.annotationsprocessor.protogen.types.AbstractType;
import com.netflix.conductor.annotationsprocessor.protogen.types.MessageType;
import com.netflix.conductor.annotationsprocessor.protogen.types.TypeMapper;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
public class Message extends AbstractMessage {
public Message(Class> cls, MessageType parent) {
super(cls, parent);
for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
ProtoField ann = field.getAnnotation(ProtoField.class);
if (ann == null) continue;
fields.add(new MessageField(ann.id(), field));
}
}
protected ProtoMessage getAnnotation() {
return (ProtoMessage) this.clazz.getAnnotation(ProtoMessage.class);
}
@Override
public String getProtoClass() {
return "message";
}
@Override
protected void javaMapToProto(TypeSpec.Builder type) {
if (!getAnnotation().toProto() || getAnnotation().wrapper()) return;
ClassName javaProtoType = (ClassName) this.type.getJavaProtoType();
MethodSpec.Builder method = MethodSpec.methodBuilder("toProto");
method.addModifiers(Modifier.PUBLIC);
method.returns(javaProtoType);
method.addParameter(this.clazz, "from");
method.addStatement(
"$T to = $T.newBuilder()", javaProtoType.nestedClass("Builder"), javaProtoType);
for (Field field : this.fields) {
if (field instanceof MessageField) {
AbstractType fieldType = ((MessageField) field).getAbstractType();
fieldType.mapToProto(field.getName(), method);
}
}
method.addStatement("return to.build()");
type.addMethod(method.build());
}
@Override
protected void javaMapFromProto(TypeSpec.Builder type) {
if (!getAnnotation().fromProto() || getAnnotation().wrapper()) return;
MethodSpec.Builder method = MethodSpec.methodBuilder("fromProto");
method.addModifiers(Modifier.PUBLIC);
method.returns(this.clazz);
method.addParameter(this.type.getJavaProtoType(), "from");
method.addStatement("$T to = new $T()", this.clazz, this.clazz);
for (Field field : this.fields) {
if (field instanceof MessageField) {
AbstractType fieldType = ((MessageField) field).getAbstractType();
fieldType.mapFromProto(field.getName(), method);
}
}
method.addStatement("return to");
type.addMethod(method.build());
}
public static class MessageField extends Field {
protected AbstractType type;
protected MessageField(int index, java.lang.reflect.Field field) {
super(index, field);
}
public AbstractType getAbstractType() {
if (type == null) {
type = TypeMapper.INSTANCE.get(field.getGenericType());
}
return type;
}
private static Pattern CAMEL_CASE_RE = Pattern.compile("(?<=[a-z])[A-Z]");
private static String toUnderscoreCase(String input) {
Matcher m = CAMEL_CASE_RE.matcher(input);
StringBuilder sb = new StringBuilder();
while (m.find()) {
m.appendReplacement(sb, "_" + m.group());
}
m.appendTail(sb);
return sb.toString().toLowerCase();
}
@Override
public String getProtoTypeDeclaration() {
return String.format(
"%s %s = %d",
getAbstractType().getProtoType(), toUnderscoreCase(getName()), getProtoIndex());
}
@Override
public void getDependencies(Set
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen;
import java.util.HashSet;
import java.util.Set;
import com.netflix.conductor.annotationsprocessor.protogen.types.TypeMapper;
import com.squareup.javapoet.ClassName;
public class ProtoFile {
public static String PROTO_SUFFIX = "Pb";
private ClassName baseClass;
private AbstractMessage message;
private String filePath;
private String protoPackageName;
private String javaPackageName;
private String goPackageName;
public ProtoFile(
Class> object,
String protoPackageName,
String javaPackageName,
String goPackageName) {
this.protoPackageName = protoPackageName;
this.javaPackageName = javaPackageName;
this.goPackageName = goPackageName;
String className = object.getSimpleName() + PROTO_SUFFIX;
this.filePath = "model/" + object.getSimpleName().toLowerCase() + ".proto";
this.baseClass = ClassName.get(this.javaPackageName, className);
this.message = new Message(object, TypeMapper.INSTANCE.baseClass(baseClass, filePath));
}
public String getJavaClassName() {
return baseClass.simpleName();
}
public String getFilePath() {
return filePath;
}
public String getProtoPackageName() {
return protoPackageName;
}
public String getJavaPackageName() {
return javaPackageName;
}
public String getGoPackageName() {
return goPackageName;
}
public AbstractMessage getMessage() {
return message;
}
public Set
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import javax.annotation.Generated;
import javax.lang.model.element.Modifier;
import com.netflix.conductor.annotations.protogen.ProtoMessage;
import com.github.jknack.handlebars.EscapingStrategy;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Template;
import com.github.jknack.handlebars.io.ClassPathTemplateLoader;
import com.github.jknack.handlebars.io.TemplateLoader;
import com.google.common.reflect.ClassPath;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
public class ProtoGen {
private static final String GENERATOR_NAME =
"com.netflix.conductor.annotationsprocessor.protogen";
private String protoPackageName;
private String javaPackageName;
private String goPackageName;
private List
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen;
import java.io.File;
import java.io.IOException;
public class ProtoGenTask {
private String protoPackage;
private String javaPackage;
private String goPackage;
private File protosDir;
private File mapperDir;
private String mapperPackage;
private File sourceJar;
private String sourcePackage;
public String getProtoPackage() {
return protoPackage;
}
public void setProtoPackage(String protoPackage) {
this.protoPackage = protoPackage;
}
public String getJavaPackage() {
return javaPackage;
}
public void setJavaPackage(String javaPackage) {
this.javaPackage = javaPackage;
}
public String getGoPackage() {
return goPackage;
}
public void setGoPackage(String goPackage) {
this.goPackage = goPackage;
}
public File getProtosDir() {
return protosDir;
}
public void setProtosDir(File protosDir) {
this.protosDir = protosDir;
}
public File getMapperDir() {
return mapperDir;
}
public void setMapperDir(File mapperDir) {
this.mapperDir = mapperDir;
}
public String getMapperPackage() {
return mapperPackage;
}
public void setMapperPackage(String mapperPackage) {
this.mapperPackage = mapperPackage;
}
public File getSourceJar() {
return sourceJar;
}
public void setSourceJar(File sourceJar) {
this.sourceJar = sourceJar;
}
public String getSourcePackage() {
return sourcePackage;
}
public void setSourcePackage(String sourcePackage) {
this.sourcePackage = sourcePackage;
}
public void generate() {
ProtoGen generator = new ProtoGen(protoPackage, javaPackage, goPackage);
try {
generator.processPackage(sourceJar, sourcePackage);
generator.writeMapper(mapperDir, mapperPackage);
generator.writeProtos(protosDir);
} catch (IOException e) {
System.err.printf("protogen: failed with %s\n", e);
}
}
public static void main(String[] args) {
if (args == null || args.length < 8) {
throw new RuntimeException(
"protogen configuration incomplete, please provide all required (8) inputs");
}
ProtoGenTask task = new ProtoGenTask();
int argsId = 0;
task.setProtoPackage(args[argsId++]);
task.setJavaPackage(args[argsId++]);
task.setGoPackage(args[argsId++]);
task.setProtosDir(new File(args[argsId++]));
task.setMapperDir(new File(args[argsId++]));
task.setMapperPackage(args[argsId++]);
task.setSourceJar(new File(args[argsId++]));
task.setSourcePackage(args[argsId]);
System.out.println("Running protogen with arguments: " + task);
task.generate();
System.out.println("protogen completed.");
}
@Override
public String toString() {
return "ProtoGenTask{"
+ "protoPackage='"
+ protoPackage
+ '\''
+ ", javaPackage='"
+ javaPackage
+ '\''
+ ", goPackage='"
+ goPackage
+ '\''
+ ", protosDir="
+ protosDir
+ ", mapperDir="
+ mapperDir
+ ", mapperPackage='"
+ mapperPackage
+ '\''
+ ", sourceJar="
+ sourceJar
+ ", sourcePackage='"
+ sourcePackage
+ '\''
+ '}';
}
}
================================================
FILE: annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/types/AbstractType.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.Type;
import java.util.Set;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
public abstract class AbstractType {
Type javaType;
TypeName javaProtoType;
AbstractType(Type javaType, TypeName javaProtoType) {
this.javaType = javaType;
this.javaProtoType = javaProtoType;
}
public Type getJavaType() {
return javaType;
}
public TypeName getJavaProtoType() {
return javaProtoType;
}
public abstract String getProtoType();
public abstract TypeName getRawJavaType();
public abstract void mapToProto(String field, MethodSpec.Builder method);
public abstract void mapFromProto(String field, MethodSpec.Builder method);
public abstract void getDependencies(Set
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.Type;
import java.util.Set;
import javax.lang.model.element.Modifier;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
public class ExternMessageType extends MessageType {
private String externProtoType;
public ExternMessageType(
Type javaType, ClassName javaProtoType, String externProtoType, String protoFilePath) {
super(javaType, javaProtoType, protoFilePath);
this.externProtoType = externProtoType;
}
@Override
public String getProtoType() {
return externProtoType;
}
@Override
public void generateAbstractMethods(Set
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Set;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
abstract class GenericType extends AbstractType {
public GenericType(Type type) {
super(type, null);
}
protected Class getRawType() {
ParameterizedType tt = (ParameterizedType) this.getJavaType();
return (Class) tt.getRawType();
}
protected AbstractType resolveGenericParam(int idx) {
ParameterizedType tt = (ParameterizedType) this.getJavaType();
Type[] types = tt.getActualTypeArguments();
AbstractType abstractType = TypeMapper.INSTANCE.get(types[idx]);
if (abstractType instanceof GenericType) {
return WrappedType.wrap((GenericType) abstractType);
}
return abstractType;
}
public abstract String getWrapperSuffix();
public abstract AbstractType getValueType();
public abstract TypeName resolveJavaProtoType();
@Override
public TypeName getRawJavaType() {
return ClassName.get(getRawType());
}
@Override
public void getDependencies(Set
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.Type;
import java.util.stream.Collectors;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
public class ListType extends GenericType {
private AbstractType valueType;
public ListType(Type type) {
super(type);
}
@Override
public String getWrapperSuffix() {
return "List";
}
@Override
public AbstractType getValueType() {
if (valueType == null) {
valueType = resolveGenericParam(0);
}
return valueType;
}
@Override
public void mapToProto(String field, MethodSpec.Builder method) {
AbstractType subtype = getValueType();
if (subtype instanceof ScalarType) {
method.addStatement(
"to.$L( from.$L() )",
protoMethodName("addAll", field),
javaMethodName("get", field));
} else {
method.beginControlFlow(
"for ($T elem : from.$L())",
subtype.getJavaType(),
javaMethodName("get", field));
method.addStatement("to.$L( toProto(elem) )", protoMethodName("add", field));
method.endControlFlow();
}
}
@Override
public void mapFromProto(String field, MethodSpec.Builder method) {
AbstractType subtype = getValueType();
Type entryType = subtype.getJavaType();
Class collector = TypeMapper.PROTO_LIST_TYPES.get(getRawType());
if (subtype instanceof ScalarType) {
if (entryType.equals(String.class)) {
method.addStatement(
"to.$L( from.$L().stream().collect($T.toCollection($T::new)) )",
javaMethodName("set", field),
protoMethodName("get", field) + "List",
Collectors.class,
collector);
} else {
method.addStatement(
"to.$L( from.$L() )",
javaMethodName("set", field),
protoMethodName("get", field) + "List");
}
} else {
method.addStatement(
"to.$L( from.$L().stream().map(this::fromProto).collect($T.toCollection($T::new)) )",
javaMethodName("set", field),
protoMethodName("get", field) + "List",
Collectors.class,
collector);
}
}
@Override
public TypeName resolveJavaProtoType() {
return ParameterizedTypeName.get(
(ClassName) getRawJavaType(), getValueType().getJavaProtoType());
}
@Override
public String getProtoType() {
return "repeated " + getValueType().getProtoType();
}
}
================================================
FILE: annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/types/MapType.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
public class MapType extends GenericType {
private AbstractType keyType;
private AbstractType valueType;
public MapType(Type type) {
super(type);
}
@Override
public String getWrapperSuffix() {
return "Map";
}
@Override
public AbstractType getValueType() {
if (valueType == null) {
valueType = resolveGenericParam(1);
}
return valueType;
}
public AbstractType getKeyType() {
if (keyType == null) {
keyType = resolveGenericParam(0);
}
return keyType;
}
@Override
public void mapToProto(String field, MethodSpec.Builder method) {
AbstractType valueType = getValueType();
if (valueType instanceof ScalarType) {
method.addStatement(
"to.$L( from.$L() )",
protoMethodName("putAll", field),
javaMethodName("get", field));
} else {
TypeName typeName =
ParameterizedTypeName.get(
Map.Entry.class,
getKeyType().getJavaType(),
getValueType().getJavaType());
method.beginControlFlow(
"for ($T pair : from.$L().entrySet())", typeName, javaMethodName("get", field));
method.addStatement(
"to.$L( pair.getKey(), toProto( pair.getValue() ) )",
protoMethodName("put", field));
method.endControlFlow();
}
}
@Override
public void mapFromProto(String field, MethodSpec.Builder method) {
AbstractType valueType = getValueType();
if (valueType instanceof ScalarType) {
method.addStatement(
"to.$L( from.$L() )",
javaMethodName("set", field),
protoMethodName("get", field) + "Map");
} else {
Type keyType = getKeyType().getJavaType();
Type valueTypeJava = getValueType().getJavaType();
TypeName valueTypePb = getValueType().getJavaProtoType();
ParameterizedTypeName entryType =
ParameterizedTypeName.get(
ClassName.get(Map.Entry.class), TypeName.get(keyType), valueTypePb);
ParameterizedTypeName mapType =
ParameterizedTypeName.get(Map.class, keyType, valueTypeJava);
ParameterizedTypeName hashMapType =
ParameterizedTypeName.get(HashMap.class, keyType, valueTypeJava);
String mapName = field + "Map";
method.addStatement("$T $L = new $T()", mapType, mapName, hashMapType);
method.beginControlFlow(
"for ($T pair : from.$L().entrySet())",
entryType,
protoMethodName("get", field) + "Map");
method.addStatement("$L.put( pair.getKey(), fromProto( pair.getValue() ) )", mapName);
method.endControlFlow();
method.addStatement("to.$L($L)", javaMethodName("set", field), mapName);
}
}
@Override
public TypeName resolveJavaProtoType() {
return ParameterizedTypeName.get(
(ClassName) getRawJavaType(),
getKeyType().getJavaProtoType(),
getValueType().getJavaProtoType());
}
@Override
public String getProtoType() {
AbstractType keyType = getKeyType();
AbstractType valueType = getValueType();
if (!(keyType instanceof ScalarType)) {
throw new IllegalArgumentException(
"cannot map non-scalar map key: " + this.getJavaType());
}
return String.format("map<%s, %s>", keyType.getProtoType(), valueType.getProtoType());
}
}
================================================
FILE: annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/types/MessageType.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Set;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
public class MessageType extends AbstractType {
private String protoFilePath;
public MessageType(Type javaType, ClassName javaProtoType, String protoFilePath) {
super(javaType, javaProtoType);
this.protoFilePath = protoFilePath;
}
@Override
public String getProtoType() {
List
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.Type;
import java.util.Set;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
public class ScalarType extends AbstractType {
private String protoType;
public ScalarType(Type javaType, TypeName javaProtoType, String protoType) {
super(javaType, javaProtoType);
this.protoType = protoType;
}
@Override
public String getProtoType() {
return protoType;
}
@Override
public TypeName getRawJavaType() {
return getJavaProtoType();
}
@Override
public void mapFromProto(String field, MethodSpec.Builder method) {
method.addStatement(
"to.$L( from.$L() )", javaMethodName("set", field), protoMethodName("get", field));
}
private boolean isNullableType() {
final Type jt = getJavaType();
return jt.equals(Boolean.class)
|| jt.equals(Byte.class)
|| jt.equals(Character.class)
|| jt.equals(Short.class)
|| jt.equals(Integer.class)
|| jt.equals(Long.class)
|| jt.equals(Double.class)
|| jt.equals(Float.class)
|| jt.equals(String.class);
}
@Override
public void mapToProto(String field, MethodSpec.Builder method) {
final boolean nullable = isNullableType();
String getter =
(getJavaType().equals(boolean.class) || getJavaType().equals(Boolean.class))
? javaMethodName("is", field)
: javaMethodName("get", field);
if (nullable) method.beginControlFlow("if (from.$L() != null)", getter);
method.addStatement("to.$L( from.$L() )", protoMethodName("set", field), getter);
if (nullable) method.endControlFlow();
}
@Override
public void getDependencies(Set
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import com.google.protobuf.Any;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;
public class TypeMapper {
static Map
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.Type;
import java.util.Set;
import javax.lang.model.element.Modifier;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
public class WrappedType extends AbstractType {
private AbstractType realType;
private MessageType wrappedType;
public static WrappedType wrap(GenericType realType) {
Type valueType = realType.getValueType().getJavaType();
if (!(valueType instanceof Class))
throw new IllegalArgumentException("cannot wrap primitive type: " + valueType);
String className = ((Class) valueType).getSimpleName() + realType.getWrapperSuffix();
MessageType wrappedType = TypeMapper.INSTANCE.get(className);
if (wrappedType == null)
throw new IllegalArgumentException("missing wrapper class: " + className);
return new WrappedType(realType, wrappedType);
}
public WrappedType(AbstractType realType, MessageType wrappedType) {
super(realType.getJavaType(), wrappedType.getJavaProtoType());
this.realType = realType;
this.wrappedType = wrappedType;
}
@Override
public String getProtoType() {
return wrappedType.getProtoType();
}
@Override
public TypeName getRawJavaType() {
return realType.getRawJavaType();
}
@Override
public void mapToProto(String field, MethodSpec.Builder method) {
wrappedType.mapToProto(field, method);
}
@Override
public void mapFromProto(String field, MethodSpec.Builder method) {
wrappedType.mapFromProto(field, method);
}
@Override
public void getDependencies(Set
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen;
import java.io.File;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import static org.junit.Assert.*;
public class ProtoGenTest {
private static final Charset charset = StandardCharsets.UTF_8;
@Rule public TemporaryFolder folder = new TemporaryFolder();
@Test
public void happyPath() throws Exception {
File rootDir = folder.getRoot();
String protoPackage = "protoPackage";
String javaPackage = "abc.protogen.example";
String goPackage = "goPackage";
String sourcePackage = "com.example";
String mapperPackage = "mapperPackage";
File jarFile = new File("./build/libs/example.jar");
assertTrue(jarFile.exists());
File mapperDir = new File(rootDir, "mapperDir");
mapperDir.mkdirs();
File protosDir = new File(rootDir, "protosDir");
protosDir.mkdirs();
File modelDir = new File(protosDir, "model");
modelDir.mkdirs();
ProtoGen generator = new ProtoGen(protoPackage, javaPackage, goPackage);
generator.processPackage(jarFile, sourcePackage);
generator.writeMapper(mapperDir, mapperPackage);
generator.writeProtos(protosDir);
List
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
dependencies {
implementation project(':conductor-common')
implementation project(':conductor-core')
compileOnly 'org.springframework.boot:spring-boot-starter'
implementation "com.amazonaws:aws-java-sdk-s3:${revAwsSdk}"
implementation "org.apache.commons:commons-lang3"
}
================================================
FILE: awss3-storage/src/main/java/com/netflix/conductor/s3/config/S3Configuration.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.s3.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.conductor.common.utils.ExternalPayloadStorage;
import com.netflix.conductor.core.utils.IDGenerator;
import com.netflix.conductor.s3.storage.S3PayloadStorage;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
@Configuration
@EnableConfigurationProperties(S3Properties.class)
@ConditionalOnProperty(name = "conductor.external-payload-storage.type", havingValue = "s3")
public class S3Configuration {
@Bean
public ExternalPayloadStorage s3ExternalPayloadStorage(
IDGenerator idGenerator, S3Properties properties, AmazonS3 s3Client) {
return new S3PayloadStorage(idGenerator, properties, s3Client);
}
@ConditionalOnProperty(
name = "conductor.external-payload-storage.s3.use_default_client",
havingValue = "true",
matchIfMissing = true)
@Bean
public AmazonS3 amazonS3(S3Properties properties) {
return AmazonS3ClientBuilder.standard().withRegion(properties.getRegion()).build();
}
}
================================================
FILE: awss3-storage/src/main/java/com/netflix/conductor/s3/config/S3Properties.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.s3.config;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
@ConfigurationProperties("conductor.external-payload-storage.s3")
public class S3Properties {
/** The s3 bucket name where the payloads will be stored */
private String bucketName = "conductor_payloads";
/** The time (in seconds) for which the signed url will be valid */
@DurationUnit(ChronoUnit.SECONDS)
private Duration signedUrlExpirationDuration = Duration.ofSeconds(5);
/** The AWS region of the s3 bucket */
private String region = "us-east-1";
public String getBucketName() {
return bucketName;
}
public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}
public Duration getSignedUrlExpirationDuration() {
return signedUrlExpirationDuration;
}
public void setSignedUrlExpirationDuration(Duration signedUrlExpirationDuration) {
this.signedUrlExpirationDuration = signedUrlExpirationDuration;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
}
================================================
FILE: awss3-storage/src/main/java/com/netflix/conductor/s3/storage/S3PayloadStorage.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.s3.storage;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.Date;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.conductor.common.run.ExternalStorageLocation;
import com.netflix.conductor.common.utils.ExternalPayloadStorage;
import com.netflix.conductor.core.exception.NonTransientException;
import com.netflix.conductor.core.exception.TransientException;
import com.netflix.conductor.core.utils.IDGenerator;
import com.netflix.conductor.s3.config.S3Properties;
import com.amazonaws.HttpMethod;
import com.amazonaws.SdkClientException;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
/**
* An implementation of {@link ExternalPayloadStorage} using AWS S3 for storing large JSON payload
* data.
*
* NOTE: The S3 client assumes that access to S3 is configured on the instance.
*
* @see DefaultAWSCredentialsProviderChain
*/
public class S3PayloadStorage implements ExternalPayloadStorage {
private static final Logger LOGGER = LoggerFactory.getLogger(S3PayloadStorage.class);
private static final String CONTENT_TYPE = "application/json";
private final IDGenerator idGenerator;
private final AmazonS3 s3Client;
private final String bucketName;
private final long expirationSec;
public S3PayloadStorage(IDGenerator idGenerator, S3Properties properties, AmazonS3 s3Client) {
this.idGenerator = idGenerator;
this.s3Client = s3Client;
bucketName = properties.getBucketName();
expirationSec = properties.getSignedUrlExpirationDuration().getSeconds();
}
/**
* @param operation the type of {@link Operation} to be performed
* @param payloadType the {@link PayloadType} that is being accessed
* @return a {@link ExternalStorageLocation} object which contains the pre-signed URL and the s3
* object key for the json payload
*/
@Override
public ExternalStorageLocation getLocation(
Operation operation, PayloadType payloadType, String path) {
try {
ExternalStorageLocation externalStorageLocation = new ExternalStorageLocation();
Date expiration = new Date();
long expTimeMillis = expiration.getTime() + 1000 * expirationSec;
expiration.setTime(expTimeMillis);
HttpMethod httpMethod = HttpMethod.GET;
if (operation == Operation.WRITE) {
httpMethod = HttpMethod.PUT;
}
String objectKey;
if (StringUtils.isNotBlank(path)) {
objectKey = path;
} else {
objectKey = getObjectKey(payloadType);
}
externalStorageLocation.setPath(objectKey);
GeneratePresignedUrlRequest generatePresignedUrlRequest =
new GeneratePresignedUrlRequest(bucketName, objectKey)
.withMethod(httpMethod)
.withExpiration(expiration);
externalStorageLocation.setUri(
s3Client.generatePresignedUrl(generatePresignedUrlRequest)
.toURI()
.toASCIIString());
return externalStorageLocation;
} catch (SdkClientException e) {
String msg =
String.format(
"Error communicating with S3 - operation:%s, payloadType: %s, path: %s",
operation, payloadType, path);
LOGGER.error(msg, e);
throw new TransientException(msg, e);
} catch (URISyntaxException e) {
String msg = "Invalid URI Syntax";
LOGGER.error(msg, e);
throw new NonTransientException(msg, e);
}
}
/**
* Uploads the payload to the given s3 object key. It is expected that the caller retrieves the
* object key using {@link #getLocation(Operation, PayloadType, String)} before making this
* call.
*
* @param path the s3 key of the object to be uploaded
* @param payload an {@link InputStream} containing the json payload which is to be uploaded
* @param payloadSize the size of the json payload in bytes
*/
@Override
public void upload(String path, InputStream payload, long payloadSize) {
try {
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(CONTENT_TYPE);
objectMetadata.setContentLength(payloadSize);
PutObjectRequest request =
new PutObjectRequest(bucketName, path, payload, objectMetadata);
s3Client.putObject(request);
} catch (SdkClientException e) {
String msg =
String.format(
"Error uploading to S3 - path:%s, payloadSize: %d", path, payloadSize);
LOGGER.error(msg, e);
throw new TransientException(msg, e);
}
}
/**
* Downloads the payload stored in the s3 object.
*
* @param path the S3 key of the object
* @return an input stream containing the contents of the object Caller is expected to close the
* input stream.
*/
@Override
public InputStream download(String path) {
try {
S3Object s3Object = s3Client.getObject(new GetObjectRequest(bucketName, path));
return s3Object.getObjectContent();
} catch (SdkClientException e) {
String msg = String.format("Error downloading from S3 - path:%s", path);
LOGGER.error(msg, e);
throw new TransientException(msg, e);
}
}
private String getObjectKey(PayloadType payloadType) {
StringBuilder stringBuilder = new StringBuilder();
switch (payloadType) {
case WORKFLOW_INPUT:
stringBuilder.append("workflow/input/");
break;
case WORKFLOW_OUTPUT:
stringBuilder.append("workflow/output/");
break;
case TASK_INPUT:
stringBuilder.append("task/input/");
break;
case TASK_OUTPUT:
stringBuilder.append("task/output/");
break;
}
stringBuilder.append(idGenerator.generate()).append(".json");
return stringBuilder.toString();
}
}
================================================
FILE: awss3-storage/src/main/resources/META-INF/additional-spring-configuration-metadata.json
================================================
{
"hints": [
{
"name": "conductor.external-payload-storage.type",
"values": [
{
"value": "s3",
"description": "Use AWS S3 as the external payload storage."
}
]
}
]
}
================================================
FILE: awssqs-event-queue/README.md
================================================
================================================
FILE: awssqs-event-queue/build.gradle
================================================
dependencies {
implementation project(':conductor-common')
implementation project(':conductor-core')
compileOnly 'org.springframework.boot:spring-boot-starter'
implementation "org.apache.commons:commons-lang3"
// SBMTODO: remove guava dep
implementation "com.google.guava:guava:${revGuava}"
implementation "com.amazonaws:aws-java-sdk-sqs:${revAwsSdk}"
implementation "io.reactivex:rxjava:${revRxJava}"
testImplementation 'org.springframework.boot:spring-boot-starter'
testImplementation project(':conductor-common').sourceSets.test.output
}
================================================
FILE: awssqs-event-queue/src/main/java/com/netflix/conductor/sqs/config/SQSEventQueueConfiguration.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.sqs.config;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.conductor.core.config.ConductorProperties;
import com.netflix.conductor.core.events.EventQueueProvider;
import com.netflix.conductor.core.events.queue.ObservableQueue;
import com.netflix.conductor.model.TaskModel.Status;
import com.netflix.conductor.sqs.eventqueue.SQSObservableQueue.Builder;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
import rx.Scheduler;
@Configuration
@EnableConfigurationProperties(SQSEventQueueProperties.class)
@ConditionalOnProperty(name = "conductor.event-queues.sqs.enabled", havingValue = "true")
public class SQSEventQueueConfiguration {
@Autowired private SQSEventQueueProperties sqsProperties;
private static final Logger LOGGER = LoggerFactory.getLogger(SQSEventQueueConfiguration.class);
@Bean
AWSCredentialsProvider createAWSCredentialsProvider() {
return new DefaultAWSCredentialsProviderChain();
}
@ConditionalOnMissingBean
@Bean
public AmazonSQS getSQSClient(AWSCredentialsProvider credentialsProvider) {
AmazonSQSClientBuilder builder =
AmazonSQSClientBuilder.standard().withCredentials(credentialsProvider);
if (!sqsProperties.getEndpoint().isEmpty()) {
LOGGER.info("Setting custom SQS endpoint to {}", sqsProperties.getEndpoint());
builder.withEndpointConfiguration(
new AwsClientBuilder.EndpointConfiguration(
sqsProperties.getEndpoint(), System.getenv("AWS_REGION")));
}
return builder.build();
}
@Bean
public EventQueueProvider sqsEventQueueProvider(
AmazonSQS sqsClient, SQSEventQueueProperties properties, Scheduler scheduler) {
return new SQSEventQueueProvider(sqsClient, properties, scheduler);
}
@ConditionalOnProperty(
name = "conductor.default-event-queue.type",
havingValue = "sqs",
matchIfMissing = true)
@Bean
public Map
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.sqs.config;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
@ConfigurationProperties("conductor.event-queues.sqs")
public class SQSEventQueueProperties {
/** The maximum number of messages to be fetched from the queue in a single request */
private int batchSize = 1;
/** The polling interval (in milliseconds) */
private Duration pollTimeDuration = Duration.ofMillis(100);
/** The visibility timeout (in seconds) for the message on the queue */
@DurationUnit(ChronoUnit.SECONDS)
private Duration visibilityTimeout = Duration.ofSeconds(60);
/** The prefix to be used for the default listener queues */
private String listenerQueuePrefix = "";
/** The AWS account Ids authorized to send messages to the queues */
private String authorizedAccounts = "";
/** The endpoint to use to connect to a local SQS server for testing */
private String endpoint = "";
public int getBatchSize() {
return batchSize;
}
public void setBatchSize(int batchSize) {
this.batchSize = batchSize;
}
public Duration getPollTimeDuration() {
return pollTimeDuration;
}
public void setPollTimeDuration(Duration pollTimeDuration) {
this.pollTimeDuration = pollTimeDuration;
}
public Duration getVisibilityTimeout() {
return visibilityTimeout;
}
public void setVisibilityTimeout(Duration visibilityTimeout) {
this.visibilityTimeout = visibilityTimeout;
}
public String getListenerQueuePrefix() {
return listenerQueuePrefix;
}
public void setListenerQueuePrefix(String listenerQueuePrefix) {
this.listenerQueuePrefix = listenerQueuePrefix;
}
public String getAuthorizedAccounts() {
return authorizedAccounts;
}
public void setAuthorizedAccounts(String authorizedAccounts) {
this.authorizedAccounts = authorizedAccounts;
}
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
}
================================================
FILE: awssqs-event-queue/src/main/java/com/netflix/conductor/sqs/config/SQSEventQueueProvider.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.sqs.config;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.lang.NonNull;
import com.netflix.conductor.core.events.EventQueueProvider;
import com.netflix.conductor.core.events.queue.ObservableQueue;
import com.netflix.conductor.sqs.eventqueue.SQSObservableQueue;
import com.amazonaws.services.sqs.AmazonSQS;
import rx.Scheduler;
public class SQSEventQueueProvider implements EventQueueProvider {
private final Map
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.sqs.eventqueue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.conductor.core.events.queue.Message;
import com.netflix.conductor.core.events.queue.ObservableQueue;
import com.netflix.conductor.metrics.Monitors;
import com.amazonaws.auth.policy.Action;
import com.amazonaws.auth.policy.Policy;
import com.amazonaws.auth.policy.Principal;
import com.amazonaws.auth.policy.Resource;
import com.amazonaws.auth.policy.Statement;
import com.amazonaws.auth.policy.Statement.Effect;
import com.amazonaws.auth.policy.actions.SQSActions;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.model.BatchResultErrorEntry;
import com.amazonaws.services.sqs.model.ChangeMessageVisibilityRequest;
import com.amazonaws.services.sqs.model.CreateQueueRequest;
import com.amazonaws.services.sqs.model.CreateQueueResult;
import com.amazonaws.services.sqs.model.DeleteMessageBatchRequest;
import com.amazonaws.services.sqs.model.DeleteMessageBatchRequestEntry;
import com.amazonaws.services.sqs.model.DeleteMessageBatchResult;
import com.amazonaws.services.sqs.model.GetQueueAttributesResult;
import com.amazonaws.services.sqs.model.ListQueuesRequest;
import com.amazonaws.services.sqs.model.ListQueuesResult;
import com.amazonaws.services.sqs.model.ReceiveMessageRequest;
import com.amazonaws.services.sqs.model.ReceiveMessageResult;
import com.amazonaws.services.sqs.model.SendMessageBatchRequest;
import com.amazonaws.services.sqs.model.SendMessageBatchRequestEntry;
import com.amazonaws.services.sqs.model.SendMessageBatchResult;
import com.amazonaws.services.sqs.model.SetQueueAttributesResult;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Scheduler;
public class SQSObservableQueue implements ObservableQueue {
private static final Logger LOGGER = LoggerFactory.getLogger(SQSObservableQueue.class);
private static final String QUEUE_TYPE = "sqs";
private final String queueName;
private final int visibilityTimeoutInSeconds;
private final int batchSize;
private final AmazonSQS client;
private final long pollTimeInMS;
private final String queueURL;
private final Scheduler scheduler;
private volatile boolean running;
private SQSObservableQueue(
String queueName,
AmazonSQS client,
int visibilityTimeoutInSeconds,
int batchSize,
long pollTimeInMS,
List
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.sqs.eventqueue;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import com.netflix.conductor.common.config.TestObjectMapperConfiguration;
import com.netflix.conductor.common.metadata.tasks.Task;
import com.netflix.conductor.common.metadata.tasks.TaskResult;
import com.netflix.conductor.core.events.queue.DefaultEventQueueProcessor;
import com.netflix.conductor.core.events.queue.Message;
import com.netflix.conductor.core.events.queue.ObservableQueue;
import com.netflix.conductor.core.execution.WorkflowExecutor;
import com.netflix.conductor.model.TaskModel;
import com.netflix.conductor.model.TaskModel.Status;
import com.netflix.conductor.model.WorkflowModel;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.Uninterruptibles;
import static com.netflix.conductor.common.metadata.tasks.TaskType.TASK_TYPE_WAIT;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@SuppressWarnings("unchecked")
@ContextConfiguration(classes = {TestObjectMapperConfiguration.class})
@RunWith(SpringRunner.class)
public class DefaultEventQueueProcessorTest {
private static SQSObservableQueue queue;
private static WorkflowExecutor workflowExecutor;
private DefaultEventQueueProcessor defaultEventQueueProcessor;
@Autowired private ObjectMapper objectMapper;
private static final List
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.sqs.eventqueue;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.mockito.stubbing.Answer;
import com.netflix.conductor.core.events.queue.Message;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.model.ListQueuesRequest;
import com.amazonaws.services.sqs.model.ListQueuesResult;
import com.amazonaws.services.sqs.model.ReceiveMessageRequest;
import com.amazonaws.services.sqs.model.ReceiveMessageResult;
import com.google.common.util.concurrent.Uninterruptibles;
import rx.Observable;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class SQSObservableQueueTest {
@Test
public void test() {
List
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
// Establish version and status
ext.githubProjectName = rootProject.name // Change if github project name is not the same as the root project's name
subprojects {
tasks.withType(Javadoc).all { enabled = false }
}
apply from: "$rootDir/dependencies.gradle"
apply from: "$rootDir/springboot-bom-overrides.gradle"
allprojects {
apply plugin: 'com.netflix.nebula.netflixoss'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'java-library'
apply plugin: 'project-report'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
group = 'com.netflix.conductor'
configurations.all {
exclude group: 'ch.qos.logback', module: 'logback-classic'
exclude group: 'ch.qos.logback', module: 'logback-core'
exclude group: 'org.apache.logging.log4j', module: 'log4j-to-slf4j'
exclude group: 'org.slf4j', module: 'slf4j-log4j12'
resolutionStrategy {
force 'org.codehaus.jettison:jettison:1.5.4'
force "org.apache.commons:commons-compress:${revCommonsCompress}"
}
}
repositories {
mavenCentral()
// oss-candidate for -rc.* verions:
maven {
url "https://artifactory-oss.prod.netflix.net/artifactory/maven-oss-candidates"
}
/**
* This repository locates artifacts that don't exist in maven central but we had to backup from jcenter
* The exclusiveContent
*/
exclusiveContent {
forRepository {
maven {
url "https://artifactory-oss.prod.netflix.net/artifactory/required-jcenter-modules-backup"
}
}
filter {
includeGroupByRegex "com\\.github\\.vmg.*"
}
}
}
dependencyManagement {
imports {
// dependency versions for the BOM can be found at https://docs.spring.io/spring-boot/docs/2.7.3/reference/htmlsingle/#appendix.dependency-versions
mavenBom(SpringBootPlugin.BOM_COORDINATES)
}
}
dependencies {
implementation('org.apache.logging.log4j:log4j-core') {
version {
// this is the preferred version this library will use
prefer '2.17.2'
// the strict bounds, effectively allowing any 2.x version greater than 2.17.2
// could also remove the upper bound entirely if we wanted too
strictly '[2.17.2,3.0)'
}
}
implementation('org.apache.logging.log4j:log4j-api') {
version {
// this is the preferred version this library will use
prefer '2.17.2'
// the strict bounds, effectively allowing any 2.x version greater than 2.17.2
// could also remove the upper bound entirely if we wanted too
strictly '[2.17.2,3.0)'
}
}
implementation('org.apache.logging.log4j:log4j-slf4j-impl') {
version {
// this is the preferred version this library will use
prefer '2.17.2'
// the strict bounds, effectively allowing any 2.x version greater than 2.17.2
// could also remove the upper bound entirely if we wanted too
strictly '[2.17.2,3.0)'
}
}
implementation('org.apache.logging.log4j:log4j-jul') {
version {
// this is the preferred version this library will use
prefer '2.17.2'
// the strict bounds, effectively allowing any 2.x version greater than 2.17.2
// could also remove the upper bound entirely if we wanted too
strictly '[2.17.2,3.0)'
}
}
implementation('org.apache.logging.log4j:log4j-web') {
version {
// this is the preferred version this library will use
prefer '2.17.2'
// the strict bounds, effectively allowing any 2.x version greater than 2.17.2
// could also remove the upper bound entirely if we wanted too
strictly '[2.17.2,3.0)'
}
}
implementation('org.yaml:snakeyaml') {
version {
// this is the preferred version this library will use
prefer '2.0'
// the strict bounds, effectively allowing any 2.x version between 2.0 and 2.1
strictly '[2.0,2.1)'
}
}
implementation('com.fasterxml.jackson.core:jackson-core') {
version {
// this is the preferred version this library will use
prefer '2.15.0'
// the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2
strictly '[2.15.0,2.15.2)'
}
}
implementation('com.fasterxml.jackson.core:jackson-databind') {
version {
// this is the preferred version this library will use
prefer '2.15.0'
// the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2
strictly '[2.15.0,2.15.2)'
}
}
implementation('com.fasterxml.jackson.dataformat:jackson-dataformat-yaml') {
version {
// this is the preferred version this library will use
prefer '2.15.0'
// the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2
strictly '[2.15.0,2.15.2)'
}
}
implementation('com.fasterxml.jackson.core:jackson-annotations') {
version {
// this is the preferred version this library will use
prefer '2.15.0'
// the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2
strictly '[2.15.0,2.15.2)'
}
}
implementation('com.fasterxml.jackson.dataformat:jackson-dataformat-smile') {
version {
// this is the preferred version this library will use
prefer '2.15.0'
// the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2
strictly '[2.15.0,2.15.2)'
}
}
implementation('com.fasterxml.jackson.dataformat:jackson-dataformat-cbor') {
version {
// this is the preferred version this library will use
prefer '2.15.0'
// the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2
strictly '[2.15.0,2.15.2)'
}
}
implementation('com.fasterxml.jackson.datatype:jackson-datatype-jdk8') {
version {
// this is the preferred version this library will use
prefer '2.15.0'
// the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2
strictly '[2.15.0,2.15.2)'
}
}
implementation('com.fasterxml.jackson.datatype:jackson-datatype-joda') {
version {
// this is the preferred version this library will use
prefer '2.15.0'
// the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2
strictly '[2.15.0,2.15.2)'
}
}
implementation('com.fasterxml.jackson.datatype:jackson-datatype-jsr310') {
version {
// this is the preferred version this library will use
prefer '2.15.0'
// the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2
strictly '[2.15.0,2.15.2)'
}
}
implementation('com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider') {
version {
// this is the preferred version this library will use
prefer '2.15.0'
// the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2
strictly '[2.15.0,2.15.2)'
}
}
implementation('com.fasterxml.jackson.module:jackson-module-afterburner') {
version {
// this is the preferred version this library will use
prefer '2.15.0'
// the strict bounds, effectively allowing any 2.15.x version between 2.15.0 and 2.15.2
strictly '[2.15.0,2.15.2)'
}
}
implementation('org.apache.logging.log4j:log4j-core')
implementation('org.apache.logging.log4j:log4j-api')
implementation('org.apache.logging.log4j:log4j-slf4j-impl')
implementation('org.apache.logging.log4j:log4j-jul')
implementation('org.apache.logging.log4j:log4j-web')
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.yaml', module: 'snakeyaml'
}
testImplementation('org.springframework.boot:spring-boot-starter-log4j2')
testImplementation 'junit:junit'
testImplementation "org.junit.vintage:junit-vintage-engine"
// Needed for build to work on m1/m2 macs
testImplementation 'net.java.dev.jna:jna:5.13.0'
}
// processes additional configuration metadata json file as described here
// https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/appendix-configuration-metadata.html#configuration-metadata-additional-metadata
compileJava.inputs.files(processResources)
test {
useJUnitPlatform()
testLogging {
events = ["SKIPPED", "FAILED"]
exceptionFormat = "full"
displayGranularity = 1
showStandardStreams = false
}
}
}
// all client and their related modules are published with Java 17 compatibility
["annotations", "common", "client", "client-spring", "grpc", "grpc-client"].each {
project(":conductor-$it") {
compileJava {
options.release = 17
}
}
}
jacocoTestReport {
reports {
html.required = true
xml.required = true
csv.required = false
}
}
task server {
dependsOn ':conductor-server:bootRun'
}
sonarqube {
properties {
property "sonar.projectKey", "com.netflix.conductor:conductor"
property "sonar.organization", "netflix"
property "sonar.host.url", "https://sonarcloud.io"
}
}
configure(allprojects - project(':conductor-grpc')) {
apply plugin: 'com.diffplug.spotless'
spotless {
java {
googleJavaFormat().aosp()
removeUnusedImports()
importOrder('java', 'javax', 'org', 'com.netflix', '', '\\#com.netflix', '\\#')
licenseHeaderFile("$rootDir/licenseheader.txt")
}
}
}
['cassandra-persistence', 'core', 'redis-concurrency-limit', 'test-harness', 'client'].each {
configure(project(":conductor-$it")) {
spotless {
groovy {
importOrder('java', 'javax', 'org', 'com.netflix', '', '\\#com.netflix', '\\#')
licenseHeaderFile("$rootDir/licenseheader.txt")
}
}
}
}
================================================
FILE: cassandra-persistence/build.gradle
================================================
/*
* Copyright 2021 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
apply plugin: 'groovy'
dependencies {
compileOnly 'org.springframework.boot:spring-boot-starter'
implementation project(':conductor-common')
implementation project(':conductor-core')
implementation "com.datastax.cassandra:cassandra-driver-core:${revCassandra}"
implementation "org.apache.commons:commons-lang3"
testImplementation project(':conductor-core').sourceSets.test.output
testImplementation project(':conductor-common').sourceSets.test.output
testImplementation "org.codehaus.groovy:groovy-all:${revGroovy}"
testImplementation "org.spockframework:spock-core:${revSpock}"
testImplementation "org.spockframework:spock-spring:${revSpock}"
testImplementation "org.testcontainers:spock:${revTestContainer}"
testImplementation "org.testcontainers:cassandra:${revTestContainer}"
testImplementation "com.google.protobuf:protobuf-java:${revProtoBuf}"
}
================================================
FILE: cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/config/CassandraConfiguration.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.conductor.cassandra.config.cache.CacheableEventHandlerDAO;
import com.netflix.conductor.cassandra.config.cache.CacheableMetadataDAO;
import com.netflix.conductor.cassandra.dao.CassandraEventHandlerDAO;
import com.netflix.conductor.cassandra.dao.CassandraExecutionDAO;
import com.netflix.conductor.cassandra.dao.CassandraMetadataDAO;
import com.netflix.conductor.cassandra.dao.CassandraPollDataDAO;
import com.netflix.conductor.cassandra.util.Statements;
import com.netflix.conductor.dao.EventHandlerDAO;
import com.netflix.conductor.dao.ExecutionDAO;
import com.netflix.conductor.dao.MetadataDAO;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Metadata;
import com.datastax.driver.core.Session;
import com.fasterxml.jackson.databind.ObjectMapper;
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(CassandraProperties.class)
@ConditionalOnProperty(name = "conductor.db.type", havingValue = "cassandra")
public class CassandraConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(CassandraConfiguration.class);
@Bean
public Cluster cluster(CassandraProperties properties) {
String host = properties.getHostAddress();
int port = properties.getPort();
LOGGER.info("Connecting to cassandra cluster with host:{}, port:{}", host, port);
Cluster cluster = Cluster.builder().addContactPoint(host).withPort(port).build();
Metadata metadata = cluster.getMetadata();
LOGGER.info("Connected to cluster: {}", metadata.getClusterName());
metadata.getAllHosts()
.forEach(
h ->
LOGGER.info(
"Datacenter:{}, host:{}, rack: {}",
h.getDatacenter(),
h.getEndPoint().resolve().getHostName(),
h.getRack()));
return cluster;
}
@Bean
public Session session(Cluster cluster) {
LOGGER.info("Initializing cassandra session");
return cluster.connect();
}
@Bean
public MetadataDAO cassandraMetadataDAO(
Session session,
ObjectMapper objectMapper,
CassandraProperties properties,
Statements statements,
CacheManager cacheManager) {
CassandraMetadataDAO cassandraMetadataDAO =
new CassandraMetadataDAO(session, objectMapper, properties, statements);
return new CacheableMetadataDAO(cassandraMetadataDAO, properties, cacheManager);
}
@Bean
public ExecutionDAO cassandraExecutionDAO(
Session session,
ObjectMapper objectMapper,
CassandraProperties properties,
Statements statements) {
return new CassandraExecutionDAO(session, objectMapper, properties, statements);
}
@Bean
public EventHandlerDAO cassandraEventHandlerDAO(
Session session,
ObjectMapper objectMapper,
CassandraProperties properties,
Statements statements,
CacheManager cacheManager) {
CassandraEventHandlerDAO cassandraEventHandlerDAO =
new CassandraEventHandlerDAO(session, objectMapper, properties, statements);
return new CacheableEventHandlerDAO(cassandraEventHandlerDAO, properties, cacheManager);
}
@Bean
public CassandraPollDataDAO cassandraPollDataDAO() {
return new CassandraPollDataDAO();
}
@Bean
public Statements statements(CassandraProperties cassandraProperties) {
return new Statements(cassandraProperties.getKeyspace());
}
}
================================================
FILE: cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/config/CassandraProperties.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.config;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
import com.datastax.driver.core.ConsistencyLevel;
@ConfigurationProperties("conductor.cassandra")
public class CassandraProperties {
/** The address for the cassandra database host */
private String hostAddress = "127.0.0.1";
/** The port to be used to connect to the cassandra database instance */
private int port = 9142;
/** The name of the cassandra cluster */
private String cluster = "";
/** The keyspace to be used in the cassandra datastore */
private String keyspace = "conductor";
/**
* The number of tasks to be stored in a single partition which will be used for sharding
* workflows in the datastore
*/
private int shardSize = 100;
/** The replication strategy with which to configure the keyspace */
private String replicationStrategy = "SimpleStrategy";
/** The key to be used while configuring the replication factor */
private String replicationFactorKey = "replication_factor";
/** The replication factor value with which the keyspace is configured */
private int replicationFactorValue = 3;
/** The consistency level to be used for read operations */
private ConsistencyLevel readConsistencyLevel = ConsistencyLevel.LOCAL_QUORUM;
/** The consistency level to be used for write operations */
private ConsistencyLevel writeConsistencyLevel = ConsistencyLevel.LOCAL_QUORUM;
/** The time in seconds after which the in-memory task definitions cache will be refreshed */
@DurationUnit(ChronoUnit.SECONDS)
private Duration taskDefCacheRefreshInterval = Duration.ofSeconds(60);
/** The time in seconds after which the in-memory event handler cache will be refreshed */
@DurationUnit(ChronoUnit.SECONDS)
private Duration eventHandlerCacheRefreshInterval = Duration.ofSeconds(60);
/** The time to live in seconds for which the event execution will be persisted */
@DurationUnit(ChronoUnit.SECONDS)
private Duration eventExecutionPersistenceTtl = Duration.ZERO;
public String getHostAddress() {
return hostAddress;
}
public void setHostAddress(String hostAddress) {
this.hostAddress = hostAddress;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getCluster() {
return cluster;
}
public void setCluster(String cluster) {
this.cluster = cluster;
}
public String getKeyspace() {
return keyspace;
}
public void setKeyspace(String keyspace) {
this.keyspace = keyspace;
}
public int getShardSize() {
return shardSize;
}
public void setShardSize(int shardSize) {
this.shardSize = shardSize;
}
public String getReplicationStrategy() {
return replicationStrategy;
}
public void setReplicationStrategy(String replicationStrategy) {
this.replicationStrategy = replicationStrategy;
}
public String getReplicationFactorKey() {
return replicationFactorKey;
}
public void setReplicationFactorKey(String replicationFactorKey) {
this.replicationFactorKey = replicationFactorKey;
}
public int getReplicationFactorValue() {
return replicationFactorValue;
}
public void setReplicationFactorValue(int replicationFactorValue) {
this.replicationFactorValue = replicationFactorValue;
}
public ConsistencyLevel getReadConsistencyLevel() {
return readConsistencyLevel;
}
public void setReadConsistencyLevel(ConsistencyLevel readConsistencyLevel) {
this.readConsistencyLevel = readConsistencyLevel;
}
public ConsistencyLevel getWriteConsistencyLevel() {
return writeConsistencyLevel;
}
public void setWriteConsistencyLevel(ConsistencyLevel writeConsistencyLevel) {
this.writeConsistencyLevel = writeConsistencyLevel;
}
public Duration getTaskDefCacheRefreshInterval() {
return taskDefCacheRefreshInterval;
}
public void setTaskDefCacheRefreshInterval(Duration taskDefCacheRefreshInterval) {
this.taskDefCacheRefreshInterval = taskDefCacheRefreshInterval;
}
public Duration getEventHandlerCacheRefreshInterval() {
return eventHandlerCacheRefreshInterval;
}
public void setEventHandlerCacheRefreshInterval(Duration eventHandlerCacheRefreshInterval) {
this.eventHandlerCacheRefreshInterval = eventHandlerCacheRefreshInterval;
}
public Duration getEventExecutionPersistenceTtl() {
return eventExecutionPersistenceTtl;
}
public void setEventExecutionPersistenceTtl(Duration eventExecutionPersistenceTtl) {
this.eventExecutionPersistenceTtl = eventExecutionPersistenceTtl;
}
}
================================================
FILE: cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/config/cache/CacheableEventHandlerDAO.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.config.cache;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import com.netflix.conductor.annotations.Trace;
import com.netflix.conductor.cassandra.config.CassandraProperties;
import com.netflix.conductor.cassandra.dao.CassandraEventHandlerDAO;
import com.netflix.conductor.common.metadata.events.EventHandler;
import com.netflix.conductor.dao.EventHandlerDAO;
import com.netflix.conductor.metrics.Monitors;
import static com.netflix.conductor.cassandra.config.cache.CachingConfig.EVENT_HANDLER_CACHE;
@Trace
public class CacheableEventHandlerDAO implements EventHandlerDAO {
private static final Logger LOGGER = LoggerFactory.getLogger(CacheableEventHandlerDAO.class);
private static final String CLASS_NAME = CacheableEventHandlerDAO.class.getSimpleName();
private final CassandraEventHandlerDAO cassandraEventHandlerDAO;
private final CassandraProperties properties;
private final CacheManager cacheManager;
public CacheableEventHandlerDAO(
CassandraEventHandlerDAO cassandraEventHandlerDAO,
CassandraProperties properties,
CacheManager cacheManager) {
this.cassandraEventHandlerDAO = cassandraEventHandlerDAO;
this.properties = properties;
this.cacheManager = cacheManager;
}
@PostConstruct
public void scheduleEventHandlerRefresh() {
long cacheRefreshTime = properties.getEventHandlerCacheRefreshInterval().getSeconds();
Executors.newSingleThreadScheduledExecutor()
.scheduleWithFixedDelay(
this::refreshEventHandlersCache, 0, cacheRefreshTime, TimeUnit.SECONDS);
}
@Override
@CachePut(value = EVENT_HANDLER_CACHE, key = "#eventHandler.name")
public void addEventHandler(EventHandler eventHandler) {
cassandraEventHandlerDAO.addEventHandler(eventHandler);
}
@Override
@CachePut(value = EVENT_HANDLER_CACHE, key = "#eventHandler.name")
public void updateEventHandler(EventHandler eventHandler) {
cassandraEventHandlerDAO.updateEventHandler(eventHandler);
}
@Override
@CacheEvict(EVENT_HANDLER_CACHE)
public void removeEventHandler(String name) {
cassandraEventHandlerDAO.removeEventHandler(name);
}
@Override
public List
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.config.cache;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import com.netflix.conductor.annotations.Trace;
import com.netflix.conductor.cassandra.config.CassandraProperties;
import com.netflix.conductor.cassandra.dao.CassandraMetadataDAO;
import com.netflix.conductor.common.metadata.tasks.TaskDef;
import com.netflix.conductor.common.metadata.workflow.WorkflowDef;
import com.netflix.conductor.dao.MetadataDAO;
import com.netflix.conductor.metrics.Monitors;
import static com.netflix.conductor.cassandra.config.cache.CachingConfig.TASK_DEF_CACHE;
@Trace
public class CacheableMetadataDAO implements MetadataDAO {
private static final String CLASS_NAME = CacheableMetadataDAO.class.getSimpleName();
private static final Logger LOGGER = LoggerFactory.getLogger(CacheableMetadataDAO.class);
private final CassandraMetadataDAO cassandraMetadataDAO;
private final CassandraProperties properties;
private final CacheManager cacheManager;
public CacheableMetadataDAO(
CassandraMetadataDAO cassandraMetadataDAO,
CassandraProperties properties,
CacheManager cacheManager) {
this.cassandraMetadataDAO = cassandraMetadataDAO;
this.properties = properties;
this.cacheManager = cacheManager;
}
@PostConstruct
public void scheduleCacheRefresh() {
long cacheRefreshTime = properties.getTaskDefCacheRefreshInterval().getSeconds();
Executors.newSingleThreadScheduledExecutor()
.scheduleWithFixedDelay(
this::refreshTaskDefsCache, 0, cacheRefreshTime, TimeUnit.SECONDS);
LOGGER.info(
"Scheduled cache refresh for Task Definitions, every {} seconds", cacheRefreshTime);
}
@Override
@CachePut(value = TASK_DEF_CACHE, key = "#taskDef.name")
public TaskDef createTaskDef(TaskDef taskDef) {
cassandraMetadataDAO.createTaskDef(taskDef);
return taskDef;
}
@Override
@CachePut(value = TASK_DEF_CACHE, key = "#taskDef.name")
public TaskDef updateTaskDef(TaskDef taskDef) {
return cassandraMetadataDAO.updateTaskDef(taskDef);
}
@Override
@Cacheable(TASK_DEF_CACHE)
public TaskDef getTaskDef(String name) {
return cassandraMetadataDAO.getTaskDef(name);
}
@Override
public List
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.config.cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class CachingConfig {
public static final String TASK_DEF_CACHE = "taskDefCache";
public static final String EVENT_HANDLER_CACHE = "eventHandlerCache";
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager(TASK_DEF_CACHE, EVENT_HANDLER_CACHE);
}
}
================================================
FILE: cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/dao/CassandraBaseDAO.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.dao;
import java.io.IOException;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.conductor.cassandra.config.CassandraProperties;
import com.netflix.conductor.core.exception.NonTransientException;
import com.netflix.conductor.metrics.Monitors;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.schemabuilder.SchemaBuilder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import static com.netflix.conductor.cassandra.util.Constants.DAO_NAME;
import static com.netflix.conductor.cassandra.util.Constants.ENTITY_KEY;
import static com.netflix.conductor.cassandra.util.Constants.EVENT_EXECUTION_ID_KEY;
import static com.netflix.conductor.cassandra.util.Constants.EVENT_HANDLER_KEY;
import static com.netflix.conductor.cassandra.util.Constants.EVENT_HANDLER_NAME_KEY;
import static com.netflix.conductor.cassandra.util.Constants.HANDLERS_KEY;
import static com.netflix.conductor.cassandra.util.Constants.MESSAGE_ID_KEY;
import static com.netflix.conductor.cassandra.util.Constants.PAYLOAD_KEY;
import static com.netflix.conductor.cassandra.util.Constants.SHARD_ID_KEY;
import static com.netflix.conductor.cassandra.util.Constants.TABLE_EVENT_EXECUTIONS;
import static com.netflix.conductor.cassandra.util.Constants.TABLE_EVENT_HANDLERS;
import static com.netflix.conductor.cassandra.util.Constants.TABLE_TASK_DEFS;
import static com.netflix.conductor.cassandra.util.Constants.TABLE_TASK_DEF_LIMIT;
import static com.netflix.conductor.cassandra.util.Constants.TABLE_TASK_LOOKUP;
import static com.netflix.conductor.cassandra.util.Constants.TABLE_WORKFLOWS;
import static com.netflix.conductor.cassandra.util.Constants.TABLE_WORKFLOW_DEFS;
import static com.netflix.conductor.cassandra.util.Constants.TABLE_WORKFLOW_DEFS_INDEX;
import static com.netflix.conductor.cassandra.util.Constants.TASK_DEFINITION_KEY;
import static com.netflix.conductor.cassandra.util.Constants.TASK_DEFS_KEY;
import static com.netflix.conductor.cassandra.util.Constants.TASK_DEF_NAME_KEY;
import static com.netflix.conductor.cassandra.util.Constants.TASK_ID_KEY;
import static com.netflix.conductor.cassandra.util.Constants.TOTAL_PARTITIONS_KEY;
import static com.netflix.conductor.cassandra.util.Constants.TOTAL_TASKS_KEY;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEFINITION_KEY;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_INDEX_KEY;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_INDEX_VALUE;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_NAME_KEY;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_NAME_VERSION_KEY;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_ID_KEY;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_VERSION_KEY;
/**
* Creates the keyspace and tables.
*
* CREATE KEYSPACE IF NOT EXISTS conductor WITH replication = { 'class' :
* 'NetworkTopologyStrategy', 'us-east': '3'};
*
* CREATE TABLE IF NOT EXISTS conductor.workflows ( workflow_id uuid, shard_id int, task_id text,
* entity text, payload text, total_tasks int STATIC, total_partitions int STATIC, PRIMARY
* KEY((workflow_id, shard_id), entity, task_id) );
*
* CREATE TABLE IF NOT EXISTS conductor.task_lookup( task_id uuid, workflow_id uuid, PRIMARY KEY
* (task_id) );
*
* CREATE TABLE IF NOT EXISTS conductor.task_def_limit( task_def_name text, task_id uuid,
* workflow_id uuid, PRIMARY KEY ((task_def_name), task_id_key) );
*
* CREATE TABLE IF NOT EXISTS conductor.workflow_definitions( workflow_def_name text, version
* int, workflow_definition text, PRIMARY KEY ((workflow_def_name), version) );
*
* CREATE TABLE IF NOT EXISTS conductor.workflow_defs_index( workflow_def_version_index text,
* workflow_def_name_version text, workflow_def_index_value text,PRIMARY KEY
* ((workflow_def_version_index), workflow_def_name_version) );
*
* CREATE TABLE IF NOT EXISTS conductor.task_definitions( task_defs text, task_def_name text,
* task_definition text, PRIMARY KEY ((task_defs), task_def_name) );
*
* CREATE TABLE IF NOT EXISTS conductor.event_handlers( handlers text, event_handler_name text,
* event_handler text, PRIMARY KEY ((handlers), event_handler_name) );
*
* CREATE TABLE IF NOT EXISTS conductor.event_executions( message_id text, event_handler_name
* text, event_execution_id text, payload text, PRIMARY KEY ((message_id, event_handler_name),
* event_execution_id) );
*/
public abstract class CassandraBaseDAO {
private static final Logger LOGGER = LoggerFactory.getLogger(CassandraBaseDAO.class);
private final ObjectMapper objectMapper;
protected final Session session;
protected final CassandraProperties properties;
private boolean initialized = false;
public CassandraBaseDAO(
Session session, ObjectMapper objectMapper, CassandraProperties properties) {
this.session = session;
this.objectMapper = objectMapper;
this.properties = properties;
init();
}
protected static UUID toUUID(String uuidString, String message) {
try {
return UUID.fromString(uuidString);
} catch (IllegalArgumentException iae) {
throw new IllegalArgumentException(message + " " + uuidString, iae);
}
}
private void init() {
try {
if (!initialized) {
session.execute(getCreateKeyspaceStatement());
session.execute(getCreateWorkflowsTableStatement());
session.execute(getCreateTaskLookupTableStatement());
session.execute(getCreateTaskDefLimitTableStatement());
session.execute(getCreateWorkflowDefsTableStatement());
session.execute(getCreateWorkflowDefsIndexTableStatement());
session.execute(getCreateTaskDefsTableStatement());
session.execute(getCreateEventHandlersTableStatement());
session.execute(getCreateEventExecutionsTableStatement());
LOGGER.info(
"{} initialization complete! Tables created!", getClass().getSimpleName());
initialized = true;
}
} catch (Exception e) {
LOGGER.error("Error initializing and setting up keyspace and table in cassandra", e);
throw e;
}
}
private String getCreateKeyspaceStatement() {
return SchemaBuilder.createKeyspace(properties.getKeyspace())
.ifNotExists()
.with()
.replication(
ImmutableMap.of(
"class",
properties.getReplicationStrategy(),
properties.getReplicationFactorKey(),
properties.getReplicationFactorValue()))
.durableWrites(true)
.getQueryString();
}
private String getCreateWorkflowsTableStatement() {
return SchemaBuilder.createTable(properties.getKeyspace(), TABLE_WORKFLOWS)
.ifNotExists()
.addPartitionKey(WORKFLOW_ID_KEY, DataType.uuid())
.addPartitionKey(SHARD_ID_KEY, DataType.cint())
.addClusteringColumn(ENTITY_KEY, DataType.text())
.addClusteringColumn(TASK_ID_KEY, DataType.text())
.addColumn(PAYLOAD_KEY, DataType.text())
.addStaticColumn(TOTAL_TASKS_KEY, DataType.cint())
.addStaticColumn(TOTAL_PARTITIONS_KEY, DataType.cint())
.getQueryString();
}
private String getCreateTaskLookupTableStatement() {
return SchemaBuilder.createTable(properties.getKeyspace(), TABLE_TASK_LOOKUP)
.ifNotExists()
.addPartitionKey(TASK_ID_KEY, DataType.uuid())
.addColumn(WORKFLOW_ID_KEY, DataType.uuid())
.getQueryString();
}
private String getCreateTaskDefLimitTableStatement() {
return SchemaBuilder.createTable(properties.getKeyspace(), TABLE_TASK_DEF_LIMIT)
.ifNotExists()
.addPartitionKey(TASK_DEF_NAME_KEY, DataType.text())
.addClusteringColumn(TASK_ID_KEY, DataType.uuid())
.addColumn(WORKFLOW_ID_KEY, DataType.uuid())
.getQueryString();
}
private String getCreateWorkflowDefsTableStatement() {
return SchemaBuilder.createTable(properties.getKeyspace(), TABLE_WORKFLOW_DEFS)
.ifNotExists()
.addPartitionKey(WORKFLOW_DEF_NAME_KEY, DataType.text())
.addClusteringColumn(WORKFLOW_VERSION_KEY, DataType.cint())
.addColumn(WORKFLOW_DEFINITION_KEY, DataType.text())
.getQueryString();
}
private String getCreateWorkflowDefsIndexTableStatement() {
return SchemaBuilder.createTable(properties.getKeyspace(), TABLE_WORKFLOW_DEFS_INDEX)
.ifNotExists()
.addPartitionKey(WORKFLOW_DEF_INDEX_KEY, DataType.text())
.addClusteringColumn(WORKFLOW_DEF_NAME_VERSION_KEY, DataType.text())
.addColumn(WORKFLOW_DEF_INDEX_VALUE, DataType.text())
.getQueryString();
}
private String getCreateTaskDefsTableStatement() {
return SchemaBuilder.createTable(properties.getKeyspace(), TABLE_TASK_DEFS)
.ifNotExists()
.addPartitionKey(TASK_DEFS_KEY, DataType.text())
.addClusteringColumn(TASK_DEF_NAME_KEY, DataType.text())
.addColumn(TASK_DEFINITION_KEY, DataType.text())
.getQueryString();
}
private String getCreateEventHandlersTableStatement() {
return SchemaBuilder.createTable(properties.getKeyspace(), TABLE_EVENT_HANDLERS)
.ifNotExists()
.addPartitionKey(HANDLERS_KEY, DataType.text())
.addClusteringColumn(EVENT_HANDLER_NAME_KEY, DataType.text())
.addColumn(EVENT_HANDLER_KEY, DataType.text())
.getQueryString();
}
private String getCreateEventExecutionsTableStatement() {
return SchemaBuilder.createTable(properties.getKeyspace(), TABLE_EVENT_EXECUTIONS)
.ifNotExists()
.addPartitionKey(MESSAGE_ID_KEY, DataType.text())
.addPartitionKey(EVENT_HANDLER_NAME_KEY, DataType.text())
.addClusteringColumn(EVENT_EXECUTION_ID_KEY, DataType.text())
.addColumn(PAYLOAD_KEY, DataType.text())
.getQueryString();
}
String toJson(Object value) {
try {
return objectMapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new NonTransientException("Error serializing to json", e);
}
}
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.dao;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.conductor.annotations.Trace;
import com.netflix.conductor.cassandra.config.CassandraProperties;
import com.netflix.conductor.cassandra.util.Statements;
import com.netflix.conductor.common.metadata.events.EventHandler;
import com.netflix.conductor.core.exception.TransientException;
import com.netflix.conductor.dao.EventHandlerDAO;
import com.netflix.conductor.metrics.Monitors;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.exceptions.DriverException;
import com.fasterxml.jackson.databind.ObjectMapper;
import static com.netflix.conductor.cassandra.util.Constants.EVENT_HANDLER_KEY;
import static com.netflix.conductor.cassandra.util.Constants.HANDLERS_KEY;
@Trace
public class CassandraEventHandlerDAO extends CassandraBaseDAO implements EventHandlerDAO {
private static final Logger LOGGER = LoggerFactory.getLogger(CassandraEventHandlerDAO.class);
private static final String CLASS_NAME = CassandraEventHandlerDAO.class.getSimpleName();
private final PreparedStatement insertEventHandlerStatement;
private final PreparedStatement selectAllEventHandlersStatement;
private final PreparedStatement deleteEventHandlerStatement;
public CassandraEventHandlerDAO(
Session session,
ObjectMapper objectMapper,
CassandraProperties properties,
Statements statements) {
super(session, objectMapper, properties);
insertEventHandlerStatement =
session.prepare(statements.getInsertEventHandlerStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
selectAllEventHandlersStatement =
session.prepare(statements.getSelectAllEventHandlersStatement())
.setConsistencyLevel(properties.getReadConsistencyLevel());
deleteEventHandlerStatement =
session.prepare(statements.getDeleteEventHandlerStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
}
@Override
public void addEventHandler(EventHandler eventHandler) {
insertOrUpdateEventHandler(eventHandler);
}
@Override
public void updateEventHandler(EventHandler eventHandler) {
insertOrUpdateEventHandler(eventHandler);
}
@Override
public void removeEventHandler(String name) {
try {
recordCassandraDaoRequests("removeEventHandler");
session.execute(deleteEventHandlerStatement.bind(name));
} catch (Exception e) {
Monitors.error(CLASS_NAME, "removeEventHandler");
String errorMsg = String.format("Failed to remove event handler: %s", name);
LOGGER.error(errorMsg, e);
throw new TransientException(errorMsg, e);
}
}
@Override
public List
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.dao;
import java.util.*;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.conductor.annotations.Trace;
import com.netflix.conductor.cassandra.config.CassandraProperties;
import com.netflix.conductor.cassandra.util.Statements;
import com.netflix.conductor.common.metadata.events.EventExecution;
import com.netflix.conductor.common.metadata.tasks.TaskDef;
import com.netflix.conductor.core.exception.NonTransientException;
import com.netflix.conductor.core.exception.NotFoundException;
import com.netflix.conductor.core.exception.TransientException;
import com.netflix.conductor.dao.ConcurrentExecutionLimitDAO;
import com.netflix.conductor.dao.ExecutionDAO;
import com.netflix.conductor.metrics.Monitors;
import com.netflix.conductor.model.TaskModel;
import com.netflix.conductor.model.WorkflowModel;
import com.datastax.driver.core.*;
import com.datastax.driver.core.exceptions.DriverException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import static com.netflix.conductor.cassandra.util.Constants.*;
@Trace
public class CassandraExecutionDAO extends CassandraBaseDAO
implements ExecutionDAO, ConcurrentExecutionLimitDAO {
private static final Logger LOGGER = LoggerFactory.getLogger(CassandraExecutionDAO.class);
private static final String CLASS_NAME = CassandraExecutionDAO.class.getSimpleName();
protected final PreparedStatement insertWorkflowStatement;
protected final PreparedStatement insertTaskStatement;
protected final PreparedStatement insertEventExecutionStatement;
protected final PreparedStatement selectTotalStatement;
protected final PreparedStatement selectTaskStatement;
protected final PreparedStatement selectWorkflowStatement;
protected final PreparedStatement selectWorkflowWithTasksStatement;
protected final PreparedStatement selectTaskLookupStatement;
protected final PreparedStatement selectTasksFromTaskDefLimitStatement;
protected final PreparedStatement selectEventExecutionsStatement;
protected final PreparedStatement updateWorkflowStatement;
protected final PreparedStatement updateTotalTasksStatement;
protected final PreparedStatement updateTotalPartitionsStatement;
protected final PreparedStatement updateTaskLookupStatement;
protected final PreparedStatement updateTaskDefLimitStatement;
protected final PreparedStatement updateEventExecutionStatement;
protected final PreparedStatement deleteWorkflowStatement;
protected final PreparedStatement deleteTaskStatement;
protected final PreparedStatement deleteTaskLookupStatement;
protected final PreparedStatement deleteTaskDefLimitStatement;
protected final PreparedStatement deleteEventExecutionStatement;
protected final int eventExecutionsTTL;
public CassandraExecutionDAO(
Session session,
ObjectMapper objectMapper,
CassandraProperties properties,
Statements statements) {
super(session, objectMapper, properties);
eventExecutionsTTL = (int) properties.getEventExecutionPersistenceTtl().getSeconds();
this.insertWorkflowStatement =
session.prepare(statements.getInsertWorkflowStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.insertTaskStatement =
session.prepare(statements.getInsertTaskStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.insertEventExecutionStatement =
session.prepare(statements.getInsertEventExecutionStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.selectTotalStatement =
session.prepare(statements.getSelectTotalStatement())
.setConsistencyLevel(properties.getReadConsistencyLevel());
this.selectTaskStatement =
session.prepare(statements.getSelectTaskStatement())
.setConsistencyLevel(properties.getReadConsistencyLevel());
this.selectWorkflowStatement =
session.prepare(statements.getSelectWorkflowStatement())
.setConsistencyLevel(properties.getReadConsistencyLevel());
this.selectWorkflowWithTasksStatement =
session.prepare(statements.getSelectWorkflowWithTasksStatement())
.setConsistencyLevel(properties.getReadConsistencyLevel());
this.selectTaskLookupStatement =
session.prepare(statements.getSelectTaskFromLookupTableStatement())
.setConsistencyLevel(properties.getReadConsistencyLevel());
this.selectTasksFromTaskDefLimitStatement =
session.prepare(statements.getSelectTasksFromTaskDefLimitStatement())
.setConsistencyLevel(properties.getReadConsistencyLevel());
this.selectEventExecutionsStatement =
session.prepare(
statements
.getSelectAllEventExecutionsForMessageFromEventExecutionsStatement())
.setConsistencyLevel(properties.getReadConsistencyLevel());
this.updateWorkflowStatement =
session.prepare(statements.getUpdateWorkflowStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.updateTotalTasksStatement =
session.prepare(statements.getUpdateTotalTasksStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.updateTotalPartitionsStatement =
session.prepare(statements.getUpdateTotalPartitionsStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.updateTaskLookupStatement =
session.prepare(statements.getUpdateTaskLookupStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.updateTaskDefLimitStatement =
session.prepare(statements.getUpdateTaskDefLimitStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.updateEventExecutionStatement =
session.prepare(statements.getUpdateEventExecutionStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.deleteWorkflowStatement =
session.prepare(statements.getDeleteWorkflowStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.deleteTaskStatement =
session.prepare(statements.getDeleteTaskStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.deleteTaskLookupStatement =
session.prepare(statements.getDeleteTaskLookupStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.deleteTaskDefLimitStatement =
session.prepare(statements.getDeleteTaskDefLimitStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.deleteEventExecutionStatement =
session.prepare(statements.getDeleteEventExecutionsStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
}
@Override
public List
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.dao;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.PriorityQueue;
import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.conductor.annotations.Trace;
import com.netflix.conductor.annotations.VisibleForTesting;
import com.netflix.conductor.cassandra.config.CassandraProperties;
import com.netflix.conductor.cassandra.util.Statements;
import com.netflix.conductor.common.metadata.tasks.TaskDef;
import com.netflix.conductor.common.metadata.workflow.WorkflowDef;
import com.netflix.conductor.core.exception.ConflictException;
import com.netflix.conductor.core.exception.TransientException;
import com.netflix.conductor.dao.MetadataDAO;
import com.netflix.conductor.metrics.Monitors;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.exceptions.DriverException;
import com.fasterxml.jackson.databind.ObjectMapper;
import static com.netflix.conductor.cassandra.util.Constants.TASK_DEFINITION_KEY;
import static com.netflix.conductor.cassandra.util.Constants.TASK_DEFS_KEY;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEFINITION_KEY;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_INDEX_KEY;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_NAME_VERSION_KEY;
import static com.netflix.conductor.common.metadata.tasks.TaskDef.ONE_HOUR;
@Trace
public class CassandraMetadataDAO extends CassandraBaseDAO implements MetadataDAO {
private static final Logger LOGGER = LoggerFactory.getLogger(CassandraMetadataDAO.class);
private static final String CLASS_NAME = CassandraMetadataDAO.class.getSimpleName();
private static final String INDEX_DELIMITER = "/";
private final PreparedStatement insertWorkflowDefStatement;
private final PreparedStatement insertWorkflowDefVersionIndexStatement;
private final PreparedStatement insertTaskDefStatement;
private final PreparedStatement selectWorkflowDefStatement;
private final PreparedStatement selectAllWorkflowDefVersionsByNameStatement;
private final PreparedStatement selectAllWorkflowDefsStatement;
private final PreparedStatement selectAllWorkflowDefsLatestVersionsStatement;
private final PreparedStatement selectTaskDefStatement;
private final PreparedStatement selectAllTaskDefsStatement;
private final PreparedStatement updateWorkflowDefStatement;
private final PreparedStatement deleteWorkflowDefStatement;
private final PreparedStatement deleteWorkflowDefIndexStatement;
private final PreparedStatement deleteTaskDefStatement;
public CassandraMetadataDAO(
Session session,
ObjectMapper objectMapper,
CassandraProperties properties,
Statements statements) {
super(session, objectMapper, properties);
this.insertWorkflowDefStatement =
session.prepare(statements.getInsertWorkflowDefStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.insertWorkflowDefVersionIndexStatement =
session.prepare(statements.getInsertWorkflowDefVersionIndexStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.insertTaskDefStatement =
session.prepare(statements.getInsertTaskDefStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.selectWorkflowDefStatement =
session.prepare(statements.getSelectWorkflowDefStatement())
.setConsistencyLevel(properties.getReadConsistencyLevel());
this.selectAllWorkflowDefVersionsByNameStatement =
session.prepare(statements.getSelectAllWorkflowDefVersionsByNameStatement())
.setConsistencyLevel(properties.getReadConsistencyLevel());
this.selectAllWorkflowDefsStatement =
session.prepare(statements.getSelectAllWorkflowDefsStatement())
.setConsistencyLevel(properties.getReadConsistencyLevel());
this.selectAllWorkflowDefsLatestVersionsStatement =
session.prepare(statements.getSelectAllWorkflowDefsLatestVersionsStatement())
.setConsistencyLevel(properties.getReadConsistencyLevel());
this.selectTaskDefStatement =
session.prepare(statements.getSelectTaskDefStatement())
.setConsistencyLevel(properties.getReadConsistencyLevel());
this.selectAllTaskDefsStatement =
session.prepare(statements.getSelectAllTaskDefsStatement())
.setConsistencyLevel(properties.getReadConsistencyLevel());
this.updateWorkflowDefStatement =
session.prepare(statements.getUpdateWorkflowDefStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.deleteWorkflowDefStatement =
session.prepare(statements.getDeleteWorkflowDefStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.deleteWorkflowDefIndexStatement =
session.prepare(statements.getDeleteWorkflowDefIndexStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
this.deleteTaskDefStatement =
session.prepare(statements.getDeleteTaskDefStatement())
.setConsistencyLevel(properties.getWriteConsistencyLevel());
}
@Override
public TaskDef createTaskDef(TaskDef taskDef) {
return insertOrUpdateTaskDef(taskDef);
}
@Override
public TaskDef updateTaskDef(TaskDef taskDef) {
return insertOrUpdateTaskDef(taskDef);
}
@Override
public TaskDef getTaskDef(String name) {
return getTaskDefFromDB(name);
}
@Override
public List
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.dao;
import java.util.List;
import com.netflix.conductor.common.metadata.tasks.PollData;
import com.netflix.conductor.dao.PollDataDAO;
/**
* This is a dummy implementation and this feature is not implemented for Cassandra backed
* Conductor.
*/
public class CassandraPollDataDAO implements PollDataDAO {
@Override
public void updateLastPollData(String taskDefName, String domain, String workerId) {
throw new UnsupportedOperationException(
"This method is not implemented in CassandraPollDataDAO. Please use ExecutionDAOFacade instead.");
}
@Override
public PollData getPollData(String taskDefName, String domain) {
throw new UnsupportedOperationException(
"This method is not implemented in CassandraPollDataDAO. Please use ExecutionDAOFacade instead.");
}
@Override
public List
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.util;
public interface Constants {
String DAO_NAME = "cassandra";
String TABLE_WORKFLOWS = "workflows";
String TABLE_TASK_LOOKUP = "task_lookup";
String TABLE_TASK_DEF_LIMIT = "task_def_limit";
String TABLE_WORKFLOW_DEFS = "workflow_definitions";
String TABLE_WORKFLOW_DEFS_INDEX = "workflow_defs_index";
String TABLE_TASK_DEFS = "task_definitions";
String TABLE_EVENT_HANDLERS = "event_handlers";
String TABLE_EVENT_EXECUTIONS = "event_executions";
String WORKFLOW_ID_KEY = "workflow_id";
String SHARD_ID_KEY = "shard_id";
String TASK_ID_KEY = "task_id";
String ENTITY_KEY = "entity";
String PAYLOAD_KEY = "payload";
String TOTAL_TASKS_KEY = "total_tasks";
String TOTAL_PARTITIONS_KEY = "total_partitions";
String TASK_DEF_NAME_KEY = "task_def_name";
String WORKFLOW_DEF_NAME_KEY = "workflow_def_name";
String WORKFLOW_VERSION_KEY = "version";
String WORKFLOW_DEFINITION_KEY = "workflow_definition";
String WORKFLOW_DEF_INDEX_KEY = "workflow_def_version_index";
String WORKFLOW_DEF_INDEX_VALUE = "workflow_def_index_value";
String WORKFLOW_DEF_NAME_VERSION_KEY = "workflow_def_name_version";
String TASK_DEFS_KEY = "task_defs";
String TASK_DEFINITION_KEY = "task_definition";
String HANDLERS_KEY = "handlers";
String EVENT_HANDLER_NAME_KEY = "event_handler_name";
String EVENT_HANDLER_KEY = "event_handler";
String MESSAGE_ID_KEY = "message_id";
String EVENT_EXECUTION_ID_KEY = "event_execution_id";
String ENTITY_TYPE_WORKFLOW = "workflow";
String ENTITY_TYPE_TASK = "task";
int DEFAULT_SHARD_ID = 1;
int DEFAULT_TOTAL_PARTITIONS = 1;
}
================================================
FILE: cassandra-persistence/src/main/java/com/netflix/conductor/cassandra/util/Statements.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.util;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import static com.netflix.conductor.cassandra.util.Constants.ENTITY_KEY;
import static com.netflix.conductor.cassandra.util.Constants.ENTITY_TYPE_TASK;
import static com.netflix.conductor.cassandra.util.Constants.ENTITY_TYPE_WORKFLOW;
import static com.netflix.conductor.cassandra.util.Constants.EVENT_EXECUTION_ID_KEY;
import static com.netflix.conductor.cassandra.util.Constants.EVENT_HANDLER_KEY;
import static com.netflix.conductor.cassandra.util.Constants.EVENT_HANDLER_NAME_KEY;
import static com.netflix.conductor.cassandra.util.Constants.HANDLERS_KEY;
import static com.netflix.conductor.cassandra.util.Constants.MESSAGE_ID_KEY;
import static com.netflix.conductor.cassandra.util.Constants.PAYLOAD_KEY;
import static com.netflix.conductor.cassandra.util.Constants.SHARD_ID_KEY;
import static com.netflix.conductor.cassandra.util.Constants.TABLE_EVENT_EXECUTIONS;
import static com.netflix.conductor.cassandra.util.Constants.TABLE_EVENT_HANDLERS;
import static com.netflix.conductor.cassandra.util.Constants.TABLE_TASK_DEFS;
import static com.netflix.conductor.cassandra.util.Constants.TABLE_TASK_DEF_LIMIT;
import static com.netflix.conductor.cassandra.util.Constants.TABLE_TASK_LOOKUP;
import static com.netflix.conductor.cassandra.util.Constants.TABLE_WORKFLOWS;
import static com.netflix.conductor.cassandra.util.Constants.TABLE_WORKFLOW_DEFS;
import static com.netflix.conductor.cassandra.util.Constants.TABLE_WORKFLOW_DEFS_INDEX;
import static com.netflix.conductor.cassandra.util.Constants.TASK_DEFINITION_KEY;
import static com.netflix.conductor.cassandra.util.Constants.TASK_DEFS_KEY;
import static com.netflix.conductor.cassandra.util.Constants.TASK_DEF_NAME_KEY;
import static com.netflix.conductor.cassandra.util.Constants.TASK_ID_KEY;
import static com.netflix.conductor.cassandra.util.Constants.TOTAL_PARTITIONS_KEY;
import static com.netflix.conductor.cassandra.util.Constants.TOTAL_TASKS_KEY;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEFINITION_KEY;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_INDEX_KEY;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_INDEX_VALUE;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_NAME_KEY;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_DEF_NAME_VERSION_KEY;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_ID_KEY;
import static com.netflix.conductor.cassandra.util.Constants.WORKFLOW_VERSION_KEY;
import static com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker;
import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
import static com.datastax.driver.core.querybuilder.QueryBuilder.set;
/**
* DML statements
*
* MetadataDAO
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.dao
import com.netflix.conductor.common.metadata.events.EventExecution
import com.netflix.conductor.common.metadata.events.EventHandler
import spock.lang.Subject
class CassandraEventHandlerDAOSpec extends CassandraSpec {
@Subject
CassandraEventHandlerDAO eventHandlerDAO
CassandraExecutionDAO executionDAO
def setup() {
eventHandlerDAO = new CassandraEventHandlerDAO(session, objectMapper, cassandraProperties, statements)
executionDAO = new CassandraExecutionDAO(session, objectMapper, cassandraProperties, statements)
}
def testEventHandlerCRUD() {
given:
String event = "event"
String eventHandlerName1 = "event_handler1"
String eventHandlerName2 = "event_handler2"
EventHandler eventHandler = new EventHandler()
eventHandler.setName(eventHandlerName1)
eventHandler.setEvent(event)
when: // create event handler
eventHandlerDAO.addEventHandler(eventHandler)
List
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.dao
import com.netflix.conductor.common.metadata.events.EventExecution
import com.netflix.conductor.common.metadata.tasks.TaskDef
import com.netflix.conductor.common.metadata.workflow.WorkflowDef
import com.netflix.conductor.common.metadata.workflow.WorkflowTask
import com.netflix.conductor.core.exception.NonTransientException
import com.netflix.conductor.core.utils.IDGenerator
import com.netflix.conductor.model.TaskModel
import com.netflix.conductor.model.WorkflowModel
import spock.lang.Subject
import static com.netflix.conductor.common.metadata.events.EventExecution.Status.COMPLETED
import static com.netflix.conductor.common.metadata.events.EventExecution.Status.IN_PROGRESS
class CassandraExecutionDAOSpec extends CassandraSpec {
@Subject
CassandraExecutionDAO executionDAO
def setup() {
executionDAO = new CassandraExecutionDAO(session, objectMapper, cassandraProperties, statements)
}
def "verify if tasks are validated"() {
given:
def tasks = []
// create tasks for a workflow and add to list
TaskModel task1 = new TaskModel(workflowInstanceId: 'uuid', taskId: 'task1id', referenceTaskName: 'task1')
TaskModel task2 = new TaskModel(workflowInstanceId: 'uuid', taskId: 'task2id', referenceTaskName: 'task2')
tasks << task1 << task2
when:
executionDAO.validateTasks(tasks)
then:
noExceptionThrown()
and:
// add a task from a different workflow to the list
TaskModel task3 = new TaskModel(workflowInstanceId: 'other-uuid', taskId: 'task3id', referenceTaskName: 'task3')
tasks << task3
when:
executionDAO.validateTasks(tasks)
then:
def ex = thrown(NonTransientException.class)
ex.message == "Tasks of multiple workflows cannot be created/updated simultaneously"
}
def "workflow CRUD"() {
given:
String workflowId = new IDGenerator().generate()
WorkflowDef workflowDef = new WorkflowDef()
workflowDef.name = "def1"
workflowDef.setVersion(1)
WorkflowModel workflow = new WorkflowModel()
workflow.setWorkflowDefinition(workflowDef)
workflow.setWorkflowId(workflowId)
workflow.setInput(new HashMap<>())
workflow.setStatus(WorkflowModel.Status.RUNNING)
workflow.setCreateTime(System.currentTimeMillis())
when:
// create a new workflow in the datastore
String id = executionDAO.createWorkflow(workflow)
then:
workflowId == id
when:
// read the workflow from the datastore
WorkflowModel found = executionDAO.getWorkflow(workflowId)
then:
workflow == found
and:
// update the workflow
workflow.setStatus(WorkflowModel.Status.COMPLETED)
executionDAO.updateWorkflow(workflow)
when:
found = executionDAO.getWorkflow(workflowId)
then:
workflow == found
when:
// remove the workflow from datastore
boolean removed = executionDAO.removeWorkflow(workflowId)
then:
removed
when:
// read workflow again
workflow = executionDAO.getWorkflow(workflowId, true)
then:
workflow == null
}
def "create tasks and verify methods that read tasks and workflow"() {
given: 'we create a workflow'
String workflowId = new IDGenerator().generate()
WorkflowDef workflowDef = new WorkflowDef(name: 'def1', version: 1)
WorkflowModel workflow = new WorkflowModel(workflowDefinition: workflowDef, workflowId: workflowId, input: new HashMap(), status: WorkflowModel.Status.RUNNING, createTime: System.currentTimeMillis())
executionDAO.createWorkflow(workflow)
and: 'create tasks for this workflow'
TaskModel task1 = new TaskModel(workflowInstanceId: workflowId, taskType: 'task1', referenceTaskName: 'task1', status: TaskModel.Status.SCHEDULED, taskId: new IDGenerator().generate())
TaskModel task2 = new TaskModel(workflowInstanceId: workflowId, taskType: 'task2', referenceTaskName: 'task2', status: TaskModel.Status.SCHEDULED, taskId: new IDGenerator().generate())
TaskModel task3 = new TaskModel(workflowInstanceId: workflowId, taskType: 'task3', referenceTaskName: 'task3', status: TaskModel.Status.SCHEDULED, taskId: new IDGenerator().generate())
def taskList = [task1, task2, task3]
when: 'add the tasks to the datastore'
List
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.dao
import com.netflix.conductor.common.metadata.tasks.TaskDef
import com.netflix.conductor.common.metadata.workflow.WorkflowDef
import spock.lang.Subject
class CassandraMetadataDAOSpec extends CassandraSpec {
@Subject
CassandraMetadataDAO metadataDAO
def setup() {
metadataDAO = new CassandraMetadataDAO(session, objectMapper, cassandraProperties, statements)
}
def cleanup() {
}
def "CRUD on WorkflowDef"() throws Exception {
given:
String name = "workflow_def_1"
int version = 1
WorkflowDef workflowDef = new WorkflowDef()
workflowDef.setName(name)
workflowDef.setVersion(version)
workflowDef.setOwnerEmail("test@junit.com")
when: 'create workflow definition'
metadataDAO.createWorkflowDef(workflowDef)
then: // fetch the workflow definition
def defOptional = metadataDAO.getWorkflowDef(name, version)
defOptional.present
defOptional.get() == workflowDef
and: // register a higher version
int higherVersion = 2
workflowDef.setVersion(higherVersion)
workflowDef.setDescription("higher version")
when: // register the higher version definition
metadataDAO.createWorkflowDef(workflowDef)
defOptional = metadataDAO.getWorkflowDef(name, higherVersion)
then: // fetch the higher version
defOptional.present
defOptional.get() == workflowDef
when: // fetch latest version
defOptional = metadataDAO.getLatestWorkflowDef(name)
then:
defOptional && defOptional.present
defOptional.get() == workflowDef
when: // modify the definition
workflowDef.setOwnerEmail("test@junit.com")
metadataDAO.updateWorkflowDef(workflowDef)
defOptional = metadataDAO.getWorkflowDef(name, higherVersion)
then: // fetch the workflow definition
defOptional.present
defOptional.get() == workflowDef
when: // delete workflow def
metadataDAO.removeWorkflowDef(name, higherVersion)
defOptional = metadataDAO.getWorkflowDef(name, higherVersion)
then:
defOptional.empty
}
def "CRUD on TaskDef"() {
given:
String task1Name = "task1"
String task2Name = "task2"
when: // fetch all task defs
def taskDefList = metadataDAO.getAllTaskDefs()
then:
taskDefList.empty
when: // register a task definition
TaskDef taskDef = new TaskDef()
taskDef.setName(task1Name)
metadataDAO.createTaskDef(taskDef)
taskDefList = metadataDAO.getAllTaskDefs()
then: // fetch all task defs
taskDefList && taskDefList.size() == 1
when: // fetch the task def
def returnTaskDef = metadataDAO.getTaskDef(task1Name)
then:
returnTaskDef == taskDef
when: // register another task definition
TaskDef taskDef1 = new TaskDef()
taskDef1.setName(task2Name)
metadataDAO.createTaskDef(taskDef1)
// fetch all task defs
taskDefList = metadataDAO.getAllTaskDefs()
then:
taskDefList && taskDefList.size() == 2
when: // update task def
taskDef.setOwnerEmail("juni@test.com")
metadataDAO.updateTaskDef(taskDef)
returnTaskDef = metadataDAO.getTaskDef(task1Name)
then:
returnTaskDef == taskDef
when: // delete task def
metadataDAO.removeTaskDef(task2Name)
taskDefList = metadataDAO.getAllTaskDefs()
then:
taskDefList && taskDefList.size() == 1
// fetch deleted task def
metadataDAO.getTaskDef(task2Name) == null
}
def "set default response timeout when not set"() {
given:
String task1Name = "task1"
when: // register a task definition
TaskDef taskDef = new TaskDef()
taskDef.setName(task1Name)
taskDef.setResponseTimeoutSeconds(0)
metadataDAO.createTaskDef(taskDef)
def returnTaskDef = metadataDAO.getTaskDef(task1Name)
then:
returnTaskDef.getResponseTimeoutSeconds() == 3600
when: // register another task definition
taskDef.setTimeoutSeconds(200)
taskDef.setResponseTimeoutSeconds(0)
metadataDAO.updateTaskDef(taskDef)
// fetch all task defs
def taskDefList = metadataDAO.getAllTaskDefs()
then:
taskDefList && taskDefList.size() == 1
taskDefList.get(0).getResponseTimeoutSeconds() == 199
}
def "Get All WorkflowDef"() {
when:
metadataDAO.removeWorkflowDef("workflow_def_1", 1)
WorkflowDef workflowDef = new WorkflowDef()
workflowDef.setName("workflow_def_1")
workflowDef.setVersion(1)
workflowDef.setOwnerEmail("test@junit.com")
metadataDAO.createWorkflowDef(workflowDef)
workflowDef.setName("workflow_def_2")
metadataDAO.createWorkflowDef(workflowDef)
workflowDef.setVersion(2)
metadataDAO.createWorkflowDef(workflowDef)
workflowDef.setName("workflow_def_3")
workflowDef.setVersion(1)
metadataDAO.createWorkflowDef(workflowDef)
workflowDef.setVersion(2)
metadataDAO.createWorkflowDef(workflowDef)
workflowDef.setVersion(3)
metadataDAO.createWorkflowDef(workflowDef)
then: // fetch the workflow definition
def allDefsLatestVersions = metadataDAO.getAllWorkflowDefsLatestVersions()
Map
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.dao
import java.time.Duration
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ContextConfiguration
import org.testcontainers.containers.CassandraContainer
import org.testcontainers.spock.Testcontainers
import com.netflix.conductor.cassandra.config.CassandraProperties
import com.netflix.conductor.cassandra.util.Statements
import com.netflix.conductor.common.config.TestObjectMapperConfiguration
import com.datastax.driver.core.ConsistencyLevel
import com.datastax.driver.core.Session
import com.fasterxml.jackson.databind.ObjectMapper
import groovy.transform.PackageScope
import spock.lang.Shared
import spock.lang.Specification
@ContextConfiguration(classes = [TestObjectMapperConfiguration.class])
@Testcontainers
@PackageScope
abstract class CassandraSpec extends Specification {
@Shared
CassandraContainer cassandra = new CassandraContainer()
@Shared
Session session
@Autowired
ObjectMapper objectMapper
CassandraProperties cassandraProperties
Statements statements
def setupSpec() {
session = cassandra.cluster.newSession()
}
def setup() {
String keyspaceName = "junit"
cassandraProperties = Mock(CassandraProperties.class) {
getKeyspace() >> keyspaceName
getReplicationStrategy() >> "SimpleStrategy"
getReplicationFactorKey() >> "replication_factor"
getReplicationFactorValue() >> 1
getReadConsistencyLevel() >> ConsistencyLevel.LOCAL_ONE
getWriteConsistencyLevel() >> ConsistencyLevel.LOCAL_ONE
getTaskDefCacheRefreshInterval() >> Duration.ofSeconds(60)
getEventHandlerCacheRefreshInterval() >> Duration.ofSeconds(60)
getEventExecutionPersistenceTtl() >> Duration.ofSeconds(5)
}
statements = new Statements(keyspaceName)
}
}
================================================
FILE: cassandra-persistence/src/test/groovy/com/netflix/conductor/cassandra/util/StatementsSpec.groovy
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.cassandra.util
import spock.lang.Specification
import spock.lang.Subject
class StatementsSpec extends Specification {
@Subject
Statements subject
def setup() {
subject = new Statements('test')
}
def "verify statements"() {
when:
subject
then:
with(subject) {
insertWorkflowDefStatement == "INSERT INTO test.workflow_definitions (workflow_def_name,version,workflow_definition) VALUES (?,?,?) IF NOT EXISTS;"
insertTaskDefStatement == "INSERT INTO test.task_definitions (task_defs,task_def_name,task_definition) VALUES ('task_defs',?,?);"
selectWorkflowDefStatement == "SELECT workflow_definition FROM test.workflow_definitions WHERE workflow_def_name=? AND version=?;"
selectAllWorkflowDefVersionsByNameStatement == "SELECT * FROM test.workflow_definitions WHERE workflow_def_name=?;"
selectAllWorkflowDefsStatement == "SELECT * FROM test.workflow_defs_index WHERE workflow_def_version_index=?;"
selectTaskDefStatement == "SELECT task_definition FROM test.task_definitions WHERE task_defs='task_defs' AND task_def_name=?;"
selectAllTaskDefsStatement == "SELECT * FROM test.task_definitions WHERE task_defs=?;"
updateWorkflowDefStatement == "UPDATE test.workflow_definitions SET workflow_definition=? WHERE workflow_def_name=? AND version=?;"
deleteWorkflowDefStatement == "DELETE FROM test.workflow_definitions WHERE workflow_def_name=? AND version=?;"
deleteWorkflowDefIndexStatement == "DELETE FROM test.workflow_defs_index WHERE workflow_def_version_index=? AND workflow_def_name_version=?;"
deleteTaskDefStatement == "DELETE FROM test.task_definitions WHERE task_defs='task_defs' AND task_def_name=?;"
insertWorkflowStatement == "INSERT INTO test.workflows (workflow_id,shard_id,task_id,entity,payload,total_tasks,total_partitions) VALUES (?,?,?,'workflow',?,?,?);"
insertTaskStatement == "INSERT INTO test.workflows (workflow_id,shard_id,task_id,entity,payload) VALUES (?,?,?,'task',?);"
insertEventExecutionStatement == "INSERT INTO test.event_executions (message_id,event_handler_name,event_execution_id,payload) VALUES (?,?,?,?) IF NOT EXISTS;"
selectTotalStatement == "SELECT total_tasks,total_partitions FROM test.workflows WHERE workflow_id=? AND shard_id=1;"
selectTaskStatement == "SELECT payload FROM test.workflows WHERE workflow_id=? AND shard_id=? AND entity='task' AND task_id=?;"
selectWorkflowStatement == "SELECT payload FROM test.workflows WHERE workflow_id=? AND shard_id=1 AND entity='workflow';"
selectWorkflowWithTasksStatement == "SELECT * FROM test.workflows WHERE workflow_id=? AND shard_id=?;"
selectTaskFromLookupTableStatement == "SELECT workflow_id FROM test.task_lookup WHERE task_id=?;"
selectTasksFromTaskDefLimitStatement == "SELECT * FROM test.task_def_limit WHERE task_def_name=?;"
selectAllEventExecutionsForMessageFromEventExecutionsStatement == "SELECT * FROM test.event_executions WHERE message_id=? AND event_handler_name=?;"
updateWorkflowStatement == "UPDATE test.workflows SET payload=? WHERE workflow_id=? AND shard_id=1 AND entity='workflow' AND task_id='';"
updateTotalTasksStatement == "UPDATE test.workflows SET total_tasks=? WHERE workflow_id=? AND shard_id=?;"
updateTotalPartitionsStatement == "UPDATE test.workflows SET total_partitions=?,total_tasks=? WHERE workflow_id=? AND shard_id=1;"
updateTaskLookupStatement == "UPDATE test.task_lookup SET workflow_id=? WHERE task_id=?;"
updateTaskDefLimitStatement == "UPDATE test.task_def_limit SET workflow_id=? WHERE task_def_name=? AND task_id=?;"
updateEventExecutionStatement == "UPDATE test.event_executions USING TTL ? SET payload=? WHERE message_id=? AND event_handler_name=? AND event_execution_id=?;"
deleteWorkflowStatement == "DELETE FROM test.workflows WHERE workflow_id=? AND shard_id=?;"
deleteTaskLookupStatement == "DELETE FROM test.task_lookup WHERE task_id=?;"
deleteTaskStatement == "DELETE FROM test.workflows WHERE workflow_id=? AND shard_id=? AND entity='task' AND task_id=?;"
deleteTaskDefLimitStatement == "DELETE FROM test.task_def_limit WHERE task_def_name=? AND task_id=?;"
deleteEventExecutionsStatement == "DELETE FROM test.event_executions WHERE message_id=? AND event_handler_name=? AND event_execution_id=?;"
insertEventHandlerStatement == "INSERT INTO test.event_handlers (handlers,event_handler_name,event_handler) VALUES ('handlers',?,?);"
selectAllEventHandlersStatement == "SELECT * FROM test.event_handlers WHERE handlers=?;"
deleteEventHandlerStatement == "DELETE FROM test.event_handlers WHERE handlers='handlers' AND event_handler_name=?;"
}
}
}
================================================
FILE: client/build.gradle
================================================
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.7.5"
}
}
apply plugin: 'groovy'
configurations.all {
exclude group: 'amazon', module: 'aws-java-sdk'
}
dependencies {
compileOnly 'org.jetbrains:annotations:23.0.0'
implementation project(':conductor-common')
implementation "com.sun.jersey:jersey-client:${revJersey}"
implementation "javax.ws.rs:javax.ws.rs-api:${revJAXRS}"
implementation "org.glassfish.jersey.core:jersey-common:${revJerseyCommon}"
implementation "com.netflix.spectator:spectator-api:${revSpectator}"
implementation ("com.netflix.eureka:eureka-client:${revEurekaClient}") {
exclude group: 'com.google.guava', module: 'guava'
}
implementation "com.amazonaws:aws-java-sdk-core:${revAwsSdk}"
implementation "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
implementation "org.apache.commons:commons-lang3"
implementation "commons-io:commons-io:${revCommonsIo}"
implementation "org.slf4j:slf4j-api"
testImplementation "org.powermock:powermock-module-junit4:${revPowerMock}"
testImplementation "org.powermock:powermock-api-mockito2:${revPowerMock}"
testImplementation "org.codehaus.groovy:groovy-all:${revGroovy}"
testImplementation "org.spockframework:spock-core:${revSpock}"
testImplementation "org.spockframework:spock-spring:${revSpock}"
}
================================================
FILE: client/spotbugsExclude.xml
================================================
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.client.automator;
import java.util.concurrent.Semaphore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class wrapping a semaphore which holds the number of permits available for polling and
* executing tasks.
*/
class PollingSemaphore {
private static final Logger LOGGER = LoggerFactory.getLogger(PollingSemaphore.class);
private final Semaphore semaphore;
PollingSemaphore(int numSlots) {
LOGGER.debug("Polling semaphore initialized with {} permits", numSlots);
semaphore = new Semaphore(numSlots);
}
/** Signals that processing is complete and the specified number of permits can be released. */
void complete(int numSlots) {
LOGGER.debug("Completed execution; releasing permit");
semaphore.release(numSlots);
}
/**
* Gets the number of threads available for processing.
*
* @return number of available permits
*/
int availableSlots() {
int available = semaphore.availablePermits();
LOGGER.debug("Number of available permits: {}", available);
return available;
}
/**
* Signals if processing is allowed based on whether specified number of permits can be
* acquired.
*
* @param numSlots the number of permits to acquire
* @return {@code true} - if permit is acquired {@code false} - if permit could not be acquired
*/
public boolean acquireSlots(int numSlots) {
boolean acquired = semaphore.tryAcquire(numSlots);
LOGGER.debug("Trying to acquire {} permit: {}", numSlots, acquired);
return acquired;
}
}
================================================
FILE: client/src/main/java/com/netflix/conductor/client/automator/TaskPollExecutor.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.client.automator;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.*;
import java.util.function.Function;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.apache.commons.lang3.time.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.conductor.client.config.PropertyFactory;
import com.netflix.conductor.client.http.TaskClient;
import com.netflix.conductor.client.telemetry.MetricsContainer;
import com.netflix.conductor.client.worker.Worker;
import com.netflix.conductor.common.metadata.tasks.Task;
import com.netflix.conductor.common.metadata.tasks.TaskResult;
import com.netflix.discovery.EurekaClient;
import com.netflix.spectator.api.Registry;
import com.netflix.spectator.api.Spectator;
import com.netflix.spectator.api.patterns.ThreadPoolMonitor;
/**
* Manages the threadpool used by the workers for execution and server communication (polling and
* task update).
*/
class TaskPollExecutor {
private static final Logger LOGGER = LoggerFactory.getLogger(TaskPollExecutor.class);
private static final Registry REGISTRY = Spectator.globalRegistry();
private final EurekaClient eurekaClient;
private final TaskClient taskClient;
private final int updateRetryCount;
private final ExecutorService executorService;
private final Map
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.client.automator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.conductor.client.exception.ConductorClientException;
import com.netflix.conductor.client.http.TaskClient;
import com.netflix.conductor.client.worker.Worker;
import com.netflix.discovery.EurekaClient;
/** Configures automated polling of tasks and execution via the registered {@link Worker}s. */
public class TaskRunnerConfigurer {
private static final Logger LOGGER = LoggerFactory.getLogger(TaskRunnerConfigurer.class);
private static final String INVALID_THREAD_COUNT =
"Invalid worker thread count specified, use either shared thread pool or config thread count per task";
private static final String MISSING_TASK_THREAD_COUNT =
"Missing task thread count config for %s";
private ScheduledExecutorService scheduledExecutorService;
private final EurekaClient eurekaClient;
private final TaskClient taskClient;
private final List Please see {@link TaskRunnerConfigurer#init()} method. The method must be called after
* this constructor for the polling to start.
*/
public TaskRunnerConfigurer build() {
return new TaskRunnerConfigurer(this);
}
}
/**
* @return Thread Count for the shared executor pool
*/
@Deprecated
public int getThreadCount() {
return threadCount;
}
/**
* @return Thread Count for individual task type
*/
public Map
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.client.config;
public interface ConductorClientConfiguration {
/**
* @return the workflow input payload size threshold in KB, beyond which the payload will be
* processed based on {@link
* ConductorClientConfiguration#isExternalPayloadStorageEnabled()}.
*/
int getWorkflowInputPayloadThresholdKB();
/**
* @return the max value of workflow input payload size threshold in KB, beyond which the
* payload will be rejected regardless external payload storage is enabled.
*/
int getWorkflowInputMaxPayloadThresholdKB();
/**
* @return the task output payload size threshold in KB, beyond which the payload will be
* processed based on {@link
* ConductorClientConfiguration#isExternalPayloadStorageEnabled()}.
*/
int getTaskOutputPayloadThresholdKB();
/**
* @return the max value of task output payload size threshold in KB, beyond which the payload
* will be rejected regardless external payload storage is enabled.
*/
int getTaskOutputMaxPayloadThresholdKB();
/**
* @return the flag which controls the use of external storage for storing workflow/task input
* and output JSON payloads with size greater than threshold. If it is set to true, the
* payload is stored in external location. If it is set to false, the payload is rejected
* and the task/workflow execution fails.
*/
boolean isExternalPayloadStorageEnabled();
}
================================================
FILE: client/src/main/java/com/netflix/conductor/client/config/DefaultConductorClientConfiguration.java
================================================
/*
* Copyright 2020 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.client.config;
/**
* A default implementation of {@link ConductorClientConfiguration} where external payload storage
* is disabled.
*/
public class DefaultConductorClientConfiguration implements ConductorClientConfiguration {
@Override
public int getWorkflowInputPayloadThresholdKB() {
return 5120;
}
@Override
public int getWorkflowInputMaxPayloadThresholdKB() {
return 10240;
}
@Override
public int getTaskOutputPayloadThresholdKB() {
return 3072;
}
@Override
public int getTaskOutputMaxPayloadThresholdKB() {
return 10240;
}
@Override
public boolean isExternalPayloadStorageEnabled() {
return false;
}
}
================================================
FILE: client/src/main/java/com/netflix/conductor/client/config/PropertyFactory.java
================================================
/*
* Copyright 2020 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.client.config;
import java.util.concurrent.ConcurrentHashMap;
import com.netflix.config.DynamicProperty;
/** Used to configure the Conductor workers using properties. */
public class PropertyFactory {
private final DynamicProperty global;
private final DynamicProperty local;
private static final String PROPERTY_PREFIX = "conductor.worker";
private static final ConcurrentHashMap
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.client.exception;
import java.util.List;
import com.netflix.conductor.common.validation.ErrorResponse;
import com.netflix.conductor.common.validation.ValidationError;
/** Client exception thrown from Conductor api clients. */
public class ConductorClientException extends RuntimeException {
private int status;
private String message;
private String instance;
private String code;
private boolean retryable;
public List
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.client.http;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;
import javax.ws.rs.core.UriBuilder;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.conductor.client.config.ConductorClientConfiguration;
import com.netflix.conductor.client.config.DefaultConductorClientConfiguration;
import com.netflix.conductor.client.exception.ConductorClientException;
import com.netflix.conductor.common.config.ObjectMapperProvider;
import com.netflix.conductor.common.model.BulkResponse;
import com.netflix.conductor.common.run.ExternalStorageLocation;
import com.netflix.conductor.common.utils.ExternalPayloadStorage;
import com.netflix.conductor.common.validation.ErrorResponse;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.GenericType;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource.Builder;
/** Abstract client for the REST server */
public abstract class ClientBase {
private static final Logger LOGGER = LoggerFactory.getLogger(ClientBase.class);
protected ClientRequestHandler requestHandler;
protected String root = "";
protected ObjectMapper objectMapper;
protected PayloadStorage payloadStorage;
protected ConductorClientConfiguration conductorClientConfiguration;
protected ClientBase(
ClientRequestHandler requestHandler, ConductorClientConfiguration clientConfiguration) {
this.objectMapper = new ObjectMapperProvider().getObjectMapper();
// https://github.com/FasterXML/jackson-databind/issues/2683
if (isNewerJacksonVersion()) {
objectMapper.registerModule(new JavaTimeModule());
}
this.requestHandler = requestHandler;
this.conductorClientConfiguration =
ObjectUtils.defaultIfNull(
clientConfiguration, new DefaultConductorClientConfiguration());
this.payloadStorage = new PayloadStorage(this);
}
public void setRootURI(String root) {
this.root = root;
}
protected void delete(String url, Object... uriVariables) {
deleteWithUriVariables(null, url, uriVariables);
}
protected void deleteWithUriVariables(
Object[] queryParams, String url, Object... uriVariables) {
delete(queryParams, url, uriVariables, null);
}
protected BulkResponse deleteWithRequestBody(Object[] queryParams, String url, Object body) {
return delete(queryParams, url, null, body);
}
private BulkResponse delete(
Object[] queryParams, String url, Object[] uriVariables, Object body) {
URI uri = null;
BulkResponse response = null;
try {
uri = getURIBuilder(root + url, queryParams).build(uriVariables);
response = requestHandler.delete(uri, body);
} catch (UniformInterfaceException e) {
handleUniformInterfaceException(e, uri);
} catch (RuntimeException e) {
handleRuntimeException(e, uri);
}
return response;
}
protected void put(String url, Object[] queryParams, Object request, Object... uriVariables) {
URI uri = null;
try {
uri = getURIBuilder(root + url, queryParams).build(uriVariables);
requestHandler.getWebResourceBuilder(uri, request).put();
} catch (RuntimeException e) {
handleException(uri, e);
}
}
protected void postForEntityWithRequestOnly(String url, Object request) {
Class> type = null;
postForEntity(url, request, null, type);
}
protected void postForEntityWithUriVariablesOnly(String url, Object... uriVariables) {
Class> type = null;
postForEntity(url, null, null, type, uriVariables);
}
protected
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.client.http;
import java.net.URI;
import javax.ws.rs.core.MediaType;
import com.netflix.conductor.common.config.ObjectMapperProvider;
import com.netflix.conductor.common.model.BulkResponse;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientHandler;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.filter.ClientFilter;
public class ClientRequestHandler {
private final Client client;
public ClientRequestHandler(
ClientConfig config, ClientHandler handler, ClientFilter... filters) {
ObjectMapper objectMapper = new ObjectMapperProvider().getObjectMapper();
// https://github.com/FasterXML/jackson-databind/issues/2683
if (isNewerJacksonVersion()) {
objectMapper.registerModule(new JavaTimeModule());
}
JacksonJsonProvider provider = new JacksonJsonProvider(objectMapper);
config.getSingletons().add(provider);
if (handler == null) {
this.client = Client.create(config);
} else {
this.client = new Client(handler, config);
}
for (ClientFilter filter : filters) {
this.client.addFilter(filter);
}
}
public BulkResponse delete(URI uri, Object body) {
if (body != null) {
return client.resource(uri)
.type(MediaType.APPLICATION_JSON_TYPE)
.delete(BulkResponse.class, body);
} else {
client.resource(uri).delete();
}
return null;
}
public ClientResponse get(URI uri) {
return client.resource(uri)
.accept(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN)
.get(ClientResponse.class);
}
public WebResource.Builder getWebResourceBuilder(URI URI, Object entity) {
return client.resource(URI)
.type(MediaType.APPLICATION_JSON)
.entity(entity)
.accept(MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON);
}
private boolean isNewerJacksonVersion() {
Version version = com.fasterxml.jackson.databind.cfg.PackageVersion.VERSION;
return version.getMajorVersion() == 2 && version.getMinorVersion() >= 12;
}
}
================================================
FILE: client/src/main/java/com/netflix/conductor/client/http/EventClient.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.client.http;
import java.util.List;
import org.apache.commons.lang3.Validate;
import com.netflix.conductor.client.config.ConductorClientConfiguration;
import com.netflix.conductor.client.config.DefaultConductorClientConfiguration;
import com.netflix.conductor.common.metadata.events.EventHandler;
import com.sun.jersey.api.client.ClientHandler;
import com.sun.jersey.api.client.GenericType;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.ClientFilter;
// Client class for all Event Handler operations
public class EventClient extends ClientBase {
private static final GenericType
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.client.http;
import java.util.List;
import org.apache.commons.lang3.Validate;
import com.netflix.conductor.client.config.ConductorClientConfiguration;
import com.netflix.conductor.client.config.DefaultConductorClientConfiguration;
import com.netflix.conductor.common.metadata.tasks.TaskDef;
import com.netflix.conductor.common.metadata.workflow.WorkflowDef;
import com.sun.jersey.api.client.ClientHandler;
import com.sun.jersey.api.client.GenericType;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.ClientFilter;
public class MetadataClient extends ClientBase {
private static final GenericType
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.client.http;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import javax.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.conductor.client.exception.ConductorClientException;
import com.netflix.conductor.common.run.ExternalStorageLocation;
import com.netflix.conductor.common.utils.ExternalPayloadStorage;
import com.amazonaws.util.IOUtils;
/** An implementation of {@link ExternalPayloadStorage} for storing large JSON payload data. */
class PayloadStorage implements ExternalPayloadStorage {
private static final Logger LOGGER = LoggerFactory.getLogger(PayloadStorage.class);
private final ClientBase clientBase;
PayloadStorage(ClientBase clientBase) {
this.clientBase = clientBase;
}
/**
* This method is not intended to be used in the client. The client makes a request to the
* server to get the {@link ExternalStorageLocation}
*/
@Override
public ExternalStorageLocation getLocation(
Operation operation, PayloadType payloadType, String path) {
String uri;
switch (payloadType) {
case WORKFLOW_INPUT:
case WORKFLOW_OUTPUT:
uri = "workflow";
break;
case TASK_INPUT:
case TASK_OUTPUT:
uri = "tasks";
break;
default:
throw new ConductorClientException(
String.format(
"Invalid payload type: %s for operation: %s",
payloadType.toString(), operation.toString()));
}
return clientBase.getForEntity(
String.format("%s/externalstoragelocation", uri),
new Object[] {
"path",
path,
"operation",
operation.toString(),
"payloadType",
payloadType.toString()
},
ExternalStorageLocation.class);
}
/**
* Uploads the payload to the uri specified.
*
* @param uri the location to which the object is to be uploaded
* @param payload an {@link InputStream} containing the json payload which is to be uploaded
* @param payloadSize the size of the json payload in bytes
* @throws ConductorClientException if the upload fails due to an invalid path or an error from
* external storage
*/
@Override
public void upload(String uri, InputStream payload, long payloadSize) {
HttpURLConnection connection = null;
try {
URL url = new URI(uri).toURL();
connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("PUT");
try (BufferedOutputStream bufferedOutputStream =
new BufferedOutputStream(connection.getOutputStream())) {
long count = IOUtils.copy(payload, bufferedOutputStream);
bufferedOutputStream.flush();
// Check the HTTP response code
int responseCode = connection.getResponseCode();
if (Response.Status.fromStatusCode(responseCode).getFamily()
!= Response.Status.Family.SUCCESSFUL) {
String errorMsg =
String.format("Unable to upload. Response code: %d", responseCode);
LOGGER.error(errorMsg);
throw new ConductorClientException(errorMsg);
}
LOGGER.debug(
"Uploaded {} bytes to uri: {}, with HTTP response code: {}",
count,
uri,
responseCode);
}
} catch (URISyntaxException | MalformedURLException e) {
String errorMsg = String.format("Invalid path specified: %s", uri);
LOGGER.error(errorMsg, e);
throw new ConductorClientException(errorMsg, e);
} catch (IOException e) {
String errorMsg = String.format("Error uploading to path: %s", uri);
LOGGER.error(errorMsg, e);
throw new ConductorClientException(errorMsg, e);
} finally {
if (connection != null) {
connection.disconnect();
}
try {
if (payload != null) {
payload.close();
}
} catch (IOException e) {
LOGGER.warn("Unable to close inputstream when uploading to uri: {}", uri);
}
}
}
/**
* Downloads the payload from the given uri.
*
* @param uri the location from where the object is to be downloaded
* @return an inputstream of the payload in the external storage
* @throws ConductorClientException if the download fails due to an invalid path or an error
* from external storage
*/
@Override
public InputStream download(String uri) {
HttpURLConnection connection = null;
String errorMsg;
try {
URL url = new URI(uri).toURL();
connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(false);
// Check the HTTP response code
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
LOGGER.debug(
"Download completed with HTTP response code: {}",
connection.getResponseCode());
return org.apache.commons.io.IOUtils.toBufferedInputStream(
connection.getInputStream());
}
errorMsg = String.format("Unable to download. Response code: %d", responseCode);
LOGGER.error(errorMsg);
throw new ConductorClientException(errorMsg);
} catch (URISyntaxException | MalformedURLException e) {
errorMsg = String.format("Invalid uri specified: %s", uri);
LOGGER.error(errorMsg, e);
throw new ConductorClientException(errorMsg, e);
} catch (IOException e) {
errorMsg = String.format("Error downloading from uri: %s", uri);
LOGGER.error(errorMsg, e);
throw new ConductorClientException(errorMsg, e);
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}
================================================
FILE: client/src/main/java/com/netflix/conductor/client/http/TaskClient.java
================================================
/*
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.client.http;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.conductor.client.config.ConductorClientConfiguration;
import com.netflix.conductor.client.config.DefaultConductorClientConfiguration;
import com.netflix.conductor.client.exception.ConductorClientException;
import com.netflix.conductor.client.telemetry.MetricsContainer;
import com.netflix.conductor.common.metadata.tasks.PollData;
import com.netflix.conductor.common.metadata.tasks.Task;
import com.netflix.conductor.common.metadata.tasks.TaskExecLog;
import com.netflix.conductor.common.metadata.tasks.TaskResult;
import com.netflix.conductor.common.run.SearchResult;
import com.netflix.conductor.common.run.TaskSummary;
import com.netflix.conductor.common.utils.ExternalPayloadStorage;
import com.netflix.conductor.common.utils.ExternalPayloadStorage.PayloadType;
import com.sun.jersey.api.client.ClientHandler;
import com.sun.jersey.api.client.GenericType;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.ClientFilter;
/** Client for conductor task management including polling for task, updating task status etc. */
public class TaskClient extends ClientBase {
private static final GenericType>)
invocation -> {
List
>) invocation -> Collections.emptyList();
when(queue.receiveMessages()).thenReturn(messages).thenAnswer(answer);
when(queue.isRunning()).thenReturn(true);
when(queue.getOnSubscribe()).thenCallRealMethod();
when(queue.observe()).thenCallRealMethod();
List
*
*
* ExecutionDAO
*
*
*
*
* EventHandlerDAO
*
*
*
*/
public class Statements {
private final String keyspace;
public Statements(String keyspace) {
this.keyspace = keyspace;
}
// MetadataDAO
// Insert Statements
/**
* @return cql query statement to insert a new workflow definition into the
* "workflow_definitions" table
*/
public String getInsertWorkflowDefStatement() {
return QueryBuilder.insertInto(keyspace, TABLE_WORKFLOW_DEFS)
.value(WORKFLOW_DEF_NAME_KEY, bindMarker())
.value(WORKFLOW_VERSION_KEY, bindMarker())
.value(WORKFLOW_DEFINITION_KEY, bindMarker())
.ifNotExists()
.getQueryString();
}
/**
* @return cql query statement to insert a workflow def name version index into the
* "workflow_defs_index" table
*/
public String getInsertWorkflowDefVersionIndexStatement() {
return QueryBuilder.insertInto(keyspace, TABLE_WORKFLOW_DEFS_INDEX)
.value(WORKFLOW_DEF_INDEX_KEY, WORKFLOW_DEF_INDEX_KEY)
.value(WORKFLOW_DEF_NAME_VERSION_KEY, bindMarker())
.value(WORKFLOW_DEF_INDEX_VALUE, bindMarker())
.getQueryString();
}
/**
* @return cql query statement to insert a new task definition into the "task_definitions" table
*/
public String getInsertTaskDefStatement() {
return QueryBuilder.insertInto(keyspace, TABLE_TASK_DEFS)
.value(TASK_DEFS_KEY, TASK_DEFS_KEY)
.value(TASK_DEF_NAME_KEY, bindMarker())
.value(TASK_DEFINITION_KEY, bindMarker())
.getQueryString();
}
// Select Statements
/**
* @return cql query statement to fetch a workflow definition by name and version from the
* "workflow_definitions" table
*/
public String getSelectWorkflowDefStatement() {
return QueryBuilder.select(WORKFLOW_DEFINITION_KEY)
.from(keyspace, TABLE_WORKFLOW_DEFS)
.where(eq(WORKFLOW_DEF_NAME_KEY, bindMarker()))
.and(eq(WORKFLOW_VERSION_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to retrieve all versions of a workflow definition by name from
* the "workflow_definitions" table
*/
public String getSelectAllWorkflowDefVersionsByNameStatement() {
return QueryBuilder.select()
.all()
.from(keyspace, TABLE_WORKFLOW_DEFS)
.where(eq(WORKFLOW_DEF_NAME_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to fetch all workflow def names and version from the
* "workflow_defs_index" table
*/
public String getSelectAllWorkflowDefsStatement() {
return QueryBuilder.select()
.all()
.from(keyspace, TABLE_WORKFLOW_DEFS_INDEX)
.where(eq(WORKFLOW_DEF_INDEX_KEY, bindMarker()))
.getQueryString();
}
public String getSelectAllWorkflowDefsLatestVersionsStatement() {
return QueryBuilder.select()
.all()
.from(keyspace, TABLE_WORKFLOW_DEFS_INDEX)
.where(eq(WORKFLOW_DEF_INDEX_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to fetch a task definition by name from the "task_definitions"
* table
*/
public String getSelectTaskDefStatement() {
return QueryBuilder.select(TASK_DEFINITION_KEY)
.from(keyspace, TABLE_TASK_DEFS)
.where(eq(TASK_DEFS_KEY, TASK_DEFS_KEY))
.and(eq(TASK_DEF_NAME_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to retrieve all task definitions from the "task_definitions"
* table
*/
public String getSelectAllTaskDefsStatement() {
return QueryBuilder.select()
.all()
.from(keyspace, TABLE_TASK_DEFS)
.where(eq(TASK_DEFS_KEY, bindMarker()))
.getQueryString();
}
// Update Statement
/**
* @return cql query statement to update a workflow definitinos in the "workflow_definitions"
* table
*/
public String getUpdateWorkflowDefStatement() {
return QueryBuilder.update(keyspace, TABLE_WORKFLOW_DEFS)
.with(set(WORKFLOW_DEFINITION_KEY, bindMarker()))
.where(eq(WORKFLOW_DEF_NAME_KEY, bindMarker()))
.and(eq(WORKFLOW_VERSION_KEY, bindMarker()))
.getQueryString();
}
// Delete Statements
/**
* @return cql query statement to delete a workflow definition by name and version from the
* "workflow_definitions" table
*/
public String getDeleteWorkflowDefStatement() {
return QueryBuilder.delete()
.from(keyspace, TABLE_WORKFLOW_DEFS)
.where(eq(WORKFLOW_DEF_NAME_KEY, bindMarker()))
.and(eq(WORKFLOW_VERSION_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to delete a workflow def name/version from the
* "workflow_defs_index" table
*/
public String getDeleteWorkflowDefIndexStatement() {
return QueryBuilder.delete()
.from(keyspace, TABLE_WORKFLOW_DEFS_INDEX)
.where(eq(WORKFLOW_DEF_INDEX_KEY, bindMarker()))
.and(eq(WORKFLOW_DEF_NAME_VERSION_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to delete a task definition by name from the "task_definitions"
* table
*/
public String getDeleteTaskDefStatement() {
return QueryBuilder.delete()
.from(keyspace, TABLE_TASK_DEFS)
.where(eq(TASK_DEFS_KEY, TASK_DEFS_KEY))
.and(eq(TASK_DEF_NAME_KEY, bindMarker()))
.getQueryString();
}
// ExecutionDAO
// Insert Statements
/**
* @return cql query statement to insert a new workflow into the "workflows" table
*/
public String getInsertWorkflowStatement() {
return QueryBuilder.insertInto(keyspace, TABLE_WORKFLOWS)
.value(WORKFLOW_ID_KEY, bindMarker())
.value(SHARD_ID_KEY, bindMarker())
.value(TASK_ID_KEY, bindMarker())
.value(ENTITY_KEY, ENTITY_TYPE_WORKFLOW)
.value(PAYLOAD_KEY, bindMarker())
.value(TOTAL_TASKS_KEY, bindMarker())
.value(TOTAL_PARTITIONS_KEY, bindMarker())
.getQueryString();
}
/**
* @return cql query statement to insert a new task into the "workflows" table
*/
public String getInsertTaskStatement() {
return QueryBuilder.insertInto(keyspace, TABLE_WORKFLOWS)
.value(WORKFLOW_ID_KEY, bindMarker())
.value(SHARD_ID_KEY, bindMarker())
.value(TASK_ID_KEY, bindMarker())
.value(ENTITY_KEY, ENTITY_TYPE_TASK)
.value(PAYLOAD_KEY, bindMarker())
.getQueryString();
}
/**
* @return cql query statement to insert a new event execution into the "event_executions" table
*/
public String getInsertEventExecutionStatement() {
return QueryBuilder.insertInto(keyspace, TABLE_EVENT_EXECUTIONS)
.value(MESSAGE_ID_KEY, bindMarker())
.value(EVENT_HANDLER_NAME_KEY, bindMarker())
.value(EVENT_EXECUTION_ID_KEY, bindMarker())
.value(PAYLOAD_KEY, bindMarker())
.ifNotExists()
.getQueryString();
}
// Select Statements
/**
* @return cql query statement to retrieve the total_tasks and total_partitions for a workflow
* from the "workflows" table
*/
public String getSelectTotalStatement() {
return QueryBuilder.select(TOTAL_TASKS_KEY, TOTAL_PARTITIONS_KEY)
.from(keyspace, TABLE_WORKFLOWS)
.where(eq(WORKFLOW_ID_KEY, bindMarker()))
.and(eq(SHARD_ID_KEY, 1))
.getQueryString();
}
/**
* @return cql query statement to retrieve a task from the "workflows" table
*/
public String getSelectTaskStatement() {
return QueryBuilder.select(PAYLOAD_KEY)
.from(keyspace, TABLE_WORKFLOWS)
.where(eq(WORKFLOW_ID_KEY, bindMarker()))
.and(eq(SHARD_ID_KEY, bindMarker()))
.and(eq(ENTITY_KEY, ENTITY_TYPE_TASK))
.and(eq(TASK_ID_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to retrieve a workflow (without its tasks) from the "workflows"
* table
*/
public String getSelectWorkflowStatement() {
return QueryBuilder.select(PAYLOAD_KEY)
.from(keyspace, TABLE_WORKFLOWS)
.where(eq(WORKFLOW_ID_KEY, bindMarker()))
.and(eq(SHARD_ID_KEY, 1))
.and(eq(ENTITY_KEY, ENTITY_TYPE_WORKFLOW))
.getQueryString();
}
/**
* @return cql query statement to retrieve a workflow with its tasks from the "workflows" table
*/
public String getSelectWorkflowWithTasksStatement() {
return QueryBuilder.select()
.all()
.from(keyspace, TABLE_WORKFLOWS)
.where(eq(WORKFLOW_ID_KEY, bindMarker()))
.and(eq(SHARD_ID_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to retrieve the workflow_id for a particular task_id from the
* "task_lookup" table
*/
public String getSelectTaskFromLookupTableStatement() {
return QueryBuilder.select(WORKFLOW_ID_KEY)
.from(keyspace, TABLE_TASK_LOOKUP)
.where(eq(TASK_ID_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to retrieve all task ids for a given taskDefName with concurrent
* execution limit configured from the "task_def_limit" table
*/
public String getSelectTasksFromTaskDefLimitStatement() {
return QueryBuilder.select()
.all()
.from(keyspace, TABLE_TASK_DEF_LIMIT)
.where(eq(TASK_DEF_NAME_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to retrieve all event executions for a given message and event
* handler from the "event_executions" table
*/
public String getSelectAllEventExecutionsForMessageFromEventExecutionsStatement() {
return QueryBuilder.select()
.all()
.from(keyspace, TABLE_EVENT_EXECUTIONS)
.where(eq(MESSAGE_ID_KEY, bindMarker()))
.and(eq(EVENT_HANDLER_NAME_KEY, bindMarker()))
.getQueryString();
}
// Update Statements
/**
* @return cql query statement to update a workflow in the "workflows" table
*/
public String getUpdateWorkflowStatement() {
return QueryBuilder.update(keyspace, TABLE_WORKFLOWS)
.with(set(PAYLOAD_KEY, bindMarker()))
.where(eq(WORKFLOW_ID_KEY, bindMarker()))
.and(eq(SHARD_ID_KEY, 1))
.and(eq(ENTITY_KEY, ENTITY_TYPE_WORKFLOW))
.and(eq(TASK_ID_KEY, ""))
.getQueryString();
}
/**
* @return cql query statement to update the total_tasks in a shard for a workflow in the
* "workflows" table
*/
public String getUpdateTotalTasksStatement() {
return QueryBuilder.update(keyspace, TABLE_WORKFLOWS)
.with(set(TOTAL_TASKS_KEY, bindMarker()))
.where(eq(WORKFLOW_ID_KEY, bindMarker()))
.and(eq(SHARD_ID_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to update the total_partitions for a workflow in the "workflows"
* table
*/
public String getUpdateTotalPartitionsStatement() {
return QueryBuilder.update(keyspace, TABLE_WORKFLOWS)
.with(set(TOTAL_PARTITIONS_KEY, bindMarker()))
.and(set(TOTAL_TASKS_KEY, bindMarker()))
.where(eq(WORKFLOW_ID_KEY, bindMarker()))
.and(eq(SHARD_ID_KEY, 1))
.getQueryString();
}
/**
* @return cql query statement to add a new task_id to workflow_id mapping to the "task_lookup"
* table
*/
public String getUpdateTaskLookupStatement() {
return QueryBuilder.update(keyspace, TABLE_TASK_LOOKUP)
.with(set(WORKFLOW_ID_KEY, bindMarker()))
.where(eq(TASK_ID_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to add a new task_id to the "task_def_limit" table
*/
public String getUpdateTaskDefLimitStatement() {
return QueryBuilder.update(keyspace, TABLE_TASK_DEF_LIMIT)
.with(set(WORKFLOW_ID_KEY, bindMarker()))
.where(eq(TASK_DEF_NAME_KEY, bindMarker()))
.and(eq(TASK_ID_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to update an event execution in the "event_executions" table
*/
public String getUpdateEventExecutionStatement() {
return QueryBuilder.update(keyspace, TABLE_EVENT_EXECUTIONS)
.using(QueryBuilder.ttl(bindMarker()))
.with(set(PAYLOAD_KEY, bindMarker()))
.where(eq(MESSAGE_ID_KEY, bindMarker()))
.and(eq(EVENT_HANDLER_NAME_KEY, bindMarker()))
.and(eq(EVENT_EXECUTION_ID_KEY, bindMarker()))
.getQueryString();
}
// Delete statements
/**
* @return cql query statement to delete a workflow from the "workflows" table
*/
public String getDeleteWorkflowStatement() {
return QueryBuilder.delete()
.from(keyspace, TABLE_WORKFLOWS)
.where(eq(WORKFLOW_ID_KEY, bindMarker()))
.and(eq(SHARD_ID_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to delete a task_id to workflow_id mapping from the "task_lookup"
* table
*/
public String getDeleteTaskLookupStatement() {
return QueryBuilder.delete()
.from(keyspace, TABLE_TASK_LOOKUP)
.where(eq(TASK_ID_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to delete a task from the "workflows" table
*/
public String getDeleteTaskStatement() {
return QueryBuilder.delete()
.from(keyspace, TABLE_WORKFLOWS)
.where(eq(WORKFLOW_ID_KEY, bindMarker()))
.and(eq(SHARD_ID_KEY, bindMarker()))
.and(eq(ENTITY_KEY, ENTITY_TYPE_TASK))
.and(eq(TASK_ID_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to delete a task_id from the "task_def_limit" table
*/
public String getDeleteTaskDefLimitStatement() {
return QueryBuilder.delete()
.from(keyspace, TABLE_TASK_DEF_LIMIT)
.where(eq(TASK_DEF_NAME_KEY, bindMarker()))
.and(eq(TASK_ID_KEY, bindMarker()))
.getQueryString();
}
/**
* @return cql query statement to delete an event execution from the "event_execution" table
*/
public String getDeleteEventExecutionsStatement() {
return QueryBuilder.delete()
.from(keyspace, TABLE_EVENT_EXECUTIONS)
.where(eq(MESSAGE_ID_KEY, bindMarker()))
.and(eq(EVENT_HANDLER_NAME_KEY, bindMarker()))
.and(eq(EVENT_EXECUTION_ID_KEY, bindMarker()))
.getQueryString();
}
// EventHandlerDAO
// Insert Statements
/**
* @return cql query statement to insert an event handler into the "event_handlers" table
*/
public String getInsertEventHandlerStatement() {
return QueryBuilder.insertInto(keyspace, TABLE_EVENT_HANDLERS)
.value(HANDLERS_KEY, HANDLERS_KEY)
.value(EVENT_HANDLER_NAME_KEY, bindMarker())
.value(EVENT_HANDLER_KEY, bindMarker())
.getQueryString();
}
// Select Statements
/**
* @return cql query statement to retrieve all event handlers from the "event_handlers" table
*/
public String getSelectAllEventHandlersStatement() {
return QueryBuilder.select()
.all()
.from(keyspace, TABLE_EVENT_HANDLERS)
.where(eq(HANDLERS_KEY, bindMarker()))
.getQueryString();
}
// Delete Statements
/**
* @return cql query statement to delete an event handler by name from the "event_handlers"
* table
*/
public String getDeleteEventHandlerStatement() {
return QueryBuilder.delete()
.from(keyspace, TABLE_EVENT_HANDLERS)
.where(eq(HANDLERS_KEY, HANDLERS_KEY))
.and(eq(EVENT_HANDLER_NAME_KEY, bindMarker()))
.getQueryString();
}
}
================================================
FILE: cassandra-persistence/src/main/resources/META-INF/additional-spring-configuration-metadata.json
================================================
{
"properties": [
{
"name": "conductor.cassandra.write-consistency-level",
"defaultValue": "LOCAL_QUORUM"
},
{
"name": "conductor.cassandra.read-consistency-level",
"defaultValue": "LOCAL_QUORUM"
}
],
"hints": [
{
"name": "conductor.cassandra.write-consistency-level",
"providers": [
{
"name": "handle-as",
"parameters": {
"target": "java.lang.Enum"
}
}
]
},
{
"name": "conductor.cassandra.read-consistency-level",
"providers": [
{
"name": "handle-as",
"parameters": {
"target": "java.lang.Enum"
}
}
]
}
]
}
================================================
FILE: cassandra-persistence/src/test/groovy/com/netflix/conductor/cassandra/dao/CassandraEventHandlerDAOSpec.groovy
================================================
/*
* Copyright 2022 Netflix, Inc.
* > eventHandlerList =
new GenericType
>() {};
/** Creates a default metadata client */
public EventClient() {
this(new DefaultClientConfig(), new DefaultConductorClientConfiguration(), null);
}
/**
* @param clientConfig REST Client configuration
*/
public EventClient(ClientConfig clientConfig) {
this(clientConfig, new DefaultConductorClientConfiguration(), null);
}
/**
* @param clientConfig REST Client configuration
* @param clientHandler Jersey client handler. Useful when plugging in various http client
* interaction modules (e.g. ribbon)
*/
public EventClient(ClientConfig clientConfig, ClientHandler clientHandler) {
this(clientConfig, new DefaultConductorClientConfiguration(), clientHandler);
}
/**
* @param config config REST Client configuration
* @param handler handler Jersey client handler. Useful when plugging in various http client
* interaction modules (e.g. ribbon)
* @param filters Chain of client side filters to be applied per request
*/
public EventClient(ClientConfig config, ClientHandler handler, ClientFilter... filters) {
this(config, new DefaultConductorClientConfiguration(), handler, filters);
}
/**
* @param config REST Client configuration
* @param clientConfiguration Specific properties configured for the client, see {@link
* ConductorClientConfiguration}
* @param handler Jersey client handler. Useful when plugging in various http client interaction
* modules (e.g. ribbon)
* @param filters Chain of client side filters to be applied per request
*/
public EventClient(
ClientConfig config,
ConductorClientConfiguration clientConfiguration,
ClientHandler handler,
ClientFilter... filters) {
super(new ClientRequestHandler(config, handler, filters), clientConfiguration);
}
EventClient(ClientRequestHandler requestHandler) {
super(requestHandler, null);
}
/**
* Register an event handler with the server
*
* @param eventHandler the eventHandler definition
*/
public void registerEventHandler(EventHandler eventHandler) {
Validate.notNull(eventHandler, "Event Handler definition cannot be null");
postForEntityWithRequestOnly("event", eventHandler);
}
/**
* Updates an event handler with the server
*
* @param eventHandler the eventHandler definition
*/
public void updateEventHandler(EventHandler eventHandler) {
Validate.notNull(eventHandler, "Event Handler definition cannot be null");
put("event", null, eventHandler);
}
/**
* @param event name of the event
* @param activeOnly if true, returns only the active handlers
* @return Returns the list of all the event handlers for a given event
*/
public List
> workflowDefList =
new GenericType
>() {};
/** Creates a default metadata client */
public MetadataClient() {
this(new DefaultClientConfig(), new DefaultConductorClientConfiguration(), null);
}
/**
* @param clientConfig REST Client configuration
*/
public MetadataClient(ClientConfig clientConfig) {
this(clientConfig, new DefaultConductorClientConfiguration(), null);
}
/**
* @param clientConfig REST Client configuration
* @param clientHandler Jersey client handler. Useful when plugging in various http client
* interaction modules (e.g. ribbon)
*/
public MetadataClient(ClientConfig clientConfig, ClientHandler clientHandler) {
this(clientConfig, new DefaultConductorClientConfiguration(), clientHandler);
}
/**
* @param config config REST Client configuration
* @param handler handler Jersey client handler. Useful when plugging in various http client
* interaction modules (e.g. ribbon)
* @param filters Chain of client side filters to be applied per request
*/
public MetadataClient(ClientConfig config, ClientHandler handler, ClientFilter... filters) {
this(config, new DefaultConductorClientConfiguration(), handler, filters);
}
/**
* @param config REST Client configuration
* @param clientConfiguration Specific properties configured for the client, see {@link
* ConductorClientConfiguration}
* @param handler Jersey client handler. Useful when plugging in various http client interaction
* modules (e.g. ribbon)
* @param filters Chain of client side filters to be applied per request
*/
public MetadataClient(
ClientConfig config,
ConductorClientConfiguration clientConfiguration,
ClientHandler handler,
ClientFilter... filters) {
super(new ClientRequestHandler(config, handler, filters), clientConfiguration);
}
MetadataClient(ClientRequestHandler requestHandler) {
super(requestHandler, null);
}
// Workflow Metadata Operations
/**
* Register a workflow definition with the server
*
* @param workflowDef the workflow definition
*/
public void registerWorkflowDef(WorkflowDef workflowDef) {
Validate.notNull(workflowDef, "Workflow definition cannot be null");
postForEntityWithRequestOnly("metadata/workflow", workflowDef);
}
public void validateWorkflowDef(WorkflowDef workflowDef) {
Validate.notNull(workflowDef, "Workflow definition cannot be null");
postForEntityWithRequestOnly("metadata/workflow/validate", workflowDef);
}
/**
* Updates a list of existing workflow definitions
*
* @param workflowDefs List of workflow definitions to be updated
*/
public void updateWorkflowDefs(List
> taskList = new GenericType
>() {};
private static final GenericType
> taskExecLogList =
new GenericType
>() {};
private static final GenericType
> pollDataList =
new GenericType
>() {};
private static final GenericType