Merge remote-tracking branch 'apache/4.20'

This commit is contained in:
Wei Zhou 2025-09-11 14:04:52 +02:00
commit 70a4503ea1
No known key found for this signature in database
GPG Key ID: 1503DFE7C8226103
11 changed files with 119 additions and 19 deletions

View File

@ -31,20 +31,22 @@ import java.util.stream.Collectors;
import static com.cloud.hypervisor.Hypervisor.HypervisorType.Functionality.DirectDownloadTemplate; import static com.cloud.hypervisor.Hypervisor.HypervisorType.Functionality.DirectDownloadTemplate;
import static com.cloud.hypervisor.Hypervisor.HypervisorType.Functionality.RootDiskSizeOverride; import static com.cloud.hypervisor.Hypervisor.HypervisorType.Functionality.RootDiskSizeOverride;
import static com.cloud.hypervisor.Hypervisor.HypervisorType.Functionality.VmStorageMigration; import static com.cloud.hypervisor.Hypervisor.HypervisorType.Functionality.VmStorageMigration;
import static com.cloud.hypervisor.Hypervisor.HypervisorType.Functionality.VmStorageMigrationWithSnapshots;
public class Hypervisor { public class Hypervisor {
public static class HypervisorType { public static class HypervisorType {
public enum Functionality { public enum Functionality {
DirectDownloadTemplate, DirectDownloadTemplate,
RootDiskSizeOverride, RootDiskSizeOverride,
VmStorageMigration VmStorageMigration,
VmStorageMigrationWithSnapshots
} }
private static final Map<String, HypervisorType> hypervisorTypeMap = new LinkedHashMap<>(); private static final Map<String, HypervisorType> hypervisorTypeMap = new LinkedHashMap<>();
public static final HypervisorType None = new HypervisorType("None"); //for storage hosts public static final HypervisorType None = new HypervisorType("None"); //for storage hosts
public static final HypervisorType XenServer = new HypervisorType("XenServer", ImageFormat.VHD, EnumSet.of(RootDiskSizeOverride, VmStorageMigration)); public static final HypervisorType XenServer = new HypervisorType("XenServer", ImageFormat.VHD, EnumSet.of(RootDiskSizeOverride, VmStorageMigration));
public static final HypervisorType KVM = new HypervisorType("KVM", ImageFormat.QCOW2, EnumSet.of(DirectDownloadTemplate, RootDiskSizeOverride, VmStorageMigration)); public static final HypervisorType KVM = new HypervisorType("KVM", ImageFormat.QCOW2, EnumSet.of(DirectDownloadTemplate, RootDiskSizeOverride, VmStorageMigration));
public static final HypervisorType VMware = new HypervisorType("VMware", ImageFormat.OVA, EnumSet.of(RootDiskSizeOverride, VmStorageMigration)); public static final HypervisorType VMware = new HypervisorType("VMware", ImageFormat.OVA, EnumSet.of(RootDiskSizeOverride, VmStorageMigration, VmStorageMigrationWithSnapshots));
public static final HypervisorType Hyperv = new HypervisorType("Hyperv"); public static final HypervisorType Hyperv = new HypervisorType("Hyperv");
public static final HypervisorType VirtualBox = new HypervisorType("VirtualBox"); public static final HypervisorType VirtualBox = new HypervisorType("VirtualBox");
public static final HypervisorType Parralels = new HypervisorType("Parralels"); public static final HypervisorType Parralels = new HypervisorType("Parralels");

View File

@ -60,6 +60,7 @@ public interface KubernetesCluster extends ControlledEntity, com.cloud.utils.fsm
Stopping("Resources for the Kubernetes cluster are being destroyed"), Stopping("Resources for the Kubernetes cluster are being destroyed"),
Stopped("All resources for the Kubernetes cluster are destroyed, Kubernetes cluster may still have ephemeral resource like persistent volumes provisioned"), Stopped("All resources for the Kubernetes cluster are destroyed, Kubernetes cluster may still have ephemeral resource like persistent volumes provisioned"),
Scaling("Transient state in which resources are either getting scaled up/down"), Scaling("Transient state in which resources are either getting scaled up/down"),
ScalingStoppedCluster("Transient state in which the service offerings of stopped clusters are getting scaled"),
Upgrading("Transient state in which cluster is getting upgraded"), Upgrading("Transient state in which cluster is getting upgraded"),
Importing("Transient state in which additional nodes are added as worker nodes to a cluster"), Importing("Transient state in which additional nodes are added as worker nodes to a cluster"),
RemovingNodes("Transient state in which additional nodes are removed from a cluster"), RemovingNodes("Transient state in which additional nodes are removed from a cluster"),
@ -93,8 +94,11 @@ public interface KubernetesCluster extends ControlledEntity, com.cloud.utils.fsm
s_fsm.addTransition(State.Running, Event.AutoscaleRequested, State.Scaling); s_fsm.addTransition(State.Running, Event.AutoscaleRequested, State.Scaling);
s_fsm.addTransition(State.Running, Event.ScaleUpRequested, State.Scaling); s_fsm.addTransition(State.Running, Event.ScaleUpRequested, State.Scaling);
s_fsm.addTransition(State.Running, Event.ScaleDownRequested, State.Scaling); s_fsm.addTransition(State.Running, Event.ScaleDownRequested, State.Scaling);
s_fsm.addTransition(State.Stopped, Event.ScaleUpRequested, State.ScalingStoppedCluster);
s_fsm.addTransition(State.Scaling, Event.OperationSucceeded, State.Running); s_fsm.addTransition(State.Scaling, Event.OperationSucceeded, State.Running);
s_fsm.addTransition(State.Scaling, Event.OperationFailed, State.Alert); s_fsm.addTransition(State.Scaling, Event.OperationFailed, State.Alert);
s_fsm.addTransition(State.ScalingStoppedCluster, Event.OperationSucceeded, State.Stopped);
s_fsm.addTransition(State.ScalingStoppedCluster, Event.OperationFailed, State.Alert);
s_fsm.addTransition(State.Running, Event.UpgradeRequested, State.Upgrading); s_fsm.addTransition(State.Running, Event.UpgradeRequested, State.Upgrading);
s_fsm.addTransition(State.Upgrading, Event.OperationSucceeded, State.Running); s_fsm.addTransition(State.Upgrading, Event.OperationSucceeded, State.Running);

View File

@ -809,7 +809,7 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
if (ObjectUtils.anyNotNull(uefiEnabled, virtv2vVersion, ovftoolVersion)) { if (ObjectUtils.anyNotNull(uefiEnabled, virtv2vVersion, ovftoolVersion)) {
_hostDao.loadDetails(host); _hostDao.loadDetails(host);
boolean updateNeeded = false; boolean updateNeeded = false;
if (!uefiEnabled.equals(host.getDetails().get(Host.HOST_UEFI_ENABLE))) { if (StringUtils.isNotBlank(uefiEnabled) && !uefiEnabled.equals(host.getDetails().get(Host.HOST_UEFI_ENABLE))) {
host.getDetails().put(Host.HOST_UEFI_ENABLE, uefiEnabled); host.getDetails().put(Host.HOST_UEFI_ENABLE, uefiEnabled);
updateNeeded = true; updateNeeded = true;
} }

View File

@ -1902,6 +1902,20 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
return ""; return "";
} }
public Integer getVlanIdForBridge(final String bridge) {
String pif = matchPifFileInDirectory(bridge);
final File vlanfile = new File("/proc/net/vlan/" + pif);
if (vlanfile.isFile()) {
String vlan = Script.runSimpleBashScript("awk '/VID:/ {print $3}' /proc/net/vlan/" + pif);
try {
return Integer.parseInt(vlan);
} catch (final NumberFormatException e) {
return null;
}
}
return null;
}
static String [] ifNamePatterns = { static String [] ifNamePatterns = {
"^eth", "^eth",
"^bond", "^bond",

View File

@ -132,7 +132,7 @@ public final class LibvirtGetUnmanagedInstancesCommandWrapper extends CommandWra
} }
instance.setPowerState(getPowerState(libvirtComputingResource.getVmState(conn,domain.getName()))); instance.setPowerState(getPowerState(libvirtComputingResource.getVmState(conn,domain.getName())));
instance.setMemory((int) LibvirtComputingResource.getDomainMemory(domain) / 1024); instance.setMemory((int) LibvirtComputingResource.getDomainMemory(domain) / 1024);
instance.setNics(getUnmanagedInstanceNics(parser.getInterfaces())); instance.setNics(getUnmanagedInstanceNics(libvirtComputingResource, parser.getInterfaces()));
instance.setDisks(getUnmanagedInstanceDisks(parser.getDisks(),libvirtComputingResource, conn, domain.getName())); instance.setDisks(getUnmanagedInstanceDisks(parser.getDisks(),libvirtComputingResource, conn, domain.getName()));
instance.setVncPassword(getFormattedVncPassword(parser.getVncPasswd())); instance.setVncPassword(getFormattedVncPassword(parser.getVncPasswd()));
if (parser.getBootType() != null) { if (parser.getBootType() != null) {
@ -169,7 +169,7 @@ public final class LibvirtGetUnmanagedInstancesCommandWrapper extends CommandWra
} }
} }
private List<UnmanagedInstanceTO.Nic> getUnmanagedInstanceNics(List<LibvirtVMDef.InterfaceDef> interfaces) { private List<UnmanagedInstanceTO.Nic> getUnmanagedInstanceNics(LibvirtComputingResource libvirtComputingResource, List<LibvirtVMDef.InterfaceDef> interfaces) {
final ArrayList<UnmanagedInstanceTO.Nic> nics = new ArrayList<>(interfaces.size()); final ArrayList<UnmanagedInstanceTO.Nic> nics = new ArrayList<>(interfaces.size());
int counter = 0; int counter = 0;
for (LibvirtVMDef.InterfaceDef interfaceDef : interfaces) { for (LibvirtVMDef.InterfaceDef interfaceDef : interfaces) {
@ -180,6 +180,10 @@ public final class LibvirtGetUnmanagedInstancesCommandWrapper extends CommandWra
nic.setNetwork(interfaceDef.getDevName()); nic.setNetwork(interfaceDef.getDevName());
nic.setPciSlot(interfaceDef.getSlot().toString()); nic.setPciSlot(interfaceDef.getSlot().toString());
nic.setVlan(interfaceDef.getVlanTag()); nic.setVlan(interfaceDef.getVlanTag());
if (nic.getVlan() == -1
&& LibvirtVMDef.InterfaceDef.GuestNetType.BRIDGE.equals(interfaceDef.getNetType())) {
nic.setVlan(libvirtComputingResource.getVlanIdForBridge(interfaceDef.getBrName()));
}
nics.add(nic); nics.add(nic);
} }
return nics; return nics;

View File

@ -358,7 +358,8 @@ public class KubernetesClusterScaleWorker extends KubernetesClusterResourceModif
private void scaleKubernetesClusterOffering(KubernetesClusterNodeType nodeType, ServiceOffering serviceOffering, private void scaleKubernetesClusterOffering(KubernetesClusterNodeType nodeType, ServiceOffering serviceOffering,
boolean updateNodeOffering, boolean updateClusterOffering) throws CloudRuntimeException { boolean updateNodeOffering, boolean updateClusterOffering) throws CloudRuntimeException {
validateKubernetesClusterScaleOfferingParameters(); validateKubernetesClusterScaleOfferingParameters();
if (!kubernetesCluster.getState().equals(KubernetesCluster.State.Scaling)) { List<KubernetesCluster.State> scalingStates = List.of(KubernetesCluster.State.Scaling, KubernetesCluster.State.ScalingStoppedCluster);
if (!scalingStates.contains(kubernetesCluster.getState())) {
stateTransitTo(kubernetesCluster.getId(), KubernetesCluster.Event.ScaleUpRequested); stateTransitTo(kubernetesCluster.getId(), KubernetesCluster.Event.ScaleUpRequested);
} }
if (KubernetesCluster.State.Created.equals(originalState)) { if (KubernetesCluster.State.Created.equals(originalState)) {
@ -581,6 +582,8 @@ public class KubernetesClusterScaleWorker extends KubernetesClusterResourceModif
scaleKubernetesClusterOffering(nodeType, scalingServiceOffering, updateNodeOffering, updateClusterOffering); scaleKubernetesClusterOffering(nodeType, scalingServiceOffering, updateNodeOffering, updateClusterOffering);
} else if (clusterSizeScalingNeeded) { } else if (clusterSizeScalingNeeded) {
scaleKubernetesClusterSize(nodeType); scaleKubernetesClusterSize(nodeType);
} else {
return true;
} }
} }

View File

@ -7712,7 +7712,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
HypervisorType.getListOfHypervisorsSupportingFunctionality(Functionality.VmStorageMigration))); HypervisorType.getListOfHypervisorsSupportingFunctionality(Functionality.VmStorageMigration)));
} }
if (_vmSnapshotDao.findByVm(vmId).size() > 0) { if (!vm.getHypervisorType().isFunctionalitySupported(Functionality.VmStorageMigrationWithSnapshots) &&
CollectionUtils.isNotEmpty(_vmSnapshotDao.findByVm(vmId))) {
throw new InvalidParameterValueException("VM with VM Snapshots cannot be migrated with storage, please remove all VM snapshots"); throw new InvalidParameterValueException("VM with VM Snapshots cannot be migrated with storage, please remove all VM snapshots");
} }

