Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions arklet-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>sofa-ark-api</artifactId>
<version>${sofa.ark.version}</version>
</dependency>
<dependency>
<groupId>com.alipay.sofa.koupleless</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.alipay.sofa.ark.common.util.BizIdentityUtils;
import com.alipay.sofa.common.utils.StringUtil;
import com.alipay.sofa.koupleless.arklet.core.api.model.ResponseCode;
import com.alipay.sofa.koupleless.arklet.core.command.builtin.handler.BatchInstallBizHandler;
import com.alipay.sofa.koupleless.arklet.core.command.builtin.handler.HelpHandler;
import com.alipay.sofa.koupleless.arklet.core.command.builtin.handler.InstallBizHandler;
import com.alipay.sofa.koupleless.arklet.core.command.builtin.handler.QueryAllBizHandler;
Expand Down Expand Up @@ -98,6 +99,7 @@ private void registerBuiltInCommands() {
registerCommandHandler(new SwitchBizHandler());
registerCommandHandler(new HealthHandler());
registerCommandHandler(new QueryBizOpsHandler());
registerCommandHandler(new BatchInstallBizHandler());
}

/** {@inheritDoc} */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public enum BuiltinCommand implements Command {

INSTALL_BIZ("installBiz", "install one ark biz"),

BATCH_INSTALL_BIZ("batchInstallBiz",
"install one ark biz"),
Comment on lines +34 to +35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect description for BATCH_INSTALL_BIZ

The description "install one ark biz" is incorrect for a batch operation. It should reflect that this command handles multiple installations.

-                                               BATCH_INSTALL_BIZ("batchInstallBiz",
-                                                                 "install one ark biz"),
+                                               BATCH_INSTALL_BIZ("batchInstallBiz",
+                                                                 "install multiple ark biz modules in batch"),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
BATCH_INSTALL_BIZ("batchInstallBiz",
"install one ark biz"),
BATCH_INSTALL_BIZ("batchInstallBiz",
"install multiple ark biz modules in batch"),


UNINSTALL_BIZ("uninstallBiz",
"uninstall one ark biz"),

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* 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 com.alipay.sofa.koupleless.arklet.core.command.builtin.handler;

import com.alipay.sofa.ark.api.ResponseCode;
import com.alipay.sofa.koupleless.arklet.core.command.builtin.handler.BatchInstallBizHandler.BatchInstallInput;
import com.alipay.sofa.koupleless.arklet.core.command.builtin.handler.BatchInstallBizHandler.BatchInstallResponse;
import com.alipay.sofa.koupleless.arklet.core.command.builtin.handler.InstallBizHandler.Input;
import com.alipay.sofa.koupleless.arklet.core.command.meta.AbstractCommandHandler;
import com.alipay.sofa.koupleless.arklet.core.command.meta.Command;
import com.alipay.sofa.koupleless.arklet.core.command.meta.Output;
import com.alipay.sofa.koupleless.arklet.core.command.meta.bizops.ArkBizMeta;
import com.alipay.sofa.koupleless.arklet.core.command.meta.bizops.ArkBizOps;
import com.alipay.sofa.koupleless.arklet.core.common.exception.ArkletRuntimeException;
import com.alipay.sofa.koupleless.arklet.core.common.exception.CommandValidationException;
import com.alipay.sofa.koupleless.arklet.core.common.model.BatchInstallRequest;
import com.alipay.sofa.koupleless.arklet.core.common.model.InstallRequest;
import com.alipay.sofa.koupleless.arklet.core.util.ResourceUtils;
import lombok.Getter;
import lombok.Setter;

import java.lang.management.MemoryPoolMXBean;
import java.util.ArrayList;

import static com.alipay.sofa.koupleless.arklet.core.command.builtin.BuiltinCommand.BATCH_INSTALL_BIZ;

/**
* @author lianglipeng.llp@alibaba-inc.com
* @version $Id: BatchInstallBizHandler.java, v 0.1 2024年12月18日 15:04 立蓬 Exp $
*/
public class BatchInstallBizHandler extends
AbstractCommandHandler<BatchInstallInput, BatchInstallResponse>
implements ArkBizOps {

@Override
public void validate(BatchInstallInput batchInstallInput) throws CommandValidationException {
notNull(batchInstallInput.getBizList(), "bizList is null");

for (Input bizInput : batchInstallInput.bizList) {
bizInput.setAsync(false);
InstallBizHandler.validateInput(bizInput);
}
}

@Override
public Output<BatchInstallResponse> handle(BatchInstallInput batchInstallInput) {
MemoryPoolMXBean metaSpaceMXBean = ResourceUtils.getMetaSpaceMXBean();
long startSpace = metaSpaceMXBean.getUsage().getUsed();
try {
BatchInstallResponse response = convertClientResponse(
getOperationService().batchInstall(convertBatchInstallRequest(batchInstallInput)));
response.setElapsedSpace(metaSpaceMXBean.getUsage().getUsed() - startSpace);
if (ResponseCode.SUCCESS.equals(response.getCode())) {
return Output.ofSuccess(response);
} else {
return Output.ofFailed(response, "install biz not success!");
}
} catch (Throwable e) {
throw new ArkletRuntimeException(e);
}
}

@Override
public Command command() {
return BATCH_INSTALL_BIZ;
}

private BatchInstallRequest convertBatchInstallRequest(BatchInstallInput input) {
ArrayList<InstallRequest> installRequestList = new ArrayList<>();

for (Input bizInput : input.getBizList()) {
installRequestList.add(InstallRequest.builder().bizName(bizInput.getBizName())
.bizVersion(bizInput.getBizVersion()).bizUrl(bizInput.getBizUrl())
.args(bizInput.getArgs()).envs(bizInput.getEnvs())
.installStrategy(bizInput.getInstallStrategy()).build());
}
return BatchInstallRequest.builder()
.installRequests(installRequestList.toArray(new InstallRequest[0])).build();
}

private BatchInstallResponse convertClientResponse(com.alipay.sofa.koupleless.arklet.core.common.model.BatchInstallResponse res) {
BatchInstallResponse response = new BatchInstallResponse();
response.setBizUrlToResponse(res.getBizUrlToResponse());
response.setCode(res.getCode());
response.setMessage(res.getMessage());
return response;
}

@Getter
@Setter
public static class BatchInstallInput extends ArkBizMeta {
private Input[] bizList;
}

@Getter
@Setter
public static class BatchInstallResponse extends
com.alipay.sofa.koupleless.arklet.core.common.model.BatchInstallResponse {
private long elapsedSpace;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.alipay.sofa.koupleless.arklet.core.command.meta.bizops.ArkBizOps;
import com.alipay.sofa.koupleless.arklet.core.common.exception.ArkletRuntimeException;
import com.alipay.sofa.koupleless.arklet.core.common.exception.CommandValidationException;
import com.alipay.sofa.koupleless.arklet.core.util.ResourceUtils;
import com.alipay.sofa.koupleless.common.log.ArkletLogger;
import com.alipay.sofa.koupleless.common.log.ArkletLoggerFactory;
import com.alipay.sofa.koupleless.arklet.core.common.model.InstallRequest;
Expand All @@ -39,10 +40,8 @@

import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.net.URL;
import java.util.List;
import java.util.Map;

import static com.alipay.sofa.koupleless.arklet.core.common.model.Constants.STRATEGY_INSTALL_ONLY_STRATEGY;
Expand All @@ -62,7 +61,7 @@ public class InstallBizHandler extends
/** {@inheritDoc} */
@Override
public Output<InstallBizClientResponse> handle(Input input) {
MemoryPoolMXBean metaSpaceMXBean = getMetaSpaceMXBean();
MemoryPoolMXBean metaSpaceMXBean = ResourceUtils.getMetaSpaceMXBean();
long startSpace = metaSpaceMXBean.getUsage().getUsed();
try {
InstallBizClientResponse installBizClientResponse = convertClientResponse(
Expand All @@ -85,15 +84,6 @@ private InstallRequest convertInstallRequest(Input input) {
.envs(input.getEnvs()).installStrategy(input.getInstallStrategy()).build();
}

private MemoryPoolMXBean getMetaSpaceMXBean() {
MemoryPoolMXBean metaSpaceMXBean = null;
List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();
for (MemoryPoolMXBean memoryPoolMXBean : memoryPoolMXBeans)
if (memoryPoolMXBean.getName().equals("Metaspace"))
metaSpaceMXBean = memoryPoolMXBean;
return metaSpaceMXBean;
}

private InstallBizClientResponse convertClientResponse(ClientResponse res) {
InstallBizClientResponse installBizClientResponse = new InstallBizClientResponse();
installBizClientResponse.setBizInfos(res.getBizInfos());
Expand All @@ -111,6 +101,10 @@ public Command command() {
/** {@inheritDoc} */
@Override
public void validate(Input input) throws CommandValidationException {
InstallBizHandler.validateInput(input);
}

public static void validateInput(Input input) throws CommandValidationException {
isTrue(!input.isAsync() || !StringUtils.isEmpty(input.getRequestId()),
"requestId should not be blank when async is true");
notBlank(input.getBizUrl(), "bizUrl should not be blank");
Expand Down Expand Up @@ -139,7 +133,7 @@ public void validate(Input input) throws CommandValidationException {
}
}

private void refreshBizInfoFromJar(Input input) throws IOException {
private static void refreshBizInfoFromJar(Input input) throws IOException {
// 如果入参里没有jar,例如模块卸载,这里就直接返回
if (StringUtils.isEmpty(input.getBizUrl())) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,15 @@ public class BatchInstallRequest {
/**
* 本地文件系统目录。
*/
private String bizDirAbsolutePath;
private String bizDirAbsolutePath;
/**
* 静态合并部署,默认没有老版本模块,可以直接使用普通安装策略。
*/
@Builder.Default
private String installStrategy = STRATEGY_INSTALL_ONLY_STRATEGY;
private String installStrategy = STRATEGY_INSTALL_ONLY_STRATEGY;

/**
* 模块批量发布请求。
*/
private InstallRequest[] installRequests = new InstallRequest[0];
Comment on lines +47 to +50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add validation constraints for installRequests

The field lacks validation annotations. Consider adding:

  • @NotNull to prevent null assignments
  • @Size(min = 1) to ensure at least one request is provided
     /**
      * 模块批量发布请求。
      */
+    @NotNull
+    @Size(min = 1, message = "At least one install request must be provided")
     private InstallRequest[] installRequests = new InstallRequest[0];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* 模块批量发布请求
*/
private InstallRequest[] installRequests = new InstallRequest[0];
/**
* 模块批量发布请求
*/
@NotNull
@Size(min = 1, message = "At least one install request must be provided")
private InstallRequest[] installRequests = new InstallRequest[0];

}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class BatchInstallHelper {
* @return a {@link java.util.List} object
*/
@SneakyThrows
public Map<Integer, List<String>> getBizUrlsFromLocalFileSystem(String absoluteBizDirPath) {
public static Map<Integer, List<String>> getBizUrlsFromLocalFileSystem(String absoluteBizDirPath) {
Map<Integer, List<String>> bizUrlsWithPriority = new HashMap<>();
Files.walkFileTree(new File(absoluteBizDirPath).toPath(), new SimpleFileVisitor<Path>() {
@Override
Expand Down Expand Up @@ -89,7 +89,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
* @return 主属性。
*/
@SneakyThrows
public Map<String, Object> getMainAttributes(String bizUrl) {
public static Map<String, Object> getMainAttributes(String bizUrl) {
try (JarFile jarFile = new JarFile(bizUrl)) {
Manifest manifest = jarFile.getManifest();
Preconditions.checkState(manifest != null, "Manifest file not found in the JAR.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@
import com.alipay.sofa.ark.api.ResponseCode;
import com.alipay.sofa.ark.spi.constant.Constants;
import com.alipay.sofa.ark.spi.model.Biz;
import com.alipay.sofa.common.utils.StringUtil;
import com.alipay.sofa.koupleless.arklet.core.command.executor.ExecutorServiceManager;
import com.alipay.sofa.koupleless.arklet.core.ops.strategy.BatchInstallBizInDirAbsolutePathStrategy;
import com.alipay.sofa.koupleless.arklet.core.ops.strategy.BatchInstallBizInRequestStrategy;
import com.alipay.sofa.koupleless.arklet.core.ops.strategy.BatchInstallStrategy;
import com.alipay.sofa.koupleless.common.log.ArkletLoggerFactory;
import com.alipay.sofa.koupleless.arklet.core.common.model.BatchInstallRequest;
import com.alipay.sofa.koupleless.arklet.core.common.model.BatchInstallResponse;
Expand All @@ -35,6 +39,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;

Expand All @@ -47,8 +52,9 @@
*/
@Singleton
public class UnifiedOperationServiceImpl implements UnifiedOperationService {
private final BatchInstallStrategy batchInstallBizInDirAbsolutePathStrategy = new BatchInstallBizInDirAbsolutePathStrategy();

private BatchInstallHelper batchInstallHelper = new BatchInstallHelper();
private final BatchInstallStrategy batchInstallBizInRequestStrategy = new BatchInstallBizInRequestStrategy();

/** {@inheritDoc} */
@Override
Expand All @@ -72,20 +78,12 @@ public ClientResponse install(InstallRequest request) throws Throwable {
/**
* <p>safeBatchInstall.</p>
*
* @param bizAbsolutePath a {@link java.lang.String} object
* @param bizRequest a {@link InstallRequest} object
* @return a {@link com.alipay.sofa.ark.api.ClientResponse} object
*/
public ClientResponse safeBatchInstall(String bizAbsolutePath, String installStrategy) {
public ClientResponse safeBatchInstall(InstallRequest bizRequest) {
try {
String bizUrl = OSUtils.getLocalFileProtocolPrefix() + bizAbsolutePath;
Map<String, Object> mainAttributes = batchInstallHelper
.getMainAttributes(bizAbsolutePath);
String bizName = (String) mainAttributes.get(Constants.ARK_BIZ_NAME);
String bizVersion = (String) mainAttributes.get(Constants.ARK_BIZ_VERSION);

InstallRequest installRequest = InstallRequest.builder().bizUrl(bizUrl).bizName(bizName)
.bizVersion(bizVersion).installStrategy(installStrategy).build();
return install(installRequest);
return install(bizRequest);
} catch (Throwable throwable) {
throwable.printStackTrace();
return new ClientResponse().setCode(ResponseCode.FAILED)
Expand All @@ -103,27 +101,29 @@ public ClientResponse uninstall(String bizName, String bizVersion) throws Throwa
@Override
public BatchInstallResponse batchInstall(BatchInstallRequest request) throws Throwable {
long startTimestamp = System.currentTimeMillis();
Map<Integer, List<String>> bizUrls = batchInstallHelper
.getBizUrlsFromLocalFileSystem(request.getBizDirAbsolutePath());
ThreadPoolExecutor executorService = ExecutorServiceManager.getArkBizOpsExecutor();

BatchInstallStrategy batchInstallStrategy = getBatchInstallStrategy(request);
Map<Integer, List<InstallRequest>> installRequestsWithOrder = batchInstallStrategy
.convertToInstallInput(request);

ThreadPoolExecutor executorService = ExecutorServiceManager.getArkBizOpsExecutor();
Map<String, ClientResponse> bizUrlToInstallResult = new HashMap<>();
boolean hasFailed = false;
for (Map.Entry<Integer, List<String>> entry : bizUrls.entrySet()) {
List<String> bizUrlsInSameOrder = entry.getValue();
for (Entry<Integer, List<InstallRequest>> entry : installRequestsWithOrder.entrySet()) {
List<InstallRequest> bizRequestInSameOrder = entry.getValue();
List<CompletableFuture<ClientResponse>> futures = new ArrayList<>();
for (String bizUrl : bizUrlsInSameOrder) {
futures.add(CompletableFuture.supplyAsync(
() -> safeBatchInstall(bizUrl, request.getInstallStrategy()), executorService));
for (InstallRequest bizRequest : bizRequestInSameOrder) {
futures.add(CompletableFuture.supplyAsync(() -> safeBatchInstall(bizRequest),
executorService));
Comment on lines +109 to +117
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add concurrent execution limits for batch installations

The current implementation might overwhelm the system if there are too many concurrent installations. Consider adding a limit to the number of concurrent installations.

 ThreadPoolExecutor executorService = ExecutorServiceManager.getArkBizOpsExecutor();
+int maxConcurrentInstalls = Math.min(bizRequestInSameOrder.size(), 5); // Adjust the limit as needed
+List<List<InstallRequest>> batches = Lists.partition(bizRequestInSameOrder, maxConcurrentInstalls);
 Map<String, ClientResponse> bizUrlToInstallResult = new HashMap<>();
 boolean hasFailed = false;
-for (InstallRequest bizRequest : bizRequestInSameOrder) {
-    futures.add(CompletableFuture.supplyAsync(() -> safeBatchInstall(bizRequest),
-        executorService));
+for (List<InstallRequest> batch : batches) {
+    for (InstallRequest bizRequest : batch) {
+        futures.add(CompletableFuture.supplyAsync(() -> safeBatchInstall(bizRequest),
+            executorService));
+    }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +115 to +117
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Safeguard exceptions in async tasks
Asynchronous exceptions might be swallowed. Verify sufficient logging or reporting is done in safeBatchInstall(...).

}

// wait for all install futures done
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get();
int counter = 0;
for (CompletableFuture<ClientResponse> future : futures) {
ClientResponse clientResponse = future.get();
String bizUrl = bizUrlsInSameOrder.get(counter);
bizUrlToInstallResult.put(bizUrl, clientResponse);
InstallRequest bizRequest = bizRequestInSameOrder.get(counter);
bizUrlToInstallResult.put(bizRequest.getBizUrl(), clientResponse);
hasFailed = hasFailed || clientResponse.getCode() != ResponseCode.SUCCESS;
counter++;
}
Expand All @@ -139,6 +139,13 @@ public BatchInstallResponse batchInstall(BatchInstallRequest request) throws Thr
.bizUrlToResponse(bizUrlToInstallResult).build();
}

private BatchInstallStrategy getBatchInstallStrategy(BatchInstallRequest request) {
if (StringUtil.isNotEmpty(request.getBizDirAbsolutePath())) {
return batchInstallBizInDirAbsolutePathStrategy;
}
return batchInstallBizInRequestStrategy;
}

/** {@inheritDoc} */
@Override
public List<Biz> queryBizList() {
Expand Down
Loading
Loading