Merge remote-tracking branch 'apache/4.20'

This commit is contained in:
Wei Zhou 2025-09-09 19:50:22 +02:00
commit 8089d32740
No known key found for this signature in database
GPG Key ID: 1503DFE7C8226103
25 changed files with 390 additions and 88 deletions

View File

@ -209,4 +209,9 @@ public class CreateSnapshotFromVMSnapshotCmd extends BaseAsyncCreateCmd {
} }
return null; return null;
} }
@Override
public Long getApiResourceId() {
return getEntityId();
}
} }

View File

@ -188,6 +188,21 @@ public class BridgeVifDriver extends VifDriverBase {
return vNetId != null && protocol != null && !vNetId.equalsIgnoreCase("untagged"); return vNetId != null && protocol != null && !vNetId.equalsIgnoreCase("untagged");
} }
protected String createStorageVnetBridgeIfNeeded(NicTO nic, String trafficLabel,
String storageBrName) throws InternalErrorException {
if (!Networks.BroadcastDomainType.Storage.equals(nic.getBroadcastType()) || nic.getBroadcastUri() == null) {
return storageBrName;
}
String vNetId = Networks.BroadcastDomainType.getValue(nic.getBroadcastUri());
String protocol = Networks.BroadcastDomainType.Vlan.scheme();
if (!isValidProtocolAndVnetId(vNetId, protocol)) {
return storageBrName;
}
logger.debug(String.format("creating a vNet dev and bridge for %s traffic per traffic label %s",
Networks.TrafficType.Storage.name(), trafficLabel));
return createVnetBr(vNetId, storageBrName, protocol);
}
@Override @Override
public LibvirtVMDef.InterfaceDef plug(NicTO nic, String guestOsType, String nicAdapter, Map<String, String> extraConfig) throws InternalErrorException, LibvirtException { public LibvirtVMDef.InterfaceDef plug(NicTO nic, String guestOsType, String nicAdapter, Map<String, String> extraConfig) throws InternalErrorException, LibvirtException {
@ -254,15 +269,7 @@ public class BridgeVifDriver extends VifDriverBase {
intf.defBridgeNet(_bridges.get("private"), null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter)); intf.defBridgeNet(_bridges.get("private"), null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter));
} else if (nic.getType() == Networks.TrafficType.Storage) { } else if (nic.getType() == Networks.TrafficType.Storage) {
String storageBrName = nic.getName() == null ? _bridges.get("private") : nic.getName(); String storageBrName = nic.getName() == null ? _bridges.get("private") : nic.getName();
if (nic.getBroadcastType() == Networks.BroadcastDomainType.Storage) { storageBrName = createStorageVnetBridgeIfNeeded(nic, trafficLabel, storageBrName);
vNetId = Networks.BroadcastDomainType.getValue(nic.getBroadcastUri());
protocol = Networks.BroadcastDomainType.Vlan.scheme();
}
if (isValidProtocolAndVnetId(vNetId, protocol)) {
logger.debug(String.format("creating a vNet dev and bridge for %s traffic per traffic label %s",
Networks.TrafficType.Storage.name(), trafficLabel));
storageBrName = createVnetBr(vNetId, storageBrName, protocol);
}
intf.defBridgeNet(storageBrName, null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter)); intf.defBridgeNet(storageBrName, null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter));
} }
if (nic.getPxeDisable()) { if (nic.getPxeDisable()) {
@ -295,7 +302,7 @@ public class BridgeVifDriver extends VifDriverBase {
return "brvx-" + vnetId; return "brvx-" + vnetId;
} }
private String createVnetBr(String vNetId, String pifKey, String protocol) throws InternalErrorException { protected String createVnetBr(String vNetId, String pifKey, String protocol) throws InternalErrorException {
String nic = _pifs.get(pifKey); String nic = _pifs.get(pifKey);
if (nic == null || isVxlanOrNetris(protocol)) { if (nic == null || isVxlanOrNetris(protocol)) {
// if not found in bridge map, maybe traffic label refers to pif already? // if not found in bridge map, maybe traffic label refers to pif already?

View File

@ -48,6 +48,7 @@ import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef.RngBackendModel;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef.WatchDogAction; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef.WatchDogAction;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef.WatchDogModel; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef.WatchDogModel;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestDef;
public class LibvirtDomainXMLParser { public class LibvirtDomainXMLParser {
protected Logger logger = LogManager.getLogger(getClass()); protected Logger logger = LogManager.getLogger(getClass());
@ -63,6 +64,8 @@ public class LibvirtDomainXMLParser {
private LibvirtVMDef.CpuTuneDef cpuTuneDef; private LibvirtVMDef.CpuTuneDef cpuTuneDef;
private LibvirtVMDef.CpuModeDef cpuModeDef; private LibvirtVMDef.CpuModeDef cpuModeDef;
private String name; private String name;
private GuestDef.BootType bootType;
private GuestDef.BootMode bootMode;
public boolean parseDomainXML(String domXML) { public boolean parseDomainXML(String domXML) {
DocumentBuilder builder; DocumentBuilder builder;
@ -388,6 +391,7 @@ public class LibvirtDomainXMLParser {
} }
extractCpuTuneDef(rootElement); extractCpuTuneDef(rootElement);
extractCpuModeDef(rootElement); extractCpuModeDef(rootElement);
extractBootDef(rootElement);
return true; return true;
} catch (ParserConfigurationException e) { } catch (ParserConfigurationException e) {
logger.debug(e.toString()); logger.debug(e.toString());
@ -516,6 +520,14 @@ public class LibvirtDomainXMLParser {
return cpuModeDef; return cpuModeDef;
} }
public GuestDef.BootType getBootType() {
return bootType;
}
public GuestDef.BootMode getBootMode() {
return bootMode;
}
private void extractCpuTuneDef(final Element rootElement) { private void extractCpuTuneDef(final Element rootElement) {
NodeList cpuTunesList = rootElement.getElementsByTagName("cputune"); NodeList cpuTunesList = rootElement.getElementsByTagName("cputune");
if (cpuTunesList.getLength() > 0) { if (cpuTunesList.getLength() > 0) {
@ -569,4 +581,26 @@ public class LibvirtDomainXMLParser {
} }
} }
} }
protected void extractBootDef(final Element rootElement) {
bootType = GuestDef.BootType.BIOS;
bootMode = GuestDef.BootMode.LEGACY;
Element osElement = (Element) rootElement.getElementsByTagName("os").item(0);
if (osElement == null) {
return;
}
NodeList loaderList = osElement.getElementsByTagName("loader");
if (loaderList.getLength() == 0) {
return;
}
Element loader = (Element) loaderList.item(0);
String type = loader.getAttribute("type");
String secure = loader.getAttribute("secure");
if ("pflash".equalsIgnoreCase(type) || loader.getTextContent().toLowerCase().contains("uefi")) {
bootType = GuestDef.BootType.UEFI;
}
if ("yes".equalsIgnoreCase(secure)) {
bootMode = GuestDef.BootMode.SECURE;
}
}
} }

View File

@ -65,7 +65,7 @@ public class LibvirtVMDef {
} }
} }
enum BootType { public enum BootType {
UEFI("UEFI"), BIOS("BIOS"); UEFI("UEFI"), BIOS("BIOS");
String _type; String _type;
@ -80,7 +80,7 @@ public class LibvirtVMDef {
} }
} }
enum BootMode { public enum BootMode {
LEGACY("LEGACY"), SECURE("SECURE"); LEGACY("LEGACY"), SECURE("SECURE");
String _mode; String _mode;

View File

@ -135,6 +135,12 @@ public final class LibvirtGetUnmanagedInstancesCommandWrapper extends CommandWra
instance.setNics(getUnmanagedInstanceNics(parser.getInterfaces())); instance.setNics(getUnmanagedInstanceNics(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) {
instance.setBootType(parser.getBootType().toString());
}
if (parser.getBootMode() != null) {
instance.setBootMode(parser.getBootMode().toString());
}
return instance; return instance;
} catch (Exception e) { } catch (Exception e) {

View File

@ -16,24 +16,29 @@
// under the License. // under the License.
package com.cloud.hypervisor.kvm.resource; package com.cloud.hypervisor.kvm.resource;
import java.net.URI;
import java.net.URISyntaxException;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.NicTO;
import com.cloud.exception.InternalErrorException;
import com.cloud.network.Networks; import com.cloud.network.Networks;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class BridgeVifDriverTest { public class BridgeVifDriverTest {
private BridgeVifDriver driver; private static final String BRIDGE_NAME = "cloudbr1";
@Before @Spy
public void setUp() throws Exception { @InjectMocks
driver = new BridgeVifDriver(); private BridgeVifDriver driver = new BridgeVifDriver();
}
@Test @Test
public void isBroadcastTypeVlanOrVxlan() { public void isBroadcastTypeVlanOrVxlan() {
@ -58,4 +63,41 @@ public class BridgeVifDriverTest {
Assert.assertTrue(driver.isValidProtocolAndVnetId("123", "vlan")); Assert.assertTrue(driver.isValidProtocolAndVnetId("123", "vlan"));
Assert.assertTrue(driver.isValidProtocolAndVnetId("456", "vxlan")); Assert.assertTrue(driver.isValidProtocolAndVnetId("456", "vxlan"));
} }
@Test
public void createStorageVnetBridgeIfNeededReturnsStorageBrNameWhenBroadcastTypeIsNotStorageButValidValues() throws InternalErrorException {
NicTO nic = new NicTO();
nic.setBroadcastType(Networks.BroadcastDomainType.Storage);
int vlan = 123;
String newBridge = "br-" + vlan;
nic.setBroadcastUri(Networks.BroadcastDomainType.Storage.toUri(vlan));
Mockito.doReturn(newBridge).when(driver).createVnetBr(Mockito.anyString(), Mockito.anyString(), Mockito.anyString());
String result = driver.createStorageVnetBridgeIfNeeded(nic, "trafficLabel", BRIDGE_NAME);
Assert.assertEquals(newBridge, result);
}
@Test
public void createStorageVnetBridgeIfNeededReturnsStorageBrNameWhenBroadcastTypeIsNotStorage() throws InternalErrorException {
NicTO nic = new NicTO();
nic.setBroadcastType(Networks.BroadcastDomainType.Vlan);
String result = driver.createStorageVnetBridgeIfNeeded(nic, "trafficLabel", BRIDGE_NAME);
Assert.assertEquals(BRIDGE_NAME, result);
}
@Test
public void createStorageVnetBridgeIfNeededReturnsStorageBrNameWhenBroadcastUriIsNull() throws InternalErrorException {
NicTO nic = new NicTO();
nic.setBroadcastType(Networks.BroadcastDomainType.Storage);
String result = driver.createStorageVnetBridgeIfNeeded(nic, "trafficLabel", BRIDGE_NAME);
Assert.assertEquals(BRIDGE_NAME, result);
}
@Test
public void createStorageVnetBridgeIfNeededCreatesVnetBridgeWhenUntaggedVlan() throws InternalErrorException, URISyntaxException {
NicTO nic = new NicTO();
nic.setBroadcastType(Networks.BroadcastDomainType.Storage);
nic.setBroadcastUri(new URI(Networks.BroadcastDomainType.Storage.scheme() + "://untagged"));
String result = driver.createStorageVnetBridgeIfNeeded(nic, "trafficLabel", BRIDGE_NAME);
Assert.assertEquals(BRIDGE_NAME, result);
}
} }

View File

@ -20,8 +20,13 @@
package com.cloud.hypervisor.kvm.resource; package com.cloud.hypervisor.kvm.resource;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.List; import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef;
@ -31,10 +36,15 @@ import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.apache.cloudstack.utils.qemu.QemuObject; import org.apache.cloudstack.utils.qemu.QemuObject;
import org.apache.cloudstack.utils.security.ParserUtils;
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.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class LibvirtDomainXMLParserTest extends TestCase { public class LibvirtDomainXMLParserTest extends TestCase {
@ -386,4 +396,84 @@ public class LibvirtDomainXMLParserTest extends TestCase {
Assert.assertEquals("CPU cores count is parsed", 4, libvirtDomainXMLParser.getCpuModeDef().getCoresPerSocket()); Assert.assertEquals("CPU cores count is parsed", 4, libvirtDomainXMLParser.getCpuModeDef().getCoresPerSocket());
Assert.assertEquals("CPU threads count is parsed", 2, libvirtDomainXMLParser.getCpuModeDef().getThreadsPerCore()); Assert.assertEquals("CPU threads count is parsed", 2, libvirtDomainXMLParser.getCpuModeDef().getThreadsPerCore());
} }
private LibvirtDomainXMLParser parseElementFromXML(String xml) {
LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser();
DocumentBuilder builder;
try {
builder = ParserUtils.getSaferDocumentBuilderFactory().newDocumentBuilder();
InputSource is = new InputSource();
is.setCharacterStream(new StringReader(xml));
Document doc = builder.parse(is);
Element element = doc.getDocumentElement();
parser.extractBootDef(element);
} catch (ParserConfigurationException | IOException | SAXException e) {
Assert.fail("Failed to parse XML: " + e.getMessage());
}
return parser;
}
@Test
public void extractBootDefParsesUEFISecureBootCorrectly() {
String xml = "<domain type='kvm'>" +
"<os>" +
"<loader type='pflash' secure='yes'>/path/to/uefi/loader</loader>" +
"</os>" +
"</domain>";
LibvirtDomainXMLParser parser = parseElementFromXML(xml);
assertEquals(LibvirtVMDef.GuestDef.BootType.UEFI, parser.getBootType());
assertEquals(LibvirtVMDef.GuestDef.BootMode.SECURE, parser.getBootMode());
}
@Test
public void extractBootDefParsesUEFILegacyBootCorrectly() {
String xml = "<domain type='kvm'>" +
"<os>" +
"<loader type='pflash' secure='no'>/path/to/uefi/loader</loader>" +
"</os>" +
"</domain>";
LibvirtDomainXMLParser parser = parseElementFromXML(xml);
assertEquals(LibvirtVMDef.GuestDef.BootType.UEFI, parser.getBootType());
assertEquals(LibvirtVMDef.GuestDef.BootMode.LEGACY, parser.getBootMode());
}
@Test
public void extractBootDefDefaultsToBIOSLegacyWhenNoLoaderPresent() {
String xml = "<domain type='kvm'>" +
"<os>" +
"<type arch='x86_64'>hvm</type>" +
"</os>" +
"</domain>";
LibvirtDomainXMLParser parser = parseElementFromXML(xml);
assertEquals(LibvirtVMDef.GuestDef.BootType.BIOS, parser.getBootType());
assertEquals(LibvirtVMDef.GuestDef.BootMode.LEGACY, parser.getBootMode());
}
@Test
public void extractBootDefHandlesEmptyOSSection() {
String xml = "<domain type='kvm'>" +
"<os></os>" +
"</domain>";
LibvirtDomainXMLParser parser = parseElementFromXML(xml);
assertEquals(LibvirtVMDef.GuestDef.BootType.BIOS, parser.getBootType());
assertEquals(LibvirtVMDef.GuestDef.BootMode.LEGACY, parser.getBootMode());
}
@Test
public void extractBootDefHandlesMissingOSSection() {
String xml = "<domain type='kvm'></domain>";
LibvirtDomainXMLParser parser = parseElementFromXML(xml);
assertEquals(LibvirtVMDef.GuestDef.BootType.BIOS, parser.getBootType());
assertEquals(LibvirtVMDef.GuestDef.BootMode.LEGACY, parser.getBootMode());
}
} }

View File

@ -416,18 +416,33 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
logTransitStateAndThrow(logLevel, message, null, null, ex); logTransitStateAndThrow(logLevel, message, null, null, ex);
} }
private boolean isKubernetesServiceNetworkOfferingConfigured(DataCenter zone) { private boolean isKubernetesServiceNetworkOfferingConfigured(DataCenter zone, Long networkId) {
// Check network offering // Check network offering
String networkOfferingName = KubernetesClusterNetworkOffering.value(); String networkOfferingName = KubernetesClusterNetworkOffering.value();
if (networkOfferingName == null || networkOfferingName.isEmpty()) { if (StringUtils.isEmpty(networkOfferingName) && networkId == null) {
logger.warn(String.format("Global setting %s is empty. Admin has not yet specified the network offering to be used for provisioning isolated network for the cluster", KubernetesClusterNetworkOffering.key())); logger.warn("Global setting: {} is empty. Admin has not yet specified the network offering to be used for provisioning isolated network for the cluster nor has a pre-created network been passed", KubernetesClusterNetworkOffering.key());
return false; return false;
} }
NetworkOfferingVO networkOffering = networkOfferingDao.findByUniqueName(networkOfferingName); NetworkOfferingVO networkOffering = null;
if (networkOffering == null) { if (networkId != null) {
logger.warn(String.format("Unable to find the network offering %s to be used for provisioning Kubernetes cluster", networkOfferingName)); NetworkVO network = networkDao.findById(networkId);
return false; if (network == null) {
logger.warn("Unable to find the network with ID: {} passed for the Kubernetes cluster", networkId);
return false;
}
networkOffering = networkOfferingDao.findById(network.getNetworkOfferingId());
if (networkOffering == null) {
logger.warn("Unable to find the network offering of the network: {} ({}) to be used for provisioning Kubernetes cluster", network.getName(), network.getUuid());
return false;
}
} else if (StringUtils.isNotEmpty(networkOfferingName)) {
networkOffering = networkOfferingDao.findByUniqueName(networkOfferingName);
if (networkOffering == null) {
logger.warn("Unable to find the network offering: {} to be used for provisioning Kubernetes cluster", networkOfferingName);
return false;
}
} }
if (networkOffering.getState() == NetworkOffering.State.Disabled) { if (networkOffering.getState() == NetworkOffering.State.Disabled) {
logger.warn("Network offering: {} is not enabled", networkOffering); logger.warn("Network offering: {} is not enabled", networkOffering);
return false; return false;
@ -462,8 +477,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
return true; return true;
} }
private boolean isKubernetesServiceConfigured(DataCenter zone) { private boolean isKubernetesServiceConfigured(DataCenter zone, Long networkId) {
if (!isKubernetesServiceNetworkOfferingConfigured(zone)) { if (!isKubernetesServiceNetworkOfferingConfigured(zone, networkId)) {
return false; return false;
} }
return true; return true;
@ -1018,7 +1033,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
DataCenter zone = validateAndGetZoneForKubernetesCreateParameters(zoneId, networkId); DataCenter zone = validateAndGetZoneForKubernetesCreateParameters(zoneId, networkId);
if (!isKubernetesServiceConfigured(zone)) { if (!isKubernetesServiceConfigured(zone, networkId)) {
throw new CloudRuntimeException("Kubernetes service has not been configured properly to provision Kubernetes clusters"); throw new CloudRuntimeException("Kubernetes service has not been configured properly to provision Kubernetes clusters");
} }

View File

@ -32,6 +32,8 @@ import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@ -545,6 +547,7 @@ public class InternalLoadBalancerVMManagerImpl extends ManagerBase implements In
} }
@Override @Override
@ActionEvent(eventType = EventTypes.EVENT_INTERNAL_LB_VM_STOP, eventDescription = "stopping internal LB VM", async = true)
public VirtualRouter stopInternalLbVm(final long vmId, final boolean forced, final Account caller, final long callerUserId) throws ConcurrentOperationException, ResourceUnavailableException { public VirtualRouter stopInternalLbVm(final long vmId, final boolean forced, final Account caller, final long callerUserId) throws ConcurrentOperationException, ResourceUnavailableException {
final DomainRouterVO internalLbVm = _internalLbVmDao.findById(vmId); final DomainRouterVO internalLbVm = _internalLbVmDao.findById(vmId);
if (internalLbVm == null || internalLbVm.getRole() != Role.INTERNAL_LB_VM) { if (internalLbVm == null || internalLbVm.getRole() != Role.INTERNAL_LB_VM) {
@ -974,6 +977,7 @@ public class InternalLoadBalancerVMManagerImpl extends ManagerBase implements In
} }
@Override @Override
@ActionEvent(eventType = EventTypes.EVENT_INTERNAL_LB_VM_START, eventDescription = "starting internal LB VM", async = true)
public VirtualRouter startInternalLbVm(final long internalLbVmId, final Account caller, final long callerUserId) throws StorageUnavailableException, InsufficientCapacityException, public VirtualRouter startInternalLbVm(final long internalLbVmId, final Account caller, final long callerUserId) throws StorageUnavailableException, InsufficientCapacityException,
ConcurrentOperationException, ResourceUnavailableException { ConcurrentOperationException, ResourceUnavailableException {

View File

@ -16,6 +16,9 @@
// under the License. // under the License.
package org.apache.cloudstack.internallbvmmgr; package org.apache.cloudstack.internallbvmmgr;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.ArgumentMatchers.nullable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@ -24,13 +27,15 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import com.cloud.event.ActionEventUtils;
import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.network.lb.InternalLoadBalancerVMService; import org.apache.cloudstack.network.lb.InternalLoadBalancerVMService;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers; import org.mockito.BDDMockito;
import org.mockito.MockedStatic;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ -82,6 +87,8 @@ public class InternalLBVMServiceTest extends TestCase {
@Inject @Inject
AccountDao _accountDao; AccountDao _accountDao;
private MockedStatic<ActionEventUtils> actionEventUtilsMocked;
long validVmId = 1L; long validVmId = 1L;
long nonExistingVmId = 2L; long nonExistingVmId = 2L;
long nonInternalLbVmId = 3L; long nonInternalLbVmId = 3L;
@ -105,7 +112,7 @@ public class InternalLBVMServiceTest extends TestCase {
Mockito.when(_accountMgr.getSystemUser()).thenReturn(new UserVO(1)); Mockito.when(_accountMgr.getSystemUser()).thenReturn(new UserVO(1));
Mockito.when(_accountMgr.getSystemAccount()).thenReturn(new AccountVO(2)); Mockito.when(_accountMgr.getSystemAccount()).thenReturn(new AccountVO(2));
Mockito.when(_accountDao.findByIdIncludingRemoved(ArgumentMatchers.anyLong())).thenReturn(new AccountVO(2)); Mockito.when(_accountDao.findByIdIncludingRemoved(anyLong())).thenReturn(new AccountVO(2));
CallContext.register(_accountMgr.getSystemUser(), _accountMgr.getSystemAccount()); CallContext.register(_accountMgr.getSystemUser(), _accountMgr.getSystemAccount());
final DomainRouterVO validVm = final DomainRouterVO validVm =
@ -120,11 +127,16 @@ public class InternalLBVMServiceTest extends TestCase {
Mockito.when(_domainRouterDao.findById(validVmId)).thenReturn(validVm); Mockito.when(_domainRouterDao.findById(validVmId)).thenReturn(validVm);
Mockito.when(_domainRouterDao.findById(nonExistingVmId)).thenReturn(null); Mockito.when(_domainRouterDao.findById(nonExistingVmId)).thenReturn(null);
Mockito.when(_domainRouterDao.findById(nonInternalLbVmId)).thenReturn(nonInternalLbVm); Mockito.when(_domainRouterDao.findById(nonInternalLbVmId)).thenReturn(nonInternalLbVm);
actionEventUtilsMocked = Mockito.mockStatic(ActionEventUtils.class);
BDDMockito.given(ActionEventUtils.onStartedActionEvent(anyLong(), anyLong(), anyString(), anyString(), anyLong(), anyString(), anyBoolean(), anyLong()))
.willReturn(1L);
} }
@Override @Override
@After @After
public void tearDown() { public void tearDown() {
actionEventUtilsMocked.close();
CallContext.unregister(); CallContext.unregister();
} }

View File

@ -121,6 +121,10 @@ public class PrimeraAdapter implements ProviderAdapter {
public void disconnect() { public void disconnect() {
logger.info("PrimeraAdapter:disconnect(): closing session"); logger.info("PrimeraAdapter:disconnect(): closing session");
try { try {
//Delete session safely without triggering refreshSession
if (key != null && _client != null) {
logout();
}
_client.close(); _client.close();
} catch (IOException e) { } catch (IOException e) {
logger.warn("PrimeraAdapter:refreshSession(): Error closing client connection", e); logger.warn("PrimeraAdapter:refreshSession(): Error closing client connection", e);
@ -130,6 +134,40 @@ public class PrimeraAdapter implements ProviderAdapter {
} }
return; return;
} }
/**
* Delete session directly without going through refreshSession to avoid infinite recursion
*/
private void logout() {
CloseableHttpResponse response = null;
try {
logger.debug("PrimeraAdapter:logout(): Delete session directly");
HttpDelete request = new HttpDelete(url + "/credentials/" + key);
request.addHeader("Content-Type", "application/json");
request.addHeader("Accept", "application/json");
request.addHeader("X-HP3PAR-WSAPI-SessionKey", key);
response = (CloseableHttpResponse) _client.execute(request);
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200 || statusCode == 404) {
logger.debug("PrimeraAdapter:logout(): Session deleted successfully or was already expired");
} else if (statusCode == 401 || statusCode == 403) {
logger.warn("PrimeraAdapter:logout(): Session already invalid or expired during deletion");
} else {
logger.warn("PrimeraAdapter:logout(): Unexpected response when deleting session: {}", statusCode);
}
} catch (IOException e) {
logger.warn("PrimeraAdapter:logout(): Error deleting session: {}", e.getMessage());
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
logger.debug("PrimeraAdapter:logout(): Error closing response from session deletion", e);
}
}
}
}
@Override @Override
public ProviderVolume create(ProviderAdapterContext context, ProviderAdapterDataObject dataIn, public ProviderVolume create(ProviderAdapterContext context, ProviderAdapterDataObject dataIn,

View File

@ -54,7 +54,9 @@ public class TotpUserTwoFactorAuthenticator extends AdapterBase implements UserT
logger.info("2FA matches user's input"); logger.info("2FA matches user's input");
return; return;
} }
throw new CloudTwoFactorAuthenticationException("two-factor authentication code provided is invalid"); String msg = "two-factor authentication code provided is invalid";
logger.error(msg);
throw new CloudTwoFactorAuthenticationException(msg);
} }
private String get2FAKey(UserAccount userAccount) { private String get2FAKey(UserAccount userAccount) {

View File

@ -172,10 +172,10 @@ public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation<Templa
} }
} else if (template.getDownloadState() == Status.BYPASSED) { } else if (template.getDownloadState() == Status.BYPASSED) {
templateStatus = "Bypassed Secondary Storage"; templateStatus = "Bypassed Secondary Storage";
}else if (template.getErrorString()==null){ } else if (template.getErrorString() == null) {
templateStatus = template.getTemplateState().toString(); templateStatus = template.getTemplateState().toString();
}else { } else {
templateStatus = template.getErrorString(); templateStatus = template.getErrorString().trim();
} }
} else if (template.getDownloadState() == Status.DOWNLOADED) { } else if (template.getDownloadState() == Status.DOWNLOADED) {
templateStatus = "Download Complete"; templateStatus = "Download Complete";

View File

@ -5370,10 +5370,6 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
vlan.getVlanTag())); vlan.getVlanTag()));
} }
} }
if (NetUtils.isSameIsolationId(vlanId, vlan.getVlanTag()) && !vlanIp6Gateway.equals(vlan.getIp6Gateway())) {
throw new InvalidParameterValueException(String.format("The IP range with tag: %s has already been added with gateway %s. Please specify a different tag.",
vlan.getVlanTag(), vlan.getIp6Gateway()));
}
} }
} }