View File

@ -85,9 +85,11 @@ import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.cloudstack.utils.security.DigestHelper; import org.apache.cloudstack.utils.security.DigestHelper;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
@ -2742,6 +2744,20 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S
return new PingStorageCommand(Host.Type.Storage, id, new HashMap<String, Boolean>()); return new PingStorageCommand(Host.Type.Storage, id, new HashMap<String, Boolean>());
} }
protected void configureStorageNetwork(Map<String, Object> params) {
_storageIp = MapUtils.getString(params, "storageip");
_storageNetmask = (String) params.get("storagenetmask");
_storageGateway = (String) params.get("storagegateway");
if (_storageIp == null && _inSystemVM && _eth1ip != null) {
String eth1Gateway = ObjectUtils.firstNonNull(_localgw, MapUtils.getString(params, "localgw"));
logger.info("Storage network not configured, using management network[ip: {}, netmask: {}, gateway: {}] for storage traffic",
_eth1ip, _eth1mask, eth1Gateway);
_storageIp = _eth1ip;
_storageNetmask = _eth1mask;
_storageGateway = eth1Gateway;
}
}
@Override @Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException { public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
_eth1ip = (String)params.get("eth1ip"); _eth1ip = (String)params.get("eth1ip");
@ -2764,12 +2780,10 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S
_inSystemVM = true; _inSystemVM = true;
} }
_storageIp = (String)params.get("storageip"); configureStorageNetwork(params);
if (_storageIp == null && _inSystemVM) { if (_storageIp == null && _inSystemVM) {
logger.warn("There is no storageip in /proc/cmdline, something wrong!"); logger.warn("No storageip in /proc/cmdline, something wrong! Even fallback to management network did not resolve storage IP.");
} }
_storageNetmask = (String)params.get("storagenetmask");
_storageGateway = (String)params.get("storagegateway");
super.configure(name, params); super.configure(name, params);
_params = params; _params = params;

