diff --git a/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/file/FileClient.java b/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/file/FileClient.java index eba307e6..4d807513 100644 --- a/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/file/FileClient.java +++ b/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/file/FileClient.java @@ -120,4 +120,20 @@ public interface FileClient { */ OutputStream getContentOutputStream(String path, boolean append) throws Exception; + /** + * 获取返回路径 用于客户端返回 + * + * @param path path + * @return returnPath + */ + String getReturnPath(String path); + + /** + * 获取实际存储路径 用于服务端的存储 + * + * @param returnPath returnPath + * @return absolutePath + */ + String getAbsolutePath(String returnPath); + } diff --git a/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/utils/FileClientUtils.java b/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/utils/FileClientUtils.java index 94200f4a..e0b0f3cf 100644 --- a/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/utils/FileClientUtils.java +++ b/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/utils/FileClientUtils.java @@ -152,6 +152,26 @@ public class FileClientUtils { return delegate.getContentOutputStream(path, append); } + /** + * 获取返回路径 用于客户端返回 + * + * @param path path + * @return returnPath + */ + public static String getReturnPath(String path) { + return delegate.getReturnPath(path); + } + + /** + * 获取实际存储路径 用于服务端的存储 + * + * @param returnPath returnPath + * @return absolutePath + */ + public static String getAbsolutePath(String returnPath) { + return delegate.getAbsolutePath(returnPath); + } + public static void setDelegate(FileClient delegate) { if (FileClientUtils.delegate != null) { // unmodified diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/AbstractFileClient.java b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/AbstractFileClient.java index 52655262..b8cac3ce 100644 --- a/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/AbstractFileClient.java +++ b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/AbstractFileClient.java @@ -80,22 +80,6 @@ public abstract class AbstractFileClient implem */ protected abstract String doUpload(String path, InputStream in, boolean autoClose, boolean overrideIfExist) throws Exception; - /** - * 获取返回路径 用于客户端返回 - * - * @param path path - * @return returnPath - */ - protected abstract String getReturnPath(String path); - - /** - * 获取实际存储路径 用于服务端的存储 - * - * @param returnPath returnPath - * @return absolutePath - */ - protected abstract String getAbsolutePath(String returnPath); - /** * 获取文件路径 拼接前缀 * diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/PrimaryFileClient.java b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/PrimaryFileClient.java index 9fff4243..79f9c4ba 100644 --- a/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/PrimaryFileClient.java +++ b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/PrimaryFileClient.java @@ -72,6 +72,16 @@ public class PrimaryFileClient implements FileClient { return delegate.getContentOutputStream(path, append); } + @Override + public String getReturnPath(String path) { + return delegate.getReturnPath(path); + } + + @Override + public String getAbsolutePath(String returnPath) { + return delegate.getAbsolutePath(returnPath); + } + public static void setDelegate(FileClient delegate) { if (PrimaryFileClient.delegate != null) { // unmodified diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/local/LocalFileClient.java b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/local/LocalFileClient.java index 409ff5c8..e4345091 100644 --- a/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/local/LocalFileClient.java +++ b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/local/LocalFileClient.java @@ -57,13 +57,13 @@ public class LocalFileClient extends AbstractFileClient { } @Override - protected String getReturnPath(String path) { + public String getReturnPath(String path) { // 拼接公共路径 return Files1.getPath(config.getBasePath() + Const.SLASH + this.getFilePath(path)); } @Override - protected String getAbsolutePath(String returnPath) { + public String getAbsolutePath(String returnPath) { // 拼接存储路径 return Files1.getPath(config.getStoragePath() + Const.SLASH + returnPath); } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/controller/ExecController.http b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/controller/ExecController.http index a4f063cf..59d2effb 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/controller/ExecController.http +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/controller/ExecController.http @@ -1,5 +1,15 @@ ### 批量执行 POST {{baseUrl}}/asset/exec/start +Content-Type: application/json Authorization: {{token}} +{ + "description": 1, + "timeout": 10, + "command": "echo @{{ hostAddress }}\nsleep 10\necho @{{ str }}", + "parameter": "{\"str\":\"end\"}", + "hostIdList": [1,7] +} + + ### diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/controller/ExecLogController.http b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/controller/ExecLogController.http index 088059d8..e34d45a8 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/controller/ExecLogController.http +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/controller/ExecLogController.http @@ -13,9 +13,10 @@ Authorization: {{token}} "limit": 10, "id": "", "userId": "", + "username": "", "source": "", "sourceId": "", - "desc": "", + "description": "", "command": "", "status": "" } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/define/AssetThreadPools.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/define/AssetThreadPools.java index 582a5f02..053186a5 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/define/AssetThreadPools.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/define/AssetThreadPools.java @@ -15,6 +15,18 @@ import java.util.concurrent.ThreadPoolExecutor; */ public interface AssetThreadPools { + /** + * 超时检查线程池 + */ + ThreadPoolExecutor TIMEOUT_CHECK = ExecutorBuilder.create() + .namedThreadFactory("timeout-check-") + .corePoolSize(1) + .maxPoolSize(Integer.MAX_VALUE) + .keepAliveTime(Const.MS_S_60) + .workQueue(new SynchronousQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + /** * terminal 标准输出线程池 */ @@ -39,4 +51,28 @@ public interface AssetThreadPools { .allowCoreThreadTimeout(true) .build(); + /** + * 批量执行任务线程池 + */ + ThreadPoolExecutor EXEC_TASK = ExecutorBuilder.create() + .namedThreadFactory("exec-task-") + .corePoolSize(1) + .maxPoolSize(Integer.MAX_VALUE) + .keepAliveTime(Const.MS_S_60) + .workQueue(new SynchronousQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + /** + * 批量执行主机命令线程池 + */ + ThreadPoolExecutor EXEC_HOST = ExecutorBuilder.create() + .namedThreadFactory("exec-host-") + .corePoolSize(1) + .maxPoolSize(Integer.MAX_VALUE) + .keepAliveTime(Const.MS_S_60) + .workQueue(new SynchronousQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/domain/ExecHostLogDO.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/domain/ExecHostLogDO.java index bf5f6107..e5b3522b 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/domain/ExecHostLogDO.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/domain/ExecHostLogDO.java @@ -64,6 +64,10 @@ public class ExecHostLogDO extends BaseDO { @TableField("log_path") private String logPath; + @Schema(description = "错误信息") + @TableField("error_message") + private String errorMessage; + @Schema(description = "执行开始时间") @TableField("start_time") private Date startTime; diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/domain/ExecLogDO.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/domain/ExecLogDO.java index a664f192..e30ddfcd 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/domain/ExecLogDO.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/domain/ExecLogDO.java @@ -36,6 +36,10 @@ public class ExecLogDO extends BaseDO { @TableField("user_id") private Long userId; + @Schema(description = "执行用户名") + @TableField("username") + private String username; + @Schema(description = "执行来源") @TableField("source") private String source; @@ -45,13 +49,17 @@ public class ExecLogDO extends BaseDO { private Long sourceId; @Schema(description = "执行描述") - @TableField("desc") - private String desc; + @TableField("description") + private String description; @Schema(description = "执行命令") @TableField("command") private String command; + @Schema(description = "超时时间") + @TableField("timeout") + private Integer timeout; + @Schema(description = "执行状态") @TableField("status") private String status; diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/request/exec/ExecLogQueryRequest.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/request/exec/ExecLogQueryRequest.java index 6ad36f3a..4000bcb6 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/request/exec/ExecLogQueryRequest.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/request/exec/ExecLogQueryRequest.java @@ -29,6 +29,9 @@ public class ExecLogQueryRequest extends PageRequest { @Schema(description = "执行用户id") private Long userId; + @Schema(description = "执行用户名") + private String username; + @Size(max = 12) @Schema(description = "执行来源") private String source; @@ -38,7 +41,7 @@ public class ExecLogQueryRequest extends PageRequest { @Size(max = 128) @Schema(description = "执行描述") - private String desc; + private String description; @Schema(description = "执行命令") private String command; diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/request/exec/ExecRequest.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/request/exec/ExecRequest.java index ad8d4f86..c084b013 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/request/exec/ExecRequest.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/request/exec/ExecRequest.java @@ -29,7 +29,11 @@ public class ExecRequest extends PageRequest { @Size(max = 128) @Schema(description = "执行描述") - private String desc; + private String description; + + @NonNull + @Schema(description = "超时时间") + private Integer timeout; @NotBlank @Schema(description = "执行命令") diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/vo/ExecHostLogVO.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/vo/ExecHostLogVO.java index d105a7ae..0dad090f 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/vo/ExecHostLogVO.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/vo/ExecHostLogVO.java @@ -1,11 +1,13 @@ package com.orion.ops.module.asset.entity.vo; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; import java.io.Serializable; -import java.util.*; -import java.math.*; +import java.util.Date; /** * 批量执行主机日志 视图响应对象 @@ -50,6 +52,9 @@ public class ExecHostLogVO implements Serializable { @Schema(description = "日志路径") private String logPath; + @Schema(description = "错误信息") + private String errorMessage; + @Schema(description = "执行开始时间") private Date startTime; diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/vo/ExecLogVO.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/vo/ExecLogVO.java index a27ff110..d9477a1b 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/vo/ExecLogVO.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/vo/ExecLogVO.java @@ -31,6 +31,9 @@ public class ExecLogVO implements Serializable { @Schema(description = "执行用户id") private Long userId; + @Schema(description = "执行用户名") + private String username; + @Schema(description = "执行来源") private String source; @@ -38,11 +41,14 @@ public class ExecLogVO implements Serializable { private Long sourceId; @Schema(description = "执行描述") - private String desc; + private String description; @Schema(description = "执行命令") private String command; + @Schema(description = "超时时间") + private Integer timeout; + @Schema(description = "执行状态") private String status; diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/ExecTaskExecutors.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/ExecTaskExecutors.java new file mode 100644 index 00000000..57884cec --- /dev/null +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/ExecTaskExecutors.java @@ -0,0 +1,25 @@ +package com.orion.ops.module.asset.handler.host.exec; + +import com.orion.ops.module.asset.define.AssetThreadPools; +import com.orion.ops.module.asset.handler.host.exec.dto.ExecCommandDTO; +import com.orion.ops.module.asset.handler.host.exec.handler.ExecTaskHandler; + +/** + * 批量执行命令执行器 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2024/3/12 11:10 + */ +public class ExecTaskExecutors { + + /** + * 执行命令 + * + * @param command command + */ + public static void start(ExecCommandDTO command) { + AssetThreadPools.EXEC_TASK.execute(new ExecTaskHandler(command)); + } + +} diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/ExecStartDTO.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/dto/ExecCommandDTO.java similarity index 61% rename from orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/ExecStartDTO.java rename to orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/dto/ExecCommandDTO.java index 0f9299c9..40edb110 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/ExecStartDTO.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/dto/ExecCommandDTO.java @@ -1,4 +1,4 @@ -package com.orion.ops.module.asset.entity.dto; +package com.orion.ops.module.asset.handler.host.exec.dto; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; @@ -19,13 +19,16 @@ import java.util.List; @Builder @NoArgsConstructor @AllArgsConstructor -@Schema(name = "ExecStartDTO", description = "批量执行启动对象") -public class ExecStartDTO { +@Schema(name = "ExecCommandDTO", description = "批量执行启动对象") +public class ExecCommandDTO { @Schema(description = "hostId") private Long logId; + @Schema(description = "超时时间") + private Integer timeout; + @Schema(description = "主机") - private List hosts; + private List hosts; } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/ExecStartHostDTO.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/dto/ExecCommandHostDTO.java similarity index 70% rename from orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/ExecStartHostDTO.java rename to orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/dto/ExecCommandHostDTO.java index 4edbfe7f..814a018b 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/ExecStartHostDTO.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/dto/ExecCommandHostDTO.java @@ -1,4 +1,4 @@ -package com.orion.ops.module.asset.entity.dto; +package com.orion.ops.module.asset.handler.host.exec.dto; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; @@ -17,8 +17,8 @@ import lombok.NoArgsConstructor; @Builder @NoArgsConstructor @AllArgsConstructor -@Schema(name = "ExecStartHostDTO", description = "批量执行启动主机对象") -public class ExecStartHostDTO { +@Schema(name = "ExecCommandHostDTO", description = "批量执行启动主机对象") +public class ExecCommandHostDTO { @Schema(description = "hostLogId") private Long hostLogId; @@ -32,4 +32,7 @@ public class ExecStartHostDTO { @Schema(description = "执行命令") private String command; + @Schema(description = "超时时间") + private Integer timeout; + } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/handler/ExecCommandHandler.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/handler/ExecCommandHandler.java new file mode 100644 index 00000000..cca68f82 --- /dev/null +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/handler/ExecCommandHandler.java @@ -0,0 +1,189 @@ +package com.orion.ops.module.asset.handler.host.exec.handler; + +import com.alibaba.fastjson.JSON; +import com.orion.lang.exception.AuthenticationException; +import com.orion.lang.exception.argument.InvalidArgumentException; +import com.orion.lang.support.timeout.TimeoutChecker; +import com.orion.lang.utils.Strings; +import com.orion.lang.utils.io.Streams; +import com.orion.net.host.SessionStore; +import com.orion.net.host.ssh.command.CommandExecutor; +import com.orion.ops.framework.common.file.FileClient; +import com.orion.ops.module.asset.dao.ExecHostLogDAO; +import com.orion.ops.module.asset.entity.domain.ExecHostLogDO; +import com.orion.ops.module.asset.enums.ExecHostStatusEnum; +import com.orion.ops.module.asset.handler.host.exec.dto.ExecCommandHostDTO; +import com.orion.ops.module.asset.service.HostTerminalService; +import com.orion.spring.SpringHolder; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Date; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * 命令执行器 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2024/3/12 11:30 + */ +@Slf4j +public class ExecCommandHandler implements IExecCommandHandler { + + private final FileClient fileClient = SpringHolder.getBean("logsFileClient"); + + private final HostTerminalService hostTerminalService = SpringHolder.getBean(HostTerminalService.class); + + private final ExecHostLogDAO execHostLogDAO = SpringHolder.getBean(ExecHostLogDAO.class); + + private final ExecCommandHostDTO command; + + private final TimeoutChecker timeoutChecker; + + private SessionStore sessionStore; + + private CommandExecutor executor; + + private OutputStream logOutputStream; + + private volatile boolean closed; + + private volatile boolean interrupted; + + public ExecCommandHandler(ExecCommandHostDTO command, TimeoutChecker timeoutChecker) { + this.command = command; + this.timeoutChecker = timeoutChecker; + } + + @Override + public void run() { + Long id = command.getHostLogId(); + log.info("ExecCommandHandler run start id: {}, info: {}", id, JSON.toJSONString(command)); + try { + // 更新状态 + this.updateStatus(ExecHostStatusEnum.RUNNING, null); + // 执行命令 + this.execCommand(); + if (executor.isTimeout()) { + // 更新状态 + this.updateStatus(ExecHostStatusEnum.FAILED, new TimeoutException()); + } else { + // 更新状态 + this.updateStatus(ExecHostStatusEnum.COMPLETED, null); + } + log.info("ExecCommandHandler run complete id: {}", id); + } catch (Exception e) { + log.error("ExecCommandHandler run error id: {}", id, e); + // TODO + if (this.interrupted) { + this.updateStatus(ExecHostStatusEnum.INTERRUPTED, null); + } else { + this.updateStatus(ExecHostStatusEnum.FAILED, e); + } + } finally { + Streams.close(this); + } + } + + /** + * 执行命令 + * + * @throws IOException IOException + */ + private void execCommand() throws Exception { + // 打开日志流 + this.logOutputStream = fileClient.getContentOutputStream(command.getLogPath()); + // 打开会话 + this.sessionStore = hostTerminalService.openSessionStore(command.getHostId()); + this.executor = sessionStore.getCommandExecutor(Strings.replaceCRLF(command.getCommand())); + // TODO 超时 + // 执行命令 + executor.timeout(command.getTimeout(), TimeUnit.SECONDS, timeoutChecker); + executor.merge(); + executor.transfer(logOutputStream); + executor.connect(); + executor.exec(); + } + + /** + * 更新状态 + * + * @param status status + * @param ex ex + */ + private void updateStatus(ExecHostStatusEnum status, Exception ex) { + Long id = command.getHostLogId(); + String statusName = status.name(); + log.info("ExecCommandHandler.updateStatus id: {}, status: {}", id, statusName); + ExecHostLogDO update = new ExecHostLogDO(); + update.setId(id); + update.setStatus(statusName); + if (ExecHostStatusEnum.RUNNING.equals(status)) { + // 运行中 + update.setStartTime(new Date()); + } else if (ExecHostStatusEnum.COMPLETED.equals(status)) { + // 完成 + update.setFinishTime(new Date()); + update.setExitStatus(executor.getExitCode()); + } else if (ExecHostStatusEnum.FAILED.equals(status)) { + // 失败 + update.setFinishTime(new Date()); + update.setErrorMessage(this.getErrorMessage(ex)); + } else if (ExecHostStatusEnum.INTERRUPTED.equals(status)) { + // 中断 + update.setFinishTime(new Date()); + } + execHostLogDAO.updateById(update); + } + + @Override + public void write(String msg) { + this.executor.write(msg); + } + + @Override + public void interrupted() { + if (this.interrupted || this.closed) { + return; + } + // 关闭 + this.interrupted = true; + this.close(); + } + + /** + * 获取错误信息 + * + * @param ex ex + * @return errorMessage + */ + private String getErrorMessage(Exception ex) { + String message; + if (ex instanceof TimeoutException) { + message = "执行超时"; + } else if (ex instanceof InvalidArgumentException) { + message = ex.getMessage(); + } else if (ex instanceof AuthenticationException) { + message = "认证失败"; + } else { + message = "执行失败"; + } + return Strings.retain(message, 250); + } + + @Override + public void close() { + if (this.closed) { + return; + } + this.closed = true; + Streams.close(executor); + Streams.close(sessionStore); + Streams.close(logOutputStream); + // TODO 关闭日志 + } + +} diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/handler/ExecTaskHandler.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/handler/ExecTaskHandler.java new file mode 100644 index 00000000..34bfca3a --- /dev/null +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/handler/ExecTaskHandler.java @@ -0,0 +1,120 @@ +package com.orion.ops.module.asset.handler.host.exec.handler; + +import com.orion.lang.support.timeout.TimeoutChecker; +import com.orion.lang.utils.Threads; +import com.orion.lang.utils.collect.Lists; +import com.orion.lang.utils.io.Streams; +import com.orion.ops.framework.common.constant.Const; +import com.orion.ops.module.asset.dao.ExecLogDAO; +import com.orion.ops.module.asset.define.AssetThreadPools; +import com.orion.ops.module.asset.entity.domain.ExecLogDO; +import com.orion.ops.module.asset.enums.ExecStatusEnum; +import com.orion.ops.module.asset.handler.host.exec.dto.ExecCommandDTO; +import com.orion.ops.module.asset.handler.host.exec.dto.ExecCommandHostDTO; +import com.orion.spring.SpringHolder; +import lombok.extern.slf4j.Slf4j; + +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 命令执行任务 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2024/3/12 11:43 + */ +@Slf4j +public class ExecTaskHandler implements IExecTaskHandler { + + private static final ExecLogDAO execLogDAO = SpringHolder.getBean(ExecLogDAO.class); + + private final ExecCommandDTO command; + + private TimeoutChecker timeoutChecker; + + private List handlers; + + public ExecTaskHandler(ExecCommandDTO command) { + this.command = command; + this.handlers = Lists.newList(); + } + + // TODO manager + + @Override + public void run() { + Long id = command.getLogId(); + log.info("ExecTaskHandler.run start id: {}", id); + try { + // TODO 添加 + // 更新状态 + this.updateStatus(ExecStatusEnum.RUNNING); + // 执行命令 + this.runHostCommand(command.getHosts()); + // 更新状态-执行完成 + log.info("ExecTaskHandler.run completed id: {}", id); + this.updateStatus(ExecStatusEnum.COMPLETED); + } catch (Exception e) { + // 更新状态-执行失败 + this.updateStatus(ExecStatusEnum.FAILED); + log.error("ExecTaskHandler.run error id: {}", id, e); + // TODO 移除 + } finally { + this.close(); + } + } + + /** + * 执行主机命令 + * + * @param hosts hosts + * @throws Exception Exception + */ + private void runHostCommand(List hosts) throws Exception { + // 超时检查 + if (command.getTimeout() != 0) { + this.timeoutChecker = TimeoutChecker.create(Const.MS_S_1); + AssetThreadPools.TIMEOUT_CHECK.execute(this.timeoutChecker); + } + if (hosts.size() == 1) { + // 单个主机直接执行 + new ExecCommandHandler(hosts.get(0), timeoutChecker).run(); + } else { + this.handlers = hosts.stream() + .map(s -> new ExecCommandHandler(s, timeoutChecker)) + .collect(Collectors.toList()); + // 多个主机异步阻塞执行 + Threads.blockRun(handlers, AssetThreadPools.EXEC_HOST); + } + } + + + private Integer updateStatus(ExecStatusEnum status) { + Long id = command.getLogId(); + String statusName = status.name(); + log.info("ExecTaskHandler-updateStatus id: {}, status: {}", id, statusName); + ExecLogDO update = new ExecLogDO(); + update.setId(id); + update.setStatus(statusName); + if (ExecStatusEnum.RUNNING.equals(status)) { + // 执行中 + update.setStartTime(new Date()); + } else if (ExecStatusEnum.COMPLETED.equals(status)) { + // 执行完成 + update.setFinishTime(new Date()); + } else if (ExecStatusEnum.FAILED.equals(status)) { + // 执行失败 + update.setFinishTime(new Date()); + } + return execLogDAO.updateById(update); + } + + @Override + public void close() { + Streams.close(timeoutChecker); + this.handlers.forEach(Streams::close); + } + +} diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/handler/IExecCommandHandler.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/handler/IExecCommandHandler.java new file mode 100644 index 00000000..55510f3f --- /dev/null +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/handler/IExecCommandHandler.java @@ -0,0 +1,26 @@ +package com.orion.ops.module.asset.handler.host.exec.handler; + +import com.orion.lang.able.SafeCloseable; + +/** + * 命令执行器定义 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2024/3/12 11:31 + */ +public interface IExecCommandHandler extends Runnable, SafeCloseable { + + /** + * 写入 + * + * @param msg msg + */ + void write(String msg); + + /** + * 中断执行 + */ + void interrupted(); + +} diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/handler/IExecTaskHandler.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/handler/IExecTaskHandler.java new file mode 100644 index 00000000..5cb36685 --- /dev/null +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/handler/IExecTaskHandler.java @@ -0,0 +1,14 @@ +package com.orion.ops.module.asset.handler.host.exec.handler; + +import com.orion.lang.able.SafeCloseable; + +/** + * 执行任务处理器 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2024/3/12 11:43 + */ +public interface IExecTaskHandler extends Runnable, SafeCloseable { + +} diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/session/TerminalSession.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/session/TerminalSession.java index 22e8f84f..eeada7e2 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/session/TerminalSession.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/session/TerminalSession.java @@ -31,7 +31,7 @@ public abstract class TerminalSession implements ITerminalSession { @Getter protected final TerminalConfig config; - protected volatile boolean close; + protected volatile boolean closed; protected volatile boolean forceOffline; @@ -50,7 +50,7 @@ public abstract class TerminalSession implements ITerminalSession { * 发送关闭消息 */ protected void sendCloseMessage() { - log.info("TerminalSession close {}, forClose: {}, forceOffline: {}", sessionId, this.close, this.forceOffline); + log.info("TerminalSession close {}, forClose: {}, forceOffline: {}", sessionId, this.closed, this.forceOffline); // 发送关闭信息 TerminalCloseResponse resp = TerminalCloseResponse.builder() .type(OutputTypeEnum.CLOSE.getType()) @@ -86,10 +86,10 @@ public abstract class TerminalSession implements ITerminalSession { * @return close */ private boolean checkAndClose() { - if (close) { + if (closed) { return false; } - this.close = true; + this.closed = true; // 释放资源 try { this.releaseResource(); diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/ExecLogServiceImpl.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/ExecLogServiceImpl.java index 4e124a09..93abb77a 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/ExecLogServiceImpl.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/ExecLogServiceImpl.java @@ -80,9 +80,10 @@ public class ExecLogServiceImpl implements ExecLogService { return execLogDAO.wrapper() .eq(ExecLogDO::getId, request.getId()) .eq(ExecLogDO::getUserId, request.getUserId()) + .eq(ExecLogDO::getUsername, request.getUsername()) .eq(ExecLogDO::getSource, request.getSource()) .eq(ExecLogDO::getSourceId, request.getSourceId()) - .eq(ExecLogDO::getDesc, request.getDesc()) + .eq(ExecLogDO::getDescription, request.getDescription()) .eq(ExecLogDO::getCommand, request.getCommand()) .eq(ExecLogDO::getStatus, request.getStatus()) .ge(ExecLogDO::getStartTime, Arrays1.getIfPresent(request.getStartTimeRange(), 0)) diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/ExecServiceImpl.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/ExecServiceImpl.java index 96ab07e1..bf38821c 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/ExecServiceImpl.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/ExecServiceImpl.java @@ -9,8 +9,10 @@ import com.orion.lang.utils.json.matcher.NoMatchStrategy; import com.orion.lang.utils.json.matcher.ReplacementFormatter; import com.orion.lang.utils.json.matcher.ReplacementFormatters; import com.orion.lang.utils.time.Dates; +import com.orion.ops.framework.biz.operator.log.core.utils.OperatorLogs; import com.orion.ops.framework.common.constant.Const; import com.orion.ops.framework.common.constant.ErrorMessage; +import com.orion.ops.framework.common.file.FileClient; import com.orion.ops.framework.common.security.LoginUser; import com.orion.ops.framework.common.utils.Valid; import com.orion.ops.framework.security.core.utils.SecurityUtils; @@ -26,6 +28,9 @@ import com.orion.ops.module.asset.enums.ExecHostStatusEnum; import com.orion.ops.module.asset.enums.ExecSourceEnum; import com.orion.ops.module.asset.enums.ExecStatusEnum; import com.orion.ops.module.asset.enums.HostConfigTypeEnum; +import com.orion.ops.module.asset.handler.host.exec.ExecTaskExecutors; +import com.orion.ops.module.asset.handler.host.exec.dto.ExecCommandDTO; +import com.orion.ops.module.asset.handler.host.exec.dto.ExecCommandHostDTO; import com.orion.ops.module.asset.service.AssetAuthorizedDataService; import com.orion.ops.module.asset.service.ExecService; import lombok.extern.slf4j.Slf4j; @@ -53,6 +58,9 @@ public class ExecServiceImpl implements ExecService { private static final ReplacementFormatter FORMATTER = ReplacementFormatters.create("@{{ ", " }}") .noMatchStrategy(NoMatchStrategy.EMPTY); + @Resource + private FileClient logsFileClient; + @Resource private ExecLogDAO execLogDAO; @@ -81,10 +89,12 @@ public class ExecServiceImpl implements ExecService { // 插入日志 ExecLogDO execLog = ExecLogDO.builder() .userId(userId) + .username(user.getUsername()) .source(ExecSourceEnum.BATCH.name()) - .desc(Strings.ifBlank(request.getDesc(), Strings.retain(command, 60) + Const.OMIT)) + .description(Strings.ifBlank(request.getDescription(), Strings.retain(command, 60) + Const.OMIT)) .command(command) - .status(ExecStatusEnum.COMPLETED.name()) + .timeout(request.getTimeout()) + .status(ExecStatusEnum.WAITING.name()) .build(); execLogDAO.insert(execLog); Long execId = execLog.getId(); @@ -105,9 +115,23 @@ public class ExecServiceImpl implements ExecService { .build(); }).collect(Collectors.toList()); execHostLogDAO.insertBatch(execHostLogs); - // TODO 开始执行 - - + // 开始执行 + ExecCommandDTO exec = ExecCommandDTO.builder() + .logId(execId) + .timeout(request.getTimeout()) + .hosts(execHostLogs.stream() + .map(s -> ExecCommandHostDTO.builder() + .hostId(s.getHostId()) + .hostLogId(s.getId()) + .command(s.getCommand()) + .timeout(request.getTimeout()) + .logPath(s.getLogPath()) + .build()) + .collect(Collectors.toList())) + .build(); + ExecTaskExecutors.start(exec); + // 操作日志 + OperatorLogs.add(OperatorLogs.ID, execId); // 返回 Map hostIdRel = execHostLogs.stream() .collect(Collectors.toMap(s -> String.valueOf(s.getHostId()), ExecHostLogDO::getId)); @@ -125,7 +149,8 @@ public class ExecServiceImpl implements ExecService { * @return logPath */ private String buildLogPath(Long logId, Long hostId) { - return "/exec/" + logId + "/" + hostId + ".log"; + String logFile = "/exec/" + logId + "/" + hostId + ".log"; + return logsFileClient.getReturnPath(logFile); } /** diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/resources/mapper/ExecHostLogMapper.xml b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/resources/mapper/ExecHostLogMapper.xml index 4deca150..f3a5888d 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/resources/mapper/ExecHostLogMapper.xml +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/resources/mapper/ExecHostLogMapper.xml @@ -13,6 +13,7 @@ + @@ -24,7 +25,7 @@ - id, log_id, host_id, host_name, status, command, parameter, exit_status, log_path, start_time, finish_time, create_time, update_time, creator, updater, deleted + id, log_id, host_id, host_name, status, command, parameter, exit_status, log_path, error_message, start_time, finish_time, create_time, update_time, creator, updater, deleted diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/resources/mapper/ExecLogMapper.xml b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/resources/mapper/ExecLogMapper.xml index a2332936..4d9cd14a 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/resources/mapper/ExecLogMapper.xml +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/resources/mapper/ExecLogMapper.xml @@ -6,10 +6,12 @@ + - + + @@ -22,7 +24,7 @@ - id, user_id, source, source_id, desc, command, status, start_time, finish_time, create_time, update_time, creator, updater, deleted + id, user_id, username, source, source_id, description, command, timeout, status, start_time, finish_time, create_time, update_time, creator, updater, deleted diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/DictKeyController.http b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/DictKeyController.http index e72891b0..78e7d00a 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/DictKeyController.http +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/DictKeyController.http @@ -7,7 +7,7 @@ Authorization: {{token}} "key": "", "valueType": "", "extraSchema": "", - "desc": "" + "description": "" } @@ -21,7 +21,7 @@ Authorization: {{token}} "key": "", "valueType": "", "extraSchema": "", - "desc": "" + "description": "" } diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/DictValueController.http b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/DictValueController.http index 5c0bd191..12adbc92 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/DictValueController.http +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/DictValueController.http @@ -8,7 +8,7 @@ Authorization: {{token}} "key": "", "label": "", "value": "", - "desc": "", + "description": "", "extra": "", "sort": "" } @@ -25,7 +25,7 @@ Authorization: {{token}} "key": "", "label": "", "value": "", - "desc": "", + "description": "", "extra": "", "sort": "" } @@ -47,7 +47,7 @@ Authorization: {{token}} "keyId": "", "label": "", "value": "", - "desc": "" + "description": "" }