View File

@ -1399,6 +1399,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
} }
@Override @Override
@DB
@ActionEvent(eventType = EventTypes.EVENT_MAINTENANCE_CANCEL, eventDescription = "cancel maintenance for host", async = true)
public Host cancelMaintenance(final CancelHostMaintenanceCmd cmd) { public Host cancelMaintenance(final CancelHostMaintenanceCmd cmd) {
final Long hostId = cmd.getId(); final Long hostId = cmd.getId();
@ -1422,6 +1424,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
} }
@Override @Override
@DB
@ActionEvent(eventType = EventTypes.EVENT_HOST_RECONNECT, eventDescription = "reconnecting host", async = true)
public Host reconnectHost(ReconnectHostCmd cmd) throws AgentUnavailableException { public Host reconnectHost(ReconnectHostCmd cmd) throws AgentUnavailableException {
Long hostId = cmd.getId(); Long hostId = cmd.getId();
@ -1628,6 +1632,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
} }
@Override @Override
@DB
@ActionEvent(eventType = EventTypes.EVENT_MAINTENANCE_PREPARE, eventDescription = "prepare maintenance for host", async = true)
public Host maintain(final PrepareForHostMaintenanceCmd cmd) { public Host maintain(final PrepareForHostMaintenanceCmd cmd) {
final Long hostId = cmd.getId(); final Long hostId = cmd.getId();
final HostVO host = _hostDao.findById(hostId); final HostVO host = _hostDao.findById(hostId);

View File

@ -622,6 +622,8 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
} }
@Override @Override
@DB
@ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_CREATE, eventDescription = "creating snapshot from VM snapshot", async = true)
public Snapshot backupSnapshotFromVmSnapshot(Long snapshotId, Long vmId, Long volumeId, Long vmSnapshotId) { public Snapshot backupSnapshotFromVmSnapshot(Long snapshotId, Long vmId, Long volumeId, Long vmSnapshotId) {
VMInstanceVO vm = _vmDao.findById(vmId); VMInstanceVO vm = _vmDao.findById(vmId);
if (vm == null) { if (vm == null) {

View File

@ -18,6 +18,29 @@
*/ */
package org.apache.cloudstack.vm.schedule; package org.apache.cloudstack.vm.schedule;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.TimeZone;
import javax.inject.Inject;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.command.user.vm.CreateVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.DeleteVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.ListVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.UpdateVMScheduleCmd;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.vm.schedule.dao.VMScheduleDao;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.support.CronExpression;
import com.cloud.api.query.MutualExclusiveIdsManagerBase; import com.cloud.api.query.MutualExclusiveIdsManagerBase;
import com.cloud.event.ActionEvent; import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes; import com.cloud.event.EventTypes;
@ -32,26 +55,6 @@ import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.UserVmManager; import com.cloud.vm.UserVmManager;
import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.command.user.vm.CreateVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.DeleteVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.ListVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.UpdateVMScheduleCmd;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.vm.schedule.dao.VMScheduleDao;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.support.CronExpression;
import javax.inject.Inject;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.TimeZone;
public class VMScheduleManagerImpl extends MutualExclusiveIdsManagerBase implements VMScheduleManager, PluggableService { public class VMScheduleManagerImpl extends MutualExclusiveIdsManagerBase implements VMScheduleManager, PluggableService {
@ -205,6 +208,9 @@ public class VMScheduleManagerImpl extends MutualExclusiveIdsManagerBase impleme
Date cmdStartDate = cmd.getStartDate(); Date cmdStartDate = cmd.getStartDate();
Date cmdEndDate = cmd.getEndDate(); Date cmdEndDate = cmd.getEndDate();
Boolean enabled = cmd.getEnabled(); Boolean enabled = cmd.getEnabled();
final String originalTimeZone = vmSchedule.getTimeZone();
final Date originalStartDate = vmSchedule.getStartDate();
final Date originalEndDate = vmSchedule.getEndDate();
TimeZone timeZone; TimeZone timeZone;
String timeZoneId; String timeZoneId;
@ -231,7 +237,13 @@ public class VMScheduleManagerImpl extends MutualExclusiveIdsManagerBase impleme
startDate = Date.from(DateUtil.getZoneDateTime(cmdStartDate, timeZone.toZoneId()).toInstant()); startDate = Date.from(DateUtil.getZoneDateTime(cmdStartDate, timeZone.toZoneId()).toInstant());
} }
validateStartDateEndDate(Objects.requireNonNullElse(startDate, DateUtils.addMinutes(new Date(), 1)), endDate, timeZone); if (ObjectUtils.anyNotNull(cmdStartDate, cmdEndDate, cmdTimeZone) &&
(!Objects.equals(originalTimeZone, timeZoneId) ||
!Objects.equals(originalStartDate, startDate) ||
!Objects.equals(originalEndDate, endDate))) {
validateStartDateEndDate(Objects.requireNonNullElse(startDate, DateUtils.addMinutes(new Date(), 1)),
endDate, timeZone);
}
if (enabled != null) { if (enabled != null) {
vmSchedule.setEnabled(enabled); vmSchedule.setEnabled(enabled);

View File

@ -33,7 +33,7 @@
"headless": true, "headless": true,
"http_directory": "http", "http_directory": "http",
"iso_checksum": "sha512:892cf1185a214d16ff62a18c6b89cdcd58719647c99916f6214bfca6f9915275d727b666c0b8fbf022c425ef18647e9759974abf7fc440431c39b50c296a98d3", "iso_checksum": "sha512:892cf1185a214d16ff62a18c6b89cdcd58719647c99916f6214bfca6f9915275d727b666c0b8fbf022c425ef18647e9759974abf7fc440431c39b50c296a98d3",
"iso_url": "https://cdimage.debian.org/mirror/cdimage/release/12.11.0/arm64/iso-cd/debian-12.11.0-arm64-netinst.iso", "iso_url": "https://cdimage.debian.org/mirror/cdimage/archive/12.11.0/arm64/iso-cd/debian-12.11.0-arm64-netinst.iso",
"net_device": "virtio-net", "net_device": "virtio-net",
"output_directory": "../dist", "output_directory": "../dist",
"qemu_binary": "qemu-system-aarch64", "qemu_binary": "qemu-system-aarch64",

View File

@ -32,7 +32,7 @@
"headless": true, "headless": true,
"http_directory": "http", "http_directory": "http",
"iso_checksum": "sha512:892cf1185a214d16ff62a18c6b89cdcd58719647c99916f6214bfca6f9915275d727b666c0b8fbf022c425ef18647e9759974abf7fc440431c39b50c296a98d3", "iso_checksum": "sha512:892cf1185a214d16ff62a18c6b89cdcd58719647c99916f6214bfca6f9915275d727b666c0b8fbf022c425ef18647e9759974abf7fc440431c39b50c296a98d3",
"iso_url": "https://cdimage.debian.org/mirror/cdimage/release/12.11.0/arm64/iso-cd/debian-12.11.0-arm64-netinst.iso", "iso_url": "https://cdimage.debian.org/mirror/cdimage/archive/12.11.0/arm64/iso-cd/debian-12.11.0-arm64-netinst.iso",
"net_device": "virtio-net", "net_device": "virtio-net",
"output_directory": "../dist", "output_directory": "../dist",
"qemu_binary": "qemu-system-aarch64", "qemu_binary": "qemu-system-aarch64",

View File

@ -28,7 +28,7 @@
"headless": true, "headless": true,
"http_directory": "http", "http_directory": "http",
"iso_checksum": "sha512:0921d8b297c63ac458d8a06f87cd4c353f751eb5fe30fd0d839ca09c0833d1d9934b02ee14bbd0c0ec4f8917dde793957801ae1af3c8122cdf28dde8f3c3e0da", "iso_checksum": "sha512:0921d8b297c63ac458d8a06f87cd4c353f751eb5fe30fd0d839ca09c0833d1d9934b02ee14bbd0c0ec4f8917dde793957801ae1af3c8122cdf28dde8f3c3e0da",
"iso_url": "https://cdimage.debian.org/mirror/cdimage/release/12.11.0/amd64/iso-cd/debian-12.11.0-amd64-netinst.iso", "iso_url": "https://cdimage.debian.org/mirror/cdimage/archive/12.11.0/amd64/iso-cd/debian-12.11.0-amd64-netinst.iso",
"net_device": "virtio-net", "net_device": "virtio-net",
"output_directory": "../dist", "output_directory": "../dist",
"qemuargs": [ "qemuargs": [

View File

@ -663,10 +663,14 @@
/> />
</template> </template>
<template v-if="column.key === 'domain'"> <template v-if="column.key === 'domain'">
<router-link <span v-if="record.domainid && $store.getters.userInfo.roletype !== 'User'">
v-if="record.domainid && !record.domainid.toString().includes(',') && $store.getters.userInfo.roletype !== 'User'" <template v-for="(id, idx) in record.domainid.split(',')" :key="id">
:to="{ path: '/domain/' + record.domainid, query: { tab: 'details' } }" <router-link :to="{ path: '/domain/' + id, query: { tab: 'details' } }">
>{{ text }}</router-link> {{ record.domain.split(',')[idx] || id }}
</router-link>
<span v-if="idx < record.domainid.split(',').length - 1">, </span>
</template>
</span>
<span v-else>{{ text }}</span> <span v-else>{{ text }}</span>
</template> </template>
<template v-if="column.key === 'domainpath'"> <template v-if="column.key === 'domainpath'">

View File

@ -496,9 +496,17 @@ const user = {
}).catch(() => { }).catch(() => {
resolve() resolve()
}).finally(() => { }).finally(() => {
const paths = ['/', '/client']
const hostname = window.location.hostname
const domains = [undefined, hostname, `.${hostname}`]
Object.keys(Cookies.get()).forEach(cookieName => { Object.keys(Cookies.get()).forEach(cookieName => {
Cookies.remove(cookieName) paths.forEach(path => {
Cookies.remove(cookieName, { path: '/client' }) domains.forEach(domain => {
const options = { path }
if (domain) options.domain = domain
Cookies.remove(cookieName, options)
})
})
}) })
}) })
}) })

View File

@ -1879,11 +1879,13 @@ export default {
const query = Object.assign({}, this.$route.query) const query = Object.assign({}, this.$route.query)
delete query.templatefilter delete query.templatefilter
delete query.isofilter delete query.isofilter
delete query.account
delete query.domainid
delete query.state delete query.state
delete query.annotationfilter delete query.annotationfilter
delete query.leased delete query.leased
if (!['publicip'].includes(this.$route.name)) {
delete query.account
delete query.domainid
}
if (this.$route.name === 'template') { if (this.$route.name === 'template') {
query.templatefilter = filter query.templatefilter = filter
} else if (this.$route.name === 'iso') { } else if (this.$route.name === 'iso') {

View File

@ -228,8 +228,8 @@ export default {
getAPI('listDiskOfferings', { getAPI('listDiskOfferings', {
id: this.selectedOffering.diskofferingid id: this.selectedOffering.diskofferingid
}).then(response => { }).then(response => {
const diskOfferings = response.listdiskofferingsresponse.diskoffering || [] const diskOfferings = response?.listdiskofferingsresponse?.diskoffering || []
if (this.diskOfferings) { if (diskOfferings?.length > 0) {
this.selectedDiskOffering = diskOfferings[0] this.selectedDiskOffering = diskOfferings[0]
} }
}).catch(error => { }).catch(error => {

View File

@ -85,7 +85,7 @@
<template #label> <template #label>
<tooltip-label :title="$t('label.directdownload')" :tooltip="apiParams.directdownload.description"/> <tooltip-label :title="$t('label.directdownload')" :tooltip="apiParams.directdownload.description"/>
</template> </template>
<a-switch v-model:checked="form.directdownload"/> <a-switch v-model:checked="form.directdownload" @change="handleDirectDownloadChange"/>
</a-form-item> </a-form-item>
<a-form-item ref="checksum" name="checksum"> <a-form-item ref="checksum" name="checksum">
@ -110,7 +110,7 @@
}" }"
:loading="zoneLoading" :loading="zoneLoading"
:placeholder="apiParams.zoneid.description"> :placeholder="apiParams.zoneid.description">
<a-select-option :value="opt.id" v-for="opt in zones" :key="opt.id" :label="opt.name || opt.description"> <a-select-option :value="opt.id" v-for="opt in zoneList" :key="opt.id" :label="opt.name || opt.description">
<span> <span>
<resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/> <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px" /> <global-outlined v-else style="margin-right: 5px" />
@ -361,17 +361,18 @@ export default {
}, },
created () { created () {
this.initForm() this.initForm()
this.zones = [] this.initZones()
if (this.$store.getters.userInfo.roletype === 'Admin' && this.currentForm === 'Create') {
this.zones = [
{
id: '-1',
name: this.$t('label.all.zone')
}
]
}
this.fetchData() this.fetchData()
}, },
computed: {
zoneList () {
let filteredZones = this.zones
if (!this.form.directdownload) {
filteredZones = this.zones.filter(zone => zone.type !== 'Edge')
}
return filteredZones
}
},
methods: { methods: {
initForm () { initForm () {
this.formRef = ref() this.formRef = ref()
@ -390,6 +391,17 @@ export default {
ostypeid: [{ required: true, message: this.$t('message.error.select') }] ostypeid: [{ required: true, message: this.$t('message.error.select') }]
}) })
}, },
initZones () {
this.zones = []
if (this.$store.getters.userInfo.roletype === 'Admin' && this.currentForm === 'Create') {
this.zones = [
{
id: '-1',
name: this.$t('label.all.zone')
}
]
}
},
fetchData () { fetchData () {
this.fetchZoneData() this.fetchZoneData()
this.fetchOsType() this.fetchOsType()
@ -412,11 +424,10 @@ export default {
const listZones = json.listzonesresponse.zone const listZones = json.listzonesresponse.zone
if (listZones) { if (listZones) {
this.zones = this.zones.concat(listZones) this.zones = this.zones.concat(listZones)
this.zones = this.zones.filter(zone => zone.type !== 'Edge')
} }
}).finally(() => { }).finally(() => {
this.zoneLoading = false this.zoneLoading = false
this.form.zoneid = (this.zones[0].id ? this.zones[0].id : '') this.form.zoneid = this.zoneList?.[0]?.id || ''
}) })
}, },
fetchOsType () { fetchOsType () {
@ -467,6 +478,12 @@ export default {
this.fileList = newFileList this.fileList = newFileList
this.form.file = undefined this.form.file = undefined
}, },
handleDirectDownloadChange () {
if (this.form.zoneid && this.zoneList.find(entry => entry.id === this.form.zoneid)) {
return
}
this.form.zoneid = this.zoneList?.[0]?.id || ''
},
beforeUpload (file) { beforeUpload (file) {
this.fileList = [file] this.fileList = [file]
this.form.file = file this.form.file = file
@ -531,7 +548,7 @@ export default {
} }
switch (key) { switch (key) {
case 'zoneid': case 'zoneid':
var zone = this.zones.filter(zone => zone.id === input) var zone = this.zoneList.filter(zone => zone.id === input)
params[key] = zone[0].id params[key] = zone[0].id
break break
case 'ostypeid': case 'ostypeid':