View File

@ -18,35 +18,38 @@
*/ */
package org.apache.cloudstack.storage.resource; package org.apache.cloudstack.storage.resource;
import org.apache.logging.log4j.Logger;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import org.mockito.Mock;
import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.times;
import java.io.File; import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.EncryptionUtil;
import com.cloud.utils.net.NetUtils;
import org.apache.cloudstack.storage.command.DeleteCommand; import org.apache.cloudstack.storage.command.DeleteCommand;
import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyAnswer; import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyAnswer;
import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyCommand; import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyCommand;
import org.apache.cloudstack.storage.to.SnapshotObjectTO; import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.to.TemplateObjectTO; import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.apache.logging.log4j.Logger;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.Mockito; import org.mockito.Mockito;
import static org.mockito.Mockito.times;
import org.mockito.Spy; import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.agent.api.to.DataStoreTO; import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.EncryptionUtil;
import com.cloud.utils.net.NetUtils;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class NfsSecondaryStorageResourceTest { public class NfsSecondaryStorageResourceTest {
@ -241,4 +244,45 @@ public class NfsSecondaryStorageResourceTest {
Assert.assertEquals(NetUtils.HTTP_PROTO, result); Assert.assertEquals(NetUtils.HTTP_PROTO, result);
} }
@Test
public void configureStorageNetworkSetsStorageNetworkWhenParamsContainValues() {
Map<String, Object> params = new HashMap<>();
String ip = "192.168.1.10";
String netmask = "255.255.255.0";
String gateway = "192.168.1.1";
params.put("storageip", ip);
params.put("storagenetmask", netmask);
params.put("storagegateway", gateway);
resource.configureStorageNetwork(params);
Assert.assertEquals(ip, ReflectionTestUtils.getField(resource, "_storageIp"));
Assert.assertEquals(netmask, ReflectionTestUtils.getField(resource, "_storageNetmask"));
Assert.assertEquals(gateway, ReflectionTestUtils.getField(resource, "_storageGateway"));
}
@Test
public void configureStorageNetworkUsesManagementNetworkWhenStorageIpIsNullAndInSystemVM() {
Map<String, Object> params = new HashMap<>();
resource._inSystemVM = true;
String ip = "10.0.0.10";
String netmask = "255.255.255.0";
String gateway = "10.0.0.1";
ReflectionTestUtils.setField(resource, "_eth1ip", ip);
ReflectionTestUtils.setField(resource, "_eth1mask", netmask);
ReflectionTestUtils.setField(resource, "_localgw", gateway);
resource.configureStorageNetwork(params);
Assert.assertEquals(ip, ReflectionTestUtils.getField(resource, "_storageIp"));
Assert.assertEquals(netmask, ReflectionTestUtils.getField(resource, "_storageNetmask"));
Assert.assertEquals(gateway, ReflectionTestUtils.getField(resource, "_storageGateway"));
}
@Test
public void configureStorageNetworkDoesNotSetStorageNetworkWhenNotInSystemVMAndStorageIpIsNull() {
Map<String, Object> params = new HashMap<>();
resource._inSystemVM = false;
resource.configureStorageNetwork(params);
Assert.assertNull(ReflectionTestUtils.getField(resource, "_storageIp"));
Assert.assertNull(ReflectionTestUtils.getField(resource, "_storageNetmask"));
Assert.assertNull(ReflectionTestUtils.getField(resource, "_storageGateway"));
}
} }

View File

@ -232,6 +232,8 @@ class CsNetfilters(object):
if hook == "input" or hook == "output": if hook == "input" or hook == "output":
CsHelper.execute("nft add rule %s %s %s icmpv6 type { echo-request, echo-reply, \ CsHelper.execute("nft add rule %s %s %s icmpv6 type { echo-request, echo-reply, \
nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept" % (address_family, table, chain)) nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept" % (address_family, table, chain))
elif hook == "forward":
CsHelper.execute("nft add rule %s %s %s ct state established,related accept" % (address_family, table, chain))
def add_ip4_chain(self, address_family, table, chain, hook, action): def add_ip4_chain(self, address_family, table, chain, hook, action):
chain_policy = "" chain_policy = ""

View File

@ -47,7 +47,7 @@
<a-select <a-select
style="width: 100%" style="width: 100%"
v-if="validNetworks[record.id] && validNetworks[record.id].length > 0" v-if="validNetworks[record.id] && validNetworks[record.id].length > 0"
:defaultValue="validNetworks[record.id][0].id" :defaultValue="getDefaultNetwork(record)"
@change="val => handleNetworkChange(record, val)" @change="val => handleNetworkChange(record, val)"
showSearch showSearch
optionFilterProp="label" optionFilterProp="label"
@ -265,7 +265,16 @@ export default {
this.values = {} this.values = {}
this.ipAddresses = {} this.ipAddresses = {}
for (const item of this.items) { for (const item of this.items) {
var network = this.validNetworks[item.id]?.[0] || null let network = null
if (item.vlanid && item.vlanid !== -1) {
const matched = this.validNetworks[item.id].filter(x => Number(x.vlan) === item.vlanid)
if (matched.length > 0) {
network = matched[0]
}
}
if (!network) {
network = this.validNetworks[item.id]?.[0] || null
}
this.values[item.id] = network ? network.id : '' this.values[item.id] = network ? network.id : ''
this.ipAddresses[item.id] = (!network || network.type === 'L2') ? null : 'auto' this.ipAddresses[item.id] = (!network || network.type === 'L2') ? null : 'auto'
this.setIpAddressEnabled(item, network) this.setIpAddressEnabled(item, network)
@ -280,6 +289,9 @@ export default {
} }
this.sendValuesTimed() this.sendValuesTimed()
}, },
getDefaultNetwork (record) {
return this.values[record.id] || this.validNetworks[record.id]?.[0]?.id
},
sendValuesTimed () { sendValuesTimed () {
clearTimeout(this.sendValuesTimer) clearTimeout(this.sendValuesTimer)
this.sendValuesTimer = setTimeout(() => { this.sendValuesTimer = setTimeout(() => {