mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Merge remote-tracking branch 'origin/4.17'
Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
commit
7ff3e7f968
@ -22,6 +22,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.log4j.Level;
|
import org.apache.log4j.Level;
|
||||||
|
|
||||||
import com.cloud.hypervisor.Hypervisor;
|
import com.cloud.hypervisor.Hypervisor;
|
||||||
@ -36,7 +37,6 @@ import com.cloud.uservm.UserVm;
|
|||||||
import com.cloud.utils.Pair;
|
import com.cloud.utils.Pair;
|
||||||
import com.cloud.utils.exception.CloudRuntimeException;
|
import com.cloud.utils.exception.CloudRuntimeException;
|
||||||
import com.cloud.utils.ssh.SshHelper;
|
import com.cloud.utils.ssh.SshHelper;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
public class KubernetesClusterUpgradeWorker extends KubernetesClusterActionWorker {
|
public class KubernetesClusterUpgradeWorker extends KubernetesClusterActionWorker {
|
||||||
|
|
||||||
@ -122,6 +122,9 @@ public class KubernetesClusterUpgradeWorker extends KubernetesClusterActionWorke
|
|||||||
logTransitStateDetachIsoAndThrow(Level.ERROR, String.format("Failed to upgrade Kubernetes cluster : %s, unable to get control Kubernetes node on VM : %s in ready state", kubernetesCluster.getName(), vm.getDisplayName()), kubernetesCluster, clusterVMs, KubernetesCluster.Event.OperationFailed, null);
|
logTransitStateDetachIsoAndThrow(Level.ERROR, String.format("Failed to upgrade Kubernetes cluster : %s, unable to get control Kubernetes node on VM : %s in ready state", kubernetesCluster.getName(), vm.getDisplayName()), kubernetesCluster, clusterVMs, KubernetesCluster.Event.OperationFailed, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!KubernetesClusterUtil.clusterNodeVersionMatches(upgradeVersion.getSemanticVersion(), i==0, publicIpAddress, sshPort, getControlNodeLoginUser(), getManagementServerSshPublicKeyFile(), hostName)) {
|
||||||
|
logTransitStateDetachIsoAndThrow(Level.ERROR, String.format("Failed to upgrade Kubernetes cluster : %s, unable to get Kubernetes node on VM : %s upgraded to version %s", kubernetesCluster.getName(), vm.getDisplayName(), upgradeVersion.getSemanticVersion()), kubernetesCluster, clusterVMs, KubernetesCluster.Event.OperationFailed, null);
|
||||||
|
}
|
||||||
if (LOGGER.isInfoEnabled()) {
|
if (LOGGER.isInfoEnabled()) {
|
||||||
LOGGER.info(String.format("Successfully upgraded node on VM %s in Kubernetes cluster %s with Kubernetes version(%s) ID: %s",
|
LOGGER.info(String.format("Successfully upgraded node on VM %s in Kubernetes cluster %s with Kubernetes version(%s) ID: %s",
|
||||||
vm.getDisplayName(), kubernetesCluster.getName(), upgradeVersion.getSemanticVersion(), upgradeVersion.getUuid()));
|
vm.getDisplayName(), kubernetesCluster.getName(), upgradeVersion.getSemanticVersion(), upgradeVersion.getUuid()));
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import javax.net.ssl.SSLContext;
|
|||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
|
|
||||||
import org.apache.cloudstack.utils.security.SSLUtils;
|
import org.apache.cloudstack.utils.security.SSLUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import com.cloud.kubernetes.cluster.KubernetesCluster;
|
import com.cloud.kubernetes.cluster.KubernetesCluster;
|
||||||
@ -39,12 +40,13 @@ import com.cloud.uservm.UserVm;
|
|||||||
import com.cloud.utils.Pair;
|
import com.cloud.utils.Pair;
|
||||||
import com.cloud.utils.nio.TrustAllManager;
|
import com.cloud.utils.nio.TrustAllManager;
|
||||||
import com.cloud.utils.ssh.SshHelper;
|
import com.cloud.utils.ssh.SshHelper;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
public class KubernetesClusterUtil {
|
public class KubernetesClusterUtil {
|
||||||
|
|
||||||
protected static final Logger LOGGER = Logger.getLogger(KubernetesClusterUtil.class);
|
protected static final Logger LOGGER = Logger.getLogger(KubernetesClusterUtil.class);
|
||||||
|
|
||||||
|
public static final String CLUSTER_NODE_VERSION_COMMAND = "sudo /opt/bin/kubectl version --short";
|
||||||
|
|
||||||
public static boolean isKubernetesClusterNodeReady(final KubernetesCluster kubernetesCluster, String ipAddress, int port,
|
public static boolean isKubernetesClusterNodeReady(final KubernetesCluster kubernetesCluster, String ipAddress, int port,
|
||||||
String user, File sshKeyFile, String nodeName) throws Exception {
|
String user, File sshKeyFile, String nodeName) throws Exception {
|
||||||
Pair<Boolean, String> result = SshHelper.sshExecute(ipAddress, port,
|
Pair<Boolean, String> result = SshHelper.sshExecute(ipAddress, port,
|
||||||
@ -324,4 +326,38 @@ public class KubernetesClusterUtil {
|
|||||||
}
|
}
|
||||||
return token.toString().substring(0, 64);
|
return token.toString().substring(0, 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean clusterNodeVersionMatches(final String version, boolean isControlNode,
|
||||||
|
final String ipAddress, final int port,
|
||||||
|
final String user, final File sshKeyFile,
|
||||||
|
final String hostName) {
|
||||||
|
Pair<Boolean, String> result = null;
|
||||||
|
try {
|
||||||
|
result = SshHelper.sshExecute(
|
||||||
|
ipAddress, port,
|
||||||
|
user, sshKeyFile, null,
|
||||||
|
CLUSTER_NODE_VERSION_COMMAND,
|
||||||
|
10000, 10000, 20000);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug(String.format("Failed to retrieve Kubernetes version from cluster node : %s due to exception", hostName), e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Boolean.FALSE.equals(result.first()) || StringUtils.isBlank(result.second())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String response = result.second();
|
||||||
|
boolean clientVersionPresent = false;
|
||||||
|
boolean serverVersionPresent = false;
|
||||||
|
for (String line : response.split("\n")) {
|
||||||
|
if (line.contains("Client Version") && line.contains(String.format("v%s", version))) {
|
||||||
|
clientVersionPresent = true;
|
||||||
|
}
|
||||||
|
if (isControlNode && line.contains("Server Version") && line.contains(String.format("v%s", version))) {
|
||||||
|
serverVersionPresent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clientVersionPresent && (!isControlNode || serverVersionPresent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,6 +37,9 @@ if [ $# -gt 3 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
export PATH=$PATH:/opt/bin
|
export PATH=$PATH:/opt/bin
|
||||||
|
if [[ "$PATH" != *:/usr/sbin && "$PATH" != *:/usr/sbin:* ]]; then
|
||||||
|
export PATH=$PATH:/usr/sbin
|
||||||
|
fi
|
||||||
|
|
||||||
ISO_MOUNT_DIR=/mnt/k8sdisk
|
ISO_MOUNT_DIR=/mnt/k8sdisk
|
||||||
BINARIES_DIR=${ISO_MOUNT_DIR}/
|
BINARIES_DIR=${ISO_MOUNT_DIR}/
|
||||||
@ -149,4 +152,7 @@ if [ -d "$BINARIES_DIR" ]; then
|
|||||||
if [ "$EJECT_ISO_FROM_OS" = true ] && [ "$iso_drive_path" != "" ]; then
|
if [ "$EJECT_ISO_FROM_OS" = true ] && [ "$iso_drive_path" != "" ]; then
|
||||||
eject "${iso_drive_path}"
|
eject "${iso_drive_path}"
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
echo "ERROR: Unable to access Binaries directory for upgrade version ${UPGRADE_VERSION}"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -0,0 +1,93 @@
|
|||||||
|
// 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.cloud.kubernetes.cluster.utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.powermock.api.mockito.PowerMockito;
|
||||||
|
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||||
|
import org.powermock.modules.junit4.PowerMockRunner;
|
||||||
|
|
||||||
|
import com.cloud.utils.Pair;
|
||||||
|
import com.cloud.utils.ssh.SshHelper;
|
||||||
|
|
||||||
|
@RunWith(PowerMockRunner.class)
|
||||||
|
@PrepareForTest(SshHelper.class)
|
||||||
|
public class KubernetesClusterUtilTest {
|
||||||
|
String ipAddress = "10.1.1.1";
|
||||||
|
int port = 2222;
|
||||||
|
String user = "user";
|
||||||
|
File sshKeyFile = Mockito.mock(File.class);
|
||||||
|
String hostName = "host";
|
||||||
|
|
||||||
|
private void mockSshHelperExecuteThrowAndTestVersionMatch() {
|
||||||
|
try {
|
||||||
|
Mockito.when(SshHelper.sshExecute(ipAddress, port, user, sshKeyFile, null, KubernetesClusterUtil.CLUSTER_NODE_VERSION_COMMAND, 10000, 10000, 20000)).thenThrow(Exception.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Assert.fail(String.format("Exception: %s", e.getMessage()));
|
||||||
|
}
|
||||||
|
Assert.assertFalse(KubernetesClusterUtil.clusterNodeVersionMatches("1.24.0", false, ipAddress, port, user, sshKeyFile, hostName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mockSshHelperExecuteAndTestVersionMatch(boolean status, String response, boolean isControlNode, boolean expectedResult) {
|
||||||
|
try {
|
||||||
|
Mockito.when(SshHelper.sshExecute(ipAddress, port, user, sshKeyFile, null, KubernetesClusterUtil.CLUSTER_NODE_VERSION_COMMAND, 10000, 10000, 20000)).thenReturn(new Pair<>(status, response));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Assert.fail(String.format("Exception: %s", e.getMessage()));
|
||||||
|
}
|
||||||
|
boolean result = KubernetesClusterUtil.clusterNodeVersionMatches("1.24.0", isControlNode, ipAddress, port, user, sshKeyFile, hostName);
|
||||||
|
Assert.assertEquals(expectedResult, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClusterNodeVersionMatches() {
|
||||||
|
PowerMockito.mockStatic(SshHelper.class);
|
||||||
|
String v1233WorkerNodeOutput = "Client Version: v1.23.3\n" +
|
||||||
|
"The connection to the server localhost:8080 was refused - did you specify the right host or port?";
|
||||||
|
String v1240WorkerNodeOutput = "Client Version: v1.24.0\n" +
|
||||||
|
"Kustomize Version: v4.5.4\n" +
|
||||||
|
"The connection to the server localhost:8080 was refused - did you specify the right host or port?";
|
||||||
|
String v1240ControlNodeOutput = "Client Version: v1.24.0\n" +
|
||||||
|
"Kustomize Version: v4.5.4\n" +
|
||||||
|
"Server Version: v1.24.0";
|
||||||
|
mockSshHelperExecuteAndTestVersionMatch(true, v1240WorkerNodeOutput, false, true);
|
||||||
|
|
||||||
|
mockSshHelperExecuteAndTestVersionMatch(true, v1240ControlNodeOutput, true, true);
|
||||||
|
|
||||||
|
mockSshHelperExecuteAndTestVersionMatch(true, v1240WorkerNodeOutput, true, false);
|
||||||
|
|
||||||
|
mockSshHelperExecuteAndTestVersionMatch(false, v1240WorkerNodeOutput, false, false);
|
||||||
|
|
||||||
|
mockSshHelperExecuteAndTestVersionMatch(true, v1233WorkerNodeOutput, false, false);
|
||||||
|
|
||||||
|
mockSshHelperExecuteAndTestVersionMatch(true, "Client Version: v1.24.0\n" +
|
||||||
|
"Kustomize Version: v4.5.4\n" +
|
||||||
|
"Server Version: v1.23.0", true, false);
|
||||||
|
|
||||||
|
mockSshHelperExecuteAndTestVersionMatch(true, null, false, false);
|
||||||
|
|
||||||
|
mockSshHelperExecuteAndTestVersionMatch(false, "-\n-", false, false);
|
||||||
|
|
||||||
|
mockSshHelperExecuteAndTestVersionMatch(false, "1.24.0", false, false);
|
||||||
|
|
||||||
|
mockSshHelperExecuteThrowAndTestVersionMatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -72,6 +72,26 @@
|
|||||||
v-model:value="form[field.key]"
|
v-model:value="form[field.key]"
|
||||||
v-focus="index === 0"
|
v-focus="index === 0"
|
||||||
/>
|
/>
|
||||||
|
<a-radio-group
|
||||||
|
v-else-if="field.radioGroup"
|
||||||
|
v-model:value="form[field.key]"
|
||||||
|
buttonStyle="solid">
|
||||||
|
<span
|
||||||
|
style="margin-right: 5px;"
|
||||||
|
v-for="(radioItem, idx) in field.radioOption"
|
||||||
|
:key="idx">
|
||||||
|
<a-radio-button
|
||||||
|
:value="radioItem.value"
|
||||||
|
v-if="isDisplayItem(radioItem.condition)">
|
||||||
|
{{ $t(radioItem.label) }}
|
||||||
|
</a-radio-button>
|
||||||
|
</span>
|
||||||
|
<a-alert style="margin-top: 5px" type="warning" v-if="field.alert && isDisplayItem(field.alert.display)">
|
||||||
|
<template #message>
|
||||||
|
<span v-html="$t(field.alert.message)" />
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</a-radio-group>
|
||||||
<a-input
|
<a-input
|
||||||
v-else
|
v-else
|
||||||
v-model:value="form[field.key]"
|
v-model:value="form[field.key]"
|
||||||
@ -123,6 +143,11 @@ export default {
|
|||||||
created () {
|
created () {
|
||||||
this.initForm()
|
this.initForm()
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
hypervisor () {
|
||||||
|
return this.prefillContent?.hypervisor || null
|
||||||
|
}
|
||||||
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.fillValue()
|
this.fillValue()
|
||||||
},
|
},
|
||||||
@ -190,7 +215,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
getPrefilled (field) {
|
getPrefilled (field) {
|
||||||
return this.prefillContent?.[field.key] || field.value || undefined
|
if (field.key === 'authmethod' && this.hypervisor !== 'KVM') {
|
||||||
|
return field.value || field.defaultValue || 'password'
|
||||||
|
}
|
||||||
|
return this.prefillContent?.[field.key] || field.value || field.defaultValue || null
|
||||||
},
|
},
|
||||||
handleSubmit () {
|
handleSubmit () {
|
||||||
this.formRef.value.validate().then(() => {
|
this.formRef.value.validate().then(() => {
|
||||||
@ -259,6 +287,27 @@ export default {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
},
|
||||||
|
isDisplayItem (conditions) {
|
||||||
|
if (!conditions || Object.keys(conditions).length === 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
let isShow = true
|
||||||
|
Object.keys(conditions).forEach(key => {
|
||||||
|
if (!isShow) return false
|
||||||
|
|
||||||
|
const condition = conditions[key]
|
||||||
|
const fieldVal = this.form[key]
|
||||||
|
? this.form[key]
|
||||||
|
: (this.prefillContent?.[key] || null)
|
||||||
|
if (Array.isArray(condition) && !condition.includes(fieldVal)) {
|
||||||
|
isShow = false
|
||||||
|
} else if (!Array.isArray(condition) && fieldVal !== condition) {
|
||||||
|
isShow = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return isShow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -284,6 +284,33 @@ export default {
|
|||||||
hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 'XenServer', 'LXC', 'Simulator']
|
hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 'XenServer', 'LXC', 'Simulator']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'label.authentication.method',
|
||||||
|
key: 'authmethod',
|
||||||
|
placeHolder: 'message.error.authmethod',
|
||||||
|
required: false,
|
||||||
|
radioGroup: true,
|
||||||
|
defaultValue: 'password',
|
||||||
|
radioOption: [{
|
||||||
|
label: 'label.password',
|
||||||
|
value: 'password'
|
||||||
|
}, {
|
||||||
|
label: 'label.authentication.sshkey',
|
||||||
|
value: 'sshkey',
|
||||||
|
condition: {
|
||||||
|
hypervisor: ['KVM']
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
display: {
|
||||||
|
hypervisor: ['BareMetal', 'Ovm', 'Hyperv', 'KVM', 'XenServer', 'LXC', 'Simulator']
|
||||||
|
},
|
||||||
|
alert: {
|
||||||
|
message: 'message.add.host.sshkey',
|
||||||
|
display: {
|
||||||
|
authmethod: 'sshkey'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'label.password',
|
title: 'label.password',
|
||||||
key: 'hostPassword',
|
key: 'hostPassword',
|
||||||
@ -291,7 +318,8 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
password: true,
|
password: true,
|
||||||
display: {
|
display: {
|
||||||
hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 'XenServer', 'LXC', 'Simulator']
|
hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 'XenServer', 'LXC', 'Simulator'],
|
||||||
|
authmethod: 'password'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1211,6 +1211,7 @@ export default {
|
|||||||
this.addStep('message.adding.host', 'hostResource')
|
this.addStep('message.adding.host', 'hostResource')
|
||||||
|
|
||||||
const hostData = {}
|
const hostData = {}
|
||||||
|
const hostPassword = this.prefillContent?.authmethod !== 'password' ? '' : (this.prefillContent?.hostPassword || null)
|
||||||
hostData.zoneid = this.stepData.zoneReturned.id
|
hostData.zoneid = this.stepData.zoneReturned.id
|
||||||
hostData.podid = this.stepData.podReturned.id
|
hostData.podid = this.stepData.podReturned.id
|
||||||
hostData.clusterid = this.stepData.clusterReturned.id
|
hostData.clusterid = this.stepData.clusterReturned.id
|
||||||
@ -1218,7 +1219,7 @@ export default {
|
|||||||
hostData.clustertype = this.stepData.clusterReturned.clustertype
|
hostData.clustertype = this.stepData.clusterReturned.clustertype
|
||||||
hostData.hosttags = this.prefillContent?.hostTags || null
|
hostData.hosttags = this.prefillContent?.hostTags || null
|
||||||
hostData.username = this.prefillContent?.hostUserName || null
|
hostData.username = this.prefillContent?.hostUserName || null
|
||||||
hostData.password = this.prefillContent?.hostPassword || null
|
hostData.password = hostPassword
|
||||||
const hostname = this.prefillContent?.hostName || null
|
const hostname = this.prefillContent?.hostName || null
|
||||||
let url = null
|
let url = null
|
||||||
if (hostname.indexOf('http://') === -1) {
|
if (hostname.indexOf('http://') === -1) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user