Browse Source
* Init DS python SDK pydolphinscheduler: python code definition * Doc first * Add quick start and developer doc * Java documentation change * Add LICENSE-py4j.txt * Add py4j to release-docs/LICENSE * Move dependency version to parent pom * Remove outdated code * Add tenant parameter to tutorial3.0.0/version-upgrade
Jiajie Zhong
3 years ago
committed by
GitHub
52 changed files with 2363 additions and 3 deletions
@ -0,0 +1,26 @@ |
|||||||
|
Copyright (c) 2009-2018, Barthelemy Dagenais and individual contributors. All |
||||||
|
rights reserved. |
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without |
||||||
|
modification, are permitted provided that the following conditions are met: |
||||||
|
|
||||||
|
- Redistributions of source code must retain the above copyright notice, this |
||||||
|
list of conditions and the following disclaimer. |
||||||
|
|
||||||
|
- Redistributions in binary form must reproduce the above copyright notice, |
||||||
|
this list of conditions and the following disclaimer in the documentation |
||||||
|
and/or other materials provided with the distribution. |
||||||
|
|
||||||
|
- The name of the author may not be used to endorse or promote products |
||||||
|
derived from this software without specific prior written permission. |
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,60 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!-- |
||||||
|
~ Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
~ contributor license agreements. See the NOTICE file distributed with |
||||||
|
~ this work for additional information regarding copyright ownership. |
||||||
|
~ The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
~ (the "License"); you may not use this file except in compliance with |
||||||
|
~ the License. You may obtain a copy of the License at |
||||||
|
~ |
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
~ |
||||||
|
~ Unless required by applicable law or agreed to in writing, software |
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
~ See the License for the specific language governing permissions and |
||||||
|
~ limitations under the License. |
||||||
|
--> |
||||||
|
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||||
|
<modelVersion>4.0.0</modelVersion> |
||||||
|
<parent> |
||||||
|
<groupId>org.apache.dolphinscheduler</groupId> |
||||||
|
<artifactId>dolphinscheduler</artifactId> |
||||||
|
<version>2.0.0-SNAPSHOT</version> |
||||||
|
</parent> |
||||||
|
<artifactId>dolphinscheduler-python</artifactId> |
||||||
|
<name>${project.artifactId}</name> |
||||||
|
<packaging>jar</packaging> |
||||||
|
|
||||||
|
<dependencies> |
||||||
|
<!-- dolphinscheduler --> |
||||||
|
<dependency> |
||||||
|
<groupId>org.apache.dolphinscheduler</groupId> |
||||||
|
<artifactId>dolphinscheduler-api</artifactId> |
||||||
|
</dependency> |
||||||
|
|
||||||
|
<!--springboot--> |
||||||
|
<dependency> |
||||||
|
<groupId>org.springframework.boot</groupId> |
||||||
|
<artifactId>spring-boot-starter-web</artifactId> |
||||||
|
<exclusions> |
||||||
|
<exclusion> |
||||||
|
<groupId>org.springframework.boot</groupId> |
||||||
|
<artifactId>spring-boot-starter-tomcat</artifactId> |
||||||
|
</exclusion> |
||||||
|
<exclusion> |
||||||
|
<artifactId>log4j-to-slf4j</artifactId> |
||||||
|
<groupId>org.apache.logging.log4j</groupId> |
||||||
|
</exclusion> |
||||||
|
</exclusions> |
||||||
|
</dependency> |
||||||
|
|
||||||
|
<dependency> |
||||||
|
<groupId>net.sf.py4j</groupId> |
||||||
|
<artifactId>py4j</artifactId> |
||||||
|
</dependency> |
||||||
|
|
||||||
|
</dependencies> |
||||||
|
</project> |
@ -0,0 +1,103 @@ |
|||||||
|
<!-- |
||||||
|
Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
or more contributor license agreements. See the NOTICE file |
||||||
|
distributed with this work for additional information |
||||||
|
regarding copyright ownership. The ASF licenses this file |
||||||
|
to you under the Apache License, Version 2.0 (the |
||||||
|
"License"); you may not use this file except in compliance |
||||||
|
with the License. You may obtain a copy of the License at |
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, |
||||||
|
software distributed under the License is distributed on an |
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
KIND, either express or implied. See the License for the |
||||||
|
specific language governing permissions and limitations |
||||||
|
under the License. |
||||||
|
--> |
||||||
|
|
||||||
|
# pydolphinscheduler |
||||||
|
|
||||||
|
pydolphinscheduler is python API for Apache DolphinScheduler, which allow you definition |
||||||
|
your workflow by python code, aka workflow-as-codes. |
||||||
|
|
||||||
|
## Quick Start |
||||||
|
|
||||||
|
> **_Notice:_** For now, due to pydolphinscheduler without release to any binary tarball or [PyPI][pypi], you |
||||||
|
> have to clone Apache DolphinScheduler code from GitHub to ensure quick start setup |
||||||
|
|
||||||
|
Here we show you how to install and run a simple example of pydolphinscheduler |
||||||
|
|
||||||
|
### Prepare |
||||||
|
|
||||||
|
```shell |
||||||
|
# Clone code from github |
||||||
|
git clone git@github.com:apache/dolphinscheduler.git |
||||||
|
|
||||||
|
# Install pydolphinscheduler from source |
||||||
|
cd dolphinscheduler-python/pydolphinscheduler |
||||||
|
pip setup.py install |
||||||
|
``` |
||||||
|
|
||||||
|
### Start Server And Run Example |
||||||
|
|
||||||
|
Before you run an example, you have to start backend server. You could follow [development setup][dev-setup] |
||||||
|
section "DolphinScheduler Standalone Quick Start" to set up developer environment. You have to start backend |
||||||
|
and frontend server in this step, which mean that you could view DolphinScheduler UI in your browser with URL |
||||||
|
http://localhost:12345/dolphinscheduler |
||||||
|
|
||||||
|
After backend server is being start, all requests from `pydolphinscheduler` would be sends to backend server. |
||||||
|
And for now we could run a simple example by: |
||||||
|
|
||||||
|
```shell |
||||||
|
cd dolphinscheduler-python/pydolphinscheduler |
||||||
|
python example/tutorial.py |
||||||
|
``` |
||||||
|
|
||||||
|
> **_NOTICE:_** Since Apache DolphinScheduler's tenant is requests while running command, you might need to change |
||||||
|
> tenant value in `example/tutorial.py`. For now the value is `tenant_exists`, please change it to username exists |
||||||
|
> in you environment. |
||||||
|
|
||||||
|
After command execute, you could see a new project with single process definition named *tutorial* in the [UI][ui-project]. |
||||||
|
|
||||||
|
Until now, we finish quick start by an example of pydolphinscheduler and run it. If you want to inspect or join |
||||||
|
pydolphinscheduler develop, you could take a look at [develop](#develop) |
||||||
|
|
||||||
|
## Develop |
||||||
|
|
||||||
|
pydolphinscheduler is python API for Apache DolphinScheduler, it just defines what workflow look like instead of |
||||||
|
store or execute it. We here use [py4j][py4j] to dynamically access Java Virtual Machine. |
||||||
|
|
||||||
|
### Setup Develop Environment |
||||||
|
|
||||||
|
We already clone the code in [quick start](#quick-start), so next step we have to open pydolphinscheduler project |
||||||
|
in you editor. We recommend you use [pycharm][pycharm] instead of [IntelliJ IDEA][idea] to open it. And you could |
||||||
|
just open directory `dolphinscheduler-python/pydolphinscheduler` instead of `dolphinscheduler-python`. |
||||||
|
|
||||||
|
### Brief Concept |
||||||
|
|
||||||
|
Apache DolphinScheduler is design to define workflow by UI, and pydolphinscheduler try to define it by code. When |
||||||
|
define by code, user usually do not care user, tanant, or queue exists or not. All user care about is create |
||||||
|
a new workflow by the code his/her definition. So we have some **side object** in `pydolphinscheduler/side` |
||||||
|
directory, their only check object exists or not, and create them if not exists. |
||||||
|
|
||||||
|
#### Process Definition |
||||||
|
|
||||||
|
pydolphinscheduler workflow object name, process definition is also same name as Java object(maybe would be change to |
||||||
|
other word for more simple). |
||||||
|
|
||||||
|
#### Tasks |
||||||
|
|
||||||
|
pydolphinscheduler tasks object, we use tasks to define exact job we want DolphinScheduler do for us. For now, |
||||||
|
we only support `shell` task to execute shell task. [This link][all-task] list all tasks support in DolphinScheduler |
||||||
|
and would be implement in the further. |
||||||
|
|
||||||
|
|
||||||
|
[pypi]: https://pypi.org/ |
||||||
|
[dev-setup]: https://dolphinscheduler.apache.org/en-us/development/development-environment-setup.html |
||||||
|
[ui-project]: http://8.142.34.29:12345/dolphinscheduler/ui/#/projects/list |
||||||
|
[py4j]: https://www.py4j.org/index.html |
||||||
|
[pycharm]: https://www.jetbrains.com/pycharm |
||||||
|
[idea]: https://www.jetbrains.com/idea/ |
||||||
|
[all-task]: https://dolphinscheduler.apache.org/en-us/docs/dev/user_doc/guide/task/shell.html |
@ -0,0 +1,34 @@ |
|||||||
|
<!-- |
||||||
|
Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
or more contributor license agreements. See the NOTICE file |
||||||
|
distributed with this work for additional information |
||||||
|
regarding copyright ownership. The ASF licenses this file |
||||||
|
to you under the Apache License, Version 2.0 (the |
||||||
|
"License"); you may not use this file except in compliance |
||||||
|
with the License. You may obtain a copy of the License at |
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, |
||||||
|
software distributed under the License is distributed on an |
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
KIND, either express or implied. See the License for the |
||||||
|
specific language governing permissions and limitations |
||||||
|
under the License. |
||||||
|
--> |
||||||
|
|
||||||
|
## Roadmap |
||||||
|
|
||||||
|
### v0.0.3 |
||||||
|
|
||||||
|
Add other features, tasks, parameters in DS, keep code coverage up to 90% |
||||||
|
|
||||||
|
### v0.0.2 |
||||||
|
|
||||||
|
Add docs about how to use and develop package, code coverage up to 90%, add CI/CD |
||||||
|
for package |
||||||
|
|
||||||
|
### v0.0.1(current) |
||||||
|
|
||||||
|
Setup up POC, for defining DAG with python code, running DAG manually, |
||||||
|
releasing to pypi |
@ -0,0 +1,43 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
r""" |
||||||
|
After tutorial.py file submit to Apache DolphinScheduler server a DAG would be create, |
||||||
|
and workflow DAG graph as below: |
||||||
|
|
||||||
|
--> task_child_one |
||||||
|
/ \ |
||||||
|
task_parent --> --> task_union |
||||||
|
\ / |
||||||
|
--> task_child_two |
||||||
|
""" |
||||||
|
|
||||||
|
from pydolphinscheduler.core.process_definition import ProcessDefinition |
||||||
|
from pydolphinscheduler.tasks.shell import Shell |
||||||
|
|
||||||
|
with ProcessDefinition(name="tutorial", tenant="tenant_exists") as pd: |
||||||
|
task_parent = Shell(name="task_parent", command="echo hello pydolphinscheduler") |
||||||
|
task_child_one = Shell(name="task_child_one", command="echo 'child one'") |
||||||
|
task_child_two = Shell(name="task_child_two", command="echo 'child two'") |
||||||
|
task_union = Shell(name="task_union", command="echo union") |
||||||
|
|
||||||
|
task_group = [task_child_one, task_child_two] |
||||||
|
task_parent.set_downstream(task_group) |
||||||
|
|
||||||
|
task_union << task_group |
||||||
|
|
||||||
|
pd.run() |
@ -0,0 +1,18 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
py4j~=0.10.9.2 |
@ -0,0 +1,24 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
# testting |
||||||
|
pytest~=6.2.5 |
||||||
|
# code linting and formatting |
||||||
|
flake8-black~=0.2.3 |
||||||
|
# flake8 |
||||||
|
# flake8-docstrings |
||||||
|
# flake8-black |
@ -0,0 +1,16 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
@ -0,0 +1,90 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
|
||||||
|
import sys |
||||||
|
from os.path import dirname, join |
||||||
|
|
||||||
|
from setuptools import find_packages, setup |
||||||
|
|
||||||
|
version = '0.0.1.dev0' |
||||||
|
|
||||||
|
if sys.version_info[0] < 3: |
||||||
|
raise Exception("pydolphinscheduler does not support Python 2. Please upgrade to Python 3.") |
||||||
|
|
||||||
|
|
||||||
|
def read(*names, **kwargs): |
||||||
|
return open( |
||||||
|
join(dirname(__file__), *names), encoding=kwargs.get("encoding", "utf8") |
||||||
|
).read() |
||||||
|
|
||||||
|
|
||||||
|
setup( |
||||||
|
name="pydolphinscheduler", |
||||||
|
version=version, |
||||||
|
license="Apache License 2.0", |
||||||
|
description="Apache DolphinScheduler python SDK", |
||||||
|
long_description=read("README.md"), |
||||||
|
# Make sure pypi is expecting markdown |
||||||
|
long_description_content_type="text/markdown", |
||||||
|
author="Apache Software Foundation", |
||||||
|
author_email="dev@dolphinscheduler.apache.org", |
||||||
|
url="https://dolphinscheduler.apache.org/", |
||||||
|
python_requires=">=3.6", |
||||||
|
keywords=[ |
||||||
|
"dolphinscheduler", |
||||||
|
"workflow", |
||||||
|
"scheduler", |
||||||
|
"taskflow", |
||||||
|
], |
||||||
|
project_urls={ |
||||||
|
"Homepage": "https://dolphinscheduler.apache.org", |
||||||
|
"Documentation": "https://dolphinscheduler.apache.org/en-us/docs/latest/user_doc/quick-start.html", |
||||||
|
"Source": "https://github.com/apache/dolphinscheduler", |
||||||
|
"Issue Tracker": "https://github.com/apache/dolphinscheduler/issues", |
||||||
|
"Discussion": "https://github.com/apache/dolphinscheduler/discussions", |
||||||
|
"Twitter": "https://twitter.com/dolphinschedule", |
||||||
|
}, |
||||||
|
packages=find_packages(where="src"), |
||||||
|
package_dir={"": "src"}, |
||||||
|
include_package_data=True, |
||||||
|
classifiers=[ |
||||||
|
# complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers |
||||||
|
"Development Status :: 1 - Planning", |
||||||
|
"Environment :: Console", |
||||||
|
"Intended Audience :: Developers", |
||||||
|
"License :: OSI Approved :: Apache Software License", |
||||||
|
"Operating System :: Unix", |
||||||
|
"Operating System :: POSIX", |
||||||
|
"Operating System :: Microsoft :: Windows", |
||||||
|
"Programming Language :: Python", |
||||||
|
"Programming Language :: Python :: 3", |
||||||
|
"Programming Language :: Python :: 3.6", |
||||||
|
"Programming Language :: Python :: 3.7", |
||||||
|
"Programming Language :: Python :: 3.8", |
||||||
|
"Programming Language :: Python :: 3.9", |
||||||
|
"Programming Language :: Python :: Implementation :: CPython", |
||||||
|
"Programming Language :: Python :: Implementation :: PyPy", |
||||||
|
"Topic :: Software Development :: User Interfaces", |
||||||
|
], |
||||||
|
install_requires=[ |
||||||
|
# Core |
||||||
|
"py4j~=0.10", |
||||||
|
# Dev |
||||||
|
"pytest~=6.2", |
||||||
|
] |
||||||
|
) |
@ -0,0 +1,16 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
@ -0,0 +1,16 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
@ -0,0 +1,74 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
class ProcessDefinitionReleaseState: |
||||||
|
""" |
||||||
|
ProcessDefinition release state |
||||||
|
""" |
||||||
|
ONLINE: str = "ONLINE" |
||||||
|
OFFLINE: str = "OFFLINE" |
||||||
|
|
||||||
|
|
||||||
|
class ProcessDefinitionDefault: |
||||||
|
""" |
||||||
|
ProcessDefinition default values |
||||||
|
""" |
||||||
|
PROJECT: str = "project-pydolphin" |
||||||
|
TENANT: str = "tenant_pydolphin" |
||||||
|
USER: str = "userPythonGateway" |
||||||
|
# TODO simple set password same as username |
||||||
|
USER_PWD: str = "userPythonGateway" |
||||||
|
USER_EMAIL: str = "userPythonGateway@dolphinscheduler.com" |
||||||
|
USER_PHONE: str = "11111111111" |
||||||
|
USER_STATE: int = 1 |
||||||
|
QUEUE: str = "queuePythonGateway" |
||||||
|
WORKER_GROUP: str = "default" |
||||||
|
|
||||||
|
|
||||||
|
class TaskPriority(str): |
||||||
|
HIGHEST = "HIGHEST" |
||||||
|
HIGH = "HIGH" |
||||||
|
MEDIUM = "MEDIUM" |
||||||
|
LOW = "LOW" |
||||||
|
LOWEST = "LOWEST" |
||||||
|
|
||||||
|
|
||||||
|
class TaskFlag(str): |
||||||
|
YES = "YES" |
||||||
|
NO = "NO" |
||||||
|
|
||||||
|
|
||||||
|
class TaskTimeoutFlag(str): |
||||||
|
CLOSE = "CLOSE" |
||||||
|
|
||||||
|
|
||||||
|
class TaskType(str): |
||||||
|
SHELL = "SHELL" |
||||||
|
|
||||||
|
|
||||||
|
class DefaultTaskCodeNum(str): |
||||||
|
DEFAULT = 1 |
||||||
|
|
||||||
|
|
||||||
|
class JavaGatewayDefault(str): |
||||||
|
RESULT_MESSAGE_KEYWORD = "msg" |
||||||
|
RESULT_MESSAGE_SUCCESS = "success" |
||||||
|
|
||||||
|
RESULT_STATUS_KEYWORD = "status" |
||||||
|
RESULT_STATUS_SUCCESS = "SUCCESS" |
||||||
|
|
||||||
|
RESULT_DATA = "data" |
@ -0,0 +1,16 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
@ -0,0 +1,72 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
from typing import Optional, Dict |
||||||
|
|
||||||
|
# from pydolphinscheduler.side.user import User |
||||||
|
from pydolphinscheduler.utils.string import attr2camel |
||||||
|
|
||||||
|
|
||||||
|
class Base: |
||||||
|
""" |
||||||
|
Base |
||||||
|
""" |
||||||
|
|
||||||
|
_KEY_ATTR: set = { |
||||||
|
"name", |
||||||
|
"description" |
||||||
|
} |
||||||
|
|
||||||
|
_TO_DICT_ATTR: set = set() |
||||||
|
|
||||||
|
DEFAULT_ATTR: Dict = {} |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, |
||||||
|
name: str, |
||||||
|
description: Optional[str] = None |
||||||
|
): |
||||||
|
self.name = name |
||||||
|
self.description = description |
||||||
|
|
||||||
|
def __repr__(self) -> str: |
||||||
|
return f'<{type(self).__name__}: name="{self.name}">' |
||||||
|
|
||||||
|
def __eq__(self, other): |
||||||
|
return type(self) == type(other) and \ |
||||||
|
all(getattr(self, a, None) == getattr(other, a, None) for a in self._KEY_ATTR) |
||||||
|
|
||||||
|
# TODO check how Redash do |
||||||
|
# TODO DRY |
||||||
|
def to_dict(self, camel_attr=True) -> Dict: |
||||||
|
# content = {} |
||||||
|
# for attr, value in self.__dict__.items(): |
||||||
|
# # Don't publish private variables |
||||||
|
# if attr.startswith("_"): |
||||||
|
# continue |
||||||
|
# else: |
||||||
|
# content[snake2camel(attr)] = value |
||||||
|
# content.update(self.DEFAULT_ATTR) |
||||||
|
# return content |
||||||
|
content = {} |
||||||
|
for attr in self._TO_DICT_ATTR: |
||||||
|
val = getattr(self, attr, None) |
||||||
|
if camel_attr: |
||||||
|
content[attr2camel(attr)] = val |
||||||
|
else: |
||||||
|
content[attr] = val |
||||||
|
return content |
@ -0,0 +1,43 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
from typing import Optional |
||||||
|
|
||||||
|
from pydolphinscheduler.constants import ProcessDefinitionDefault |
||||||
|
from pydolphinscheduler.core.base import Base |
||||||
|
|
||||||
|
|
||||||
|
class BaseSide(Base): |
||||||
|
def __init__( |
||||||
|
self, |
||||||
|
name: str, |
||||||
|
description: Optional[str] = None |
||||||
|
): |
||||||
|
super().__init__(name, description) |
||||||
|
|
||||||
|
@classmethod |
||||||
|
def create_if_not_exists( |
||||||
|
cls, |
||||||
|
# TODO comment for avoiding cycle import |
||||||
|
# user: Optional[User] = ProcessDefinitionDefault.USER |
||||||
|
user=ProcessDefinitionDefault.USER |
||||||
|
): |
||||||
|
""" |
||||||
|
Create Base if not exists |
||||||
|
""" |
||||||
|
|
||||||
|
raise NotImplementedError |
@ -0,0 +1,249 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
import json |
||||||
|
from typing import Optional, List, Dict, Set |
||||||
|
|
||||||
|
from pydolphinscheduler.constants import ProcessDefinitionReleaseState, ProcessDefinitionDefault |
||||||
|
from pydolphinscheduler.core.base import Base |
||||||
|
from pydolphinscheduler.java_gateway import launch_gateway |
||||||
|
from pydolphinscheduler.side import Tenant, Project, User |
||||||
|
|
||||||
|
|
||||||
|
class ProcessDefinitionContext: |
||||||
|
_context_managed_process_definition: Optional["ProcessDefinition"] = None |
||||||
|
|
||||||
|
@classmethod |
||||||
|
def set(cls, pd: "ProcessDefinition") -> None: |
||||||
|
cls._context_managed_process_definition = pd |
||||||
|
|
||||||
|
@classmethod |
||||||
|
def get(cls) -> Optional["ProcessDefinition"]: |
||||||
|
return cls._context_managed_process_definition |
||||||
|
|
||||||
|
@classmethod |
||||||
|
def delete(cls) -> None: |
||||||
|
cls._context_managed_process_definition = None |
||||||
|
|
||||||
|
|
||||||
|
class ProcessDefinition(Base): |
||||||
|
""" |
||||||
|
ProcessDefinition |
||||||
|
TODO :ref: comment may not correct ref |
||||||
|
TODO: maybe we should rename this class, currently use DS object name |
||||||
|
""" |
||||||
|
|
||||||
|
# key attribute for identify ProcessDefinition object |
||||||
|
_KEY_ATTR = { |
||||||
|
"name", |
||||||
|
"project", |
||||||
|
"tenant", |
||||||
|
"release_state", |
||||||
|
"param", |
||||||
|
} |
||||||
|
|
||||||
|
_TO_DICT_ATTR = { |
||||||
|
"name", |
||||||
|
"description", |
||||||
|
"_project", |
||||||
|
"_tenant", |
||||||
|
"timeout", |
||||||
|
"release_state", |
||||||
|
"param", |
||||||
|
"tasks", |
||||||
|
"task_definition_json", |
||||||
|
"task_relation_json", |
||||||
|
} |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, |
||||||
|
name: str, |
||||||
|
description: Optional[str] = None, |
||||||
|
user: Optional[str] = ProcessDefinitionDefault.USER, |
||||||
|
project: Optional[str] = ProcessDefinitionDefault.PROJECT, |
||||||
|
tenant: Optional[str] = ProcessDefinitionDefault.TENANT, |
||||||
|
queue: Optional[str] = ProcessDefinitionDefault.QUEUE, |
||||||
|
timeout: Optional[int] = 0, |
||||||
|
release_state: Optional[str] = ProcessDefinitionReleaseState.ONLINE, |
||||||
|
param: Optional[List] = None |
||||||
|
): |
||||||
|
super().__init__(name, description) |
||||||
|
self._user = user |
||||||
|
self._project = project |
||||||
|
self._tenant = tenant |
||||||
|
self._queue = queue |
||||||
|
self.timeout = timeout |
||||||
|
self.release_state = release_state |
||||||
|
self.param = param |
||||||
|
self.tasks: dict = {} |
||||||
|
# TODO how to fix circle import |
||||||
|
self._task_relations: set["TaskRelation"] = set() |
||||||
|
self._process_definition_code = None |
||||||
|
|
||||||
|
def __enter__(self) -> "ProcessDefinition": |
||||||
|
ProcessDefinitionContext.set(self) |
||||||
|
return self |
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb) -> None: |
||||||
|
ProcessDefinitionContext.delete() |
||||||
|
|
||||||
|
@property |
||||||
|
def tenant(self) -> Tenant: |
||||||
|
return Tenant(self._tenant) |
||||||
|
|
||||||
|
@tenant.setter |
||||||
|
def tenant(self, tenant: Tenant) -> None: |
||||||
|
self._tenant = tenant.name |
||||||
|
|
||||||
|
@property |
||||||
|
def project(self) -> Project: |
||||||
|
return Project(self._project) |
||||||
|
|
||||||
|
@project.setter |
||||||
|
def project(self, project: Project) -> None: |
||||||
|
self._project = project.name |
||||||
|
|
||||||
|
@property |
||||||
|
def user(self) -> User: |
||||||
|
return User(self._user, |
||||||
|
ProcessDefinitionDefault.USER_PWD, |
||||||
|
ProcessDefinitionDefault.USER_EMAIL, |
||||||
|
ProcessDefinitionDefault.USER_PHONE, |
||||||
|
ProcessDefinitionDefault.TENANT, |
||||||
|
ProcessDefinitionDefault.QUEUE, |
||||||
|
ProcessDefinitionDefault.USER_STATE) |
||||||
|
|
||||||
|
@property |
||||||
|
def task_definition_json(self) -> List[Dict]: |
||||||
|
if not self.tasks: |
||||||
|
return [self.tasks] |
||||||
|
else: |
||||||
|
return [task.to_dict() for task in self.tasks.values()] |
||||||
|
|
||||||
|
@property |
||||||
|
def task_relation_json(self) -> List[Dict]: |
||||||
|
if not self.tasks: |
||||||
|
return [self.tasks] |
||||||
|
else: |
||||||
|
self._handle_root_relation() |
||||||
|
return [tr.to_dict() for tr in self._task_relations] |
||||||
|
|
||||||
|
# TODO inti DAG's tasks are in the same place |
||||||
|
@property |
||||||
|
def task_location(self) -> List[Dict]: |
||||||
|
if not self.tasks: |
||||||
|
return [self.tasks] |
||||||
|
else: |
||||||
|
return [{"taskCode": task_code, "x": 0, "y": 0} for task_code in self.tasks] |
||||||
|
|
||||||
|
@property |
||||||
|
def task_list(self) -> List["Task"]: |
||||||
|
return list(self.tasks.values()) |
||||||
|
|
||||||
|
def _handle_root_relation(self): |
||||||
|
from pydolphinscheduler.core.task import TaskRelation |
||||||
|
post_relation_code = set() |
||||||
|
for relation in self._task_relations: |
||||||
|
post_relation_code.add(relation.post_task_code) |
||||||
|
for task in self.task_list: |
||||||
|
if task.code not in post_relation_code: |
||||||
|
root_relation = TaskRelation(pre_task_code=0, post_task_code=task.code) |
||||||
|
self._task_relations.add(root_relation) |
||||||
|
|
||||||
|
def add_task(self, task: "Task") -> None: |
||||||
|
self.tasks[task.code] = task |
||||||
|
task._process_definition = self |
||||||
|
|
||||||
|
def add_tasks(self, tasks: List["Task"]) -> None: |
||||||
|
for task in tasks: |
||||||
|
self.add_task(task) |
||||||
|
|
||||||
|
def get_task(self, code: str) -> "Task": |
||||||
|
if code not in self.tasks: |
||||||
|
raise ValueError("Task with code %s can not found in process definition %", (code, self.name)) |
||||||
|
return self.tasks[code] |
||||||
|
|
||||||
|
# TODO which tying should return in this case |
||||||
|
def get_tasks_by_name(self, name: str) -> Set["Task"]: |
||||||
|
find = set() |
||||||
|
for task in self.tasks.values(): |
||||||
|
if task.name == name: |
||||||
|
find.add(task) |
||||||
|
return find |
||||||
|
|
||||||
|
def get_one_task_by_name(self, name: str) -> "Task": |
||||||
|
tasks = self.get_tasks_by_name(name) |
||||||
|
if not tasks: |
||||||
|
raise ValueError(f"Can not find task with name {name}.") |
||||||
|
return tasks.pop() |
||||||
|
|
||||||
|
def run(self): |
||||||
|
""" |
||||||
|
Run ProcessDefinition instance, a shortcut for :ref: submit and :ref: start |
||||||
|
Only support manual for now, schedule run will coming soon |
||||||
|
:return: |
||||||
|
""" |
||||||
|
self.submit() |
||||||
|
self.start() |
||||||
|
|
||||||
|
def _ensure_side_model_exists(self): |
||||||
|
""" |
||||||
|
Ensure side model exists which including :ref: Project, Tenant, User. |
||||||
|
If those model not exists, would create default value in :ref: ProcessDefinitionDefault |
||||||
|
""" |
||||||
|
# TODO used metaclass for more pythonic |
||||||
|
self.tenant.create_if_not_exists(self._queue) |
||||||
|
# model User have to create after Tenant created |
||||||
|
self.user.create_if_not_exists() |
||||||
|
# Project model need User object exists |
||||||
|
self.project.create_if_not_exists(self._user) |
||||||
|
|
||||||
|
def submit(self) -> int: |
||||||
|
""" |
||||||
|
Submit ProcessDefinition instance to java gateway |
||||||
|
:return: |
||||||
|
""" |
||||||
|
self._ensure_side_model_exists() |
||||||
|
gateway = launch_gateway() |
||||||
|
self._process_definition_code = gateway.entry_point.createOrUpdateProcessDefinition( |
||||||
|
self._user, |
||||||
|
self._project, |
||||||
|
self.name, |
||||||
|
str(self.description) if self.description else "", |
||||||
|
str(self.param) if self.param else None, |
||||||
|
json.dumps(self.task_location), |
||||||
|
self.timeout, |
||||||
|
self._tenant, |
||||||
|
# TODO add serialization function |
||||||
|
json.dumps(self.task_relation_json), |
||||||
|
json.dumps(self.task_definition_json), |
||||||
|
) |
||||||
|
return self._process_definition_code |
||||||
|
|
||||||
|
def start(self) -> None: |
||||||
|
""" |
||||||
|
Start ProcessDefinition instance which post to `start-process-instance` to java gateway |
||||||
|
:return: |
||||||
|
""" |
||||||
|
gateway = launch_gateway() |
||||||
|
gateway.entry_point.execProcessInstance( |
||||||
|
self._user, |
||||||
|
self._project, |
||||||
|
self.name, |
||||||
|
"", |
||||||
|
"default", |
||||||
|
24 * 3600, |
||||||
|
) |
@ -0,0 +1,237 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
from typing import Optional, List, Dict, Set, Union, Sequence, Tuple |
||||||
|
|
||||||
|
from pydolphinscheduler.constants import TaskPriority, ProcessDefinitionDefault, TaskFlag, TaskTimeoutFlag, \ |
||||||
|
DefaultTaskCodeNum, JavaGatewayDefault |
||||||
|
from pydolphinscheduler.core.base import Base |
||||||
|
from pydolphinscheduler.core.process_definition import ProcessDefinition |
||||||
|
from pydolphinscheduler.core.process_definition import ProcessDefinitionContext |
||||||
|
from pydolphinscheduler.java_gateway import launch_gateway, gateway_result_checker |
||||||
|
from pydolphinscheduler.utils.string import snake2camel, class_name2camel |
||||||
|
|
||||||
|
|
||||||
|
class ObjectJsonBase: |
||||||
|
DEFAULT_ATTR = {} |
||||||
|
|
||||||
|
def __int__(self, *args, **kwargs): |
||||||
|
pass |
||||||
|
|
||||||
|
def __str__(self) -> str: |
||||||
|
content = [] |
||||||
|
for attribute, value in self.__dict__.items(): |
||||||
|
content.append(f"\"{snake2camel(attribute)}\": {value}") |
||||||
|
content = ",".join(content) |
||||||
|
return f"\"{class_name2camel(type(self).__name__)}\":{{{content}}}" |
||||||
|
|
||||||
|
# TODO check how Redash do |
||||||
|
# TODO DRY |
||||||
|
def to_dict(self) -> Dict: |
||||||
|
content = {snake2camel(attr): value for attr, value in self.__dict__.items()} |
||||||
|
content.update(self.DEFAULT_ATTR) |
||||||
|
return content |
||||||
|
|
||||||
|
|
||||||
|
class TaskParams(ObjectJsonBase): |
||||||
|
DEFAULT_CONDITION_RESULT = { |
||||||
|
"successNode": [ |
||||||
|
"" |
||||||
|
], |
||||||
|
"failedNode": [ |
||||||
|
"" |
||||||
|
] |
||||||
|
} |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, |
||||||
|
raw_script: str, |
||||||
|
local_params: Optional[List] = None, |
||||||
|
resource_list: Optional[List] = None, |
||||||
|
dependence: Optional[Dict] = None, |
||||||
|
wait_start_timeout: Optional[Dict] = None, |
||||||
|
condition_result: Optional[Dict] = None, |
||||||
|
): |
||||||
|
super().__init__() |
||||||
|
self.raw_script = raw_script |
||||||
|
self.local_params = local_params or [] |
||||||
|
self.resource_list = resource_list or [] |
||||||
|
self.dependence = dependence or {} |
||||||
|
self.wait_start_timeout = wait_start_timeout or {} |
||||||
|
# TODO need better way to handle it, this code just for POC |
||||||
|
self.condition_result = condition_result or self.DEFAULT_CONDITION_RESULT |
||||||
|
|
||||||
|
|
||||||
|
class TaskRelation(ObjectJsonBase): |
||||||
|
DEFAULT_ATTR = { |
||||||
|
"name": "", |
||||||
|
"preTaskVersion": 1, |
||||||
|
"postTaskVersion": 1, |
||||||
|
"conditionType": 0, |
||||||
|
"conditionParams": {} |
||||||
|
} |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, |
||||||
|
pre_task_code: int, |
||||||
|
post_task_code: int, |
||||||
|
): |
||||||
|
super().__init__() |
||||||
|
self.pre_task_code = pre_task_code |
||||||
|
self.post_task_code = post_task_code |
||||||
|
|
||||||
|
def __hash__(self): |
||||||
|
return hash(f"{self.post_task_code}, {self.post_task_code}") |
||||||
|
|
||||||
|
|
||||||
|
class Task(Base): |
||||||
|
|
||||||
|
DEFAULT_DEPS_ATTR = { |
||||||
|
"name": "", |
||||||
|
"preTaskVersion": 1, |
||||||
|
"postTaskVersion": 1, |
||||||
|
"conditionType": 0, |
||||||
|
"conditionParams": {} |
||||||
|
} |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, |
||||||
|
name: str, |
||||||
|
task_type: str, |
||||||
|
task_params: TaskParams, |
||||||
|
description: Optional[str] = None, |
||||||
|
flag: Optional[str] = TaskFlag.YES, |
||||||
|
task_priority: Optional[str] = TaskPriority.MEDIUM, |
||||||
|
worker_group: Optional[str] = ProcessDefinitionDefault.WORKER_GROUP, |
||||||
|
delay_time: Optional[int] = 0, |
||||||
|
fail_retry_times: Optional[int] = 0, |
||||||
|
fail_retry_interval: Optional[int] = 1, |
||||||
|
timeout_flag: Optional[int] = TaskTimeoutFlag.CLOSE, |
||||||
|
timeout_notify_strategy: Optional = None, |
||||||
|
timeout: Optional[int] = 0, |
||||||
|
process_definition: Optional[ProcessDefinition] = None, |
||||||
|
): |
||||||
|
|
||||||
|
super().__init__(name, description) |
||||||
|
self.task_type = task_type |
||||||
|
self.task_params = task_params |
||||||
|
self.flag = flag |
||||||
|
self.task_priority = task_priority |
||||||
|
self.worker_group = worker_group |
||||||
|
self.fail_retry_times = fail_retry_times |
||||||
|
self.fail_retry_interval = fail_retry_interval |
||||||
|
self.delay_time = delay_time |
||||||
|
self.timeout_flag = timeout_flag |
||||||
|
self.timeout_notify_strategy = timeout_notify_strategy |
||||||
|
self.timeout = timeout |
||||||
|
self._process_definition = None |
||||||
|
self.process_definition: ProcessDefinition = process_definition or ProcessDefinitionContext.get() |
||||||
|
self._upstream_task_codes: Set[int] = set() |
||||||
|
self._downstream_task_codes: Set[int] = set() |
||||||
|
self._task_relation: Set[TaskRelation] = set() |
||||||
|
# move attribute code and version after _process_definition and process_definition declare |
||||||
|
self.code, self.version = self.gen_code_and_version() |
||||||
|
# Add task to process definition, maybe we could put into property process_definition latter |
||||||
|
if self.process_definition is not None and self.code not in self.process_definition.tasks: |
||||||
|
self.process_definition.add_task(self) |
||||||
|
|
||||||
|
@property |
||||||
|
def process_definition(self) -> Optional[ProcessDefinition]: |
||||||
|
if self._process_definition: |
||||||
|
return self._process_definition |
||||||
|
else: |
||||||
|
raise ValueError(f'Task {self} has not been assigned to a ProcessDefinition yet') |
||||||
|
|
||||||
|
@process_definition.setter |
||||||
|
def process_definition(self, process_definition: Optional[ProcessDefinition]): |
||||||
|
self._process_definition = process_definition |
||||||
|
|
||||||
|
def __hash__(self): |
||||||
|
return hash(self.code) |
||||||
|
|
||||||
|
def __lshift__(self, other: Union["Task", Sequence["Task"]]): |
||||||
|
"""Implements Task << Task""" |
||||||
|
self.set_upstream(other) |
||||||
|
return other |
||||||
|
|
||||||
|
def __rshift__(self, other: Union["Task", Sequence["Task"]]): |
||||||
|
"""Implements Task >> Task""" |
||||||
|
self.set_downstream(other) |
||||||
|
return other |
||||||
|
|
||||||
|
def __rrshift__(self, other: Union["Task", Sequence["Task"]]): |
||||||
|
"""Called for Task >> [Task] because list don't have __rshift__ operators.""" |
||||||
|
self.__lshift__(other) |
||||||
|
return self |
||||||
|
|
||||||
|
def __rlshift__(self, other: Union["Task", Sequence["Task"]]): |
||||||
|
"""Called for Task << [Task] because list don't have __lshift__ operators.""" |
||||||
|
self.__rshift__(other) |
||||||
|
return self |
||||||
|
|
||||||
|
def _set_deps(self, tasks: Union["Task", Sequence["Task"]], upstream: bool = True) -> None: |
||||||
|
if not isinstance(tasks, Sequence): |
||||||
|
tasks = [tasks] |
||||||
|
|
||||||
|
for task in tasks: |
||||||
|
if upstream: |
||||||
|
self._upstream_task_codes.add(task.code) |
||||||
|
task._downstream_task_codes.add(self.code) |
||||||
|
|
||||||
|
if self._process_definition: |
||||||
|
task_relation = TaskRelation( |
||||||
|
pre_task_code=task.code, |
||||||
|
post_task_code=self.code, |
||||||
|
) |
||||||
|
self.process_definition._task_relations.add(task_relation) |
||||||
|
else: |
||||||
|
self._downstream_task_codes.add(task.code) |
||||||
|
task._upstream_task_codes.add(self.code) |
||||||
|
|
||||||
|
if self._process_definition: |
||||||
|
task_relation = TaskRelation( |
||||||
|
pre_task_code=self.code, |
||||||
|
post_task_code=task.code, |
||||||
|
) |
||||||
|
self.process_definition._task_relations.add(task_relation) |
||||||
|
|
||||||
|
def set_upstream(self, tasks: Union["Task", Sequence["Task"]]) -> None: |
||||||
|
self._set_deps(tasks, upstream=True) |
||||||
|
|
||||||
|
def set_downstream(self, tasks: Union["Task", Sequence["Task"]]) -> None: |
||||||
|
self._set_deps(tasks, upstream=False) |
||||||
|
|
||||||
|
# TODO code should better generate in bulk mode when :ref: processDefinition run submit or start |
||||||
|
def gen_code_and_version(self) -> Tuple: |
||||||
|
# TODO get code from specific project process definition and task name |
||||||
|
gateway = launch_gateway() |
||||||
|
result = gateway.entry_point.getCodeAndVersion(self.process_definition._project, self.name) |
||||||
|
# result = gateway.entry_point.genTaskCodeList(DefaultTaskCodeNum.DEFAULT) |
||||||
|
# gateway_result_checker(result) |
||||||
|
return result.get("code"), result.get("version") |
||||||
|
|
||||||
|
def to_dict(self, camel_attr=True) -> Dict: |
||||||
|
content = {} |
||||||
|
for attr, value in self.__dict__.items(): |
||||||
|
# Don't publish private variables |
||||||
|
if attr.startswith("_"): |
||||||
|
continue |
||||||
|
elif isinstance(value, TaskParams): |
||||||
|
content[snake2camel(attr)] = value.to_dict() |
||||||
|
else: |
||||||
|
content[snake2camel(attr)] = value |
||||||
|
return content |
@ -0,0 +1,43 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
from typing import Any, Optional |
||||||
|
|
||||||
|
from py4j.java_collections import JavaMap |
||||||
|
from py4j.java_gateway import JavaGateway, GatewayParameters |
||||||
|
|
||||||
|
from pydolphinscheduler.constants import JavaGatewayDefault |
||||||
|
|
||||||
|
|
||||||
|
def launch_gateway() -> JavaGateway: |
||||||
|
# TODO Note that automatic conversion makes calling Java methods slightly less efficient because |
||||||
|
# in the worst case, Py4J needs to go through all registered converters for all parameters. |
||||||
|
# This is why automatic conversion is disabled by default. |
||||||
|
gateway = JavaGateway(gateway_parameters=GatewayParameters(auto_convert=True)) |
||||||
|
return gateway |
||||||
|
|
||||||
|
|
||||||
|
def gateway_result_checker( |
||||||
|
result: JavaMap, |
||||||
|
msg_check: Optional[str] = JavaGatewayDefault.RESULT_MESSAGE_SUCCESS |
||||||
|
) -> Any: |
||||||
|
if result[JavaGatewayDefault.RESULT_STATUS_KEYWORD].toString() != \ |
||||||
|
JavaGatewayDefault.RESULT_STATUS_SUCCESS: |
||||||
|
raise RuntimeError(f"Failed when try to got result for java gateway") |
||||||
|
if msg_check is not None and result[JavaGatewayDefault.RESULT_MESSAGE_KEYWORD] != msg_check: |
||||||
|
raise ValueError(f"Get result state not success.") |
||||||
|
return result |
@ -0,0 +1,20 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
from pydolphinscheduler.side.project import Project |
||||||
|
from pydolphinscheduler.side.tenant import Tenant |
||||||
|
from pydolphinscheduler.side.user import User |
@ -0,0 +1,45 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
from typing import Optional |
||||||
|
|
||||||
|
from pydolphinscheduler.core.base_side import BaseSide |
||||||
|
from pydolphinscheduler.constants import ProcessDefinitionDefault |
||||||
|
from pydolphinscheduler.java_gateway import launch_gateway, gateway_result_checker |
||||||
|
|
||||||
|
|
||||||
|
class Project(BaseSide): |
||||||
|
""" |
||||||
|
Project |
||||||
|
""" |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, |
||||||
|
name: str = ProcessDefinitionDefault.PROJECT, |
||||||
|
description: Optional[str] = None |
||||||
|
): |
||||||
|
super().__init__(name, description) |
||||||
|
|
||||||
|
def create_if_not_exists(self, user=ProcessDefinitionDefault.USER) -> None: |
||||||
|
""" |
||||||
|
Create Project if not exists |
||||||
|
""" |
||||||
|
gateway = launch_gateway() |
||||||
|
result = gateway.entry_point.createProject(user, self.name, self.description) |
||||||
|
# TODO recover result checker |
||||||
|
# gateway_result_checker(result, None) |
||||||
|
|
@ -0,0 +1,44 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
from typing import Optional |
||||||
|
|
||||||
|
from pydolphinscheduler.constants import ProcessDefinitionDefault |
||||||
|
from pydolphinscheduler.core.base_side import BaseSide |
||||||
|
from pydolphinscheduler.java_gateway import launch_gateway, gateway_result_checker |
||||||
|
|
||||||
|
|
||||||
|
class Queue(BaseSide): |
||||||
|
""" |
||||||
|
Queue |
||||||
|
""" |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, |
||||||
|
name: str = ProcessDefinitionDefault.QUEUE, |
||||||
|
description: Optional[str] = "" |
||||||
|
): |
||||||
|
super().__init__(name, description) |
||||||
|
|
||||||
|
def create_if_not_exists(self, user=ProcessDefinitionDefault.USER) -> None: |
||||||
|
""" |
||||||
|
Create Queue if not exists |
||||||
|
""" |
||||||
|
gateway = launch_gateway() |
||||||
|
# Here we set Queue.name and Queue.queueName same as self.name |
||||||
|
result = gateway.entry_point.createProject(user, self.name, self.name) |
||||||
|
gateway_result_checker(result, None) |
@ -0,0 +1,45 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
from typing import Optional |
||||||
|
|
||||||
|
from pydolphinscheduler.constants import ProcessDefinitionDefault |
||||||
|
from pydolphinscheduler.core.base_side import BaseSide |
||||||
|
from pydolphinscheduler.java_gateway import launch_gateway, gateway_result_checker |
||||||
|
|
||||||
|
|
||||||
|
class Tenant(BaseSide): |
||||||
|
""" |
||||||
|
Tenant |
||||||
|
""" |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, |
||||||
|
name: str = ProcessDefinitionDefault.TENANT, |
||||||
|
queue: str = ProcessDefinitionDefault.QUEUE, |
||||||
|
description: Optional[str] = None |
||||||
|
): |
||||||
|
super().__init__(name, description) |
||||||
|
self.queue = queue |
||||||
|
|
||||||
|
def create_if_not_exists(self, queue_name: str, user=ProcessDefinitionDefault.USER) -> None: |
||||||
|
""" |
||||||
|
Create Tenant if not exists |
||||||
|
""" |
||||||
|
gateway = launch_gateway() |
||||||
|
result = gateway.entry_point.createTenant(self.name, self.description, queue_name) |
||||||
|
# gateway_result_checker(result, None) |
@ -0,0 +1,68 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
from typing import Optional |
||||||
|
|
||||||
|
from pydolphinscheduler.core.base_side import BaseSide |
||||||
|
from pydolphinscheduler.java_gateway import launch_gateway, gateway_result_checker |
||||||
|
|
||||||
|
|
||||||
|
class User(BaseSide): |
||||||
|
_KEY_ATTR = { |
||||||
|
"name", |
||||||
|
"password", |
||||||
|
"email", |
||||||
|
"phone", |
||||||
|
"tenant", |
||||||
|
"queue", |
||||||
|
"status", |
||||||
|
} |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, |
||||||
|
name: str, |
||||||
|
password: str, |
||||||
|
email: str, |
||||||
|
phone: str, |
||||||
|
tenant: str, |
||||||
|
queue: Optional[str] = None, |
||||||
|
status: Optional[int] = 1, |
||||||
|
): |
||||||
|
super().__init__(name) |
||||||
|
self.password = password |
||||||
|
self.email = email |
||||||
|
self.phone = phone |
||||||
|
self.tenant = tenant |
||||||
|
self.queue = queue |
||||||
|
self.status = status |
||||||
|
|
||||||
|
def create_if_not_exists(self, **kwargs): |
||||||
|
""" |
||||||
|
Create User if not exists |
||||||
|
""" |
||||||
|
gateway = launch_gateway() |
||||||
|
result = gateway.entry_point.createUser( |
||||||
|
self.name, |
||||||
|
self.password, |
||||||
|
self.email, |
||||||
|
self.phone, |
||||||
|
self.tenant, |
||||||
|
self.queue, |
||||||
|
self.status |
||||||
|
) |
||||||
|
# TODO recover result checker |
||||||
|
# gateway_result_checker(result, None) |
@ -0,0 +1,35 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
from typing import Optional |
||||||
|
|
||||||
|
from pydolphinscheduler.core.base_side import BaseSide |
||||||
|
|
||||||
|
|
||||||
|
class WorkerGroup(BaseSide): |
||||||
|
""" |
||||||
|
Worker Group |
||||||
|
""" |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, |
||||||
|
name: str, |
||||||
|
address: str, |
||||||
|
description: Optional[str] = None |
||||||
|
): |
||||||
|
super().__init__(name, description) |
||||||
|
self.address = address |
@ -0,0 +1,16 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
@ -0,0 +1,34 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
from pydolphinscheduler.constants import TaskType |
||||||
|
from pydolphinscheduler.core.task import Task, TaskParams |
||||||
|
|
||||||
|
|
||||||
|
class Shell(Task): |
||||||
|
# TODO maybe we could use instance name to replace attribute `name` |
||||||
|
# which is simplify as `task_shell = Shell(command = "echo 1")` and |
||||||
|
# task.name assign to `task_shell` |
||||||
|
def __init__( |
||||||
|
self, |
||||||
|
name: str, |
||||||
|
command: str, |
||||||
|
task_type: str = TaskType.SHELL, |
||||||
|
*args, **kwargs |
||||||
|
): |
||||||
|
task_params = TaskParams(raw_script=command) |
||||||
|
super().__init__(name, task_type, task_params, *args, **kwargs) |
@ -0,0 +1,16 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
@ -0,0 +1,30 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
def attr2camel(attr: str, include_private=True): |
||||||
|
if include_private: |
||||||
|
attr = attr.lstrip("_") |
||||||
|
return snake2camel(attr) |
||||||
|
|
||||||
|
|
||||||
|
def snake2camel(snake: str): |
||||||
|
components = snake.split("_") |
||||||
|
return components[0] + "".join(x.title() for x in components[1:]) |
||||||
|
|
||||||
|
|
||||||
|
def class_name2camel(class_name: str): |
||||||
|
return class_name[0].lower() + class_name[1:] |
@ -0,0 +1,16 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
@ -0,0 +1,16 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
@ -0,0 +1,118 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
import pytest |
||||||
|
|
||||||
|
from pydolphinscheduler.constants import ProcessDefinitionDefault, ProcessDefinitionReleaseState |
||||||
|
from pydolphinscheduler.core.process_definition import ProcessDefinition |
||||||
|
from pydolphinscheduler.core.task import Task, TaskParams |
||||||
|
from pydolphinscheduler.side import Tenant, Project, User |
||||||
|
|
||||||
|
TEST_PROCESS_DEFINITION_NAME = "simple-test-process-definition" |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize( |
||||||
|
"func", |
||||||
|
[ |
||||||
|
"run", "submit", "start" |
||||||
|
] |
||||||
|
) |
||||||
|
def test_process_definition_key_attr(func): |
||||||
|
with ProcessDefinition(TEST_PROCESS_DEFINITION_NAME) as pd: |
||||||
|
assert hasattr(pd, func), f"ProcessDefinition instance don't have attribute `{func}`" |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize( |
||||||
|
"name,value", |
||||||
|
[ |
||||||
|
("project", Project(ProcessDefinitionDefault.PROJECT)), |
||||||
|
("tenant", Tenant(ProcessDefinitionDefault.TENANT)), |
||||||
|
("user", User(ProcessDefinitionDefault.USER, |
||||||
|
ProcessDefinitionDefault.USER_PWD, |
||||||
|
ProcessDefinitionDefault.USER_EMAIL, |
||||||
|
ProcessDefinitionDefault.USER_PHONE, |
||||||
|
ProcessDefinitionDefault.TENANT, |
||||||
|
ProcessDefinitionDefault.QUEUE, |
||||||
|
ProcessDefinitionDefault.USER_STATE)), |
||||||
|
("release_state", ProcessDefinitionReleaseState.ONLINE), |
||||||
|
], |
||||||
|
) |
||||||
|
def test_process_definition_default_value(name, value): |
||||||
|
with ProcessDefinition(TEST_PROCESS_DEFINITION_NAME) as pd: |
||||||
|
assert getattr(pd, name) == value, \ |
||||||
|
f"ProcessDefinition instance attribute `{name}` have not except default value `{getattr(pd, name)}`" |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize( |
||||||
|
"name,cls,expect", |
||||||
|
[ |
||||||
|
("project", Project, "project"), |
||||||
|
("tenant", Tenant, "tenant"), |
||||||
|
], |
||||||
|
) |
||||||
|
def test_process_definition_set_attr(name, cls, expect): |
||||||
|
with ProcessDefinition(TEST_PROCESS_DEFINITION_NAME) as pd: |
||||||
|
setattr(pd, name, cls(expect)) |
||||||
|
assert getattr(pd, name) == cls( |
||||||
|
expect), f"ProcessDefinition set attribute `{name}` do not work expect" |
||||||
|
|
||||||
|
|
||||||
|
def test_process_definition_to_dict_without_task(): |
||||||
|
expect = { |
||||||
|
"name": TEST_PROCESS_DEFINITION_NAME, |
||||||
|
"description": None, |
||||||
|
"project": ProcessDefinitionDefault.PROJECT, |
||||||
|
"tenant": ProcessDefinitionDefault.TENANT, |
||||||
|
"timeout": 0, |
||||||
|
"releaseState": ProcessDefinitionReleaseState.ONLINE, |
||||||
|
"param": None, |
||||||
|
"tasks": {}, |
||||||
|
"taskDefinitionJson": [{}], |
||||||
|
"taskRelationJson": [{}], |
||||||
|
} |
||||||
|
with ProcessDefinition(TEST_PROCESS_DEFINITION_NAME) as pd: |
||||||
|
assert pd.to_dict() == expect |
||||||
|
|
||||||
|
|
||||||
|
def test_process_definition_simple(): |
||||||
|
expect_tasks_num = 5 |
||||||
|
with ProcessDefinition(TEST_PROCESS_DEFINITION_NAME) as pd: |
||||||
|
for i in range(expect_tasks_num): |
||||||
|
task_params = TaskParams(raw_script=f"test-raw-script-{i}") |
||||||
|
curr_task = Task(name=f"task-{i}", task_type=f"type-{i}", task_params=task_params) |
||||||
|
# Set deps task i as i-1 parent |
||||||
|
if i > 0: |
||||||
|
pre_task = pd.get_one_task_by_name(f"task-{i - 1}") |
||||||
|
curr_task.set_upstream(pre_task) |
||||||
|
assert len(pd.tasks) == expect_tasks_num |
||||||
|
|
||||||
|
# Test if task process_definition same as origin one |
||||||
|
task: Task = pd.get_one_task_by_name("task-0") |
||||||
|
assert pd is task.process_definition |
||||||
|
|
||||||
|
# Test if all tasks with expect deps |
||||||
|
for i in range(expect_tasks_num): |
||||||
|
task: Task = pd.get_one_task_by_name(f"task-{i}") |
||||||
|
if i == 0: |
||||||
|
assert task._upstream_task_codes == set() |
||||||
|
assert task._downstream_task_codes == {pd.get_one_task_by_name("task-1").code} |
||||||
|
elif i == expect_tasks_num - 1: |
||||||
|
assert task._upstream_task_codes == {pd.get_one_task_by_name(f"task-{i - 1}").code} |
||||||
|
assert task._downstream_task_codes == set() |
||||||
|
else: |
||||||
|
assert task._upstream_task_codes == {pd.get_one_task_by_name(f"task-{i - 1}").code} |
||||||
|
assert task._downstream_task_codes == {pd.get_one_task_by_name(f"task-{i + 1}").code} |
@ -0,0 +1,96 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
|
||||||
|
from unittest.mock import patch |
||||||
|
|
||||||
|
from pydolphinscheduler.core.task import TaskParams, TaskRelation, Task |
||||||
|
|
||||||
|
|
||||||
|
def test_task_params_to_dict(): |
||||||
|
raw_script = "test_task_params_to_dict" |
||||||
|
expect = { |
||||||
|
"resourceList": [], |
||||||
|
"localParams": [], |
||||||
|
"rawScript": raw_script, |
||||||
|
"dependence": {}, |
||||||
|
"conditionResult": TaskParams.DEFAULT_CONDITION_RESULT, |
||||||
|
"waitStartTimeout": {} |
||||||
|
} |
||||||
|
task_param = TaskParams(raw_script=raw_script) |
||||||
|
assert task_param.to_dict() == expect |
||||||
|
|
||||||
|
|
||||||
|
def test_task_relation_to_dict(): |
||||||
|
pre_task_code = 123 |
||||||
|
post_task_code = 456 |
||||||
|
expect = { |
||||||
|
"name": "", |
||||||
|
"preTaskCode": pre_task_code, |
||||||
|
"postTaskCode": post_task_code, |
||||||
|
"preTaskVersion": 1, |
||||||
|
"postTaskVersion": 1, |
||||||
|
"conditionType": 0, |
||||||
|
"conditionParams": {} |
||||||
|
} |
||||||
|
task_param = TaskRelation(pre_task_code=pre_task_code, post_task_code=post_task_code) |
||||||
|
assert task_param.to_dict() == expect |
||||||
|
|
||||||
|
|
||||||
|
def test_task_to_dict(): |
||||||
|
code = "123" |
||||||
|
name = "test_task_to_dict" |
||||||
|
task_type = "test_task_to_dict_type" |
||||||
|
raw_script = "test_task_params_to_dict" |
||||||
|
expect = { |
||||||
|
"code": code, |
||||||
|
"name": name, |
||||||
|
"version": 1, |
||||||
|
"description": None, |
||||||
|
"delayTime": 0, |
||||||
|
"taskType": task_type, |
||||||
|
"taskParams": { |
||||||
|
"resourceList": [], |
||||||
|
"localParams": [], |
||||||
|
"rawScript": raw_script, |
||||||
|
"dependence": {}, |
||||||
|
"conditionResult": { |
||||||
|
"successNode": [ |
||||||
|
"" |
||||||
|
], |
||||||
|
"failedNode": [ |
||||||
|
"" |
||||||
|
] |
||||||
|
}, |
||||||
|
"waitStartTimeout": {} |
||||||
|
}, |
||||||
|
"flag": "YES", |
||||||
|
"taskPriority": "MEDIUM", |
||||||
|
"workerGroup": "worker-group-pydolphin", |
||||||
|
"failRetryTimes": 0, |
||||||
|
"failRetryInterval": 1, |
||||||
|
"timeoutFlag": "CLOSE", |
||||||
|
"timeoutNotifyStrategy": None, |
||||||
|
"timeout": 0 |
||||||
|
} |
||||||
|
with patch('pydolphinscheduler.core.task.Task.gen_code', return_value=code): |
||||||
|
task = Task( |
||||||
|
name=name, |
||||||
|
task_type=task_type, |
||||||
|
task_params=TaskParams(raw_script) |
||||||
|
) |
||||||
|
assert task.to_dict() == expect |
@ -0,0 +1,16 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
@ -0,0 +1,16 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
@ -0,0 +1,61 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
|
||||||
|
from unittest.mock import patch |
||||||
|
|
||||||
|
from pydolphinscheduler.tasks.shell import Shell |
||||||
|
|
||||||
|
|
||||||
|
def test_shell_to_dict(): |
||||||
|
code = "123" |
||||||
|
name = "test_shell_to_dict" |
||||||
|
command = "echo test shell" |
||||||
|
expect = { |
||||||
|
"code": code, |
||||||
|
"name": name, |
||||||
|
"version": 1, |
||||||
|
"description": None, |
||||||
|
"delayTime": 0, |
||||||
|
"taskType": "SHELL", |
||||||
|
"taskParams": { |
||||||
|
"resourceList": [], |
||||||
|
"localParams": [], |
||||||
|
"rawScript": command, |
||||||
|
"dependence": {}, |
||||||
|
"conditionResult": { |
||||||
|
"successNode": [ |
||||||
|
"" |
||||||
|
], |
||||||
|
"failedNode": [ |
||||||
|
"" |
||||||
|
] |
||||||
|
}, |
||||||
|
"waitStartTimeout": {} |
||||||
|
}, |
||||||
|
"flag": "YES", |
||||||
|
"taskPriority": "MEDIUM", |
||||||
|
"workerGroup": "worker-group-pydolphin", |
||||||
|
"failRetryTimes": 0, |
||||||
|
"failRetryInterval": 1, |
||||||
|
"timeoutFlag": "CLOSE", |
||||||
|
"timeoutNotifyStrategy": None, |
||||||
|
"timeout": 0 |
||||||
|
} |
||||||
|
with patch('pydolphinscheduler.core.task.Task.gen_code', return_value=code): |
||||||
|
shell = Shell(name, command) |
||||||
|
assert shell.to_dict() == expect |
@ -0,0 +1,46 @@ |
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||||
|
# or more contributor license agreements. See the NOTICE file |
||||||
|
# distributed with this work for additional information |
||||||
|
# regarding copyright ownership. The ASF licenses this file |
||||||
|
# to you under the Apache License, Version 2.0 (the |
||||||
|
# "License"); you may not use this file except in compliance |
||||||
|
# with the License. You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, |
||||||
|
# software distributed under the License is distributed on an |
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
# KIND, either express or implied. See the License for the |
||||||
|
# specific language governing permissions and limitations |
||||||
|
# under the License. |
||||||
|
|
||||||
|
|
||||||
|
from py4j.java_gateway import java_import, JavaGateway |
||||||
|
|
||||||
|
|
||||||
|
def test_gateway_connect(): |
||||||
|
gateway = JavaGateway() |
||||||
|
app = gateway.entry_point |
||||||
|
assert app.ping() == "PONG" |
||||||
|
|
||||||
|
|
||||||
|
def test_jvm_simple(): |
||||||
|
gateway = JavaGateway() |
||||||
|
smaller = gateway.jvm.java.lang.Integer.MIN_VALUE |
||||||
|
bigger = gateway.jvm.java.lang.Integer.MAX_VALUE |
||||||
|
assert bigger > smaller |
||||||
|
|
||||||
|
|
||||||
|
def test_python_client_java_import_single(): |
||||||
|
gateway = JavaGateway() |
||||||
|
java_import(gateway.jvm, "org.apache.dolphinscheduler.common.utils.FileUtils") |
||||||
|
assert hasattr(gateway.jvm, "FileUtils") |
||||||
|
|
||||||
|
|
||||||
|
def test_python_client_java_import_package(): |
||||||
|
gateway = JavaGateway() |
||||||
|
java_import(gateway.jvm, "org.apache.dolphinscheduler.common.utils.*") |
||||||
|
# test if jvm view have some common utils |
||||||
|
for util in ("FileUtils", "OSUtils", "DateUtils"): |
||||||
|
assert hasattr(gateway.jvm, util) |
@ -0,0 +1,310 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.apache.dolphinscheduler.server; |
||||||
|
|
||||||
|
import org.apache.dolphinscheduler.api.enums.Status; |
||||||
|
import org.apache.dolphinscheduler.api.service.ExecutorService; |
||||||
|
import org.apache.dolphinscheduler.api.service.ProcessDefinitionService; |
||||||
|
import org.apache.dolphinscheduler.api.service.ProjectService; |
||||||
|
import org.apache.dolphinscheduler.api.service.QueueService; |
||||||
|
import org.apache.dolphinscheduler.api.service.TaskDefinitionService; |
||||||
|
import org.apache.dolphinscheduler.api.service.TenantService; |
||||||
|
import org.apache.dolphinscheduler.api.service.UsersService; |
||||||
|
import org.apache.dolphinscheduler.api.utils.Result; |
||||||
|
import org.apache.dolphinscheduler.common.Constants; |
||||||
|
import org.apache.dolphinscheduler.common.enums.FailureStrategy; |
||||||
|
import org.apache.dolphinscheduler.common.enums.Priority; |
||||||
|
import org.apache.dolphinscheduler.common.enums.ReleaseState; |
||||||
|
import org.apache.dolphinscheduler.common.enums.RunMode; |
||||||
|
import org.apache.dolphinscheduler.common.enums.TaskDependType; |
||||||
|
import org.apache.dolphinscheduler.common.enums.UserType; |
||||||
|
import org.apache.dolphinscheduler.common.enums.WarningType; |
||||||
|
import org.apache.dolphinscheduler.common.utils.SnowFlakeUtils; |
||||||
|
import org.apache.dolphinscheduler.dao.entity.ProcessDefinition; |
||||||
|
import org.apache.dolphinscheduler.dao.entity.Project; |
||||||
|
import org.apache.dolphinscheduler.dao.entity.Queue; |
||||||
|
import org.apache.dolphinscheduler.dao.entity.TaskDefinition; |
||||||
|
import org.apache.dolphinscheduler.dao.entity.Tenant; |
||||||
|
import org.apache.dolphinscheduler.dao.entity.User; |
||||||
|
import org.apache.dolphinscheduler.dao.mapper.ProcessDefinitionMapper; |
||||||
|
import org.apache.dolphinscheduler.dao.mapper.ProjectMapper; |
||||||
|
import org.apache.dolphinscheduler.dao.mapper.TaskDefinitionMapper; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Objects; |
||||||
|
|
||||||
|
import javax.annotation.PostConstruct; |
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.boot.SpringApplication; |
||||||
|
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; |
||||||
|
import org.springframework.context.annotation.ComponentScan; |
||||||
|
import org.springframework.context.annotation.FilterType; |
||||||
|
|
||||||
|
import py4j.GatewayServer; |
||||||
|
|
||||||
|
@ComponentScan(value = "org.apache.dolphinscheduler", excludeFilters = { |
||||||
|
@ComponentScan.Filter(type = FilterType.REGEX, pattern = { |
||||||
|
"org.apache.dolphinscheduler.server.master.*", |
||||||
|
"org.apache.dolphinscheduler.server.worker.*", |
||||||
|
"org.apache.dolphinscheduler.server.monitor.*", |
||||||
|
"org.apache.dolphinscheduler.server.log.*" |
||||||
|
}) |
||||||
|
}) |
||||||
|
public class PythonGatewayServer extends SpringBootServletInitializer { |
||||||
|
@Autowired |
||||||
|
private ProcessDefinitionMapper processDefinitionMapper; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private ProjectService projectService; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private TenantService tenantService; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private ExecutorService executorService; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private ProcessDefinitionService processDefinitionService; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private TaskDefinitionService taskDefinitionService; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private UsersService usersService; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private QueueService queueService; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private ProjectMapper projectMapper; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private TaskDefinitionMapper taskDefinitionMapper; |
||||||
|
|
||||||
|
// TODO replace this user to build in admin user if we make sure build in one could not be change
|
||||||
|
private final User dummyAdminUser = new User() { |
||||||
|
{ |
||||||
|
setId(Integer.MAX_VALUE); |
||||||
|
setUserName("dummyUser"); |
||||||
|
setUserType(UserType.ADMIN_USER); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
private final Queue queuePythonGateway = new Queue() { |
||||||
|
{ |
||||||
|
setId(Integer.MAX_VALUE); |
||||||
|
setQueueName("queuePythonGateway"); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
public String ping() { |
||||||
|
return "PONG"; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO Should we import package in python client side? utils package can but service can not, why
|
||||||
|
// Core api
|
||||||
|
public Map<String, Object> genTaskCodeList(Integer genNum) { |
||||||
|
return taskDefinitionService.genTaskCodeList(genNum); |
||||||
|
} |
||||||
|
|
||||||
|
public Map<String, Long> getCodeAndVersion(String projectName, String taskName) throws SnowFlakeUtils.SnowFlakeException { |
||||||
|
Project project = projectMapper.queryByName(projectName); |
||||||
|
Map<String, Long> result = new HashMap<>(); |
||||||
|
// project do not exists, mean task not exists too, so we should directly return init value
|
||||||
|
if (project == null) { |
||||||
|
result.put("code", SnowFlakeUtils.getInstance().nextId()); |
||||||
|
result.put("version", 0L); |
||||||
|
return result; |
||||||
|
} |
||||||
|
TaskDefinition taskDefinition = taskDefinitionMapper.queryByName(project.getCode(), taskName); |
||||||
|
if (taskDefinition == null) { |
||||||
|
result.put("code", SnowFlakeUtils.getInstance().nextId()); |
||||||
|
result.put("version", 0L); |
||||||
|
} else { |
||||||
|
result.put("code", taskDefinition.getCode()); |
||||||
|
result.put("version", (long) taskDefinition.getVersion()); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* create or update process definition. |
||||||
|
* If process definition do not exists in Project=`projectCode` would create a new one |
||||||
|
* If process definition already exists in Project=`projectCode` would update it |
||||||
|
* All requests |
||||||
|
* <p> |
||||||
|
* |
||||||
|
* @param name process definition name |
||||||
|
* @param description description |
||||||
|
* @param globalParams global params |
||||||
|
* @param locations locations for nodes |
||||||
|
* @param timeout timeout |
||||||
|
* @param tenantCode tenantCode |
||||||
|
* @param taskRelationJson relation json for nodes |
||||||
|
* @param taskDefinitionJson taskDefinitionJson |
||||||
|
* @return create result code |
||||||
|
*/ |
||||||
|
public Long createOrUpdateProcessDefinition(String userName, |
||||||
|
String projectName, |
||||||
|
String name, |
||||||
|
String description, |
||||||
|
String globalParams, |
||||||
|
String locations, |
||||||
|
int timeout, |
||||||
|
String tenantCode, |
||||||
|
String taskRelationJson, |
||||||
|
String taskDefinitionJson) { |
||||||
|
User user = usersService.queryUser(userName); |
||||||
|
Project project = (Project) projectService.queryByName(user, projectName).get(Constants.DATA_LIST); |
||||||
|
long projectCode = project.getCode(); |
||||||
|
Map<String, Object> verifyProcessDefinitionExists = processDefinitionService.verifyProcessDefinitionName(user, projectCode, name); |
||||||
|
|
||||||
|
if (verifyProcessDefinitionExists.get(Constants.STATUS) != Status.SUCCESS) { |
||||||
|
// update process definition
|
||||||
|
ProcessDefinition processDefinition = processDefinitionMapper.queryByDefineName(projectCode, name); |
||||||
|
long processDefinitionCode = processDefinition.getCode(); |
||||||
|
// make sure process definition offline which could edit
|
||||||
|
processDefinitionService.releaseProcessDefinition(user, projectCode, processDefinitionCode, ReleaseState.OFFLINE); |
||||||
|
Map<String, Object> result = processDefinitionService.updateProcessDefinition(user, projectCode, name, processDefinitionCode, description, globalParams, |
||||||
|
locations, timeout, tenantCode, taskRelationJson, taskDefinitionJson); |
||||||
|
return processDefinitionCode; |
||||||
|
} else { |
||||||
|
// create process definition
|
||||||
|
Map<String, Object> result = processDefinitionService.createProcessDefinition(user, projectCode, name, description, globalParams, |
||||||
|
locations, timeout, tenantCode, taskRelationJson, taskDefinitionJson); |
||||||
|
ProcessDefinition processDefinition = (ProcessDefinition) result.get(Constants.DATA_LIST); |
||||||
|
return processDefinition.getCode(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void execProcessInstance(String userName, |
||||||
|
String projectName, |
||||||
|
String processDefinitionName, |
||||||
|
String cronTime, |
||||||
|
String workerGroup, |
||||||
|
Integer timeout |
||||||
|
) { |
||||||
|
User user = usersService.queryUser(userName); |
||||||
|
Project project = projectMapper.queryByName(projectName); |
||||||
|
ProcessDefinition processDefinition = processDefinitionMapper.queryByDefineName(project.getCode(), processDefinitionName); |
||||||
|
|
||||||
|
// temp default value
|
||||||
|
FailureStrategy failureStrategy = FailureStrategy.CONTINUE; |
||||||
|
TaskDependType taskDependType = TaskDependType.TASK_POST; |
||||||
|
WarningType warningType = WarningType.NONE; |
||||||
|
RunMode runMode = RunMode.RUN_MODE_SERIAL; |
||||||
|
Priority priority = Priority.MEDIUM; |
||||||
|
int warningGroupId = 0; |
||||||
|
Long environmentCode = -1L; |
||||||
|
Map<String, String> startParams = null; |
||||||
|
Integer expectedParallelismNumber = null; |
||||||
|
String startNodeList = null; |
||||||
|
|
||||||
|
// make sure process definition online
|
||||||
|
processDefinitionService.releaseProcessDefinition(user, project.getCode(), processDefinition.getCode(), ReleaseState.ONLINE); |
||||||
|
|
||||||
|
executorService.execProcessInstance(user, |
||||||
|
project.getCode(), |
||||||
|
processDefinition.getCode(), |
||||||
|
cronTime, |
||||||
|
null, |
||||||
|
failureStrategy, |
||||||
|
startNodeList, |
||||||
|
taskDependType, |
||||||
|
warningType, |
||||||
|
warningGroupId, |
||||||
|
runMode, |
||||||
|
priority, |
||||||
|
workerGroup, |
||||||
|
environmentCode, |
||||||
|
timeout, |
||||||
|
startParams, |
||||||
|
expectedParallelismNumber, |
||||||
|
0 |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// side object
|
||||||
|
public Map<String, Object> createProject(String userName, String name, String desc) { |
||||||
|
User user = usersService.queryUser(userName); |
||||||
|
return projectService.createProject(user, name, desc); |
||||||
|
} |
||||||
|
|
||||||
|
public Map<String, Object> createQueue(String name, String queueName) { |
||||||
|
Result<Object> verifyQueueExists = queueService.verifyQueue(name, queueName); |
||||||
|
if (verifyQueueExists.getCode() == 0) { |
||||||
|
return queueService.createQueue(dummyAdminUser, name, queueName); |
||||||
|
} else { |
||||||
|
Map<String, Object> result = new HashMap<>(); |
||||||
|
// TODO function putMsg do not work here
|
||||||
|
result.put(Constants.STATUS, Status.SUCCESS); |
||||||
|
result.put(Constants.MSG, Status.SUCCESS.getMsg()); |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public Map<String, Object> createTenant(String tenantCode, String desc, String queueName) throws Exception { |
||||||
|
if (tenantService.checkTenantExists(tenantCode)) { |
||||||
|
Map<String, Object> result = new HashMap<>(); |
||||||
|
// TODO function putMsg do not work here
|
||||||
|
result.put(Constants.STATUS, Status.SUCCESS); |
||||||
|
result.put(Constants.MSG, Status.SUCCESS.getMsg()); |
||||||
|
return result; |
||||||
|
} else { |
||||||
|
Result<Object> verifyQueueExists = queueService.verifyQueue(queueName, queueName); |
||||||
|
if (verifyQueueExists.getCode() == 0) { |
||||||
|
// TODO why create do not return id?
|
||||||
|
queueService.createQueue(dummyAdminUser, queueName, queueName); |
||||||
|
} |
||||||
|
Map<String, Object> result = queueService.queryQueueName(queueName); |
||||||
|
List<Queue> queueList = (List<Queue>) result.get(Constants.DATA_LIST); |
||||||
|
Queue queue = queueList.get(0); |
||||||
|
return tenantService.createTenant(dummyAdminUser, tenantCode, queue.getId(), desc); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void createUser(String userName, |
||||||
|
String userPassword, |
||||||
|
String email, |
||||||
|
String phone, |
||||||
|
String tenantCode, |
||||||
|
String queue, |
||||||
|
int state) { |
||||||
|
User user = usersService.queryUser(userName); |
||||||
|
if (Objects.isNull(user)) { |
||||||
|
Map<String, Object> tenantResult = tenantService.queryByTenantCode(tenantCode); |
||||||
|
Tenant tenant = (Tenant) tenantResult.get(Constants.DATA_LIST); |
||||||
|
usersService.createUser(userName, userPassword, email, tenant.getId(), phone, queue, state); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@PostConstruct |
||||||
|
public void run() { |
||||||
|
GatewayServer server = new GatewayServer(this); |
||||||
|
GatewayServer.turnLoggingOn(); |
||||||
|
// Start server to accept python client RPC
|
||||||
|
server.start(); |
||||||
|
} |
||||||
|
|
||||||
|
public static void main(String[] args) { |
||||||
|
SpringApplication.run(PythonGatewayServer.class, args); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue