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;
}
@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");
}
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
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));
} else if (nic.getType() == Networks.TrafficType.Storage) {
String storageBrName = nic.getName() == null ? _bridges.get("private") : nic.getName();
if (nic.getBroadcastType() == Networks.BroadcastDomainType.Storage) {
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);
}
storageBrName = createStorageVnetBridgeIfNeeded(nic, trafficLabel, storageBrName);
intf.defBridgeNet(storageBrName, null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter));
}
if (nic.getPxeDisable()) {
@ -295,7 +302,7 @@ public class BridgeVifDriver extends VifDriverBase {
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);
if (nic == null || isVxlanOrNetris(protocol)) {
// 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.WatchDogAction;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef.WatchDogModel;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestDef;
public class LibvirtDomainXMLParser {
protected Logger logger = LogManager.getLogger(getClass());
@ -63,6 +64,8 @@ public class LibvirtDomainXMLParser {
private LibvirtVMDef.CpuTuneDef cpuTuneDef;
private LibvirtVMDef.CpuModeDef cpuModeDef;
private String name;
private GuestDef.BootType bootType;
private GuestDef.BootMode bootMode;
public boolean parseDomainXML(String domXML) {
DocumentBuilder builder;
@ -388,6 +391,7 @@ public class LibvirtDomainXMLParser {
}
extractCpuTuneDef(rootElement);
extractCpuModeDef(rootElement);
extractBootDef(rootElement);
return true;
} catch (ParserConfigurationException e) {
logger.debug(e.toString());
@ -516,6 +520,14 @@ public class LibvirtDomainXMLParser {
return cpuModeDef;
}
public GuestDef.BootType getBootType() {
return bootType;
}
public GuestDef.BootMode getBootMode() {
return bootMode;
}
private void extractCpuTuneDef(final Element rootElement) {
NodeList cpuTunesList = rootElement.getElementsByTagName("cputune");
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");
String _type;
@ -80,7 +80,7 @@ public class LibvirtVMDef {
}
}
enum BootMode {
public enum BootMode {
LEGACY("LEGACY"), SECURE("SECURE");
String _mode;

View File

@ -135,6 +135,12 @@ public final class LibvirtGetUnmanagedInstancesCommandWrapper extends CommandWra
instance.setNics(getUnmanagedInstanceNics(parser.getInterfaces()));
instance.setDisks(getUnmanagedInstanceDisks(parser.getDisks(),libvirtComputingResource, conn, domain.getName()));
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;
} catch (Exception e) {

View File

@ -16,24 +16,29 @@
// under the License.
package com.cloud.hypervisor.kvm.resource;
import java.net.URI;
import java.net.URISyntaxException;
import org.junit.Assert;
import org.junit.Before;
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.exception.InternalErrorException;
import com.cloud.network.Networks;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class BridgeVifDriverTest {
private BridgeVifDriver driver;
private static final String BRIDGE_NAME = "cloudbr1";
@Before
public void setUp() throws Exception {
driver = new BridgeVifDriver();
}
@Spy
@InjectMocks
private BridgeVifDriver driver = new BridgeVifDriver();
@Test
public void isBroadcastTypeVlanOrVxlan() {
@ -58,4 +63,41 @@ public class BridgeVifDriverTest {
Assert.assertTrue(driver.isValidProtocolAndVnetId("123", "vlan"));
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;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
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.DiskDef;
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 org.apache.cloudstack.utils.qemu.QemuObject;
import org.apache.cloudstack.utils.security.ParserUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
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)
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 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);
}
private boolean isKubernetesServiceNetworkOfferingConfigured(DataCenter zone) {
private boolean isKubernetesServiceNetworkOfferingConfigured(DataCenter zone, Long networkId) {
// Check network offering
String networkOfferingName = KubernetesClusterNetworkOffering.value();
if (networkOfferingName == null || networkOfferingName.isEmpty()) {
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()));
if (StringUtils.isEmpty(networkOfferingName) && networkId == null) {
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;
}
NetworkOfferingVO networkOffering = networkOfferingDao.findByUniqueName(networkOfferingName);
if (networkOffering == null) {
logger.warn(String.format("Unable to find the network offering %s to be used for provisioning Kubernetes cluster", networkOfferingName));
return false;
NetworkOfferingVO networkOffering = null;
if (networkId != null) {
NetworkVO network = networkDao.findById(networkId);
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) {
logger.warn("Network offering: {} is not enabled", networkOffering);
return false;
@ -462,8 +477,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
return true;
}
private boolean isKubernetesServiceConfigured(DataCenter zone) {
if (!isKubernetesServiceNetworkOfferingConfigured(zone)) {
private boolean isKubernetesServiceConfigured(DataCenter zone, Long networkId) {
if (!isKubernetesServiceNetworkOfferingConfigured(zone, networkId)) {
return false;
}
return true;
@ -1018,7 +1033,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
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");
}

View File

@ -32,6 +32,8 @@ import java.util.Map;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@ -545,6 +547,7 @@ public class InternalLoadBalancerVMManagerImpl extends ManagerBase implements In
}
@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 {
final DomainRouterVO internalLbVm = _internalLbVmDao.findById(vmId);
if (internalLbVm == null || internalLbVm.getRole() != Role.INTERNAL_LB_VM) {
@ -974,6 +977,7 @@ public class InternalLoadBalancerVMManagerImpl extends ManagerBase implements In
}
@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,
ConcurrentOperationException, ResourceUnavailableException {

View File

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

View File

@ -121,6 +121,10 @@ public class PrimeraAdapter implements ProviderAdapter {
public void disconnect() {
logger.info("PrimeraAdapter:disconnect(): closing session");
try {
//Delete session safely without triggering refreshSession
if (key != null && _client != null) {
logout();
}
_client.close();
} catch (IOException e) {
logger.warn("PrimeraAdapter:refreshSession(): Error closing client connection", e);
@ -130,6 +134,40 @@ public class PrimeraAdapter implements ProviderAdapter {
}
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
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");
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) {

View File

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

View File

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

View File

@ -622,6 +622,8 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
}
@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) {
VMInstanceVO vm = _vmDao.findById(vmId);
if (vm == null) {

View File

@ -18,6 +18,29 @@
*/
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.event.ActionEvent;
import com.cloud.event.EventTypes;
@ -32,26 +55,6 @@ import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.UserVmManager;
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 {
@ -205,6 +208,9 @@ public class VMScheduleManagerImpl extends MutualExclusiveIdsManagerBase impleme
Date cmdStartDate = cmd.getStartDate();
Date cmdEndDate = cmd.getEndDate();
Boolean enabled = cmd.getEnabled();
final String originalTimeZone = vmSchedule.getTimeZone();
final Date originalStartDate = vmSchedule.getStartDate();
final Date originalEndDate = vmSchedule.getEndDate();
TimeZone timeZone;
String timeZoneId;
@ -231,7 +237,13 @@ public class VMScheduleManagerImpl extends MutualExclusiveIdsManagerBase impleme
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) {
vmSchedule.setEnabled(enabled);

View File

@ -33,7 +33,7 @@
"headless": true,
"http_directory": "http",
"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",
"output_directory": "../dist",
"qemu_binary": "qemu-system-aarch64",

View File

@ -32,7 +32,7 @@
"headless": true,
"http_directory": "http",
"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",
"output_directory": "../dist",
"qemu_binary": "qemu-system-aarch64",

View File

@ -28,7 +28,7 @@
"headless": true,
"http_directory": "http",
"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",
"output_directory": "../dist",
"qemuargs": [

View File

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

View File

@ -496,9 +496,17 @@ const user = {
}).catch(() => {
resolve()
}).finally(() => {
const paths = ['/', '/client']
const hostname = window.location.hostname
const domains = [undefined, hostname, `.${hostname}`]
Object.keys(Cookies.get()).forEach(cookieName => {
Cookies.remove(cookieName)
Cookies.remove(cookieName, { path: '/client' })
paths.forEach(path => {
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)
delete query.templatefilter
delete query.isofilter
delete query.account
delete query.domainid
delete query.state
delete query.annotationfilter
delete query.leased
if (!['publicip'].includes(this.$route.name)) {
delete query.account
delete query.domainid
}
if (this.$route.name === 'template') {
query.templatefilter = filter
} else if (this.$route.name === 'iso') {

View File

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

View File

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