Browse Source
* add data transfer between tasks * add delete DATA_TRANSFER API * convert Result to DeleteDataTransferResponse * add api UT * fix final * fix doc3.2.0-release
JieguangZhou
2 years ago
committed by
GitHub
21 changed files with 1135 additions and 1 deletions
@ -0,0 +1,102 @@
|
||||
# FILE Parameter |
||||
|
||||
Use the file parameter to pass files (or folders, hereinafter referred to as **files**) in the working directory of the upstream task to the downstream task in the same workflow instance. The following scenarios may be used |
||||
|
||||
- In the ETL task, pass the data files processed by multiple upstream tasks to a specific downstream task. |
||||
- In the machine learning scenario, pass the data set file of the upstream data preparation task to the downstream model training task. |
||||
|
||||
## Usage |
||||
|
||||
### Configure file parameter |
||||
|
||||
File parameter configuration method: click the plus sign on the right side of "Custom Parameters" on the task definition page to configure. |
||||
|
||||
### Output file to downstream task |
||||
|
||||
**Four options of custom parameters are:** |
||||
|
||||
- Parameter name: the identifier used when passing tasks, such as `KEY1` and `KEY2` in the figure below |
||||
- Direction: OUT, which means outputting the file to the downstream task |
||||
- Parameter type: FILE, indicating file parameter |
||||
- Parameter value: output file path, such as `data` and `data/test2/text.txt` in the figure below |
||||
|
||||
The configuration in the figure below indicates that the `output` task passes two file data to the downstream task, respectively: |
||||
|
||||
- Pass out the folder `data`, and mark it as `dir-data`. The downstream task can get this folder through `output.dir-data` |
||||
- Pass out the file `data/test2/text.txt`, and mark it as `file-text`. The downstream task can get this folder through `output.file-text` |
||||
|
||||
![img.png](../../../../img/new_ui/dev/parameter/file_parameter_output.png) |
||||
|
||||
### Get the file from the upstream task |
||||
|
||||
**Four options of custom parameters are:** |
||||
|
||||
- Parameter name: the position where the upstream file is saved after input, such as `input_dir` used in the figure below |
||||
- Direction: IN, which means to get the file from the upstream task |
||||
- Parameter type: FILE, indicating file parameter |
||||
- Parameter value: the identifier of the upstream file, in the format of `taskName.KEY`. For example, `output.dir-data` in the figure below, where `output` is the name of the upstream task, and `dir-data` is the file identifier output by the upstream task |
||||
|
||||
The configuration in the figure below indicates that the task gets the folder identified by `dir-data` from the upstream task `output` and saves it as `input_dir` |
||||
|
||||
![img.png](../../../../img/new_ui/dev/parameter/file_parameter_input_dir.png) |
||||
|
||||
The configuration in the figure below indicates that the task gets the file identified by `file-text` from the upstream task `output` and saves it as `input.txt` |
||||
|
||||
![img.png](../../../../img/new_ui/dev/parameter/file_parameter_input_file.png) |
||||
|
||||
## Other |
||||
|
||||
### Note |
||||
|
||||
- The file transfer between upstream and downstream tasks is based on the resource center as a transfer, and the data is saved in the `DATA_TRANSFER` directory of the resource center. Therefore, **the resource center function must be enabled**, please refer to [Resource Center Configuration Details](../resource/configuration.md) for details, otherwise the file parameter function cannot be used. |
||||
- The file naming rule is `DATA_TRANSFER/DATE/ProcessDefineCode/ProcessDefineVersion_ProcessInstanceID/TaskName_TaskInstanceID_FileName` |
||||
- If the transferred file data is a folder, it will be packaged into a compressed file with a suffix of `.zip` and uploaded. The downstream task will unzip and save it in the corresponding directory after receiving it |
||||
- If you need to delete the file data, you can delete the corresponding folder in the `DATA_TRANSFER` directory of the resource center. If you delete the date subdirectory directly, all the file data under that date will be deleted. You can also use the [Open API interface](../open-api.md) (`resources/data-transfer`) to delete the corresponding file data (delete data N days ago). |
||||
- If there is a task chain task1->task2->tas3, then the downstream task task3 can also get the file data of task1 |
||||
- Support one-to-many transmission and many-to-one transmission |
||||
- If you frequently transfer a large number of files, it is obvious that the system IO performance will be affected by the amount of transferred data |
||||
|
||||
### Example |
||||
|
||||
You can save the following YAML file locally and then execute `pydolphinscheduler yaml -f data-transfer.yaml` to run the Demo. |
||||
|
||||
```yaml |
||||
# Define the workflow |
||||
workflow: |
||||
name: "data-transfer" |
||||
run: true |
||||
|
||||
# Define the tasks under the workflow |
||||
tasks: |
||||
- name: output |
||||
task_type: Shell |
||||
command: | |
||||
mkdir -p data/test1 data/test2 |
||||
echo "test1 message" >> data/test1/text.txt |
||||
echo "test2 message" >> data/test2/text.txt |
||||
tree . |
||||
local_params: |
||||
- { "prop": "dir-data", "direct": "OUT", "type": "FILE", "value": "data" } |
||||
- { "prop": "file-text", "direct": "OUT", "type": "FILE", "value": "data/test2/text.txt" } |
||||
|
||||
- name: input_dir |
||||
task_type: Shell |
||||
deps: [output] |
||||
command: | |
||||
tree . |
||||
cat input_dir/test1/text.txt |
||||
cat input_dir/test2/text.txt |
||||
local_params: |
||||
- { "prop": "input_dir", "direct": "IN", "type": "FILE", "value": "output.dir-data" } |
||||
|
||||
|
||||
- name: input_file |
||||
task_type: Shell |
||||
deps: [output] |
||||
command: | |
||||
tree . |
||||
cat input.txt |
||||
local_params: |
||||
- { "prop": "input.txt", "direct": "IN", "type": "FILE", "value": "output.file-text" } |
||||
``` |
||||
|
@ -0,0 +1,101 @@
|
||||
# 文件参数 |
||||
|
||||
通过配置文件参数,在同一工作流实例中,可以将上游任务工作目录下的文件(或文件夹,下统一以**文件**代替)传递给下游任务。 如以下场景可能使用到 |
||||
|
||||
- 在ETL任务中,将多个上游任务处理好的数据文件一起传递给特定的下游任务。 |
||||
- 在机器学习场景中,将上游数据准备任务的数据集文件传递给下游模型训练任务。 |
||||
|
||||
## 使用方式 |
||||
|
||||
### 配置文件参数 |
||||
|
||||
文件参数配置方式如下:在任务定义页面,点击“自定义参数”右边的加号,即可进行配置。 |
||||
|
||||
### 输出文件给下游任务 |
||||
|
||||
**自定义参数四个选项分别为:** |
||||
|
||||
- 参数名:任务间传递时使用的标识,如下图中使用的`KEY1`和`KEY2` |
||||
- 方向:OUT, 则表示输出文件给下游任务 |
||||
- 参数类型:FILE, 表示文件参数 |
||||
- 参数值:输出的文件路径,如下图中的`data`和`data/test2/text.txt` |
||||
|
||||
下图的配置表示任务`output`向下游任务传递两个文件数据,分别为: |
||||
- 传出文件夹 `data`, 并标记为`dir-data`, 下游任务可以通过`output.dir-data`获取该文件夹 |
||||
- 传出文件 `data/test2/text.txt`, 并标记为`file-text`, 下游任务可以通过`output.file-text`获取该文件夹 |
||||
|
||||
![img.png](../../../../img/new_ui/dev/parameter/file_parameter_output.png) |
||||
|
||||
### 获取上游任务的文件 |
||||
|
||||
**自定义参数四个选项分别为:** |
||||
|
||||
- 参数名:上游文件输入后保存的位置,如下图中使用的`input_dir` |
||||
- 方向:IN, 则表示从上游任务获取文件 |
||||
- 参数类型:FILE, 表示文件参数 |
||||
- 参数值:上游文件的标识,为 `taskName.KEY` 的格式 如下图中的`output.dir-data`, 其中`output`为上游任务的名称,`dir-data`为上游任务中输出的文件标识 |
||||
|
||||
下图的配置表示任务从上游任务`output`中获取标识为`dir-data`的文件夹,并保存为`input_dir` |
||||
|
||||
![img.png](../../../../img/new_ui/dev/parameter/file_parameter_input_dir.png) |
||||
|
||||
下图的配置表示任务从上游任务`output`中获取标识为`file-text`的文件,并保存为`input.txt` |
||||
|
||||
![img.png](../../../../img/new_ui/dev/parameter/file_parameter_input_file.png) |
||||
|
||||
## 其他 |
||||
|
||||
### 备注 |
||||
|
||||
- 上下游任务间的文件传递基于资源中心作为中转,数据保存在资源中心`DATA_TRANSFER`的目录下, 因此**必须开启资源中心功能**,详情请参考[资源中心配置详情](../resource/configuration.md), 否则无法使用文件参数功能。 |
||||
- 文件命名规则为 `DATA_TRANSFER/日期/工作流Code/工作流版本_工作流实例ID/任务名称_任务实例ID_文件名` |
||||
- 若传输的文件数据为文件夹,则会打包成后缀为`.zip`的压缩文件再上传,下游任务接到后会解压并保存在对应目录 |
||||
- 若需要删除文件数据,可以在资源中心的`DATA_TRANSFER`目录下删除对应文件夹即可, 如直接按照日期子目录删除,会删除该日期下所有的文件数据. 也可以使用`resources/data-transfer`[Open API 接口](../open-api.md)(删除N天前的数据)删除对应文件数据。 |
||||
- 如果存在任务链 task1->task2->tas3, 则最下游任务task3也能获取task1的文件数据 |
||||
- 支持一对多传输以及多对一传输 |
||||
- 如果频繁大量传输文件,毫无疑问会因传输的数据量影响到系统IO性能 |
||||
|
||||
### 样例 |
||||
|
||||
你可以保存以下YAML文件到本地,然后执行`pydolphinscheduler yaml -f data-transfer.yaml`即可运行Demo. |
||||
|
||||
```yaml |
||||
# Define the workflow |
||||
workflow: |
||||
name: "data-transfer" |
||||
run: true |
||||
|
||||
# Define the tasks under the workflow |
||||
tasks: |
||||
- name: output |
||||
task_type: Shell |
||||
command: | |
||||
mkdir -p data/test1 data/test2 |
||||
echo "test1 message" >> data/test1/text.txt |
||||
echo "test2 message" >> data/test2/text.txt |
||||
tree . |
||||
local_params: |
||||
- { "prop": "dir-data", "direct": "OUT", "type": "FILE", "value": "data" } |
||||
- { "prop": "file-text", "direct": "OUT", "type": "FILE", "value": "data/test2/text.txt" } |
||||
|
||||
- name: input_dir |
||||
task_type: Shell |
||||
deps: [output] |
||||
command: | |
||||
tree . |
||||
cat input_dir/test1/text.txt |
||||
cat input_dir/test2/text.txt |
||||
local_params: |
||||
- { "prop": "input_dir", "direct": "IN", "type": "FILE", "value": "output.dir-data" } |
||||
|
||||
|
||||
- name: input_file |
||||
task_type: Shell |
||||
deps: [output] |
||||
command: | |
||||
tree . |
||||
cat input.txt |
||||
local_params: |
||||
- { "prop": "input.txt", "direct": "IN", "type": "FILE", "value": "output.file-text" } |
||||
``` |
||||
|
After Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 121 KiB |
@ -0,0 +1,33 @@
|
||||
/* |
||||
* 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.api.dto.resources; |
||||
|
||||
import org.apache.dolphinscheduler.api.utils.Result; |
||||
|
||||
import java.util.List; |
||||
|
||||
import lombok.Data; |
||||
|
||||
@Data |
||||
public class DeleteDataTransferResponse extends Result { |
||||
|
||||
private List<String> successList; |
||||
|
||||
private List<String> failedList; |
||||
|
||||
} |
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License |
||||
Version 2.0, January 2004 |
||||
http://www.apache.org/licenses/ |
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
||||
1. Definitions. |
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, |
||||
and distribution as defined by Sections 1 through 9 of this document. |
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by |
||||
the copyright owner that is granting the License. |
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all |
||||
other entities that control, are controlled by, or are under common |
||||
control with that entity. For the purposes of this definition, |
||||
"control" means (i) the power, direct or indirect, to cause the |
||||
direction or management of such entity, whether by contract or |
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity |
||||
exercising permissions granted by this License. |
||||
|
||||
"Source" form shall mean the preferred form for making modifications, |
||||
including but not limited to software source code, documentation |
||||
source, and configuration files. |
||||
|
||||
"Object" form shall mean any form resulting from mechanical |
||||
transformation or translation of a Source form, including but |
||||
not limited to compiled object code, generated documentation, |
||||
and conversions to other media types. |
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or |
||||
Object form, made available under the License, as indicated by a |
||||
copyright notice that is included in or attached to the work |
||||
(an example is provided in the Appendix below). |
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object |
||||
form, that is based on (or derived from) the Work and for which the |
||||
editorial revisions, annotations, elaborations, or other modifications |
||||
represent, as a whole, an original work of authorship. For the purposes |
||||
of this License, Derivative Works shall not include works that remain |
||||
separable from, or merely link (or bind by name) to the interfaces of, |
||||
the Work and Derivative Works thereof. |
||||
|
||||
"Contribution" shall mean any work of authorship, including |
||||
the original version of the Work and any modifications or additions |
||||
to that Work or Derivative Works thereof, that is intentionally |
||||
submitted to Licensor for inclusion in the Work by the copyright owner |
||||
or by an individual or Legal Entity authorized to submit on behalf of |
||||
the copyright owner. For the purposes of this definition, "submitted" |
||||
means any form of electronic, verbal, or written communication sent |
||||
to the Licensor or its representatives, including but not limited to |
||||
communication on electronic mailing lists, source code control systems, |
||||
and issue tracking systems that are managed by, or on behalf of, the |
||||
Licensor for the purpose of discussing and improving the Work, but |
||||
excluding communication that is conspicuously marked or otherwise |
||||
designated in writing by the copyright owner as "Not a Contribution." |
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||
on behalf of whom a Contribution has been received by Licensor and |
||||
subsequently incorporated within the Work. |
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
copyright license to reproduce, prepare Derivative Works of, |
||||
publicly display, publicly perform, sublicense, and distribute the |
||||
Work and such Derivative Works in Source or Object form. |
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
(except as stated in this section) patent license to make, have made, |
||||
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||
where such license applies only to those patent claims licensable |
||||
by such Contributor that are necessarily infringed by their |
||||
Contribution(s) alone or by combination of their Contribution(s) |
||||
with the Work to which such Contribution(s) was submitted. If You |
||||
institute patent litigation against any entity (including a |
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||
or a Contribution incorporated within the Work constitutes direct |
||||
or contributory patent infringement, then any patent licenses |
||||
granted to You under this License for that Work shall terminate |
||||
as of the date such litigation is filed. |
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the |
||||
Work or Derivative Works thereof in any medium, with or without |
||||
modifications, and in Source or Object form, provided that You |
||||
meet the following conditions: |
||||
|
||||
(a) You must give any other recipients of the Work or |
||||
Derivative Works a copy of this License; and |
||||
|
||||
(b) You must cause any modified files to carry prominent notices |
||||
stating that You changed the files; and |
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works |
||||
that You distribute, all copyright, patent, trademark, and |
||||
attribution notices from the Source form of the Work, |
||||
excluding those notices that do not pertain to any part of |
||||
the Derivative Works; and |
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its |
||||
distribution, then any Derivative Works that You distribute must |
||||
include a readable copy of the attribution notices contained |
||||
within such NOTICE file, excluding those notices that do not |
||||
pertain to any part of the Derivative Works, in at least one |
||||
of the following places: within a NOTICE text file distributed |
||||
as part of the Derivative Works; within the Source form or |
||||
documentation, if provided along with the Derivative Works; or, |
||||
within a display generated by the Derivative Works, if and |
||||
wherever such third-party notices normally appear. The contents |
||||
of the NOTICE file are for informational purposes only and |
||||
do not modify the License. You may add Your own attribution |
||||
notices within Derivative Works that You distribute, alongside |
||||
or as an addendum to the NOTICE text from the Work, provided |
||||
that such additional attribution notices cannot be construed |
||||
as modifying the License. |
||||
|
||||
You may add Your own copyright statement to Your modifications and |
||||
may provide additional or different license terms and conditions |
||||
for use, reproduction, or distribution of Your modifications, or |
||||
for any such Derivative Works as a whole, provided Your use, |
||||
reproduction, and distribution of the Work otherwise complies with |
||||
the conditions stated in this License. |
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||
any Contribution intentionally submitted for inclusion in the Work |
||||
by You to the Licensor shall be under the terms and conditions of |
||||
this License, without any additional terms or conditions. |
||||
Notwithstanding the above, nothing herein shall supersede or modify |
||||
the terms of any separate license agreement you may have executed |
||||
with Licensor regarding such Contributions. |
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade |
||||
names, trademarks, service marks, or product names of the Licensor, |
||||
except as required for reasonable and customary use in describing the |
||||
origin of the Work and reproducing the content of the NOTICE file. |
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or |
||||
agreed to in writing, Licensor provides the Work (and each |
||||
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
implied, including, without limitation, any warranties or conditions |
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||
appropriateness of using or redistributing the Work and assume any |
||||
risks associated with Your exercise of permissions under this License. |
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, |
||||
whether in tort (including negligence), contract, or otherwise, |
||||
unless required by applicable law (such as deliberate and grossly |
||||
negligent acts) or agreed to in writing, shall any Contributor be |
||||
liable to You for damages, including any direct, indirect, special, |
||||
incidental, or consequential damages of any character arising as a |
||||
result of this License or out of the use or inability to use the |
||||
Work (including but not limited to damages for loss of goodwill, |
||||
work stoppage, computer failure or malfunction, or any and all |
||||
other commercial damages or losses), even if such Contributor |
||||
has been advised of the possibility of such damages. |
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing |
||||
the Work or Derivative Works thereof, You may choose to offer, |
||||
and charge a fee for, acceptance of support, warranty, indemnity, |
||||
or other liability obligations and/or rights consistent with this |
||||
License. However, in accepting such obligations, You may act only |
||||
on Your own behalf and on Your sole responsibility, not on behalf |
||||
of any other Contributor, and only if You agree to indemnify, |
||||
defend, and hold each Contributor harmless for any liability |
||||
incurred by, or claims asserted against, such Contributor by reason |
||||
of your accepting any such warranty or additional liability. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work. |
||||
|
||||
To apply the Apache License to your work, attach the following |
||||
boilerplate notice, with the fields enclosed by brackets "[]" |
||||
replaced with your own identifying information. (Don't include |
||||
the brackets!) The text should be enclosed in the appropriate |
||||
comment syntax for the file format. We also recommend that a |
||||
file or class name and description of purpose be included on the |
||||
same "printed page" as the copyright notice for easier |
||||
identification within third-party archives. |
||||
|
||||
Copyright 2012 ZeroTurnaround LLC. |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
@ -0,0 +1,270 @@
|
||||
/* |
||||
* 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.worker.utils; |
||||
|
||||
import org.apache.dolphinscheduler.common.utils.DateUtils; |
||||
import org.apache.dolphinscheduler.common.utils.JSONUtils; |
||||
import org.apache.dolphinscheduler.plugin.task.api.TaskConstants; |
||||
import org.apache.dolphinscheduler.plugin.task.api.TaskException; |
||||
import org.apache.dolphinscheduler.plugin.task.api.TaskExecutionContext; |
||||
import org.apache.dolphinscheduler.plugin.task.api.enums.DataType; |
||||
import org.apache.dolphinscheduler.plugin.task.api.enums.Direct; |
||||
import org.apache.dolphinscheduler.plugin.task.api.model.Property; |
||||
import org.apache.dolphinscheduler.service.storage.StorageOperate; |
||||
|
||||
import org.apache.commons.lang3.StringUtils; |
||||
|
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.time.format.DateTimeFormatter; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.zeroturnaround.zip.ZipUtil; |
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
||||
public class TaskFilesTransferUtils { |
||||
|
||||
protected final static Logger logger = LoggerFactory |
||||
.getLogger(String.format(TaskConstants.TASK_LOG_LOGGER_NAME_FORMAT, TaskFilesTransferUtils.class)); |
||||
|
||||
// tmp path in local path for transfer
|
||||
final static String DOWNLOAD_TMP = ".DT_TMP"; |
||||
|
||||
// suffix of the package file
|
||||
final static String PACK_SUFFIX = "_ds_pack.zip"; |
||||
|
||||
// root path in resource storage
|
||||
final static String RESOURCE_TAG = "DATA_TRANSFER"; |
||||
|
||||
private TaskFilesTransferUtils() { |
||||
throw new IllegalStateException("Utility class"); |
||||
} |
||||
|
||||
/** |
||||
* upload output files to resource storage |
||||
* |
||||
* @param taskExecutionContext is the context of task |
||||
* @param storageOperate is the storage operate |
||||
* @throws TaskException TaskException |
||||
*/ |
||||
public static void uploadOutputFiles(TaskExecutionContext taskExecutionContext, |
||||
StorageOperate storageOperate) throws TaskException { |
||||
List<Property> varPools = getVarPools(taskExecutionContext); |
||||
// get map of varPools for quick search
|
||||
Map<String, Property> varPoolsMap = varPools.stream().collect(Collectors.toMap(Property::getProp, x -> x)); |
||||
|
||||
// get OUTPUT FILE parameters
|
||||
List<Property> localParamsProperty = getFileLocalParams(taskExecutionContext, Direct.OUT); |
||||
|
||||
if (localParamsProperty.isEmpty()) { |
||||
return; |
||||
} |
||||
|
||||
logger.info("Upload output files ..."); |
||||
for (Property property : localParamsProperty) { |
||||
// get local file path
|
||||
String srcPath = |
||||
packIfDir(String.format("%s/%s", taskExecutionContext.getExecutePath(), property.getValue())); |
||||
// get remote file path
|
||||
String resourcePath = getResourcePath(taskExecutionContext, new File(srcPath).getName()); |
||||
try { |
||||
// upload file to storage
|
||||
String resourceWholePath = |
||||
storageOperate.getResourceFileName(taskExecutionContext.getTenantCode(), resourcePath); |
||||
logger.info("{} --- Local:{} to Remote:{}", property, srcPath, resourceWholePath); |
||||
storageOperate.upload(taskExecutionContext.getTenantCode(), srcPath, resourceWholePath, false, true); |
||||
} catch (IOException ex) { |
||||
throw new TaskException("Upload file to storage error", ex); |
||||
} |
||||
|
||||
// update varPool
|
||||
Property oriProperty; |
||||
// if the property is not in varPool, add it
|
||||
if (varPoolsMap.containsKey(property.getProp())) { |
||||
oriProperty = varPoolsMap.get(property.getProp()); |
||||
} else { |
||||
oriProperty = new Property(property.getProp(), Direct.OUT, DataType.FILE, property.getValue()); |
||||
varPools.add(oriProperty); |
||||
} |
||||
oriProperty.setProp(String.format("%s.%s", taskExecutionContext.getTaskName(), oriProperty.getProp())); |
||||
oriProperty.setValue(resourcePath); |
||||
} |
||||
taskExecutionContext.setVarPool(JSONUtils.toJsonString(varPools)); |
||||
} |
||||
|
||||
/** |
||||
* download upstream files from storage |
||||
* only download files which are defined in the task parameters |
||||
* |
||||
* @param taskExecutionContext is the context of task |
||||
* @param storageOperate is the storage operate |
||||
* @throws TaskException task exception |
||||
*/ |
||||
public static void downloadUpstreamFiles(TaskExecutionContext taskExecutionContext, StorageOperate storageOperate) { |
||||
List<Property> varPools = getVarPools(taskExecutionContext); |
||||
// get map of varPools for quick search
|
||||
Map<String, Property> varPoolsMap = varPools.stream().collect(Collectors.toMap(Property::getProp, x -> x)); |
||||
|
||||
// get "IN FILE" parameters
|
||||
List<Property> localParamsProperty = getFileLocalParams(taskExecutionContext, Direct.IN); |
||||
|
||||
if (localParamsProperty.isEmpty()) { |
||||
return; |
||||
} |
||||
|
||||
String executePath = taskExecutionContext.getExecutePath(); |
||||
// data path to download packaged data
|
||||
String downloadTmpPath = String.format("%s/%s", executePath, DOWNLOAD_TMP); |
||||
|
||||
logger.info("Download upstream files..."); |
||||
for (Property property : localParamsProperty) { |
||||
Property inVarPool = varPoolsMap.get(property.getValue()); |
||||
if (inVarPool == null) { |
||||
logger.error("{} not in {}", property.getValue(), varPoolsMap.keySet()); |
||||
throw new TaskException(String.format("Can not find upstream file using %s, please check the key", |
||||
property.getValue())); |
||||
} |
||||
|
||||
String resourcePath = inVarPool.getValue(); |
||||
String targetPath = String.format("%s/%s", executePath, property.getProp()); |
||||
|
||||
String downloadPath; |
||||
// If the data is packaged, download it to a special directory (DOWNLOAD_TMP) and unpack it to the
|
||||
// targetPath
|
||||
boolean isPack = resourcePath.endsWith(PACK_SUFFIX); |
||||
if (isPack) { |
||||
downloadPath = String.format("%s/%s", downloadTmpPath, new File(resourcePath).getName()); |
||||
} else { |
||||
downloadPath = targetPath; |
||||
} |
||||
|
||||
try { |
||||
String resourceWholePath = |
||||
storageOperate.getResourceFileName(taskExecutionContext.getTenantCode(), resourcePath); |
||||
logger.info("{} --- Remote:{} to Local:{}", property, resourceWholePath, downloadPath); |
||||
storageOperate.download(taskExecutionContext.getTenantCode(), resourceWholePath, downloadPath, false, |
||||
true); |
||||
} catch (IOException ex) { |
||||
throw new TaskException("Download file from storage error", ex); |
||||
} |
||||
|
||||
// unpack if the data is packaged
|
||||
if (isPack) { |
||||
File downloadFile = new File(downloadPath); |
||||
logger.info("Unpack {} to {}", downloadPath, targetPath); |
||||
ZipUtil.unpack(downloadFile, new File(targetPath)); |
||||
} |
||||
} |
||||
|
||||
// delete DownloadTmp Folder if DownloadTmpPath exists
|
||||
try { |
||||
org.apache.commons.io.FileUtils.deleteDirectory(new File(downloadTmpPath)); |
||||
} catch (IOException e) { |
||||
logger.error("Delete DownloadTmpPath {} failed, this will not affect the task status", downloadTmpPath, e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* get local parameters property which type is FILE and direction is equal to direct |
||||
* |
||||
* @param taskExecutionContext is the context of task |
||||
* @param direct may be Direct.IN or Direct.OUT. |
||||
* @return List<Property> |
||||
*/ |
||||
public static List<Property> getFileLocalParams(TaskExecutionContext taskExecutionContext, Direct direct) { |
||||
List<Property> localParamsProperty = new ArrayList<>(); |
||||
JsonNode taskParams = JSONUtils.parseObject(taskExecutionContext.getTaskParams()); |
||||
for (JsonNode localParam : taskParams.get("localParams")) { |
||||
Property property = JSONUtils.parseObject(localParam.toString(), Property.class); |
||||
|
||||
if (property.getDirect().equals(direct) && property.getType().equals(DataType.FILE)) { |
||||
localParamsProperty.add(property); |
||||
} |
||||
} |
||||
return localParamsProperty; |
||||
} |
||||
|
||||
/** |
||||
* get Resource path for manage files in storage |
||||
* |
||||
* @param taskExecutionContext is the context of task |
||||
* @param fileName is the file name |
||||
* @return resource path, RESOURCE_TAG/DATE/ProcessDefineCode/ProcessDefineVersion_ProcessInstanceID/TaskName_TaskInstanceID_FileName |
||||
*/ |
||||
public static String getResourcePath(TaskExecutionContext taskExecutionContext, String fileName) { |
||||
String date = |
||||
DateUtils.formatTimeStamp(taskExecutionContext.getEndTime(), DateTimeFormatter.ofPattern("yyyyMMdd")); |
||||
// get resource Folder: RESOURCE_TAG/DATE/ProcessDefineCode/ProcessDefineVersion_ProcessInstanceID
|
||||
String resourceFolder = String.format("%s/%s/%d/%d_%d", RESOURCE_TAG, date, |
||||
taskExecutionContext.getProcessDefineCode(), taskExecutionContext.getProcessDefineVersion(), |
||||
taskExecutionContext.getProcessInstanceId()); |
||||
// get resource fileL: resourceFolder/TaskName_TaskInstanceID_FileName
|
||||
return String.format("%s/%s_%s_%s", resourceFolder, taskExecutionContext.getTaskName().replace(" ", "_"), |
||||
taskExecutionContext.getTaskInstanceId(), fileName); |
||||
} |
||||
|
||||
/** |
||||
* get varPool from taskExecutionContext |
||||
* |
||||
* @param taskExecutionContext is the context of task |
||||
* @return List<Property> |
||||
*/ |
||||
public static List<Property> getVarPools(TaskExecutionContext taskExecutionContext) { |
||||
List<Property> varPools = new ArrayList<>(); |
||||
|
||||
// get varPool
|
||||
String varPoolString = taskExecutionContext.getVarPool(); |
||||
if (StringUtils.isEmpty(varPoolString)) { |
||||
return varPools; |
||||
} |
||||
// parse varPool
|
||||
for (JsonNode varPoolData : JSONUtils.parseArray(varPoolString)) { |
||||
Property property = JSONUtils.parseObject(varPoolData.toString(), Property.class); |
||||
varPools.add(property); |
||||
} |
||||
return varPools; |
||||
} |
||||
|
||||
/** |
||||
* If the path is a directory, pack it and return the path of the package
|
||||
* |
||||
* @param path is the input path, may be a file or a directory |
||||
* @return new path |
||||
*/ |
||||
public static String packIfDir(String path) throws TaskException { |
||||
File file = new File(path); |
||||
if (!file.exists()) { |
||||
throw new TaskException(String.format("%s dose not exists", path)); |
||||
} |
||||
String newPath; |
||||
if (file.isDirectory()) { |
||||
newPath = file.getPath() + PACK_SUFFIX; |
||||
logger.info("Pack {} to {}", path, newPath); |
||||
ZipUtil.pack(file, new File(newPath)); |
||||
} else { |
||||
newPath = path; |
||||
} |
||||
return newPath; |
||||
} |
||||
} |
@ -0,0 +1,255 @@
|
||||
/* |
||||
* 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.worker.utils; |
||||
|
||||
import org.apache.dolphinscheduler.common.utils.DateUtils; |
||||
import org.apache.dolphinscheduler.plugin.task.api.TaskExecutionContext; |
||||
import org.apache.dolphinscheduler.plugin.task.api.enums.DataType; |
||||
import org.apache.dolphinscheduler.plugin.task.api.enums.Direct; |
||||
import org.apache.dolphinscheduler.plugin.task.api.model.Property; |
||||
import org.apache.dolphinscheduler.service.storage.StorageOperate; |
||||
|
||||
import org.apache.curator.shaded.com.google.common.io.Files; |
||||
|
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.time.format.DateTimeFormatter; |
||||
import java.util.List; |
||||
|
||||
import org.junit.jupiter.api.Assertions; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.mockito.Mockito; |
||||
import org.zeroturnaround.zip.ZipUtil; |
||||
|
||||
public class TaskFilesTransferUtilsTest { |
||||
|
||||
private final long processDefineCode = 123; |
||||
private final int processDefineVersion = 456; |
||||
private final int processInstanceId = 678; |
||||
private final int taskInstanceId = 789; |
||||
private final String taskName = "test"; |
||||
|
||||
private final String tenantCode = "ubuntu"; |
||||
|
||||
private long endTime; |
||||
|
||||
private String exceptTemplate; |
||||
|
||||
@BeforeEach |
||||
void init() { |
||||
endTime = System.currentTimeMillis(); |
||||
String date = DateUtils.formatTimeStamp(endTime, DateTimeFormatter.ofPattern("yyyyMMdd")); |
||||
exceptTemplate = String.format("%s/%s/%d/%d_%d/%s_%d", |
||||
TaskFilesTransferUtils.RESOURCE_TAG, |
||||
date, |
||||
processDefineCode, |
||||
processDefineVersion, |
||||
processInstanceId, |
||||
taskName, |
||||
taskInstanceId); |
||||
} |
||||
|
||||
@Test |
||||
void testUploadOutputFiles() throws IOException { |
||||
File executePath = Files.createTempDir(); |
||||
File folderPath = new File(executePath, "data"); |
||||
File file = new File(folderPath.getPath() + "/test.txt"); |
||||
if (!(folderPath.mkdirs() && file.createNewFile())) { |
||||
return; |
||||
} |
||||
String varPool = "[" + |
||||
String.format("{\"prop\":\"folder\",\"direct\":\"OUT\",\"type\":\"FILE\",\"value\":\"%s\"},", |
||||
folderPath.getName()) |
||||
+ |
||||
String.format(" {\"prop\":\"file\",\"direct\":\"OUT\",\"type\":\"FILE\",\"value\":\"%s/%s\"},", |
||||
folderPath.getName(), file.getName()) |
||||
+ |
||||
"{\"prop\":\"a\",\"direct\":\"OUT\",\"type\":\"VARCHAR\",\"value\":\"a\"}," + |
||||
"{\"prop\":\"b\",\"direct\":\"OUT\",\"type\":\"VARCHAR\",\"value\":\"b\"}" + |
||||
"]"; |
||||
String taskParams = String.format("{\"localParams\": %s}", varPool); |
||||
TaskExecutionContext taskExecutionContext = TaskExecutionContext.builder() |
||||
.varPool(varPool) |
||||
.taskParams(taskParams) |
||||
.processInstanceId(processInstanceId) |
||||
.processDefineVersion(processDefineVersion) |
||||
.processDefineCode(processDefineCode) |
||||
.taskInstanceId(taskInstanceId) |
||||
.taskName(taskName) |
||||
.tenantCode(tenantCode) |
||||
.executePath(executePath.toString()) |
||||
.endTime(endTime) |
||||
.build(); |
||||
|
||||
List<Property> oriProperties = TaskFilesTransferUtils.getVarPools(taskExecutionContext); |
||||
|
||||
StorageOperate storageOperate = Mockito.mock(StorageOperate.class); |
||||
TaskFilesTransferUtils.uploadOutputFiles(taskExecutionContext, storageOperate); |
||||
System.out.println(taskExecutionContext.getVarPool()); |
||||
|
||||
String exceptFolder = |
||||
String.format("%s_%s", exceptTemplate, folderPath.getName() + TaskFilesTransferUtils.PACK_SUFFIX); |
||||
String exceptFile = String.format("%s_%s", exceptTemplate, file.getName()); |
||||
|
||||
List<Property> properties = TaskFilesTransferUtils.getVarPools(taskExecutionContext); |
||||
Assertions.assertEquals(4, properties.size()); |
||||
|
||||
Assertions.assertEquals(String.format("%s.%s", taskName, "folder"), properties.get(0).getProp()); |
||||
Assertions.assertEquals(exceptFolder, properties.get(0).getValue()); |
||||
|
||||
Assertions.assertEquals(String.format("%s.%s", taskName, "file"), properties.get(1).getProp()); |
||||
Assertions.assertEquals(exceptFile, properties.get(1).getValue()); |
||||
|
||||
Assertions.assertEquals(oriProperties.get(2).getProp(), properties.get(2).getProp()); |
||||
Assertions.assertEquals(oriProperties.get(3).getValue(), properties.get(3).getValue()); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
void testDownloadUpstreamFiles() { |
||||
File executePath = Files.createTempDir(); |
||||
String folderPath = exceptTemplate + "_folder" + TaskFilesTransferUtils.PACK_SUFFIX; |
||||
String filePath = exceptTemplate + "_file"; |
||||
String varPool = "[" + |
||||
String.format( |
||||
"{\"prop\":\"task1.folder\",\"direct\":\"IN\",\"type\":\"FILE\",\"value\":\"%s\"},", folderPath) |
||||
+ |
||||
String.format(" {\"prop\":\"task2.file\",\"direct\":\"IN\",\"type\":\"FILE\",\"value\":\"%s\"},", |
||||
filePath) |
||||
+ |
||||
"{\"prop\":\"a\",\"direct\":\"OUT\",\"type\":\"VARCHAR\",\"value\":\"a\"}," + |
||||
"{\"prop\":\"b\",\"direct\":\"OUT\",\"type\":\"VARCHAR\",\"value\":\"b\"}" + |
||||
"]"; |
||||
String varPoolParams = "[" + |
||||
"{\"prop\":\"folder\",\"direct\":\"IN\",\"type\":\"FILE\",\"value\":\"task1.folder\"}," + |
||||
" {\"prop\":\"file\",\"direct\":\"IN\",\"type\":\"FILE\",\"value\":\"task2.file\"}" + |
||||
"]"; |
||||
String taskParams = String.format("{\"localParams\": %s}", varPoolParams); |
||||
TaskExecutionContext taskExecutionContext = TaskExecutionContext.builder() |
||||
.varPool(varPool) |
||||
.taskParams(taskParams) |
||||
.processInstanceId(processInstanceId) |
||||
.processDefineVersion(processDefineVersion) |
||||
.processDefineCode(processDefineCode) |
||||
.taskInstanceId(taskInstanceId) |
||||
.taskName(taskName) |
||||
.tenantCode(tenantCode) |
||||
.executePath(executePath.toString()) |
||||
.endTime(endTime) |
||||
.build(); |
||||
|
||||
StorageOperate storageOperate = Mockito.mock(StorageOperate.class); |
||||
Mockito.mockStatic(ZipUtil.class); |
||||
Assertions.assertDoesNotThrow( |
||||
() -> TaskFilesTransferUtils.downloadUpstreamFiles(taskExecutionContext, storageOperate)); |
||||
} |
||||
|
||||
@Test |
||||
void testGetFileLocalParams() { |
||||
String taskParams = "{\"localParams\":[" + |
||||
"{\"prop\":\"inputFile\",\"direct\":\"IN\",\"type\":\"FILE\",\"value\":\"task1.data\"}," + |
||||
"{\"prop\":\"outputFile\",\"direct\":\"OUT\",\"type\":\"FILE\",\"value\":\"data\"}," + |
||||
"{\"prop\":\"a\",\"direct\":\"IN\",\"type\":\"VARCHAR\",\"value\":\"a\"}," + |
||||
"{\"prop\":\"b\",\"direct\":\"OUT\",\"type\":\"VARCHAR\",\"value\":\"b\"}" + |
||||
"]}"; |
||||
TaskExecutionContext taskExecutionContext = Mockito.mock(TaskExecutionContext.class); |
||||
Mockito.when(taskExecutionContext.getTaskParams()).thenReturn(taskParams); |
||||
|
||||
List<Property> fileLocalParamsIn = TaskFilesTransferUtils.getFileLocalParams(taskExecutionContext, Direct.IN); |
||||
Assertions.assertEquals(1, fileLocalParamsIn.size()); |
||||
Assertions.assertEquals("inputFile", fileLocalParamsIn.get(0).getProp()); |
||||
Assertions.assertEquals("task1.data", fileLocalParamsIn.get(0).getValue()); |
||||
|
||||
List<Property> fileLocalParamsOut = TaskFilesTransferUtils.getFileLocalParams(taskExecutionContext, Direct.OUT); |
||||
Assertions.assertEquals(1, fileLocalParamsOut.size()); |
||||
Assertions.assertEquals("outputFile", fileLocalParamsOut.get(0).getProp()); |
||||
Assertions.assertEquals("data", fileLocalParamsOut.get(0).getValue()); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
void testGetResourcePath() { |
||||
String fileName = "test.txt"; |
||||
TaskExecutionContext taskExecutionContext = Mockito.mock(TaskExecutionContext.class); |
||||
|
||||
Mockito.when(taskExecutionContext.getEndTime()).thenReturn(endTime); |
||||
|
||||
Mockito.when(taskExecutionContext.getProcessDefineCode()).thenReturn(processDefineCode); |
||||
Mockito.when(taskExecutionContext.getProcessDefineVersion()).thenReturn(processDefineVersion); |
||||
Mockito.when(taskExecutionContext.getProcessInstanceId()).thenReturn(processInstanceId); |
||||
Mockito.when(taskExecutionContext.getTaskInstanceId()).thenReturn(taskInstanceId); |
||||
Mockito.when(taskExecutionContext.getTaskName()).thenReturn(taskName); |
||||
|
||||
String except = String.format("%s_%s", exceptTemplate, fileName); |
||||
Assertions.assertEquals(except, TaskFilesTransferUtils.getResourcePath(taskExecutionContext, fileName)); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
void testGetVarPools() { |
||||
String varPoolsString = "[" + |
||||
"{\"prop\":\"input\",\"direct\":\"IN\",\"type\":\"FILE\",\"value\":\"task1.output\"}" + |
||||
",{\"prop\":\"a\",\"direct\":\"IN\",\"type\":\"VARCHAR\",\"value\":\"${a}\"}" + |
||||
"]"; |
||||
TaskExecutionContext taskExecutionContext = Mockito.mock(TaskExecutionContext.class); |
||||
Mockito.when(taskExecutionContext.getVarPool()).thenReturn(varPoolsString); |
||||
|
||||
List<Property> varPools = TaskFilesTransferUtils.getVarPools(taskExecutionContext); |
||||
Assertions.assertEquals(2, varPools.size()); |
||||
|
||||
Property varPool0 = varPools.get(0); |
||||
Assertions.assertEquals("input", varPool0.getProp()); |
||||
Assertions.assertEquals(Direct.IN, varPool0.getDirect()); |
||||
Assertions.assertEquals(DataType.FILE, varPool0.getType()); |
||||
Assertions.assertEquals("task1.output", varPool0.getValue()); |
||||
|
||||
Property varPool1 = varPools.get(1); |
||||
Assertions.assertEquals("a", varPool1.getProp()); |
||||
Assertions.assertEquals(Direct.IN, varPool1.getDirect()); |
||||
Assertions.assertEquals(DataType.VARCHAR, varPool1.getType()); |
||||
Assertions.assertEquals("${a}", varPool1.getValue()); |
||||
|
||||
Mockito.when(taskExecutionContext.getVarPool()).thenReturn("[]"); |
||||
List<Property> varPoolsEmpty = TaskFilesTransferUtils.getVarPools(taskExecutionContext); |
||||
Assertions.assertEquals(0, varPoolsEmpty.size()); |
||||
|
||||
Mockito.when(taskExecutionContext.getVarPool()).thenReturn(null); |
||||
List<Property> varPoolsNull = TaskFilesTransferUtils.getVarPools(taskExecutionContext); |
||||
Assertions.assertEquals(0, varPoolsNull.size()); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
void testPackIfDir() throws Exception { |
||||
File folderPath = Files.createTempDir(); |
||||
File file1 = new File(folderPath.getPath() + "/test.txt"); |
||||
File file2 = new File(folderPath.getPath() + "/test.zip"); |
||||
boolean isSuccess1 = file1.createNewFile(); |
||||
boolean isSuccess2 = file2.createNewFile(); |
||||
|
||||
Assertions.assertTrue(isSuccess1); |
||||
Assertions.assertTrue(isSuccess2); |
||||
|
||||
Assertions.assertEquals(file1.getPath(), TaskFilesTransferUtils.packIfDir(file1.getPath())); |
||||
Assertions.assertEquals(file2.getPath(), TaskFilesTransferUtils.packIfDir(file2.getPath())); |
||||
|
||||
String expectFolderPackPath = folderPath.getPath() + TaskFilesTransferUtils.PACK_SUFFIX; |
||||
Assertions.assertEquals(expectFolderPackPath, TaskFilesTransferUtils.packIfDir(folderPath.getPath())); |
||||
} |
||||
} |
Loading…
Reference in new issue