mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-10-26 08:42:29 +01:00 
			
		
		
		
	Merge remote-tracking branch 'apache/4.20'
This commit is contained in:
		
						commit
						8089d32740
					
				| @ -209,4 +209,9 @@ public class CreateSnapshotFromVMSnapshotCmd extends BaseAsyncCreateCmd { | |||||||
|         } |         } | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public Long getApiResourceId() { | ||||||
|  |         return getEntityId(); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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? | ||||||
|  | |||||||
| @ -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; | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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; | ||||||
|  | |||||||
| @ -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) { | ||||||
|  | |||||||
| @ -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); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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()); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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 (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) { |             if (networkOffering == null) { | ||||||
|             logger.warn(String.format("Unable to find the network offering %s to be used for provisioning Kubernetes cluster", networkOfferingName)); |                 logger.warn("Unable to find the network offering of the network: {} ({}) to be used for provisioning Kubernetes cluster", network.getName(), network.getUuid()); | ||||||
|                 return false; |                 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"); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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 { | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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, | ||||||
|  | |||||||
| @ -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) { | ||||||
|  | |||||||
| @ -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"; | ||||||
|  | |||||||
| @ -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())); |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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); | ||||||
|  | |||||||
| @ -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) { | ||||||
|  | |||||||
| @ -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); | ||||||
|  | |||||||
| @ -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", | ||||||
|  | |||||||
| @ -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", | ||||||
|  | |||||||
| @ -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": [ | ||||||
|  | |||||||
| @ -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'"> | ||||||
|  | |||||||
| @ -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) | ||||||
|  |               }) | ||||||
|  |             }) | ||||||
|           }) |           }) | ||||||
|         }) |         }) | ||||||
|       }) |       }) | ||||||
|  | |||||||
| @ -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') { | ||||||
|  | |||||||
| @ -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 => { | ||||||
|  | |||||||
| @ -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': | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user