bizIdentityLockMap = new ConcurrentHashMap<>(16);
- public static void putBizExecution(String bizName, String bizVersion, Command command) {
+ public synchronized static boolean checkAndLock(String bizName, String bizVersion,
+ Command command) {
String identity = BizIdentityUtils.generateBizIdentity(bizName, bizVersion);
- if (bizIdentityLockMap.containsKey(identity)) {
- throw new CommandMutexException(
- "biz {} execution meet mutex lock, conflict command:%s is processing and not finish yet",
- identity, bizIdentityLockMap.get(identity));
+ if (existBizProcessing(identity)) {
+ return false;
}
bizIdentityLockMap.put(identity, command);
+ return true;
}
- public static void popBizExecution(String bizName, String bizVersion) {
+ public static void unlock(String bizName, String bizVersion) {
String identity = BizIdentityUtils.generateBizIdentity(bizName, bizVersion);
bizIdentityLockMap.remove(identity);
}
@@ -50,4 +50,17 @@ public static boolean existBizProcessing(String bizName, String bizVersion) {
return bizIdentityLockMap.containsKey(identity);
}
+ public static boolean existBizProcessing(String identity) {
+ return bizIdentityLockMap.containsKey(identity);
+ }
+
+ public static Command getCurrentProcessingCommand(String bizName, String bizVersion) {
+ String identity = BizIdentityUtils.generateBizIdentity(bizName, bizVersion);
+ return bizIdentityLockMap.get(identity);
+ }
+
+ public static void clear() {
+ bizIdentityLockMap.clear();
+ }
+
}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/coordinate/CommandMutexException.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/coordinate/CommandMutexException.java
deleted file mode 100644
index 0275e5881..000000000
--- a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/coordinate/CommandMutexException.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.serverless.arklet.core.command.coordinate;
-
-import com.alipay.sofa.serverless.arklet.core.common.exception.ArkletRuntimeException;
-
-/**
- * @author mingmen
- * @date 2023/6/14
- */
-public class CommandMutexException extends ArkletRuntimeException {
- public CommandMutexException() {
- }
-
- public CommandMutexException(String message) {
- super(message);
- }
-
- public CommandMutexException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public CommandMutexException(Throwable cause) {
- super(cause);
- }
-
- public CommandMutexException(String message, Throwable cause, boolean enableSuppression,
- boolean writableStackTrace) {
- super(message, cause, enableSuppression, writableStackTrace);
- }
-
- public CommandMutexException(String format, Object... args) {
- super(format, args);
- }
-
- public CommandMutexException(Throwable cause, String format, Object... args) {
- super(cause, format, args);
- }
-}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/executor/ExecutorServiceManager.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/executor/ExecutorServiceManager.java
new file mode 100644
index 000000000..cd88af0be
--- /dev/null
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/executor/ExecutorServiceManager.java
@@ -0,0 +1,41 @@
+/*
+ * 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.serverless.arklet.core.command.executor;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author: yuanyuan
+ * @date: 2023/8/31 4:12 下午
+ */
+public class ExecutorServiceManager {
+
+ private static ThreadPoolExecutor ARK_BIZ_OPS_EXECUTOR = new ThreadPoolExecutor(
+ 20,
+ 50,
+ 30,
+ TimeUnit.SECONDS,
+ new ArrayBlockingQueue<>(100),
+ new NamedThreadFactory("ark-biz-ops"),
+ new ThreadPoolExecutor.CallerRunsPolicy());
+
+ public static ThreadPoolExecutor getArkBizOpsExecutor() {
+ return ARK_BIZ_OPS_EXECUTOR;
+ }
+}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/executor/NamedThreadFactory.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/executor/NamedThreadFactory.java
new file mode 100644
index 000000000..fc662cb51
--- /dev/null
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/executor/NamedThreadFactory.java
@@ -0,0 +1,86 @@
+/*
+ * 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.serverless.arklet.core.command.executor;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author: yuanyuan
+ * @date: 2023/9/1 12:02 下午
+ */
+public class NamedThreadFactory implements ThreadFactory {
+
+ private static final AtomicInteger POOL_COUNT = new AtomicInteger();
+
+ /**
+ * The current thread pool counter
+ */
+ private final AtomicInteger threadCount = new AtomicInteger(1);
+
+ /**
+ * Thread group
+ */
+ private final ThreadGroup group;
+
+ /**
+ * Thread name prefix
+ */
+ private final String namePrefix;
+
+ /**
+ * Thread daemon option
+ */
+ private final boolean isDaemon;
+
+ /**
+ * The first default prefix of thread name
+ */
+ private final static String FIRST_PREFIX = "ARKLET-";
+
+ /**
+ * specify the second prefix of thread name, default the thread created is non-daemon
+ *
+ * @param secondPrefix second prefix of thread name
+ */
+ public NamedThreadFactory(String secondPrefix) {
+ this(secondPrefix, false);
+ }
+
+ /**
+ * Construct a named thread factory
+ *
+ * @param secondPrefix second prefix of thread name
+ * @param daemon thread daemon option
+ */
+ public NamedThreadFactory(String secondPrefix, boolean daemon) {
+ SecurityManager sm = System.getSecurityManager();
+ group = (sm != null) ? sm.getThreadGroup() : Thread.currentThread().getThreadGroup();
+ namePrefix = FIRST_PREFIX + secondPrefix + "-" + POOL_COUNT.getAndIncrement() + "-T";
+ isDaemon = daemon;
+ }
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(group, r, namePrefix + threadCount.getAndIncrement(), 0);
+ t.setDaemon(isDaemon);
+ if (t.getPriority() != Thread.NORM_PRIORITY) {
+ t.setPriority(Thread.NORM_PRIORITY);
+ }
+ return t;
+ }
+}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/AbstractCommandHandler.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/AbstractCommandHandler.java
index 8364e741d..4efe83a5b 100644
--- a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/AbstractCommandHandler.java
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/AbstractCommandHandler.java
@@ -19,6 +19,7 @@
import java.lang.reflect.ParameterizedType;
import com.alipay.sofa.common.utils.StringUtil;
+import com.alipay.sofa.serverless.arklet.core.health.HealthService;
import com.alipay.sofa.serverless.arklet.core.command.CommandService;
import com.alipay.sofa.serverless.arklet.core.ArkletComponentRegistry;
import com.alipay.sofa.serverless.arklet.core.common.exception.CommandValidationException;
@@ -36,6 +37,8 @@ public abstract class AbstractCommandHandler {
.getOperationServiceInstance();
private final CommandService commandService = ArkletComponentRegistry
.getCommandServiceInstance();
+ private final HealthService healthService = ArkletComponentRegistry
+ .getHealthServiceInstance();
public abstract void validate(P p) throws CommandValidationException;
@@ -51,6 +54,10 @@ public CommandService getCommandService() {
return commandService;
}
+ public HealthService getHealthService() {
+ return healthService;
+ }
+
public Class
getInputClass() {
ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
return (Class
) parameterizedType.getActualTypeArguments()[0];
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/Output.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/Output.java
index 2bd95a24d..c997ed464 100644
--- a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/Output.java
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/Output.java
@@ -45,6 +45,22 @@ public static Output ofFailed(String message) {
return output;
}
+ public boolean success() {
+ return ResponseCode.SUCCESS.equals(code);
+ }
+
+ public boolean failed() {
+ return ResponseCode.FAILED.equals(code);
+ }
+
+ public static Output ofFailed(T data, String message) {
+ Output output = new Output<>();
+ output.code = ResponseCode.FAILED;
+ output.data = data;
+ output.message = message;
+ return output;
+ }
+
public String getMessage() {
return message;
}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/bizops/ArkBizMeta.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/bizops/ArkBizMeta.java
new file mode 100644
index 000000000..9e3a21908
--- /dev/null
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/bizops/ArkBizMeta.java
@@ -0,0 +1,62 @@
+/*
+ * 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.serverless.arklet.core.command.meta.bizops;
+
+import com.alipay.sofa.serverless.arklet.core.command.meta.InputMeta;
+
+/**
+ * @author mingmen
+ * @date 2023/8/21
+ */
+public class ArkBizMeta extends InputMeta {
+ private String bizName;
+ private String bizVersion;
+ private String requestId;
+ private boolean async;
+
+ public String getBizName() {
+ return bizName;
+ }
+
+ public void setBizName(String bizName) {
+ this.bizName = bizName;
+ }
+
+ public String getBizVersion() {
+ return bizVersion;
+ }
+
+ public void setBizVersion(String bizVersion) {
+ this.bizVersion = bizVersion;
+ }
+
+ public String getRequestId() {
+ return requestId;
+ }
+
+ public void setRequestId(String requestId) {
+ this.requestId = requestId;
+ }
+
+ public boolean isAsync() {
+ return async;
+ }
+
+ public void setAsync(boolean async) {
+ this.async = async;
+ }
+}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/CommandType.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/bizops/ArkBizOps.java
similarity index 78%
rename from arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/CommandType.java
rename to arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/bizops/ArkBizOps.java
index a38724736..6549f15a0 100644
--- a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/CommandType.java
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/meta/bizops/ArkBizOps.java
@@ -14,12 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.alipay.sofa.serverless.arklet.core.command.meta;
+package com.alipay.sofa.serverless.arklet.core.command.meta.bizops;
/**
* @author mingmen
- * @date 2023/6/14
+ * @date 2023/8/21
+ * An interface that requires manipulation of ark biz changes
+ * command handler needs to implement this interface
*/
-public enum CommandType {
- READ, WRITE
+public interface ArkBizOps {
}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/record/ProcessRecord.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/record/ProcessRecord.java
new file mode 100644
index 000000000..d3e1f62ff
--- /dev/null
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/record/ProcessRecord.java
@@ -0,0 +1,134 @@
+/*
+ * 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.serverless.arklet.core.command.record;
+
+import com.alipay.sofa.serverless.arklet.core.command.meta.bizops.ArkBizMeta;
+import com.alipay.sofa.serverless.arklet.core.common.log.ArkletLogger;
+import com.alipay.sofa.serverless.arklet.core.common.log.ArkletLoggerFactory;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+import static com.alipay.sofa.serverless.arklet.core.command.record.ProcessRecord.Status.EXECUTING;
+import static com.alipay.sofa.serverless.arklet.core.command.record.ProcessRecord.Status.FAILED;
+import static com.alipay.sofa.serverless.arklet.core.command.record.ProcessRecord.Status.INITIALIZED;
+import static com.alipay.sofa.serverless.arklet.core.command.record.ProcessRecord.Status.SUCCEEDED;
+
+/**
+ * @author: yuanyuan
+ * @date: 2023/8/31 3:27 下午
+ */
+@Getter
+@Setter
+public class ProcessRecord {
+
+ private static final ArkletLogger LOGGER = ArkletLoggerFactory.getDefaultLogger();
+
+ private String requestId;
+
+ private ArkBizMeta arkBizMeta;
+
+ private String threadName;
+
+ private Status status;
+
+ private Throwable throwable;
+
+ private String errorCode;
+
+ private String message;
+
+ private Date startTime;
+
+ private long startTimestamp;
+
+ private Date endTime;
+
+ private long endTimestamp;
+
+ private long elapsedTime;
+
+ public enum Status {
+
+ INITIALIZED("INITIALIZED"),
+
+ EXECUTING("EXECUTING"),
+
+ SUCCEEDED("SUCCEEDED"),
+
+ FAILED("FAILED");
+
+ private String name;
+
+ Status(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+
+ public void markFinishTime() {
+ Date date = new Date();
+ setEndTime(date);
+ setEndTimestamp(date.getTime());
+ setElapsedTime(date.getTime() - startTimestamp);
+ }
+
+ public boolean finished() {
+ return SUCCEEDED.equals(getStatus()) || FAILED.equals(getStatus());
+ }
+
+ public void start() {
+ if (INITIALIZED.equals(getStatus())) {
+ setStatus(EXECUTING);
+ LOGGER.info("Command execution status change: INIT -> EXECUTING");
+ }
+ }
+
+ public void success() {
+ if (EXECUTING.equals(getStatus())) {
+ setStatus(SUCCEEDED);
+ LOGGER.info("Command execution status change: EXECUTING -> SUCCESS");
+ }
+ }
+
+ public void fail() {
+ if (EXECUTING.equals(getStatus())) {
+ setStatus(FAILED);
+ LOGGER.info("Command execution status change: EXECUTING -> FAIL");
+ }
+ }
+
+ public void fail(String message) {
+ fail();
+ setMessage(message);
+ }
+
+ public void fail(String message, Throwable throwable) {
+ fail();
+ setMessage(message);
+ this.throwable = throwable;
+ }
+
+}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/record/ProcessRecordHolder.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/record/ProcessRecordHolder.java
new file mode 100644
index 000000000..01ccd4b75
--- /dev/null
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/record/ProcessRecordHolder.java
@@ -0,0 +1,71 @@
+/*
+ * 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.serverless.arklet.core.command.record;
+
+import com.alipay.sofa.serverless.arklet.core.command.meta.InputMeta;
+import com.alipay.sofa.serverless.arklet.core.command.meta.bizops.ArkBizMeta;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import static com.alipay.sofa.serverless.arklet.core.command.record.ProcessRecord.Status.INITIALIZED;
+
+/**
+ * @author: yuanyuan
+ * @date: 2023/8/31 3:28 下午
+ */
+public class ProcessRecordHolder {
+
+ private static Map processRecords = new ConcurrentHashMap<>();
+
+ public static ProcessRecord getProcessRecord(String rid) {
+ if (StringUtils.isNotBlank(rid)) {
+ return processRecords.get(rid);
+ }
+ return null;
+ }
+
+ public static List getAllProcessRecords() {
+ return new ArrayList<>(processRecords.values());
+ }
+
+ public static List getAllExecutingProcessRecords() {
+ return processRecords.values().stream().filter(record -> !record.finished()).collect(Collectors.toList());
+ }
+
+ public static List getProcessRecordsByStatus(String status) {
+ return processRecords.values().stream().filter(record -> StringUtils.equals(record.getStatus().name(), status)).collect(Collectors.toList());
+ }
+
+ public static ProcessRecord createProcessRecord(String rid, ArkBizMeta arkBizMeta) {
+ ProcessRecord pr = new ProcessRecord();
+ pr.setRequestId(rid);
+ pr.setArkBizMeta(arkBizMeta);
+ pr.setStatus(INITIALIZED);
+ Date date = new Date();
+ pr.setStartTime(date);
+ pr.setStartTimestamp(date.getTime());
+ processRecords.put(rid, pr);
+ return pr;
+ }
+
+}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/common/log/ArkletLogger.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/common/log/ArkletLogger.java
index 2fa8cb093..74e853e41 100644
--- a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/common/log/ArkletLogger.java
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/common/log/ArkletLogger.java
@@ -20,8 +20,6 @@
import org.slf4j.Marker;
/**
- * Logger Implementation for SOFAArk
- /**
* @author mingmen
* @date 2023/6/14
*/
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/common/log/ArkletLoggerFactory.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/common/log/ArkletLoggerFactory.java
index d39e3ad5a..3effb5e5a 100644
--- a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/common/log/ArkletLoggerFactory.java
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/common/log/ArkletLoggerFactory.java
@@ -19,8 +19,6 @@
import com.alipay.sofa.common.log.LoggerSpaceManager;
/**
- * LoggerFactory for SOFAArk
- /**
* @author mingmen
* @date 2023/6/14
*/
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/HealthService.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/HealthService.java
new file mode 100644
index 000000000..5716525e7
--- /dev/null
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/HealthService.java
@@ -0,0 +1,49 @@
+/*
+ * 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.serverless.arklet.core.health;
+
+import com.alipay.sofa.serverless.arklet.core.ArkletComponent;
+import com.alipay.sofa.serverless.arklet.core.health.indicator.ArkletBaseIndicator;
+import com.alipay.sofa.serverless.arklet.core.health.model.Health;
+import com.alipay.sofa.serverless.arklet.core.command.builtin.model.BizInfo;
+import com.alipay.sofa.serverless.arklet.core.command.builtin.model.PluginModel;
+
+/**
+ * @author Lunarscave
+ */
+public interface HealthService extends ArkletComponent {
+
+ Health getHealth();
+
+ Health getHealth(String indicatorId);
+
+ Health getHealth(String[] indicatorIds);
+
+ Health queryModuleInfo();
+
+ Health queryModuleInfo(String type, String name, String version);
+
+ Health queryModuleInfo(BizInfo bizInfo);
+
+ Health queryModuleInfo(PluginModel pluginModel);
+
+ Health queryMasterBiz();
+
+ ArkletBaseIndicator getIndicator(String indicatorId);
+
+ void registerIndicator(ArkletBaseIndicator arkletBaseIndicator);
+}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/HealthServiceImpl.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/HealthServiceImpl.java
new file mode 100644
index 000000000..ceee9a51c
--- /dev/null
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/HealthServiceImpl.java
@@ -0,0 +1,204 @@
+/*
+ * 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.serverless.arklet.core.health;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.alipay.sofa.ark.api.ArkClient;
+import com.alipay.sofa.ark.common.util.AssertUtils;
+import com.alipay.sofa.ark.common.util.StringUtils;
+import com.alipay.sofa.ark.spi.model.Biz;
+import com.alipay.sofa.common.utils.ArrayUtil;
+import com.alipay.sofa.serverless.arklet.core.command.builtin.model.BizInfo;
+import com.alipay.sofa.serverless.arklet.core.health.indicator.ArkletBaseIndicator;
+import com.alipay.sofa.serverless.arklet.core.health.indicator.CpuIndicator;
+import com.alipay.sofa.serverless.arklet.core.health.indicator.JvmIndicator;
+import com.alipay.sofa.serverless.arklet.core.health.model.BizHealthMeta;
+import com.alipay.sofa.serverless.arklet.core.health.model.Constants;
+import com.alipay.sofa.serverless.arklet.core.health.model.Health;
+import com.alipay.sofa.serverless.arklet.core.health.model.Health.HealthBuilder;
+import com.alipay.sofa.serverless.arklet.core.health.model.PluginHealthMeta;
+import com.alipay.sofa.serverless.arklet.core.command.builtin.model.PluginModel;
+import com.alipay.sofa.serverless.arklet.core.common.log.ArkletLogger;
+import com.alipay.sofa.serverless.arklet.core.common.log.ArkletLoggerFactory;
+import com.google.inject.Singleton;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static com.alibaba.fastjson.JSON.toJSONString;
+
+/**
+ * @author Lunarscave
+ */
+@Singleton
+public class HealthServiceImpl implements HealthService {
+
+ private static final ArkletLogger LOGGER = ArkletLoggerFactory
+ .getDefaultLogger();
+ private final HealthBuilder healthBuilder = new HealthBuilder();
+
+ private final Map indicators = new ConcurrentHashMap<>(3);
+
+ @Override
+ public void init() {
+ initIndicators();
+ healthBuilder.init();
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ @Override
+ public Health getHealth() {
+ HealthBuilder builder = new HealthBuilder();
+ for (ArkletBaseIndicator indicator : this.indicators.values()) {
+ builder.putAllHealthData(indicator.getHealthModel(healthBuilder));
+ }
+ return builder.build();
+ }
+
+ @Override
+ public Health getHealth(String indicatorId) {
+ try {
+ healthBuilder.init();
+ AssertUtils.assertNotNull(indicators.get(indicatorId), "indicator not registered");
+ healthBuilder.putAllHealthData(indicators.get(indicatorId)
+ .getHealthModel(healthBuilder));
+ } catch (Throwable e) {
+ healthBuilder.putErrorData(Constants.HEALTH_ERROR, e.getMessage());
+ }
+ return healthBuilder.build();
+ }
+
+ @Override
+ public Health getHealth(String[] indicatorIds) {
+ HealthBuilder builder = new HealthBuilder();
+ if (ArrayUtil.isEmpty(indicatorIds)) {
+ builder.putAllHealthData(getHealth());
+ } else {
+ for (String indicatorId : indicatorIds) {
+ builder.putAllHealthData(getHealth(indicatorId));
+ }
+ }
+ return builder.build();
+ }
+
+ @Override
+ public Health queryModuleInfo() {
+ HealthBuilder builder = new HealthBuilder();
+ return builder.init().putAllHealthData(queryMasterBiz())
+ .putAllHealthData(queryModuleInfo(new BizInfo()))
+ .putAllHealthData(queryModuleInfo(new PluginModel())).build();
+ }
+
+ @Override
+ public Health queryModuleInfo(String type, String name, String version) {
+ HealthBuilder builder = new HealthBuilder();
+ try {
+ AssertUtils.isTrue(StringUtils.isEmpty(type) || Constants.typeOfInfo(type),
+ "illegal type: %s", type);
+ if (StringUtils.isEmpty(type) || Constants.BIZ.equals(type)) {
+ BizInfo bizInfo = new BizInfo();
+ bizInfo.setBizName(name);
+ bizInfo.setBizVersion(version);
+ builder.putAllHealthData(queryModuleInfo(bizInfo));
+ }
+ if (StringUtils.isEmpty(type) || Constants.PLUGIN.equals(type)) {
+ PluginModel pluginModel = new PluginModel();
+ pluginModel.setPluginName(name);
+ pluginModel.setPluginVersion(version);
+ builder.putAllHealthData(queryModuleInfo(pluginModel));
+ }
+ } catch (Throwable e) {
+ builder.putErrorData(Constants.HEALTH_ERROR, e.getMessage());
+ }
+ return builder.build();
+ }
+
+ @Override
+ public Health queryModuleInfo(BizInfo bizInfo) {
+ String bizName = bizInfo.getBizName(), bizVersion = bizInfo.getBizVersion();
+ healthBuilder.init();
+ try {
+ if (StringUtils.isEmpty(bizName) && StringUtils.isEmpty(bizVersion)) {
+ List bizHealthMetaList = BizHealthMeta.createBizMetaList(ArkClient
+ .getBizManagerService().getBizInOrder());
+ healthBuilder.putHealthData(Constants.BIZ_LIST_INFO, bizHealthMetaList);
+ } else if (StringUtils.isEmpty(bizVersion)) {
+ List bizList = ArkClient.getBizManagerService().getBiz(bizName);
+ AssertUtils.isTrue(bizList.size() > 0, "can not find biz: %s", bizName);
+ List bizHealthMetaList = BizHealthMeta.createBizMetaList(bizList);
+ healthBuilder.putHealthData(Constants.BIZ_LIST_INFO, bizHealthMetaList);
+ } else {
+ BizHealthMeta bizHealthMeta = BizHealthMeta.createBizMeta(ArkClient
+ .getBizManagerService().getBiz(bizName, bizVersion));
+ healthBuilder.putHealthData(Constants.BIZ_INFO, bizHealthMeta);
+ }
+ } catch (Throwable e) {
+ healthBuilder.putErrorData(Constants.HEALTH_ERROR, e.getMessage());
+ }
+ return healthBuilder.build();
+ }
+
+ @Override
+ public Health queryModuleInfo(PluginModel pluginModel) {
+ String pluginName = pluginModel.getPluginName();
+ healthBuilder.init();
+ try {
+ if (StringUtils.isEmpty(pluginName)) {
+ List pluginHealthMetaList = PluginHealthMeta
+ .createPluginMetaList(ArkClient.getPluginManagerService().getPluginsInOrder());
+ healthBuilder.putHealthData(Constants.PLUGIN_LIST_INFO, pluginHealthMetaList);
+ } else {
+ PluginHealthMeta pluginHealthMeta = PluginHealthMeta.createPluginMeta(ArkClient
+ .getPluginManagerService().getPluginByName(pluginName));
+ healthBuilder.putHealthData(Constants.PLUGIN_INFO, pluginHealthMeta);
+ }
+ } catch (Throwable e) {
+ healthBuilder.putErrorData(Constants.HEALTH_ERROR, e.getMessage());
+ }
+ return healthBuilder.build();
+ }
+
+ @Override
+ public Health queryMasterBiz() {
+ BizHealthMeta bizHealthMeta = BizHealthMeta.createBizMeta(ArkClient.getMasterBiz());
+ return healthBuilder
+ .init()
+ .putHealthData(Constants.MASTER_BIZ_INFO,
+ JSON.parseObject(toJSONString(bizHealthMeta), JSONObject.class)).build();
+ }
+
+ @Override
+ public ArkletBaseIndicator getIndicator(String indicatorId) {
+ return indicators.get(indicatorId);
+ }
+
+ @Override
+ public void registerIndicator(ArkletBaseIndicator indicator) {
+ this.indicators.put(indicator.getIndicatorId(), indicator);
+ LOGGER.info("register indicator " + indicator.getIndicatorId());
+ }
+
+ private void initIndicators() {
+ registerIndicator(new CpuIndicator());
+ registerIndicator(new JvmIndicator());
+ }
+}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/indicator/ArkletBaseIndicator.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/indicator/ArkletBaseIndicator.java
new file mode 100644
index 000000000..c0deaad59
--- /dev/null
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/indicator/ArkletBaseIndicator.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alipay.sofa.serverless.arklet.core.health.indicator;
+
+import com.alipay.sofa.serverless.arklet.core.health.model.Health;
+import com.alipay.sofa.serverless.arklet.core.health.model.Health.HealthBuilder;
+
+import java.util.Map;
+
+/**
+ * @author Lunarscave
+ */
+public abstract class ArkletBaseIndicator {
+
+ private final String indicatorId;
+
+ public ArkletBaseIndicator(String indicatorId) {
+ this.indicatorId = indicatorId;
+ }
+
+ protected abstract Map getHealthDetails();
+
+ public String getIndicatorId() {
+ return indicatorId;
+ }
+
+ public Health getHealthModel(HealthBuilder builder) {
+ return builder.init().putHealthData(getIndicatorId(), getHealthDetails()).build();
+ }
+}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/indicator/CpuIndicator.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/indicator/CpuIndicator.java
new file mode 100644
index 000000000..42949c489
--- /dev/null
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/indicator/CpuIndicator.java
@@ -0,0 +1,136 @@
+/*
+ * 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.serverless.arklet.core.health.indicator;
+
+import com.alipay.sofa.serverless.arklet.core.health.model.Constants;
+import oshi.SystemInfo;
+import oshi.hardware.CentralProcessor;
+import oshi.util.GlobalConfig;
+import oshi.util.Util;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Arrays;
+
+/**
+ * @author Lunarscave
+ */
+public class CpuIndicator extends ArkletBaseIndicator {
+
+ private final CpuIndicatorHandler cpuIndicatorHandler;
+
+ private static final String CPU_INDICATOR_ID = Constants.CPU;
+
+ public CpuIndicator() {
+ super(CPU_INDICATOR_ID);
+ cpuIndicatorHandler = new CpuIndicatorHandler();
+ }
+
+ @Override
+ protected Map getHealthDetails() {
+ Map cpuHealthDetails = new HashMap<>(6);
+
+ cpuIndicatorHandler.collectTicks();
+ cpuHealthDetails.put(CpuMetrics.CPU_COUNT.getId(), cpuIndicatorHandler.getCpuCount());
+ cpuHealthDetails.put(CpuMetrics.CPU_TYPE.getId(), cpuIndicatorHandler.getCpuType());
+ cpuHealthDetails.put(CpuMetrics.CPU_TOTAL_USED.getId(), cpuIndicatorHandler.getTotalUsed());
+ cpuHealthDetails.put(CpuMetrics.CPU_USER_USED.getId(), cpuIndicatorHandler.getUserUsed());
+ cpuHealthDetails.put(CpuMetrics.CPU_SYSTEM_USED.getId(),
+ cpuIndicatorHandler.getSystemUsed());
+ cpuHealthDetails.put(CpuMetrics.CPU_FREE.getId(), cpuIndicatorHandler.getFree());
+ return cpuHealthDetails;
+ }
+
+ static class CpuIndicatorHandler {
+
+ private final CentralProcessor cpu;
+
+ private long[] prevTicks;
+
+ private long[] nextTicks;
+
+ public CpuIndicatorHandler() {
+ this.cpu = new SystemInfo().getHardware().getProcessor();
+ prevTicks = cpu.getSystemCpuLoadTicks();
+ }
+
+ public void collectTicks() {
+ nextTicks = cpu.getSystemCpuLoadTicks();
+ }
+
+ public double getTotalUsed() {
+ Set tickTypeSet = new HashSet(
+ Arrays.asList(CentralProcessor.TickType.class.getEnumConstants()));
+ double totalUsed = 0;
+ for (CentralProcessor.TickType tickType : tickTypeSet) {
+ totalUsed += nextTicks[tickType.getIndex()] - prevTicks[tickType.getIndex()];
+ }
+ return totalUsed;
+ }
+
+ public double getUserUsed() {
+ return getUsed(CentralProcessor.TickType.USER);
+ }
+
+ public double getSystemUsed() {
+ return getUsed(CentralProcessor.TickType.SYSTEM);
+ }
+
+ public double getFree() {
+ return getUsed(CentralProcessor.TickType.IDLE);
+ }
+
+ public int getCpuCount() {
+ return cpu.getLogicalProcessorCount();
+ }
+
+ public String getCpuType() {
+ return cpu.getProcessorIdentifier().getName();
+ }
+
+ private double getUsed(CentralProcessor.TickType tickType) {
+ double totalUsed = getTotalUsed();
+ double used = nextTicks[tickType.getIndex()] - prevTicks[tickType.getIndex()];
+ if (totalUsed == 0 || used < 0) {
+ used = 0;
+ } else {
+ used = 100d * used / totalUsed;
+ }
+ return used;
+ }
+ }
+
+ enum CpuMetrics {
+
+ CPU_COUNT("count"), CPU_TYPE("type"), CPU_TOTAL_USED("total used (%)"), CPU_USER_USED(
+ "user used (%)"), CPU_SYSTEM_USED(
+ "system used (%)"), CPU_FREE(
+ "free (%)");
+
+ private final String id;
+
+ CpuMetrics(String desc) {
+ this.id = desc;
+ }
+
+ public String getId() {
+ return id;
+ };
+ }
+}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/indicator/JvmIndicator.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/indicator/JvmIndicator.java
new file mode 100644
index 000000000..3255219eb
--- /dev/null
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/indicator/JvmIndicator.java
@@ -0,0 +1,209 @@
+/*
+ * 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.serverless.arklet.core.health.indicator;
+
+import com.alipay.sofa.serverless.arklet.core.health.model.Constants;
+import com.alipay.sofa.serverless.arklet.core.util.ConvertUtils;
+
+import java.lang.management.ClassLoadingMXBean;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryUsage;
+import java.lang.management.RuntimeMXBean;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * @author Lunarscave
+ */
+public class JvmIndicator extends ArkletBaseIndicator {
+
+ private final JvmIndicatorHandler jvmIndicatorHandler;
+
+ private static final String JVM_INDICATOR_ID = Constants.JVM;
+
+ public JvmIndicator() {
+ super(JVM_INDICATOR_ID);
+ jvmIndicatorHandler = new JvmIndicatorHandler();
+ }
+
+ @Override
+ protected Map getHealthDetails() {
+ Map jvmHealthDetails = new HashMap<>(6);
+
+ jvmIndicatorHandler.updateHandler();
+ jvmHealthDetails.put(JvmMetrics.JAVA_VERSION.getId(), jvmIndicatorHandler.getJvmVersion());
+ jvmHealthDetails.put(JvmMetrics.JAVA_HOME.getId(), jvmIndicatorHandler.getJavaHome());
+ jvmHealthDetails.put(JvmMetrics.JAVA_TOTAL_MEMORY.getId(),
+ ConvertUtils.bytes2Megabyte(jvmIndicatorHandler.getTotalMemory()));
+ jvmHealthDetails.put(JvmMetrics.JAVA_MAX_MEMORY.getId(),
+ ConvertUtils.bytes2Megabyte(jvmIndicatorHandler.getMaxMemory()));
+ jvmHealthDetails.put(JvmMetrics.JAVA_FREE_MEMORY.getId(),
+ ConvertUtils.bytes2Megabyte(jvmIndicatorHandler.getFreeMemory()));
+ jvmHealthDetails.put(JvmMetrics.JAVA_INIT_HEAP.getId(),
+ ConvertUtils.bytes2Megabyte(jvmIndicatorHandler.getInitHeapMemory()));
+ jvmHealthDetails.put(JvmMetrics.JAVA_USED_HEAP.getId(),
+ ConvertUtils.bytes2Megabyte(jvmIndicatorHandler.getUsedHeapMemory()));
+ jvmHealthDetails.put(JvmMetrics.JAVA_COMMITTED_HEAP.getId(),
+ ConvertUtils.bytes2Megabyte(jvmIndicatorHandler.getCommittedHeapMemory()));
+ jvmHealthDetails.put(JvmMetrics.JAVA_MAX_HEAP.getId(),
+ ConvertUtils.bytes2Megabyte(jvmIndicatorHandler.getMaxHeapMemory()));
+ jvmHealthDetails.put(JvmMetrics.JAVA_INIT_NON_HEAP.getId(),
+ ConvertUtils.bytes2Megabyte(jvmIndicatorHandler.getInitNonHeapMemory()));
+ jvmHealthDetails.put(JvmMetrics.JAVA_USED_NON_HEAP.getId(),
+ ConvertUtils.bytes2Megabyte(jvmIndicatorHandler.getUsedNonHeapMemory()));
+ jvmHealthDetails.put(JvmMetrics.JAVA_COMMITTED_NON_HEAP.getId(),
+ ConvertUtils.bytes2Megabyte(jvmIndicatorHandler.getCommittedNonHeapMemory()));
+ jvmHealthDetails.put(JvmMetrics.JAVA_MAX_NON_HEAP.getId(),
+ ConvertUtils.bytes2Megabyte(jvmIndicatorHandler.getMaxNonHeapMemory()));
+ jvmHealthDetails.put(JvmMetrics.JAVA_LOADED_CLASS_COUNT.getId(),
+ jvmIndicatorHandler.getLoadedClassCount());
+ jvmHealthDetails.put(JvmMetrics.JAVA_UNLOAD_CLASS_COUNT.getId(),
+ jvmIndicatorHandler.getUnloadClassCount());
+ jvmHealthDetails.put(JvmMetrics.JAVA_TOTAL_CLASS_COUNT.getId(),
+ jvmIndicatorHandler.getTotalClassCount());
+ jvmHealthDetails.put(JvmMetrics.JAVA_RUN_TIMES.getId(), jvmIndicatorHandler.getDuration());
+ return jvmHealthDetails;
+ }
+
+ static class JvmIndicatorHandler {
+
+ private Properties properties;
+
+ private Runtime runtime;
+
+ private RuntimeMXBean runtimeMxBean;
+
+ private MemoryUsage heapMemoryUsed;
+
+ private MemoryUsage nonHeapMemoryUsed;
+
+ private ClassLoadingMXBean classLoadingMxBean;
+
+ public JvmIndicatorHandler() {
+ updateHandler();
+ }
+
+ public void updateHandler() {
+ this.properties = System.getProperties();
+ this.runtime = Runtime.getRuntime();
+ this.runtimeMxBean = ManagementFactory.getRuntimeMXBean();
+ this.heapMemoryUsed = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
+ this.nonHeapMemoryUsed = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage();
+ this.classLoadingMxBean = ManagementFactory.getClassLoadingMXBean();
+ }
+
+ public String getJvmVersion() {
+ return this.properties.getProperty("java.version");
+ }
+
+ public String getJavaHome() {
+ return this.properties.getProperty("java.home");
+ }
+
+ public long getTotalMemory() {
+ return this.runtime.totalMemory();
+ }
+
+ public long getMaxMemory() {
+ return this.runtime.maxMemory();
+ }
+
+ public long getFreeMemory() {
+ return this.runtime.freeMemory();
+ }
+
+ public double getDuration() {
+ return ConvertUtils.millisecond2Second(new Date(runtimeMxBean.getStartTime()));
+ }
+
+ public long getInitHeapMemory() {
+ return this.heapMemoryUsed.getInit();
+ }
+
+ public long getUsedHeapMemory() {
+ return this.heapMemoryUsed.getUsed();
+ }
+
+ public long getCommittedHeapMemory() {
+ return this.heapMemoryUsed.getCommitted();
+ }
+
+ public long getMaxHeapMemory() {
+ return this.heapMemoryUsed.getMax();
+ }
+
+ public long getInitNonHeapMemory() {
+ return this.nonHeapMemoryUsed.getInit();
+ }
+
+ public long getUsedNonHeapMemory() {
+ return this.nonHeapMemoryUsed.getUsed();
+ }
+
+ public long getCommittedNonHeapMemory() {
+ return this.nonHeapMemoryUsed.getCommitted();
+ }
+
+ public long getMaxNonHeapMemory() {
+ return this.nonHeapMemoryUsed.getMax();
+ }
+
+ public long getLoadedClassCount() {
+ return this.classLoadingMxBean.getLoadedClassCount();
+ }
+
+ public long getUnloadClassCount() {
+ return this.classLoadingMxBean.getUnloadedClassCount();
+ }
+
+ public long getTotalClassCount() {
+ return this.classLoadingMxBean.getTotalLoadedClassCount();
+ }
+
+ }
+
+ public enum JvmMetrics {
+
+ JAVA_VERSION("java version"), JAVA_HOME("java home"), JAVA_TOTAL_MEMORY("total memory(M)"), JAVA_MAX_MEMORY(
+ "max memory(M)"), JAVA_FREE_MEMORY(
+ "free memory(M)"), JAVA_RUN_TIMES(
+ "run time(s)"), JAVA_INIT_HEAP(
+ "init heap memory(M)"), JAVA_USED_HEAP(
+ "used heap memory(M)"), JAVA_COMMITTED_HEAP(
+ "committed heap memory(M)"), JAVA_MAX_HEAP(
+ "max heap memory(M)"), JAVA_INIT_NON_HEAP(
+ "init non heap memory(M)"), JAVA_USED_NON_HEAP(
+ "used non heap memory(M)"), JAVA_COMMITTED_NON_HEAP(
+ "committed non heap memory(M)"), JAVA_MAX_NON_HEAP(
+ "max non heap memory(M)"), JAVA_LOADED_CLASS_COUNT(
+ "loaded class count"), JAVA_UNLOAD_CLASS_COUNT(
+ "unload class count"), JAVA_TOTAL_CLASS_COUNT(
+ "total class count");
+
+ private final String id;
+
+ JvmMetrics(String desc) {
+ this.id = desc;
+ }
+
+ public String getId() {
+ return id;
+ };
+ }
+}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/model/BizHealthMeta.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/model/BizHealthMeta.java
new file mode 100644
index 000000000..3e5214e27
--- /dev/null
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/model/BizHealthMeta.java
@@ -0,0 +1,95 @@
+/*
+ * 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.serverless.arklet.core.health.model;
+
+import com.alipay.sofa.ark.spi.model.Biz;
+import com.alipay.sofa.ark.spi.model.BizState;
+import com.alipay.sofa.serverless.arklet.core.util.AssertUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Lunarscave
+ */
+public class BizHealthMeta {
+
+ private String bizName;
+
+ private String bizVersion;
+
+ private BizState bizState;
+
+ private String webContextPath;
+
+ public String getBizName() {
+ return bizName;
+ }
+
+ public void setBizName(String bizName) {
+ this.bizName = bizName;
+ }
+
+ public String getBizVersion() {
+ return bizVersion;
+ }
+
+ public void setBizVersion(String bizVersion) {
+ this.bizVersion = bizVersion;
+ }
+
+ public BizState getBizState() {
+ return bizState;
+ }
+
+ public void setBizState(BizState bizState) {
+ this.bizState = bizState;
+ }
+
+ public String getWebContextPath() {
+ return webContextPath;
+ }
+
+ public void setWebContextPath(String webContextPath) {
+ this.webContextPath = webContextPath;
+ }
+
+ public static BizHealthMeta createBizMeta(Biz biz) {
+ AssertUtils.assertNotNull(biz, "can not find biz");
+ BizHealthMeta bizHealthMeta = createBizMeta(biz.getBizName(), biz.getBizVersion());
+ bizHealthMeta.bizState = biz.getBizState();
+ bizHealthMeta.webContextPath = biz.getWebContextPath();
+ return bizHealthMeta;
+ }
+
+ public static BizHealthMeta createBizMeta(String bizName, String bizVersion) {
+ BizHealthMeta bizHealthMeta = new BizHealthMeta();
+ bizHealthMeta.bizName = bizName;
+ bizHealthMeta.bizVersion = bizVersion;
+ return bizHealthMeta;
+ }
+
+ public static List createBizMetaList(List bizList) {
+ AssertUtils.isTrue(bizList.size() > 0, "no biz found");
+ List bizHealthMetaList = new ArrayList<>();
+ for (Biz biz : bizList) {
+ bizHealthMetaList.add(createBizMeta(biz));
+ }
+ return bizHealthMetaList;
+ }
+
+}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/model/Constants.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/model/Constants.java
new file mode 100644
index 000000000..4566601f1
--- /dev/null
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/model/Constants.java
@@ -0,0 +1,59 @@
+/*
+ * 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.serverless.arklet.core.health.model;
+
+/**
+ * @author Lunarscave
+ */
+public class Constants {
+
+ public static final String SYSTEM = "system";
+
+ public static final String BIZ = "biz";
+
+ public static final String PLUGIN = "plugin";
+
+ public static final String CPU = "cpu";
+
+ public static final String JVM = "jvm";
+
+ public static final String MASTER_BIZ_HEALTH = "masterBizHealth";
+
+ public static final String MASTER_BIZ_INFO = "masterBizInfo";
+
+ public static final String HEALTH_ERROR = "error";
+
+ public static final String HEALTH_ENDPOINT_ERROR = "endpointError";
+
+ public static final String BIZ_INFO = "bizInfo";
+
+ public static final String BIZ_LIST_INFO = "bizListInfo";
+
+ public static final String PLUGIN_INFO = "pluginInfo";
+
+ public static final String PLUGIN_LIST_INFO = "pluginListInfo";
+
+ public static final String READINESS_HEALTHY = "ACCEPTING_TRAFFIC";
+
+ public static boolean typeOfQuery(String type) {
+ return SYSTEM.equals(type) || BIZ.equals(type) || PLUGIN.equals(type);
+ }
+
+ public static boolean typeOfInfo(String type) {
+ return Constants.BIZ.equals(type) || Constants.PLUGIN.equals(type);
+ }
+}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/model/Health.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/model/Health.java
new file mode 100644
index 000000000..f7e086e7a
--- /dev/null
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/model/Health.java
@@ -0,0 +1,110 @@
+/*
+ * 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.serverless.arklet.core.health.model;
+
+import com.alipay.sofa.serverless.arklet.core.util.AssertUtils;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Lunarscave
+ */
+public class Health {
+
+ private final Map healthData;
+
+ public Health(Map healthData) {
+ AssertUtils.assertNotNull(healthData, "health data must not null");
+ this.healthData = healthData;
+ }
+
+ public Health(HealthBuilder builder) {
+ this.healthData = Collections.unmodifiableMap(builder.healthData);
+ }
+
+ public Map getHealthData() {
+ return healthData;
+ }
+
+ public void setHealthData(Map healthData) {
+ AssertUtils.assertNotNull(healthData, "health data must not null");
+ this.healthData.clear();
+ this.healthData.putAll(healthData);
+ }
+
+ public boolean containsError(String errorCode) {
+ return this.healthData.containsKey(errorCode);
+ }
+
+ public boolean containsUnhealthy(String healthyCode) {
+ Map healthData = this.getHealthData();
+ boolean isUnhealthy = false;
+ for (String key : healthData.keySet()) {
+ Object value = healthData.get(key);
+ if (value instanceof Map) {
+ if (((Map, ?>) value).containsKey("masterBizHealth")) {
+ Object health = ((Map, ?>) value).get("masterBizHealth");
+ if (health instanceof Map) {
+ String code = (String) ((Map, ?>) health).get("readinessState");
+ if (!code.equals(healthyCode)) {
+ isUnhealthy = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return isUnhealthy;
+ }
+
+ public static class HealthBuilder {
+ private final Map healthData = new HashMap<>();
+
+ public Health build() {
+ return new Health(this);
+ }
+
+ public HealthBuilder init() {
+ this.healthData.clear();
+ return this;
+ }
+
+ public HealthBuilder putAllHealthData(Health healthData) {
+ this.healthData.putAll(healthData.getHealthData());
+ return this;
+ }
+
+ public HealthBuilder putHealthData(String key, Object value) {
+ this.healthData.put(key, value);
+ return this;
+ }
+
+ public HealthBuilder putErrorData(String errorCode, String message) {
+ this.healthData.clear();
+ this.healthData.put(errorCode, message);
+ return this;
+ }
+
+ public HealthBuilder putAllHealthData(Map healthData) {
+ AssertUtils.assertNotNull(healthData, "health data must not null");
+ this.healthData.putAll(healthData);
+ return this;
+ }
+ }
+}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/model/PluginHealthMeta.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/model/PluginHealthMeta.java
new file mode 100644
index 000000000..a9beca96b
--- /dev/null
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/health/model/PluginHealthMeta.java
@@ -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.serverless.arklet.core.health.model;
+
+import com.alipay.sofa.ark.spi.model.Plugin;
+import com.alipay.sofa.serverless.arklet.core.util.AssertUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Lunarscave
+ */
+public class PluginHealthMeta {
+
+ private String pluginName;
+
+ private String groupId;
+
+ private String artifactId;
+
+ private String pluginVersion;
+
+ private String pluginUrl;
+
+ private String pluginActivator;
+
+ public String getPluginName() {
+ return pluginName;
+ }
+
+ public String getGroupId() {
+ return groupId;
+ }
+
+ public String getArtifactId() {
+ return artifactId;
+ }
+
+ public String getPluginVersion() {
+ return pluginVersion;
+ }
+
+ public String getPluginActivator() {
+ return pluginActivator;
+ }
+
+ public String getPluginUrl() {
+ return pluginUrl;
+ }
+
+ public void setPluginName(String pluginName) {
+ this.pluginName = pluginName;
+ }
+
+ public void setGroupId(String groupId) {
+ this.groupId = groupId;
+ }
+
+ public void setArtifactId(String artifactId) {
+ this.artifactId = artifactId;
+ }
+
+ public void setPluginVersion(String pluginVersion) {
+ this.pluginVersion = pluginVersion;
+ }
+
+ public void setPluginUrl(String pluginUrl) {
+ this.pluginUrl = pluginUrl;
+ }
+
+ public void setPluginActivator(String pluginActivator) {
+ this.pluginActivator = pluginActivator;
+ }
+
+ public static PluginHealthMeta createPluginMeta(Plugin plugin) {
+ AssertUtils.assertNotNull(plugin, "can not find plugin");
+ PluginHealthMeta pluginHealthMeta = PluginHealthMeta.createPluginMeta(
+ plugin.getPluginName(), plugin.getVersion());
+ pluginHealthMeta.setGroupId(plugin.getGroupId());
+ pluginHealthMeta.setArtifactId(plugin.getArtifactId());
+ pluginHealthMeta.setPluginActivator(plugin.getPluginActivator());
+ pluginHealthMeta.setPluginUrl(plugin.getPluginURL().getPath());
+ return pluginHealthMeta;
+ }
+
+ public static PluginHealthMeta createPluginMeta(String pluginName, String pluginVersion) {
+ PluginHealthMeta pluginHealthMeta = new PluginHealthMeta();
+ pluginHealthMeta.setPluginName(pluginName);
+ pluginHealthMeta.setPluginVersion(pluginVersion);
+ return pluginHealthMeta;
+ }
+
+ public static List createPluginMetaList(List pluginList) {
+ List pluginHealthMetaList = new ArrayList<>();
+ for (Plugin plugin : pluginList) {
+ pluginHealthMetaList.add(createPluginMeta(plugin));
+ }
+ return pluginHealthMetaList;
+ }
+}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/ops/UnifiedOperationService.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/ops/UnifiedOperationService.java
index 15692aae4..167755e1f 100644
--- a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/ops/UnifiedOperationService.java
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/ops/UnifiedOperationService.java
@@ -19,22 +19,46 @@
import java.util.List;
import com.alipay.sofa.ark.api.ClientResponse;
-import com.alipay.sofa.ark.api.ResponseCode;
import com.alipay.sofa.ark.spi.model.Biz;
import com.alipay.sofa.serverless.arklet.core.ArkletComponent;
/**
+ * Unified operation service interface, mainly interacts with the sofa-ark container
* @author mingmen
* @date 2023/6/14
*/
public interface UnifiedOperationService extends ArkletComponent {
- ClientResponse install(String bizPath) throws Throwable;
+ /**
+ * install biz
+ * @param bizUrl biz URL
+ * @return response
+ * @throws Throwable error
+ */
+ ClientResponse install(String bizUrl) throws Throwable;
+ /**
+ * uninstall biz
+ * @param bizName bizName
+ * @param bizVersion bizVersion
+ * @return response
+ * @throws Throwable error
+ */
ClientResponse uninstall(String bizName, String bizVersion) throws Throwable;
+ /**
+ * query biz list
+ * @return biz list
+ */
List queryBizList();
+ /**
+ * switch biz
+ * @param bizName bizName
+ * @param bizVersion bizVersion
+ * @return response
+ * @throws Throwable error
+ */
ClientResponse switchBiz(String bizName, String bizVersion) throws Throwable;
}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/ops/UnifiedOperationServiceImpl.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/ops/UnifiedOperationServiceImpl.java
index 5ccf3100c..bce808af4 100644
--- a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/ops/UnifiedOperationServiceImpl.java
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/ops/UnifiedOperationServiceImpl.java
@@ -20,7 +20,6 @@
import com.alipay.sofa.ark.api.ArkClient;
import com.alipay.sofa.ark.api.ClientResponse;
-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.ark.spi.model.BizOperation;
@@ -43,10 +42,11 @@ public void destroy() {
}
- public ClientResponse install(String bizPath) throws Throwable {
+ @Override
+ public ClientResponse install(String bizUrl) throws Throwable {
BizOperation bizOperation = new BizOperation()
.setOperationType(BizOperation.OperationType.INSTALL);
- bizOperation.putParameter(Constants.CONFIG_BIZ_URL, "file://" + bizPath);
+ bizOperation.putParameter(Constants.CONFIG_BIZ_URL, bizUrl);
return ArkClient.installOperation(bizOperation);
}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/coordinate/CoordinatorConfig.java b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/util/ConvertUtils.java
similarity index 58%
rename from arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/coordinate/CoordinatorConfig.java
rename to arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/util/ConvertUtils.java
index adbbe786c..7129449c9 100644
--- a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/coordinate/CoordinatorConfig.java
+++ b/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/util/ConvertUtils.java
@@ -14,26 +14,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.alipay.sofa.serverless.arklet.core.command.coordinate;
+package com.alipay.sofa.serverless.arklet.core.util;
-import java.util.ArrayList;
-import java.util.List;
-
-import com.alipay.sofa.serverless.arklet.core.command.meta.Command;
+import java.text.SimpleDateFormat;
+import java.util.Date;
/**
- * @author mingmen
- * @date 2023/6/14
+ * @author Lunarscave
*/
-public class CoordinatorConfig {
- private List> mutexCmds = new ArrayList<>();
+public class ConvertUtils {
+
+ public static double bytes2Megabyte(Long bytes) {
+ return ((double) bytes) / 1024 / 1024;
+ }
- public List> getMutexCmds() {
- return mutexCmds;
+ public static String endDate2Duration(Date date) {
+ long duration = System.currentTimeMillis() - date.getTime();
+ return new SimpleDateFormat("HH-mm-ss").format(duration);
}
- public CoordinatorConfig addMutex(List commands) {
- mutexCmds.add(commands);
- return this;
+ public static double millisecond2Second(Date date) {
+ return ((double) System.currentTimeMillis() - date.getTime()) / 1000;
}
+
}
diff --git a/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/BaseTest.java b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/BaseTest.java
new file mode 100644
index 000000000..b15cd13eb
--- /dev/null
+++ b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/BaseTest.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alipay.sofa.serverless.arklet.core;
+
+import com.alipay.sofa.serverless.arklet.core.command.CommandService;
+import com.alipay.sofa.serverless.arklet.core.health.HealthService;
+import com.alipay.sofa.serverless.arklet.core.health.model.Health;
+import com.alipay.sofa.serverless.arklet.core.ops.UnifiedOperationService;
+import org.junit.Before;
+
+/**
+ * @author mingmen
+ * @date 2023/9/5
+ */
+public class BaseTest {
+ public static ArkletComponentRegistry componentRegistry;
+ public static CommandService commandService;
+ public static UnifiedOperationService operationService;
+ public static HealthService healthService;
+
+ @Before
+ public void setup() {
+ if (componentRegistry == null) {
+ ArkletComponentRegistry registry = new ArkletComponentRegistry();
+ registry.initComponents();
+ componentRegistry = registry;
+ commandService = ArkletComponentRegistry.getCommandServiceInstance();
+ operationService = ArkletComponentRegistry.getOperationServiceInstance();
+ healthService = ArkletComponentRegistry.getHealthServiceInstance();
+ }
+ }
+}
diff --git a/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/BizOpsCommandCoordinatorTests.java b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/BizOpsCommandCoordinatorTests.java
new file mode 100644
index 000000000..83ad38c9f
--- /dev/null
+++ b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/BizOpsCommandCoordinatorTests.java
@@ -0,0 +1,123 @@
+/*
+ * 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.serverless.arklet.core.command;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import com.alipay.sofa.serverless.arklet.core.command.builtin.BuiltinCommand;
+import com.alipay.sofa.serverless.arklet.core.command.coordinate.BizOpsCommandCoordinator;
+import com.alipay.sofa.serverless.arklet.core.command.meta.Command;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+public class BizOpsCommandCoordinatorTests {
+
+ private final BuiltinCommand command = BuiltinCommand.INSTALL_BIZ;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ BizOpsCommandCoordinator.clear();
+ }
+
+ /**
+ * 测试putBizExecution方法,验证当bizIdentityLockMap中已存在相同的identity时,会检查失败。
+ */
+ @Test
+ public void testPutBizExecutionWithExistingIdentity() {
+ String bizName = "biz";
+ String bizVersion = "1.0";
+ BizOpsCommandCoordinator.checkAndLock(bizName, bizVersion, command);
+ Assert.assertFalse(BizOpsCommandCoordinator.checkAndLock(bizName, bizVersion, command));
+ }
+
+ /**
+ * 测试putBizExecution方法,验证当bizIdentityLockMap中不存在相同的identity时,会成功将command放入bizIdentityLockMap中。
+ */
+ @Test
+ public void testPutBizExecutionWithNonExistingIdentity() {
+ String bizName = "biz";
+ String bizVersionV1 = "1.0";
+ String bizVersionV2 = "2.0";
+ BizOpsCommandCoordinator.checkAndLock(bizName, bizVersionV1, command);
+ BizOpsCommandCoordinator.checkAndLock(bizName, bizVersionV2, command);
+ Assert.assertTrue(BizOpsCommandCoordinator.existBizProcessing(bizName, bizVersionV1));
+ Assert.assertTrue(BizOpsCommandCoordinator.existBizProcessing(bizName, bizVersionV2));
+ }
+
+ /**
+ * 测试popBizExecution方法,验证当bizIdentityLockMap中存在相同的identity时,会成功将该identity从bizIdentityLockMap中移除。
+ */
+ @Test
+ public void testPopBizExecutionWithExistingIdentity() {
+ String bizName = "biz";
+ String bizVersion = "1.0";
+ BizOpsCommandCoordinator.checkAndLock(bizName, bizVersion, command);
+ BizOpsCommandCoordinator.unlock(bizName, bizVersion);
+ Assert.assertFalse(BizOpsCommandCoordinator.existBizProcessing(bizName, bizVersion));
+ }
+
+ /**
+ * 测试popBizExecution方法,验证当bizIdentityLockMap中不存在相同的identity时,不会有任何影响。
+ */
+ @Test
+ public void testPopBizExecutionWithNonExistingIdentity() {
+ String bizName = "biz";
+ String bizVersion = "1.0";
+ BizOpsCommandCoordinator.unlock(bizName, bizVersion);
+ }
+
+ /**
+ * 测试getCurrentProcessingCommand方法,验证当bizIdentityLockMap中存在相同的identity时,返回对应的command。
+ */
+ @Test
+ public void testGetCurrentProcessingCommandWithExistingIdentity() {
+ String bizName = "biz";
+ String bizVersion = "1.0";
+ BizOpsCommandCoordinator.checkAndLock(bizName, bizVersion, command);
+ Command result = BizOpsCommandCoordinator.getCurrentProcessingCommand(bizName, bizVersion);
+ Assert.assertEquals(command, result);
+ }
+
+ /**
+ * 验证在高并发场景下,`checkAndLock`方法是否能够正常执行,不会出现锁异常
+ */
+ @Test
+ public void testCheckAndLock_Concurrency() throws InterruptedException {
+ // 创建并发线程池
+ ExecutorService executor = Executors.newFixedThreadPool(10);
+
+ // 模拟高并发场景,同时调用putBizExecution方法
+ for (int i = 0; i < 1000; i++) {
+ executor.execute(() -> {
+ try {
+ BizOpsCommandCoordinator.checkAndLock("bizName", "bizVersion", command);
+ } finally {
+ BizOpsCommandCoordinator.unlock("bizName", "bizVersion");
+ }
+ });
+ }
+ executor.shutdown();
+ executor.awaitTermination(10, TimeUnit.SECONDS);
+
+ Assert.assertFalse("存在锁异常", BizOpsCommandCoordinator.existBizProcessing("bizName", "bizVersion"));
+ }
+}
diff --git a/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/CommandTests.java b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/CommandTests.java
new file mode 100644
index 000000000..452294aea
--- /dev/null
+++ b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/CommandTests.java
@@ -0,0 +1,78 @@
+/*
+ * 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.serverless.arklet.core.command;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.alibaba.fastjson.JSONObject;
+
+import com.alipay.sofa.serverless.arklet.core.BaseTest;
+import com.alipay.sofa.serverless.arklet.core.command.builtin.BuiltinCommand;
+import com.alipay.sofa.serverless.arklet.core.command.builtin.handler.InstallBizHandler;
+import com.alipay.sofa.serverless.arklet.core.command.builtin.handler.QueryBizOpsHandler;
+import com.alipay.sofa.serverless.arklet.core.command.custom.CustomCommand;
+import com.alipay.sofa.serverless.arklet.core.command.custom.CustomCommandHandler;
+import com.alipay.sofa.serverless.arklet.core.command.meta.Output;
+import com.alipay.sofa.serverless.arklet.core.command.record.ProcessRecord;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * @author mingmen
+ * @date 2023/6/26
+ */
+public class CommandTests extends BaseTest {
+
+ @Test
+ public void registerCustomCommand() {
+ commandService.registerCommandHandler(new CustomCommandHandler());
+ CustomCommandHandler handler = (CustomCommandHandler) commandService
+ .getHandler(CustomCommand.HELLO);
+ Assert.assertNotNull(handler);
+ }
+
+ @Test
+ public void commandProcess() throws Exception {
+ Output output = commandService.process(BuiltinCommand.HELP.getId(), new HashMap());
+ Assert.assertNotNull(output);
+ }
+
+ @Test
+ public void testInstallHandler() throws InterruptedException {
+ String rid = "testRequestId";
+
+ InstallBizHandler.Input input = new InstallBizHandler.Input();
+ input.setBizName("testBizName");
+ input.setBizVersion("testBizVersion");
+ input.setAsync(true);
+ input.setRequestId(rid);
+ input.setBizUrl("testBizUrl");
+ Map map = JSONObject.parseObject(JSONObject.toJSONString(input), Map.class);
+ Output> output = commandService.process(BuiltinCommand.INSTALL_BIZ.getId(), map);
+ Assert.assertNotNull(output);
+ ProcessRecord processRecord = (ProcessRecord) output.getData();
+ Assert.assertNotNull(processRecord);
+
+ QueryBizOpsHandler.Input input1 = new QueryBizOpsHandler.Input();
+ input1.setRequestId(rid);
+ Map map1 = JSONObject.parseObject(JSONObject.toJSONString(input1), Map.class);
+ Output> output1 = commandService.process(BuiltinCommand.QUERY_BIZ_OPS.getId(), map1);
+ Assert.assertNotNull(output1);
+ }
+
+}
diff --git a/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/HelpHandlerTests.java b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/HelpHandlerTests.java
new file mode 100644
index 000000000..aed4e55f0
--- /dev/null
+++ b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/HelpHandlerTests.java
@@ -0,0 +1,76 @@
+/*
+ * 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.serverless.arklet.core.command;
+
+import java.util.List;
+
+import com.alipay.sofa.serverless.arklet.core.BaseTest;
+import com.alipay.sofa.serverless.arklet.core.command.builtin.BuiltinCommand;
+import com.alipay.sofa.serverless.arklet.core.command.builtin.handler.HelpHandler;
+import com.alipay.sofa.serverless.arklet.core.command.builtin.model.CommandModel;
+import com.alipay.sofa.serverless.arklet.core.command.meta.Command;
+import com.alipay.sofa.serverless.arklet.core.command.meta.InputMeta;
+import com.alipay.sofa.serverless.arklet.core.command.meta.Output;
+import com.alipay.sofa.serverless.arklet.core.common.exception.CommandValidationException;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * @author mingmen
+ * @date 2023/9/6
+ */
+public class HelpHandlerTests extends BaseTest {
+
+ private HelpHandler helpHandler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ helpHandler = (HelpHandler) commandService.getHandler(BuiltinCommand.HELP);
+ }
+
+ @Test
+ public void testHandle() {
+ Output> result = helpHandler.handle(new InputMeta());
+ Assert.assertTrue(result.getData() != null && !result.getData().isEmpty());
+ }
+
+ @Test
+ public void testCommand() {
+ // Act
+ Command result = helpHandler.command();
+
+ // Assert
+ assert result == BuiltinCommand.HELP;
+ }
+
+ @Test
+ public void testValidate() {
+ // Arrange
+ InputMeta input = new InputMeta();
+
+ // Act
+ try {
+ helpHandler.validate(input);
+ } catch (CommandValidationException e) {
+ assert false;
+ }
+ }
+
+}
diff --git a/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/InstallBizHandlerTests.java b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/InstallBizHandlerTests.java
new file mode 100644
index 000000000..2078acfc6
--- /dev/null
+++ b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/InstallBizHandlerTests.java
@@ -0,0 +1,140 @@
+/*
+ * 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.serverless.arklet.core.command;
+
+import com.alipay.sofa.ark.api.ClientResponse;
+import com.alipay.sofa.ark.api.ResponseCode;
+import com.alipay.sofa.ark.exception.ArkRuntimeException;
+import com.alipay.sofa.serverless.arklet.core.BaseTest;
+import com.alipay.sofa.serverless.arklet.core.command.builtin.BuiltinCommand;
+import com.alipay.sofa.serverless.arklet.core.command.builtin.handler.InstallBizHandler;
+import com.alipay.sofa.serverless.arklet.core.command.builtin.handler.InstallBizHandler.Input;
+import com.alipay.sofa.serverless.arklet.core.command.meta.Output;
+import com.alipay.sofa.serverless.arklet.core.common.exception.ArkletRuntimeException;
+import com.alipay.sofa.serverless.arklet.core.common.exception.CommandValidationException;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * @author mingmen
+ * @date 2023/9/5
+ */
+public class InstallBizHandlerTests extends BaseTest {
+
+ //@Test
+ //public void testHandle_Success() throws Throwable {
+ //
+ // InstallBizHandler handler = (InstallBizHandler)commandService.getHandler(BuiltinCommand.INSTALL_BIZ);
+ //
+ // // 准备测试数据
+ // Input input = new Input();
+ // input.setBizUrl("testUrl");
+ //
+ // ClientResponse response = new ClientResponse();
+ // response.setCode(ResponseCode.SUCCESS);
+ //
+ // // 设置Mock行为
+ // when(operationService.install(anyString())).thenReturn(response);
+ //
+ // // 执行测试
+ // Output result = handler.handle(input);
+ //
+ // // 验证结果
+ // assertSame(response, result.getData());
+ // assertTrue(result.success());
+ //}
+
+ // @Test(expected = ArkRuntimeException.class)
+ // public void testHandle_Failure() throws Throwable {
+ //
+ // InstallBizHandler handler = (InstallBizHandler) commandService
+ // .getHandler(BuiltinCommand.INSTALL_BIZ);
+ //
+ // // 准备测试数据
+ // Input input = new Input();
+ // input.setBizUrl("testUrl");
+ //
+ // ClientResponse response = new ClientResponse();
+ // response.setCode(ResponseCode.FAILED);
+ //
+ // // 设置Mock行为
+ // when(operationService.install(input.getBizUrl())).thenReturn(response);
+ //
+ // // 执行测试
+ // Output result = handler.handle(input);
+ //
+ // // 验证结果
+ // Assert.assertSame(response, result.getData());
+ // Assert.assertTrue(result.failed());
+ // }
+
+ @Test(expected = CommandValidationException.class)
+ public void testValidate_BlankBizName() throws CommandValidationException {
+
+ InstallBizHandler handler = (InstallBizHandler) commandService
+ .getHandler(BuiltinCommand.INSTALL_BIZ);
+ // 准备测试数据
+ Input input = new Input();
+ input.setBizName("");
+
+ // 执行测试
+ handler.validate(input);
+ }
+
+ @Test(expected = CommandValidationException.class)
+ public void testValidate_BlankBizVersion() throws CommandValidationException {
+
+ InstallBizHandler handler = (InstallBizHandler) commandService
+ .getHandler(BuiltinCommand.INSTALL_BIZ);
+ // 准备测试数据
+ Input input = new Input();
+ input.setBizVersion("");
+
+ // 执行测试
+ handler.validate(input);
+ }
+
+ @Test(expected = CommandValidationException.class)
+ public void testValidate_BlankRequestId() throws CommandValidationException {
+
+ InstallBizHandler handler = (InstallBizHandler) commandService
+ .getHandler(BuiltinCommand.INSTALL_BIZ);
+
+ // 准备测试数据
+ Input input = new Input();
+ input.setAsync(true);
+ input.setRequestId("");
+
+ // 执行测试
+ handler.validate(input);
+ }
+
+ @Test(expected = CommandValidationException.class)
+ public void testValidate_BlankBizUrl() throws CommandValidationException {
+ InstallBizHandler handler = (InstallBizHandler) commandService
+ .getHandler(BuiltinCommand.INSTALL_BIZ);
+
+ // 准备测试数据
+ Input input = new Input();
+ input.setBizUrl("");
+
+ // 执行测试
+ handler.validate(input);
+ }
+}
diff --git a/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/CustomCommand.java b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/custom/CustomCommand.java
similarity index 95%
rename from arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/CustomCommand.java
rename to arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/custom/CustomCommand.java
index 0b492d74b..6bf58e25d 100644
--- a/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/CustomCommand.java
+++ b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/custom/CustomCommand.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.alipay.sofa.serverless.arklet.core.command;
+package com.alipay.sofa.serverless.arklet.core.command.custom;
import com.alipay.sofa.serverless.arklet.core.command.meta.Command;
diff --git a/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/CustomCommandHandler.java b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/custom/CustomCommandHandler.java
similarity index 96%
rename from arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/CustomCommandHandler.java
rename to arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/custom/CustomCommandHandler.java
index 844c005b7..532e96236 100644
--- a/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/CustomCommandHandler.java
+++ b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/custom/CustomCommandHandler.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.alipay.sofa.serverless.arklet.core.command;
+package com.alipay.sofa.serverless.arklet.core.command.custom;
import com.alipay.sofa.ark.common.util.StringUtils;
import com.alipay.sofa.serverless.arklet.core.command.meta.AbstractCommandHandler;
diff --git a/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/Input.java b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/custom/Input.java
similarity index 94%
rename from arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/Input.java
rename to arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/custom/Input.java
index b514db043..b850136b9 100644
--- a/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/Input.java
+++ b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/command/custom/Input.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.alipay.sofa.serverless.arklet.core.command;
+package com.alipay.sofa.serverless.arklet.core.command.custom;
import com.alipay.sofa.serverless.arklet.core.command.meta.InputMeta;
diff --git a/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/component/ComponentRegistryTest.java b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/component/ComponentRegistryTest.java
index 0f867c726..f68cad083 100644
--- a/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/component/ComponentRegistryTest.java
+++ b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/component/ComponentRegistryTest.java
@@ -33,6 +33,7 @@ public void run() {
Assert.assertNotNull(ArkletComponentRegistry.getCommandServiceInstance());
Assert.assertNotNull(ArkletComponentRegistry.getOperationServiceInstance());
Assert.assertNotNull(ArkletComponentRegistry.getApiClientInstance());
+ Assert.assertNotNull(ArkletComponentRegistry.getHealthServiceInstance());
Assert.assertTrue(ArkletComponentRegistry.getApiClientInstance().getTunnels().size() > 0);
registry.destroyComponents();
}
diff --git a/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/health/HealthTests.java b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/health/HealthTests.java
new file mode 100644
index 000000000..40be30153
--- /dev/null
+++ b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/health/HealthTests.java
@@ -0,0 +1,85 @@
+/*
+ * 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.serverless.arklet.core.health;
+
+import com.alipay.sofa.serverless.arklet.core.ArkletComponentRegistry;
+import com.alipay.sofa.serverless.arklet.core.BaseTest;
+import com.alipay.sofa.serverless.arklet.core.health.custom.CustomIndicator;
+import com.alipay.sofa.serverless.arklet.core.health.model.Constants;
+import com.alipay.sofa.serverless.arklet.core.health.model.Health;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+
+public class HealthTests extends BaseTest {
+
+ private HealthService healthService;
+
+ private void validateHealth(Health health, final String[] expectedMetrics) {
+ Assert.assertTrue(health != null && !health.getHealthData().isEmpty());
+ Map healthData = health.getHealthData();
+ for (String metric : expectedMetrics) {
+ Assert.assertTrue(healthData.containsKey(metric)
+ && !((Map, ?>) healthData.get(metric)).isEmpty());
+ }
+ }
+
+ private void validateHealth(Health health, String errorCode, String errorMessage) {
+ Assert.assertTrue(health != null && !health.getHealthData().isEmpty());
+ Assert.assertTrue(health.getHealthData().containsKey(errorCode));
+ Assert.assertEquals(health.getHealthData().get(errorCode), errorMessage);
+ }
+
+ @Before
+ public void initHealthService() throws IOException {
+ this.healthService = ArkletComponentRegistry.getHealthServiceInstance();
+
+ // ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ // URL testBiz = cl.getResource("test-biz.jar");
+ // BizOperation bizOperation = new BizOperation();
+ // bizOperation.setBizVersion("test version");
+ // ArkClient.getBizFactoryService().createBiz(bizOperation, new File(testBiz.getFile()));
+ }
+
+ @Test
+ public void registerCustomCIndicator() {
+ healthService.registerIndicator(new CustomIndicator());
+ CustomIndicator indicator = (CustomIndicator) healthService.getIndicator("custom");
+ Assert.assertNotNull(indicator);
+ }
+
+ @Test
+ public void testGetHealth() {
+ final String[] allMetrics = new String[] { Constants.CPU, Constants.JVM };
+ final String[] testMetrics = new String[] { Constants.CPU };
+ final String[] errorMetrics = new String[] { "nonMetrics" };
+ validateHealth(healthService.getHealth(), allMetrics);
+ validateHealth(healthService.getHealth(new String[0]), allMetrics);
+ validateHealth(healthService.getHealth(testMetrics), testMetrics);
+ validateHealth(healthService.getHealth(errorMetrics), Constants.HEALTH_ERROR,
+ "indicator not registered");
+ }
+
+ @Test
+ public void testIndicators() {
+ Assert.assertNotNull(healthService.getIndicator(Constants.CPU));
+ Assert.assertNotNull(healthService.getIndicator(Constants.JVM));
+ }
+}
diff --git a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/coordinate/ExecutionLock.java b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/health/custom/CustomIndicator.java
similarity index 61%
rename from arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/coordinate/ExecutionLock.java
rename to arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/health/custom/CustomIndicator.java
index caa29866b..fdfef9642 100644
--- a/arklet/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/command/coordinate/ExecutionLock.java
+++ b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/health/custom/CustomIndicator.java
@@ -14,24 +14,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.alipay.sofa.serverless.arklet.core.command.coordinate;
+package com.alipay.sofa.serverless.arklet.core.health.custom;
-import com.alipay.sofa.serverless.arklet.core.command.meta.Command;
+import com.alipay.sofa.serverless.arklet.core.health.indicator.ArkletBaseIndicator;
-/**
- * @author mingmen
- * @date 2023/6/14
- */
-public class ExecutionLock {
- private Command command;
+import java.util.HashMap;
+import java.util.Map;
- public static ExecutionLock newInstance(Command command) {
- ExecutionLock executionLock = new ExecutionLock();
- executionLock.command = command;
- return executionLock;
+public class CustomIndicator extends ArkletBaseIndicator {
+ public CustomIndicator() {
+ super("custom");
}
- public Command getCommand() {
- return command;
+ @Override
+ protected Map getHealthDetails() {
+ Map cpuHealthDetails = new HashMap<>();
+ cpuHealthDetails.put("key", "value");
+ return cpuHealthDetails;
}
}
diff --git a/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/health/indicator/IndicatorTests.java b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/health/indicator/IndicatorTests.java
new file mode 100644
index 000000000..3d01e1107
--- /dev/null
+++ b/arklet/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/health/indicator/IndicatorTests.java
@@ -0,0 +1,60 @@
+/*
+ * 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.serverless.arklet.core.health.indicator;
+
+import com.alipay.sofa.serverless.arklet.core.health.model.Health.HealthBuilder;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Map;
+
+public class IndicatorTests {
+
+ @Test
+ public void testCpuIndicator() {
+ ArkletBaseIndicator indicator = new CpuIndicator();
+ final String[] indicatorMetrics = new String[] { "count", "type", "total used (%)",
+ "user used (%)", "system used (%)", "free (%)" };
+ final String indicatorId = "cpu";
+ Map indicatorData = indicator.getHealthDetails();
+ Assert.assertEquals(indicator.getIndicatorId(), indicatorId);
+ Assert.assertNotNull(indicatorData);
+ Assert.assertNotNull(indicator.getHealthModel(new HealthBuilder()));
+ for (String indicatorMetric : indicatorMetrics) {
+ Assert.assertNotNull(indicatorData.get(indicatorMetric));
+ }
+ }
+
+ @Test
+ public void testJvmIndicator() {
+ ArkletBaseIndicator indicator = new JvmIndicator();
+ final String[] indicatorMetrics = new String[] { "java version", "java home",
+ "total memory(M)", "max memory(M)", "free memory(M)", "run time(s)",
+ "init heap memory(M)", "used heap memory(M)", "committed heap memory(M)",
+ "max heap memory(M)", "init non heap memory(M)", "used non heap memory(M)",
+ "committed non heap memory(M)", "max non heap memory(M)", "loaded class count",
+ "unload class count", "total class count" };
+ final String indicatorId = "jvm";
+ Map indicatorData = indicator.getHealthDetails();
+ Assert.assertEquals(indicator.getIndicatorId(), indicatorId);
+ Assert.assertNotNull(indicatorData);
+ Assert.assertNotNull(indicator.getHealthModel(new HealthBuilder()));
+ for (String indicatorMetric : indicatorMetrics) {
+ Assert.assertNotNull(indicatorData.get(indicatorMetric));
+ }
+ }
+}
\ No newline at end of file
diff --git a/arklet/arklet-core/src/test/resources/test-biz.jar b/arklet/arklet-core/src/test/resources/test-biz.jar
new file mode 100644
index 000000000..d5cc1577f
Binary files /dev/null and b/arklet/arklet-core/src/test/resources/test-biz.jar differ
diff --git a/arklet/arklet-springboot-starter/pom.xml b/arklet/arklet-springboot-starter/pom.xml
index 7f8f49c08..7ec92f8ea 100644
--- a/arklet/arklet-springboot-starter/pom.xml
+++ b/arklet/arklet-springboot-starter/pom.xml
@@ -12,11 +12,6 @@
arklet-springboot-starter
-
- 2.5.15
-
-
-
com.alipay.sofa.serverless
@@ -26,14 +21,18 @@
org.springframework.boot
spring-boot-autoconfigure
- ${spring.boot.version}
provided
org.springframework.boot
spring-boot-loader
- ${spring.boot.version}
+ provided
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
provided
@@ -41,14 +40,12 @@
org.springframework.boot
spring-boot-starter-test
- ${spring.boot.version}
test
org.springframework.boot
spring-boot-starter-logging
- ${spring.boot.version}
test
@@ -72,7 +69,6 @@
com.alipay.sofa
sofa-ark-springboot-starter
- ${sofa.ark.version}
test
@@ -82,6 +78,7 @@
test
+
\ No newline at end of file
diff --git a/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/ArkletAutoConfiguration.java b/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/ArkletAutoConfiguration.java
index 0fa060ea6..e95915e31 100644
--- a/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/ArkletAutoConfiguration.java
+++ b/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/ArkletAutoConfiguration.java
@@ -32,7 +32,7 @@ public class ArkletAutoConfiguration {
@Bean
@ConditionalOnMasterBiz
- public ArkletComponentRegistry componentRegistry() {
+ public ArkletComponentRegistry arkletComponentRegistry() {
ArkletComponentRegistry registry = new ArkletComponentRegistry();
registry.initComponents();
return registry;
@@ -40,7 +40,7 @@ public ArkletComponentRegistry componentRegistry() {
@Bean
@ConditionalOnMasterBiz
- @DependsOn("componentRegistry")
+ @DependsOn("arkletComponentRegistry")
public MasterBizCmdHandlerCollector masterBizCmdHandlerCollector() {
return new MasterBizCmdHandlerCollector();
}
diff --git a/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/command/MasterBizCmdHandlerCollector.java b/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/command/MasterBizCmdHandlerCollector.java
index 26350db33..2a6d0b651 100644
--- a/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/command/MasterBizCmdHandlerCollector.java
+++ b/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/command/MasterBizCmdHandlerCollector.java
@@ -27,6 +27,7 @@
/**
* @author mingmen
* @date 2023/6/14
+ * custom directive extension for master base application
*/
@SuppressWarnings("rawtypes")
public class MasterBizCmdHandlerCollector implements ApplicationContextAware {
@@ -35,6 +36,7 @@ public class MasterBizCmdHandlerCollector implements ApplicationContextAware {
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map map = applicationContext.getBeansOfType(AbstractCommandHandler.class);
map.forEach((k, v) -> {
+ // find custom directive beans from master base's spring context
ArkletComponentRegistry.getCommandServiceInstance().registerCommandHandler(v);
});
}
diff --git a/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/health/HealthAutoConfiguration.java b/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/health/HealthAutoConfiguration.java
new file mode 100644
index 000000000..1fc02e4ef
--- /dev/null
+++ b/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/health/HealthAutoConfiguration.java
@@ -0,0 +1,83 @@
+/*
+ * 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.serverless.arklet.springboot.starter.health;
+
+import com.alipay.sofa.serverless.arklet.core.ArkletComponentRegistry;
+import com.alipay.sofa.serverless.arklet.springboot.starter.health.endpoint.ArkHealthCodeEndpoint;
+import com.alipay.sofa.serverless.arklet.springboot.starter.health.endpoint.ArkHealthzEndpoint;
+import com.alipay.sofa.serverless.arklet.springboot.starter.health.extension.indicator.MasterBizHealthIndicator;
+import com.alipay.sofa.serverless.arklet.springboot.starter.environment.ConditionalOnMasterBiz;
+import org.springframework.beans.BeansException;
+import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
+import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
+import org.springframework.boot.availability.ApplicationAvailability;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.DependsOn;
+
+import java.util.Set;
+
+/**
+ * @author Lunarscave
+ */
+@Configuration
+@ConditionalOnMasterBiz
+public class HealthAutoConfiguration implements ApplicationContextAware {
+
+ private ApplicationContext context;
+
+ @Bean
+ public void initEndpoint() {
+ WebEndpointProperties webEndpointProperties = this.context
+ .getBean(WebEndpointProperties.class);
+ WebEndpointProperties.Exposure exposure = webEndpointProperties.getExposure();
+ Set includePath = exposure.getInclude();
+ includePath.add("*");
+ webEndpointProperties.getExposure().setInclude(includePath);
+ webEndpointProperties.setBasePath("/");
+ }
+
+ @Bean
+ @ConditionalOnAvailableEndpoint
+ public ArkHealthzEndpoint arkHealthzEndpoint() {
+ return new ArkHealthzEndpoint();
+ }
+
+ @Bean
+ @ConditionalOnAvailableEndpoint
+ public ArkHealthCodeEndpoint arkHealthCodeEndpoint() {
+ return new ArkHealthCodeEndpoint();
+ }
+
+ @Bean
+ @DependsOn("componentRegistry")
+ public MasterBizHealthIndicator masterBizHealthIndicator() {
+ MasterBizHealthIndicator masterBizHealthIndicator = new MasterBizHealthIndicator();
+ masterBizHealthIndicator.setApplicationAvailability(context
+ .getBean(ApplicationAvailability.class));
+ ArkletComponentRegistry.getHealthServiceInstance().registerIndicator(
+ masterBizHealthIndicator);
+ return masterBizHealthIndicator;
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.context = applicationContext;
+ }
+}
diff --git a/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/health/endpoint/ArkHealthCodeEndpoint.java b/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/health/endpoint/ArkHealthCodeEndpoint.java
new file mode 100644
index 000000000..c88140431
--- /dev/null
+++ b/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/health/endpoint/ArkHealthCodeEndpoint.java
@@ -0,0 +1,69 @@
+/*
+ * 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.serverless.arklet.springboot.starter.health.endpoint;
+
+import com.alipay.sofa.serverless.arklet.core.ArkletComponentRegistry;
+import com.alipay.sofa.serverless.arklet.core.health.HealthService;
+import com.alipay.sofa.serverless.arklet.core.health.model.Constants;
+import com.alipay.sofa.serverless.arklet.core.health.model.Health;
+import com.alipay.sofa.serverless.arklet.core.health.model.Health.HealthBuilder;
+import com.alipay.sofa.serverless.arklet.springboot.starter.health.endpoint.model.EndpointResponseCode;
+import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
+import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
+import org.springframework.boot.actuate.endpoint.annotation.Selector;
+
+/**
+ * @author Lunarscave
+ */
+@Endpoint(id = "arkHealthCode")
+public class ArkHealthCodeEndpoint {
+
+ private final HealthService healthService = ArkletComponentRegistry.getHealthServiceInstance();
+
+ @ReadOperation
+ public int healthCode() {
+ return ArkHealthCodeEndpoint.ofCode(new HealthBuilder().init()
+ .putAllHealthData(healthService.getHealth())
+ .putAllHealthData(healthService.queryModuleInfo()).build());
+ }
+
+ @ReadOperation
+ public int getModuleInfoHealthCode1(@Selector String moduleType) {
+ return ArkHealthCodeEndpoint.ofCode(healthService.queryModuleInfo(moduleType, null, null));
+ }
+
+ @ReadOperation
+ public int getModuleInfoHealthCode2(@Selector String moduleType, @Selector String name,
+ @Selector String version) {
+ return ArkHealthCodeEndpoint.ofCode(healthService
+ .queryModuleInfo(moduleType, name, version));
+ }
+
+ public static int ofCode(Health health) {
+ int endpointCode;
+ if (health.containsError(Constants.HEALTH_ENDPOINT_ERROR)) {
+ endpointCode = EndpointResponseCode.ENDPOINT_NOT_FOUND.getCode();
+ } else if (health.containsError(Constants.HEALTH_ERROR)) {
+ endpointCode = EndpointResponseCode.ENDPOINT_PROCESS_INTERNAL_ERROR.getCode();
+ } else if (health.containsUnhealthy(Constants.READINESS_HEALTHY)) {
+ endpointCode = EndpointResponseCode.UNHEALTHY.getCode();
+ } else {
+ endpointCode = EndpointResponseCode.HEALTHY.getCode();
+ }
+ return endpointCode;
+ }
+}
diff --git a/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/health/endpoint/ArkHealthzEndpoint.java b/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/health/endpoint/ArkHealthzEndpoint.java
new file mode 100644
index 000000000..2dfeece81
--- /dev/null
+++ b/arklet/arklet-springboot-starter/src/main/java/com/alipay/sofa/serverless/arklet/springboot/starter/health/endpoint/ArkHealthzEndpoint.java
@@ -0,0 +1,77 @@
+/*
+ * 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.serverless.arklet.springboot.starter.health.endpoint;
+
+import com.alipay.sofa.serverless.arklet.core.ArkletComponentRegistry;
+import com.alipay.sofa.serverless.arklet.core.health.HealthService;
+import com.alipay.sofa.serverless.arklet.core.health.model.Constants;
+import com.alipay.sofa.serverless.arklet.core.health.model.Health;
+import com.alipay.sofa.serverless.arklet.core.health.model.Health.HealthBuilder;
+import com.alipay.sofa.serverless.arklet.springboot.starter.health.endpoint.model.EndpointResponse;
+import com.alipay.sofa.serverless.arklet.springboot.starter.health.endpoint.model.EndpointResponseCode;
+import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
+import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
+import org.springframework.boot.actuate.endpoint.annotation.Selector;
+
+import java.util.Map;
+
+/**
+ * @author Lunarscave
+ */
+@Endpoint(id = "arkHealthz")
+public class ArkHealthzEndpoint {
+
+ private final HealthService healthService = ArkletComponentRegistry.getHealthServiceInstance();
+
+ @ReadOperation
+ public EndpointResponse
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
org.jacoco
jacoco-maven-plugin
@@ -373,4 +425,4 @@
-
+
\ No newline at end of file
diff --git a/sofa-ark b/sofa-ark
index e9c47721f..f1ce5a98f 160000
--- a/sofa-ark
+++ b/sofa-ark
@@ -1 +1 @@
-Subproject commit e9c47721fe9c0f0a7ef73e43c6571ad498b495de
+Subproject commit f1ce5a98f4dac924fde73521eeca6cbbd476362d