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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
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.common.util.BizIdentityUtils;
import com.alipay.sofa.ark.common.util.FileUtils;
import com.alipay.sofa.ark.common.util.StringUtils;
import com.alipay.sofa.ark.spi.model.Biz;
import com.alipay.sofa.ark.spi.service.biz.BizFactoryService;
import com.alipay.sofa.koupleless.arklet.core.command.builtin.BuiltinCommand;
import com.alipay.sofa.koupleless.arklet.core.command.coordinate.BizOpsPodCoordinator;
import com.alipay.sofa.koupleless.arklet.core.command.meta.AbstractCommandHandler;
import com.alipay.sofa.koupleless.arklet.core.command.meta.Command;
import com.alipay.sofa.koupleless.arklet.core.command.meta.Output;
Expand Down Expand Up @@ -69,6 +71,10 @@ public Output<InstallBizClientResponse> handle(Input input) {
installBizClientResponse
.setElapsedSpace(metaSpaceMXBean.getUsage().getUsed() - startSpace);
if (ResponseCode.SUCCESS.equals(installBizClientResponse.getCode())) {
String bizIdentity = BizIdentityUtils.generateBizIdentity(input.getBizName(),
input.getBizVersion());
String bizModelVersion = input.getBizModelVersion();
BizOpsPodCoordinator.save(bizIdentity, bizModelVersion);
return Output.ofSuccess(installBizClientResponse);
} else {
return Output.ofFailed(installBizClientResponse, "install biz not success!");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,20 @@

import com.alipay.sofa.ark.api.ClientResponse;
import com.alipay.sofa.ark.api.ResponseCode;
import com.alipay.sofa.ark.common.util.BizIdentityUtils;
import com.alipay.sofa.ark.common.util.StringUtils;
import com.alipay.sofa.koupleless.arklet.core.command.builtin.BuiltinCommand;
import com.alipay.sofa.koupleless.arklet.core.command.builtin.handler.UninstallBizHandler.Input;
import com.alipay.sofa.koupleless.arklet.core.command.coordinate.BizOpsPodCoordinator;
import com.alipay.sofa.koupleless.arklet.core.command.meta.AbstractCommandHandler;
import com.alipay.sofa.koupleless.arklet.core.command.meta.Command;
import com.alipay.sofa.koupleless.arklet.core.command.meta.Output;
import com.alipay.sofa.koupleless.arklet.core.command.meta.bizops.ArkBizMeta;
import com.alipay.sofa.koupleless.arklet.core.command.meta.bizops.ArkBizOps;
import com.alipay.sofa.koupleless.arklet.core.common.exception.ArkletRuntimeException;
import com.alipay.sofa.koupleless.arklet.core.common.exception.CommandValidationException;
import com.alipay.sofa.koupleless.common.log.ArkletLogger;
import com.alipay.sofa.koupleless.common.log.ArkletLoggerFactory;

/**
* <p>UninstallBizHandler class.</p>
Expand All @@ -39,13 +43,25 @@
public class UninstallBizHandler extends AbstractCommandHandler<Input, ClientResponse>
implements ArkBizOps {

private static final ArkletLogger LOGGER = ArkletLoggerFactory.getDefaultLogger();

/** {@inheritDoc} */
@Override
public Output<ClientResponse> handle(Input input) {
try {
String bizIdentity = BizIdentityUtils.generateBizIdentity(input.getBizName(),
input.getBizVersion());
String bizModelVersion = input.getBizModelVersion();
if (!BizOpsPodCoordinator.canAccess(bizIdentity, bizModelVersion)) {
LOGGER.error(
"can not access biz because the command is expired. bizIdentity: {}, bizModelVersion: {}",
bizIdentity, bizModelVersion);
return Output.ofFailed("can not access biz because the command is expired");
}
ClientResponse res = getOperationService().uninstall(input.getBizName(),
input.getBizVersion());
if (ResponseCode.SUCCESS.equals(res.getCode())) {
BizOpsPodCoordinator.remove(bizIdentity, bizModelVersion);
return Output.ofSuccess(res);
Comment on lines 63 to 65
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Possible NPE on remove when bizModelVersion is null (ConcurrentHashMap disallows null value).

BizOpsPodCoordinator.remove(bizIdentity, bizModelVersion) will throw if bizModelVersion == null (legacy clients). Fix in coordinator by normalizing null/empty before calling Map.remove.

Apply coordinator fix shown in that file’s comment; alternatively, add a local guard:

- BizOpsPodCoordinator.remove(bizIdentity, bizModelVersion);
+ if (!StringUtils.isEmpty(bizModelVersion)) {
+     BizOpsPodCoordinator.remove(bizIdentity, bizModelVersion);
+ } else {
+     // legacy client: best-effort cleanup; safe to remove by key
+     BizOpsPodCoordinator.remove(bizIdentity, StringUtils.EMPTY_STRING);
+ }

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

} else {
return Output.ofFailed(res, "uninstall biz not success!");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alipay.sofa.koupleless.arklet.core.command.coordinate;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.alipay.sofa.ark.common.util.StringUtils;

/**
* <p>
* BizOpsPodCoordinator class.
* </p>
*
* @author liuzhuoheng
* @since 2025/7/15
* @version 1.0.0
*/
public class BizOpsPodCoordinator {

/**
* bizIdentityLockMap
* key: bizIdentity, value: bizModelVersion
*/
private static final Map<String, String> bizIdentityLockMap = new ConcurrentHashMap<>();

/**
* <p>
* save.
* </p>
*
* @param bizIdentity a {@link java.lang.String} object
* @param bizModelVersion a {@link java.lang.String} object
* @return
*/
public static void save(String bizIdentity, String bizModelVersion) {
if (StringUtils.isEmpty(bizIdentity)) {
return;
}
if (StringUtils.isEmpty(bizModelVersion)) {
bizModelVersion = StringUtils.EMPTY_STRING;
}
bizIdentityLockMap.put(bizIdentity, bizModelVersion);
}

/**
* <p>
* remove.
* </p>
*
* @param bizIdentity a {@link java.lang.String} object
* @param bizModelVersion a {@link java.lang.String} object
* @return
*/
public static void remove(String bizIdentity, String bizModelVersion) {
if (StringUtils.isEmpty(bizIdentity)) {
return;
}
if (StringUtils.isEmpty(bizModelVersion)) {
bizIdentityLockMap.remove(bizIdentity);
return;
}
bizIdentityLockMap.remove(bizIdentity, bizModelVersion);
}

/**
* <p>
* canAccess.
* </p>
* 判断是否可以访问指定的业务模块,基于业务模块版本的协调机制
*
* @param bizIdentity 业务模块标识 (bizName:bizVersion)
* @param bizModelVersion 业务模块模型版本,用于命令协调和防止过期命令执行
* @return 是否允许访问该业务模块
*/
public static boolean canAccess(String bizIdentity, String bizModelVersion) {
// 判断逻辑说明:
// Case 1: bizModelVersion 为空 - 兼容性处理,允许访问(兼容旧版本 module-controller,arktcl,
// pod-not-exist 和 pod 紧急删除场景)
// Case 2: bizIdentityLockMap 中没有该 bizIdentity 的记录,允许访问(安装时不带
// BizModelVersion,卸载时带上 BizModelVersion)
// Case 3: bizIdentityLockMap 中的版本与当前请求的版本匹配 - 版本一致,确认卸载的是该 Biz,允许访问
// 只有当 bizModelVersion 不为空且存在 bizModelVersion 且不匹配时,才拒绝访问(防止旧的卸载命令执行)
return StringUtils.isEmpty(bizModelVersion)
|| StringUtils.isEmpty(bizIdentityLockMap.get(bizIdentity))
|| bizIdentityLockMap.get(bizIdentity).equals(bizModelVersion);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class ArkBizMeta extends InputMeta {
private String bizName;
private String bizVersion;
private String requestId;
private String bizModelVersion;
private boolean async;

/**
Expand Down Expand Up @@ -85,6 +86,24 @@ public void setRequestId(String requestId) {
this.requestId = requestId;
}

/**
* <p>Getter for the field <code>bizModelVersion</code>.</p>
*
* @return a {@link java.lang.String} object
*/
public String getBizModelVersion() {
return bizModelVersion;
}

/**
* <p>Setter for the field <code>bizModelVersion</code>.</p>
*
* @param bizModelVersion a {@link java.lang.String} object
*/
public void setBizModelVersion(String bizModelVersion) {
this.bizModelVersion = bizModelVersion;
}

/**
* <p>isAsync.</p>
*
Expand Down
Loading
Loading