feat(fix) swarm service update

This commit is contained in:
bwcx_jzy 2022-02-15 13:51:52 +08:00
parent 3cccba88de
commit 62f7b35554
No known key found for this signature in database
GPG Key ID: 5E48E9372088B9E5
4 changed files with 183 additions and 68 deletions

View File

@ -25,6 +25,7 @@ package io.jpom;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.EnumUtil; import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
@ -164,70 +165,9 @@ public class DefaultDockerSwarmPluginImpl implements IDefaultPlugin {
} }
serviceSpec.withMode(serviceModeConfig); serviceSpec.withMode(serviceModeConfig);
} }
// {
// UpdateConfig updateConfig = new UpdateConfig();
//
// serviceSpec.withUpdateConfig(updateConfig);
// }
{ {
String image = (String) parameter.get("image");
TaskSpec taskSpec = new TaskSpec(); TaskSpec taskSpec = new TaskSpec();
ContainerSpec containerSpec = new ContainerSpec(); ContainerSpec containerSpec = this.buildContainerSpec(parameter);
containerSpec.withImage(image);
//
Collection<Map<String, String>> args = (Collection) parameter.get("args");
if (CollUtil.isNotEmpty(args)) {
List<String> value = args.stream()
.map(stringStringMap -> stringStringMap.get("value"))
.filter(StrUtil::isNotEmpty)
.collect(Collectors.toList());
containerSpec.withArgs(value);
}
Collection<Map<String, String>> envs = (Collection) parameter.get("envs");
if (CollUtil.isNotEmpty(envs)) {
List<String> value = envs.stream()
.map(stringStringMap -> {
String name1 = stringStringMap.get("name");
String value1 = stringStringMap.get("value");
if (StrUtil.isEmpty(name1)) {
return null;
}
return StrUtil.format("{}={}", name1, value1);
})
.filter(StrUtil::isNotEmpty)
.collect(Collectors.toList());
containerSpec.withEnv(value);
}
Collection<Map<String, String>> commands = (Collection) parameter.get("commands");
if (CollUtil.isNotEmpty(commands)) {
List<String> value = commands.stream()
.map(stringStringMap -> stringStringMap.get("value"))
.filter(StrUtil::isNotEmpty)
.collect(Collectors.toList());
containerSpec.withCommand(value);
}
//
Collection<Map<String, String>> volumes = (Collection<Map<String, String>>) parameter.get("volumes");
if (CollUtil.isNotEmpty(volumes)) {
List<Mount> value = volumes.stream()
.map(stringStringMap -> {
String source = stringStringMap.get("source");
String target = stringStringMap.get("target");
if (StrUtil.hasBlank(source, target)) {
return null;
}
String type = stringStringMap.get("type");
MountType mountType = EnumUtil.fromString(MountType.class, type);
Mount mount = new Mount();
mount.withSource(source);
mount.withTarget(target);
mount.withType(mountType);
return mount;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
containerSpec.withMounts(value);
}
taskSpec.withContainerSpec(containerSpec); taskSpec.withContainerSpec(containerSpec);
// //
serviceSpec.withTaskTemplate(taskSpec); serviceSpec.withTaskTemplate(taskSpec);
@ -263,6 +203,14 @@ public class DefaultDockerSwarmPluginImpl implements IDefaultPlugin {
} }
serviceSpec.withEndpointSpec(endpointSpec); serviceSpec.withEndpointSpec(endpointSpec);
} }
{
Map<String, Object> update = (Map<String, Object>) parameter.get("update");
UpdateConfig updateConfig = this.buildUpdateConfig(update);
serviceSpec.withUpdateConfig(updateConfig);
Map<String, Object> rollback = (Map<String, Object>) parameter.get("rollback");
UpdateConfig rollbackConfig = this.buildUpdateConfig(rollback);
serviceSpec.withRollbackConfig(rollbackConfig);
}
String serviceId = (String) parameter.get("serviceId"); String serviceId = (String) parameter.get("serviceId");
String version = (String) parameter.get("version"); String version = (String) parameter.get("version");
if (StrUtil.isNotEmpty(serviceId)) { if (StrUtil.isNotEmpty(serviceId)) {
@ -278,6 +226,102 @@ public class DefaultDockerSwarmPluginImpl implements IDefaultPlugin {
} }
} }
private ContainerSpec buildContainerSpec(Map<String, Object> parameter) {
String image = (String) parameter.get("image");
ContainerSpec containerSpec = new ContainerSpec();
containerSpec.withImage(image);
//
Collection<Map<String, String>> args = (Collection) parameter.get("args");
if (CollUtil.isNotEmpty(args)) {
List<String> value = args.stream()
.map(stringStringMap -> stringStringMap.get("value"))
.filter(StrUtil::isNotEmpty)
.collect(Collectors.toList());
containerSpec.withArgs(value);
}
Collection<Map<String, String>> envs = (Collection) parameter.get("envs");
if (CollUtil.isNotEmpty(envs)) {
List<String> value = envs.stream()
.map(stringStringMap -> {
String name1 = stringStringMap.get("name");
String value1 = stringStringMap.get("value");
if (StrUtil.isEmpty(name1)) {
return null;
}
return StrUtil.format("{}={}", name1, value1);
})
.filter(StrUtil::isNotEmpty)
.collect(Collectors.toList());
containerSpec.withEnv(value);
}
Collection<Map<String, String>> commands = (Collection) parameter.get("commands");
if (CollUtil.isNotEmpty(commands)) {
List<String> value = commands.stream()
.map(stringStringMap -> stringStringMap.get("value"))
.filter(StrUtil::isNotEmpty)
.collect(Collectors.toList());
containerSpec.withCommand(value);
}
//
Collection<Map<String, String>> volumes = (Collection<Map<String, String>>) parameter.get("volumes");
if (CollUtil.isNotEmpty(volumes)) {
List<Mount> value = volumes.stream()
.map(stringStringMap -> {
String source = stringStringMap.get("source");
String target = stringStringMap.get("target");
if (StrUtil.hasBlank(source, target)) {
return null;
}
String type = stringStringMap.get("type");
MountType mountType = EnumUtil.fromString(MountType.class, type);
Mount mount = new Mount();
mount.withSource(source);
mount.withTarget(target);
mount.withType(mountType);
return mount;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
containerSpec.withMounts(value);
}
return containerSpec;
}
private UpdateConfig buildUpdateConfig(Map<String, Object> update) {
if (MapUtil.isNotEmpty(update)) {
UpdateConfig updateConfig = new UpdateConfig();
String failureAction = (String) update.get("failureAction");
if (StrUtil.isNotEmpty(failureAction)) {
UpdateFailureAction updateFailureAction = EnumUtil.fromString(UpdateFailureAction.class, failureAction);
updateConfig.withFailureAction(updateFailureAction);
}
String order = (String) update.get("order");
if (StrUtil.isNotEmpty(order)) {
UpdateOrder updateOrder = EnumUtil.fromString(UpdateOrder.class, order);
updateConfig.withOrder(updateOrder);
}
Object parallelism = update.get("parallelism");
if (parallelism != null) {
updateConfig.withParallelism(Convert.toLong(parallelism));
}
Object delay = update.get("delay");
if (delay != null) {
updateConfig.withDelay(Convert.toLong(delay));
}
Object maxFailureRatio = update.get("maxFailureRatio");
if (maxFailureRatio != null) {
updateConfig.withMaxFailureRatio(Convert.toFloat(maxFailureRatio));
}
Object monitor = update.get("monitor");
if (monitor != null) {
updateConfig.withMonitor(Convert.toLong(monitor));
}
return updateConfig;
}
return null;
}
public void removeServiceCmd(Map<String, Object> parameter) { public void removeServiceCmd(Map<String, Object> parameter) {
DockerClient dockerClient = DockerUtil.build(parameter); DockerClient dockerClient = DockerUtil.build(parameter);
try { try {

View File

@ -329,6 +329,7 @@ public class TestSwarm {
taskSpec.withContainerSpec(containerSpec); taskSpec.withContainerSpec(containerSpec);
serviceSpec.withTaskTemplate(taskSpec); serviceSpec.withTaskTemplate(taskSpec);
} }
// serviceSpec.withRollbackConfig()
serviceSpec.withEndpointSpec(new EndpointSpec()); serviceSpec.withEndpointSpec(new EndpointSpec());
String serviceId = "tf2r29awevz2fcprybv6cvlm9"; String serviceId = "tf2r29awevz2fcprybv6cvlm9";
InspectServiceCmd inspectServiceCmd = client.inspectServiceCmd(serviceId); InspectServiceCmd inspectServiceCmd = client.inspectServiceCmd(serviceId);

View File

@ -50,7 +50,7 @@
</span> </span>
</a-tooltip> </a-tooltip>
<a-tooltip slot="replicas" slot-scope="text, record" placement="topLeft" :title="text"> <a-tooltip slot="replicas" slot-scope="text, record" placement="topLeft" :title="`点击数字查看运行中的任务,点击图标查看关联的所有任务`">
<a-tag @click="handleTask(record, 'RUNNING')">{{ text }}</a-tag> <a-tag @click="handleTask(record, 'RUNNING')">{{ text }}</a-tag>
<a-icon type="read" @click="handleTask(record)" /> <a-icon type="read" @click="handleTask(record)" />
@ -303,6 +303,60 @@
</a-row> </a-row>
</a-form-model-item> </a-form-model-item>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="update" tab="升级策略" v-if="temp.update">
<a-form-model-item label="并行度" prop="parallelism">
<a-input-number style="width: 80%" :min="0" v-model="temp.update.parallelism" placeholder="并行度" />
</a-form-model-item>
<a-form-model-item label="延迟" prop="delay">
<a-input-number style="width: 80%" :min="1" v-model="temp.update.delay" placeholder="延迟" />
</a-form-model-item>
<a-form-model-item label="失败率" prop="maxFailureRatio">
<a-input-number style="width: 80%" :min="0" v-model="temp.update.maxFailureRatio" placeholder="失败率" />
</a-form-model-item>
<a-form-model-item label="失败策略" prop="failureAction">
<a-radio-group name="failureAction" v-model="temp.update.failureAction">
<a-radio value="PAUSE">暂停</a-radio>
<a-radio value="CONTINUE">继续</a-radio>
<a-radio value="ROLLBACK">回滚</a-radio>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="执行顺序" prop="order">
<a-radio-group name="order" v-model="temp.update.order">
<a-radio value="STOP_FIRST">先停止</a-radio>
<a-radio value="START_FIRST">先启动</a-radio>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="监控" prop="monitor">
<a-input-number style="width: 80%" :min="1" v-model="temp.update.monitor" placeholder="延迟" />
</a-form-model-item>
</a-tab-pane>
<a-tab-pane key="rollback" tab="回滚策略" v-if="temp.rollback">
<a-form-model-item label="并行度" prop="parallelism">
<a-input-number style="width: 80%" :min="0" v-model="temp.rollback.parallelism" placeholder="并行度" />
</a-form-model-item>
<a-form-model-item label="延迟" prop="delay">
<a-input-number style="width: 80%" :min="1" v-model="temp.rollback.delay" placeholder="延迟" />
</a-form-model-item>
<a-form-model-item label="失败率" prop="maxFailureRatio">
<a-input-number style="width: 80%" :min="0" v-model="temp.rollback.maxFailureRatio" placeholder="失败率" />
</a-form-model-item>
<a-form-model-item label="失败策略" prop="failureAction">
<a-radio-group name="failureAction" v-model="temp.rollback.failureAction">
<a-radio value="PAUSE">暂停</a-radio>
<a-radio value="CONTINUE">继续</a-radio>
<a-radio value="ROLLBACK">回滚</a-radio>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="执行顺序" prop="order">
<a-radio-group name="order" v-model="temp.rollback.order">
<a-radio value="STOP_FIRST">先停止</a-radio>
<a-radio value="START_FIRST">先启动</a-radio>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="监控" prop="monitor">
<a-input-number style="width: 80%" :min="1" v-model="temp.rollback.monitor" placeholder="延迟" />
</a-form-model-item>
</a-tab-pane>
</a-tabs> </a-tabs>
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
@ -338,7 +392,10 @@ export default {
loading: false, loading: false,
listQuery: {}, listQuery: {},
list: [], list: [],
temp: {}, temp: {
update: {},
rollback: {},
},
editVisible: false, editVisible: false,
initSwarmVisible: false, initSwarmVisible: false,
taskVisible: false, taskVisible: false,
@ -427,6 +484,8 @@ export default {
args: [{}], args: [{}],
commands: [{}], commands: [{}],
envs: [{}], envs: [{}],
update: {},
rollback: {},
}; };
}, },
// //
@ -462,6 +521,8 @@ export default {
args: [{}], args: [{}],
commands: [{}], commands: [{}],
envs: [{}], envs: [{}],
update: {},
rollback: {},
}; };
const args = spec.taskTemplate?.containerSpec?.args; const args = spec.taskTemplate?.containerSpec?.args;
@ -469,7 +530,8 @@ export default {
const command = spec.taskTemplate?.containerSpec?.command; const command = spec.taskTemplate?.containerSpec?.command;
const env = spec.taskTemplate?.containerSpec?.env; const env = spec.taskTemplate?.containerSpec?.env;
const ports = spec.endpointSpec?.ports; const ports = spec.endpointSpec?.ports;
const updateConfig = spec.updateConfig;
const rollbackConfig = spec.rollbackConfig;
if (args) { if (args) {
this.temp = { this.temp = {
...this.temp, ...this.temp,
@ -507,6 +569,12 @@ export default {
if (mounts) { if (mounts) {
this.temp = { ...this.temp, volumes: mounts }; this.temp = { ...this.temp, volumes: mounts };
} }
if (updateConfig) {
this.temp = { ...this.temp, update: updateConfig };
}
if (rollbackConfig) {
this.temp = { ...this.temp, rollback: rollbackConfig };
}
}, },
handleEditOk() { handleEditOk() {
this.$refs["editForm"].validate((valid) => { this.$refs["editForm"].validate((valid) => {

View File

@ -8,9 +8,11 @@
<a-input v-model="listQuery['taskId']" @pressEnter="loadData" placeholder="任务id" class="search-input-item" /> <a-input v-model="listQuery['taskId']" @pressEnter="loadData" placeholder="任务id" class="search-input-item" />
<a-input v-model="listQuery['taskNode']" @pressEnter="loadData" placeholder="节点id" class="search-input-item" /> <a-input v-model="listQuery['taskNode']" @pressEnter="loadData" placeholder="节点id" class="search-input-item" />
<a-select show-search option-filter-prop="children" v-model="listQuery['taskState']" allowClear placeholder="状态" class="search-input-item"> <a-tooltip :title="TASK_STATE[listQuery['taskState']]">
<a-select-option :key="key" v-for="(item, key) in TASK_STATE">{{ item }}- {{ key }}</a-select-option> <a-select show-search option-filter-prop="children" v-model="listQuery['taskState']" allowClear placeholder="状态" class="search-input-item">
</a-select> <a-select-option :key="key" v-for="(item, key) in TASK_STATE">{{ item }}- {{ key }}</a-select-option>
</a-select>
</a-tooltip>
<a-button type="primary" @click="loadData" :loading="loading">搜索</a-button> <a-button type="primary" @click="loadData" :loading="loading">搜索</a-button>
</a-space> </a-space>
</template> </template>