diff --git a/api/src/main/java/com/cloud/cpu/CPU.java b/api/src/main/java/com/cloud/cpu/CPU.java index 4e1b9f5a5011..3016e542db65 100644 --- a/api/src/main/java/com/cloud/cpu/CPU.java +++ b/api/src/main/java/com/cloud/cpu/CPU.java @@ -16,52 +16,55 @@ // under the License. package com.cloud.cpu; -import com.cloud.utils.exception.CloudRuntimeException; import org.apache.commons.lang3.StringUtils; -import java.util.LinkedHashMap; -import java.util.Map; - public class CPU { + public enum CPUArch { + x86("i686", 32), + amd64("x86_64", 64), + arm64("aarch64", 64); - public static final String archX86Identifier = "i686"; - public static final String archX86_64Identifier = "x86_64"; - public static final String archARM64Identifier = "aarch64"; - - public static class CPUArch { - private static final Map cpuArchMap = new LinkedHashMap<>(); - - public static final CPUArch archX86 = new CPUArch(archX86Identifier, 32); - public static final CPUArch amd64 = new CPUArch(archX86_64Identifier, 64); - public static final CPUArch arm64 = new CPUArch(archARM64Identifier, 64); + private final String type; + private final int bits; - private String type; - private int bits; - - public CPUArch(String type, int bits) { + CPUArch(String type, int bits) { this.type = type; this.bits = bits; - cpuArchMap.put(type, this); + } + + public static CPUArch getDefault() { + return amd64; } public String getType() { - return this.type; + return type; } public int getBits() { - return this.bits; + return bits; } public static CPUArch fromType(String type) { if (StringUtils.isBlank(type)) { - return amd64; + return getDefault(); + } + for (CPUArch arch : values()) { + if (arch.type.equals(type)) { + return arch; + } + } + throw new IllegalArgumentException("Unsupported arch type: " + type); + } + + public static String getTypesAsCSV() { + StringBuilder sb = new StringBuilder(); + for (CPUArch arch : values()) { + sb.append(arch.getType()).append(","); } - switch (type) { - case archX86Identifier: return archX86; - case archX86_64Identifier: return amd64; - case archARM64Identifier: return arm64; - default: throw new CloudRuntimeException(String.format("Unsupported arch type: %s", type)); + if (sb.length() > 0) { + sb.setLength(sb.length() - 1); } + return sb.toString(); } } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClustersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClustersCmd.java index 67d0678410cf..362913a11388 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClustersCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClustersCmd.java @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.List; - import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseListCmd; @@ -28,7 +27,9 @@ import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.PodResponse; import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.commons.lang3.StringUtils; +import com.cloud.cpu.CPU; import com.cloud.org.Cluster; import com.cloud.utils.Pair; @@ -68,6 +69,11 @@ public class ListClustersCmd extends BaseListCmd { @Parameter(name = ApiConstants.SHOW_CAPACITIES, type = CommandType.BOOLEAN, description = "flag to display the capacity of the clusters") private Boolean showCapacities; + @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, + description = "CPU arch of the clusters", + since = "4.20.1") + private String arch; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -112,6 +118,10 @@ public Boolean getShowCapacities() { return showCapacities; } + public CPU.CPUArch getArch() { + return StringUtils.isBlank(arch) ? null : CPU.CPUArch.fromType(arch); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java index af87bbf33bb0..099ffc0bde43 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.Map; - import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; @@ -34,7 +33,9 @@ import org.apache.cloudstack.api.response.PodResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.commons.lang3.StringUtils; +import com.cloud.cpu.CPU; import com.cloud.exception.InvalidParameterValueException; import com.cloud.host.Host; import com.cloud.hypervisor.Hypervisor.HypervisorType; @@ -105,6 +106,9 @@ public class ListHostsCmd extends BaseListCmd { @Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, description = "hypervisor type of host: XenServer,KVM,VMware,Hyperv,BareMetal,Simulator") private String hypervisor; + @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, description = "CPU Arch of the host", since = "4.20.1") + private String arch; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -189,6 +193,10 @@ public String getHostOutOfBandManagementPowerState() { return outOfBandManagementPowerState; } + public CPU.CPUArch getArch() { + return StringUtils.isBlank(arch) ? null : CPU.CPUArch.fromType(arch); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ListRoutersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ListRoutersCmd.java index e0cdc0dcf807..9e34c05ce216 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ListRoutersCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ListRoutersCmd.java @@ -16,8 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.admin.router; -import org.apache.commons.lang.BooleanUtils; - import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; @@ -32,7 +30,10 @@ import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.VpcResponse; import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.commons.lang.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import com.cloud.cpu.CPU; import com.cloud.network.router.VirtualRouter.Role; import com.cloud.vm.VirtualMachine; @@ -86,6 +87,11 @@ public class ListRoutersCmd extends BaseListProjectAndAccountResourcesCmd { description = "if true is passed for this parameter, also fetch last executed health check results for the router. Default is false") private Boolean fetchHealthCheckResults; + @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, + description = "CPU arch of the router", + since = "4.20.1") + private String arch; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -146,6 +152,10 @@ public boolean shouldFetchHealthCheckResults() { return BooleanUtils.isTrue(fetchHealthCheckResults); } + public CPU.CPUArch getArch() { + return StringUtils.isBlank(arch) ? null : CPU.CPUArch.fromType(arch); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/ListSystemVMsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/ListSystemVMsCmd.java index e8e5ee0ebad6..13113e17ea4d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/ListSystemVMsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/ListSystemVMsCmd.java @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.List; - import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; @@ -31,7 +30,9 @@ import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.SystemVmResponse; import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.commons.lang3.StringUtils; +import com.cloud.cpu.CPU; import com.cloud.utils.Pair; import com.cloud.vm.VirtualMachine; @@ -74,6 +75,11 @@ public class ListSystemVMsCmd extends BaseListCmd { since = "3.0.1") private Long storageId; + @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, + description = "CPU arch of the system VM", + since = "4.20.1") + private String arch; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -110,6 +116,10 @@ public Long getStorageId() { return storageId; } + public CPU.CPUArch getArch() { + return StringUtils.isBlank(arch) ? null : CPU.CPUArch.fromType(arch); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java index ac180b6d4569..c205b3577326 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java @@ -46,9 +46,11 @@ import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.VpcResponse; import org.apache.cloudstack.api.response.ZoneResponse; -import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import com.cloud.cpu.CPU; import com.cloud.exception.InvalidParameterValueException; import com.cloud.server.ResourceIcon; import com.cloud.server.ResourceTag; @@ -153,6 +155,11 @@ public class ListVMsCmd extends BaseListRetrieveOnlyResourceCountCmd implements @Parameter(name = ApiConstants.USER_DATA_ID, type = CommandType.UUID, entityType = UserDataResponse.class, required = false, description = "the instances by userdata", since = "4.20.1") private Long userdataId; + @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, + description = "CPU arch of the VM", + since = "4.20.1") + private String arch; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -292,6 +299,10 @@ public Boolean getVnf() { return isVnf; } + public CPU.CPUArch getArch() { + return StringUtils.isBlank(arch) ? null : CPU.CPUArch.fromType(arch); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java index b23d0f4b5276..4b5e886a16e2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java @@ -245,6 +245,10 @@ public class DomainRouterResponse extends BaseResponseWithAnnotations implements @Param(description = "the version of the code / software in the router") private String softwareVersion; + @SerializedName(ApiConstants.ARCH) + @Param(description = "CPU arch of the router", since = "4.20.1") + private String arch; + public DomainRouterResponse() { nics = new LinkedHashSet(); } @@ -518,4 +522,8 @@ public String getSoftwareVersion() { public void setSoftwareVersion(String softwareVersion) { this.softwareVersion = softwareVersion; } + + public void setArch(String arch) { + this.arch = arch; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java index 31a8b731491c..be9f14f5060f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java @@ -186,6 +186,10 @@ public class SystemVmResponse extends BaseResponseWithAnnotations { @Param(description = "the name of the service offering of the system virtual machine.") private String serviceOfferingName; + @SerializedName(ApiConstants.ARCH) + @Param(description = "CPU arch of the system VM", since = "4.20.1") + private String arch; + @Override public String getObjectId() { return this.getId(); @@ -490,4 +494,8 @@ public String getServiceOfferingName() { public void setServiceOfferingName(String serviceOfferingName) { this.serviceOfferingName = serviceOfferingName; } + + public void setArch(String arch) { + this.arch = arch; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java index 1f4b493fba2f..2f519ccf3a5c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java @@ -31,13 +31,13 @@ import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponseWithTagInformation; import org.apache.cloudstack.api.EntityReference; +import org.apache.commons.collections.CollectionUtils; import com.cloud.network.router.VirtualRouter; import com.cloud.serializer.Param; import com.cloud.uservm.UserVm; import com.cloud.vm.VirtualMachine; import com.google.gson.annotations.SerializedName; -import org.apache.commons.collections.CollectionUtils; @SuppressWarnings("unused") @EntityReference(value = {VirtualMachine.class, UserVm.class, VirtualRouter.class}) @@ -396,6 +396,10 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co @Param(description = "User VM type", since = "4.20.0") private String vmType; + @SerializedName(ApiConstants.ARCH) + @Param(description = "CPU arch of the VM", since = "4.20.1") + private String arch; + public UserVmResponse() { securityGroupList = new LinkedHashSet<>(); nics = new TreeSet<>(Comparator.comparingInt(x -> Integer.parseInt(x.getDeviceId()))); @@ -1169,4 +1173,12 @@ public String getVmType() { public void setIpAddress(String ipAddress) { this.ipAddress = ipAddress; } + + public String getArch() { + return arch; + } + + public void setArch(String arch) { + this.arch = arch; + } } diff --git a/api/src/test/java/com/cloud/cpu/CPUTest.java b/api/src/test/java/com/cloud/cpu/CPUTest.java new file mode 100644 index 000000000000..dfedf21864cc --- /dev/null +++ b/api/src/test/java/com/cloud/cpu/CPUTest.java @@ -0,0 +1,67 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.cpu; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class CPUTest { + @Test + public void testCPUArchGetType() { + assertEquals("i686", CPU.CPUArch.x86.getType()); + assertEquals("x86_64", CPU.CPUArch.amd64.getType()); + assertEquals("aarch64", CPU.CPUArch.arm64.getType()); + } + + @Test + public void testCPUArchGetBits() { + assertEquals(32, CPU.CPUArch.x86.getBits()); + assertEquals(64, CPU.CPUArch.amd64.getBits()); + assertEquals(64, CPU.CPUArch.arm64.getBits()); + } + + @Test + public void testCPUArchFromTypeWithValidValues() { + assertEquals(CPU.CPUArch.x86, CPU.CPUArch.fromType("i686")); + assertEquals(CPU.CPUArch.amd64, CPU.CPUArch.fromType("x86_64")); + assertEquals(CPU.CPUArch.arm64, CPU.CPUArch.fromType("aarch64")); + } + + @Test + public void testCPUArchFromTypeWithDefaultForBlankOrNull() { + assertEquals(CPU.CPUArch.amd64, CPU.CPUArch.fromType("")); + assertEquals(CPU.CPUArch.amd64, CPU.CPUArch.fromType(" ")); + assertEquals(CPU.CPUArch.amd64, CPU.CPUArch.fromType(null)); + } + + @Test + public void testCPUArchFromTypeWithInvalidValue() { + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + CPU.CPUArch.fromType("unsupported"); + }); + assertTrue(exception.getMessage().contains("Unsupported arch type: unsupported")); + } + + @Test + public void testCPUArchGetTypesAsCSV() { + String expectedCSV = "i686,x86_64,aarch64"; + assertEquals(expectedCSV, CPU.CPUArch.getTypesAsCSV()); + } +} diff --git a/debian/cloudstack-management.postinst b/debian/cloudstack-management.postinst index fadb7e57eca1..d5d50a4718c3 100755 --- a/debian/cloudstack-management.postinst +++ b/debian/cloudstack-management.postinst @@ -56,6 +56,8 @@ if [ "$1" = configure ]; then chmod 0640 ${CONFDIR}/${DBPROPS} chgrp cloud ${CONFDIR}/${DBPROPS} chown -R cloud:cloud /var/log/cloudstack/management + chown -R cloud:cloud /usr/share/cloudstack-management/templates + find /usr/share/cloudstack-management/templates -type d -exec chmod 0770 {} \; ln -sf ${CONFDIR}/log4j-cloud.xml ${CONFDIR}/log4j2.xml diff --git a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java index 94c73d8f4d60..238a78e89aff 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -309,4 +309,8 @@ static String getHypervisorHostname(String name) { Map getDiskOfferingSuitabilityForVm(long vmId, List diskOfferingIds); + void checkDeploymentPlan(VirtualMachine virtualMachine, VirtualMachineTemplate template, + ServiceOffering serviceOffering, Account systemAccount, DeploymentPlan plan) + throws InsufficientServerCapacityException; + } diff --git a/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java b/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java index 8eb6462b4838..89dc4badcbcf 100755 --- a/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java +++ b/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java @@ -28,6 +28,7 @@ import com.cloud.agent.api.StartupRoutingCommand; import com.cloud.agent.api.VgpuTypesInfo; import com.cloud.agent.api.to.GPUDeviceTO; +import com.cloud.cpu.CPU; import com.cloud.dc.DataCenterVO; import com.cloud.dc.HostPodVO; import com.cloud.dc.PodCluster; @@ -61,6 +62,17 @@ public interface ResourceManager extends ResourceService, Configurable { + "To force-stop VMs, choose 'ForceStop' strategy", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.Select, "Error,Migration,ForceStop"); + ConfigKey SystemVmPreferredArchitecture = new ConfigKey<>( + String.class, + "system.vm.preferred.architecture", + "Advanced", + CPU.CPUArch.getDefault().getType(), + "Preferred architecture for the system VMs including virtual routers", + true, + ConfigKey.Scope.Zone, null, null, null, null, null, + ConfigKey.Kind.Select, + "," + CPU.CPUArch.getTypesAsCSV()); + /** * Register a listener for different types of resource life cycle events. * There can only be one type of listener per type of host. diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index 472fe148a5d7..f55848334934 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -6084,4 +6084,18 @@ public Map getDiskOfferingSuitabilityForVm(long vmId, List } return result; } + + @Override + public void checkDeploymentPlan(VirtualMachine virtualMachine, VirtualMachineTemplate template, + ServiceOffering serviceOffering, Account systemAccount, DeploymentPlan plan) + throws InsufficientServerCapacityException { + final VirtualMachineProfileImpl vmProfile = + new VirtualMachineProfileImpl(virtualMachine, template, serviceOffering, systemAccount, null); + DeployDestination destination = + _dpMgr.planDeployment(vmProfile, plan, new DeploymentPlanner.ExcludeList(), null); + if (destination == null) { + throw new InsufficientServerCapacityException(String.format("Unable to create a deployment for %s", + vmProfile), DataCenter.class, plan.getDataCenterId(), areAffinityGroupsAssociated(vmProfile)); + } + } } diff --git a/engine/schema/pom.xml b/engine/schema/pom.xml index 431c2fd2b03c..e8fbf74571df 100644 --- a/engine/schema/pom.xml +++ b/engine/schema/pom.xml @@ -165,6 +165,15 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + + true + + + diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDao.java b/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDao.java index bf12abd5114c..69b5f0e146e8 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDao.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDao.java @@ -18,11 +18,11 @@ import java.util.List; import java.util.Map; -import java.util.Set; import com.cloud.cpu.CPU; import com.cloud.dc.ClusterVO; import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.utils.Pair; import com.cloud.utils.db.GenericDao; public interface ClusterDao extends GenericDao { @@ -30,13 +30,11 @@ public interface ClusterDao extends GenericDao { ClusterVO findBy(String name, long podId); - List listByHyTypeWithoutGuid(String hyType); - List listByZoneId(long zoneId); List getAvailableHypervisorInZone(Long zoneId); - Set getDistinctAvailableHypervisorsAcrossClusters(); + List> listDistinctHypervisorsArchAcrossClusters(Long zoneId); List listByDcHyType(long dcId, String hyType); diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java index af6b83976430..59614b547456 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java @@ -21,10 +21,8 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; import javax.inject.Inject; @@ -39,6 +37,7 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.org.Grouping; import com.cloud.org.Managed; +import com.cloud.utils.Pair; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.GenericSearchBuilder; import com.cloud.utils.db.JoinBuilder; @@ -148,14 +147,6 @@ public ClusterVO findBy(String name, long podId) { return findOneBy(sc); } - @Override - public List listByHyTypeWithoutGuid(String hyType) { - SearchCriteria sc = HyTypeWithoutGuidSearch.create(); - sc.setParameters("hypervisorType", hyType); - - return listBy(sc); - } - @Override public List listByDcHyType(long dcId, String hyType) { SearchCriteria sc = ZoneHyTypeSearch.create(); @@ -178,8 +169,19 @@ public List getAvailableHypervisorInZone(Long zoneId) { } @Override - public Set getDistinctAvailableHypervisorsAcrossClusters() { - return new HashSet<>(getAvailableHypervisorInZone(null)); + public List> listDistinctHypervisorsArchAcrossClusters(Long zoneId) { + SearchBuilder sb = createSearchBuilder(); + sb.select(null, Func.DISTINCT_PAIR, sb.entity().getHypervisorType(), sb.entity().getArch()); + sb.and("zoneId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); + sb.done(); + SearchCriteria sc = sb.create(); + if (zoneId != null) { + sc.setParameters("zoneId", zoneId); + } + final List clusters = search(sc, null); + return clusters.stream() + .map(c -> new Pair<>(c.getHypervisorType(), c.getArch())) + .collect(Collectors.toList()); } @Override diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java b/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java index 003bf4a34a62..ecab7ced7313 100644 --- a/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java +++ b/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java @@ -19,6 +19,7 @@ import java.util.Date; import java.util.List; +import com.cloud.cpu.CPU; import com.cloud.host.Host; import com.cloud.host.Host.Type; import com.cloud.host.HostVO; @@ -194,5 +195,7 @@ List findHostIdsByZoneClusterResourceStateTypeAndHypervisorType(final Long List listDistinctHypervisorTypes(final Long zoneId); + List> listDistinctHypervisorArchTypes(final Long zoneId); + List listByIds(final List ids); } diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java b/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java index 94a16497e878..6b2cfe4f5f63 100644 --- a/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java @@ -42,6 +42,7 @@ import com.cloud.cluster.agentlb.HostTransferMapVO; import com.cloud.cluster.agentlb.dao.HostTransferMapDao; import com.cloud.configuration.ManagementServiceConfiguration; +import com.cloud.cpu.CPU; import com.cloud.dc.ClusterVO; import com.cloud.dc.dao.ClusterDao; import com.cloud.gpu.dao.HostGpuGroupsDao; @@ -1756,6 +1757,24 @@ public List listDistinctHypervisorTypes(final Long zoneId) { return customSearch(sc, null); } + @Override + public List> listDistinctHypervisorArchTypes(final Long zoneId) { + SearchBuilder sb = createSearchBuilder(); + sb.select(null, Func.DISTINCT_PAIR, sb.entity().getHypervisorType(), sb.entity().getArch()); + sb.and("type", sb.entity().getType(), SearchCriteria.Op.EQ); + sb.and("zoneId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("type", Type.Routing); + if (zoneId != null) { + sc.setParameters("zoneId", zoneId); + } + final List hosts = search(sc, null); + return hosts.stream() + .map(h -> new Pair<>(h.getHypervisorType(), h.getArch())) + .collect(Collectors.toList()); + } + @Override public List listByIds(List ids) { if (CollectionUtils.isEmpty(ids)) { diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDao.java index 3ac514530ced..0b40366a8665 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDao.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Map; +import com.cloud.cpu.CPU; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.storage.Storage; import com.cloud.storage.VMTemplateVO; @@ -73,9 +74,13 @@ public interface VMTemplateDao extends GenericDao, StateDao< VMTemplateVO findSystemVMReadyTemplate(long zoneId, HypervisorType hypervisorType); + List findSystemVMReadyTemplates(long zoneId, HypervisorType hypervisorType, String preferredArch); + VMTemplateVO findRoutingTemplate(HypervisorType type, String templateName); - VMTemplateVO findLatestTemplateByTypeAndHypervisor(HypervisorType hypervisorType, Storage.TemplateType type); + List findRoutingTemplates(HypervisorType type, String templateName, String preferredArch); + + VMTemplateVO findLatestTemplateByTypeAndHypervisorAndArch(HypervisorType hypervisorType, CPU.CPUArch arch, Storage.TemplateType type); public Long countTemplatesForAccount(long accountId); @@ -87,7 +92,7 @@ public interface VMTemplateDao extends GenericDao, StateDao< List listByParentTemplatetId(long parentTemplatetId); - VMTemplateVO findLatestTemplateByName(String name); + VMTemplateVO findLatestTemplateByName(String name, CPU.CPUArch arch); List findTemplatesLinkedToUserdata(long userdataId); diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java index 7513848536b2..12c00a3209ae 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java @@ -18,7 +18,9 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -28,8 +30,10 @@ import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; +import com.cloud.cpu.CPU; import com.cloud.dc.dao.DataCenterDao; import com.cloud.domain.dao.DomainDao; import com.cloud.host.Host; @@ -47,6 +51,7 @@ import com.cloud.tags.ResourceTagVO; import com.cloud.tags.dao.ResourceTagDao; import com.cloud.template.VirtualMachineTemplate; +import com.cloud.utils.Pair; import com.cloud.utils.db.DB; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; @@ -111,6 +116,7 @@ public VMTemplateDaoImpl() { LatestTemplateByHypervisorTypeSearch = createSearchBuilder(); LatestTemplateByHypervisorTypeSearch.and("hypervisorType", LatestTemplateByHypervisorTypeSearch.entity().getHypervisorType(), SearchCriteria.Op.EQ); LatestTemplateByHypervisorTypeSearch.and("templateType", LatestTemplateByHypervisorTypeSearch.entity().getTemplateType(), SearchCriteria.Op.EQ); + LatestTemplateByHypervisorTypeSearch.and("arch", LatestTemplateByHypervisorTypeSearch.entity().getArch(), SearchCriteria.Op.EQ); LatestTemplateByHypervisorTypeSearch.and("removed", LatestTemplateByHypervisorTypeSearch.entity().getRemoved(), SearchCriteria.Op.NULL); } @@ -238,10 +244,16 @@ public List listReadyTemplates() { @Override - public VMTemplateVO findLatestTemplateByName(String name) { - SearchCriteria sc = createSearchCriteria(); - sc.addAnd("name", SearchCriteria.Op.EQ, name); - sc.addAnd("removed", SearchCriteria.Op.NULL); + public VMTemplateVO findLatestTemplateByName(String name, CPU.CPUArch arch) { + SearchBuilder sb = createSearchBuilder(); + sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ); + sb.and("arch", sb.entity().getArch(), SearchCriteria.Op.EQ); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("name", name); + if (arch != null) { + sc.setParameters("arch", arch); + } Filter filter = new Filter(VMTemplateVO.class, "id", false, null, 1L); List templates = listBy(sc, filter); if ((templates != null) && !templates.isEmpty()) { @@ -580,6 +592,59 @@ public VMTemplateVO findSystemVMReadyTemplate(long zoneId, HypervisorType hyperv .orElse(null); } + protected List getSortedTemplatesListWithPreferredArch( + Map, VMTemplateVO> uniqueTemplates, String preferredArch) { + List result = new ArrayList<>(uniqueTemplates.values()); + if (StringUtils.isNotBlank(preferredArch)) { + result.sort((t1, t2) -> { + boolean t1Preferred = t1.getArch().getType().equalsIgnoreCase(preferredArch); + boolean t2Preferred = t2.getArch().getType().equalsIgnoreCase(preferredArch); + if (t1Preferred && !t2Preferred) { + return -1; // t1 comes before t2 + } else if (!t1Preferred && t2Preferred) { + return 1; // t2 comes before t1 + } else { + // Both are either preferred or not preferred; use template id as a secondary sorting key. + return Long.compare(t1.getId(), t2.getId()); + } + }); + } else { + result.sort(Comparator.comparing(VMTemplateVO::getId).reversed()); + } + return result; + } + + @Override + public List findSystemVMReadyTemplates(long zoneId, HypervisorType hypervisorType, + String preferredArch) { + List> availableHypervisors = _hostDao.listDistinctHypervisorArchTypes(zoneId); + if (CollectionUtils.isEmpty(availableHypervisors)) { + return Collections.emptyList(); + } + SearchCriteria sc = readySystemTemplateSearch.create(); + sc.setParameters("templateType", Storage.TemplateType.SYSTEM); + sc.setParameters("state", VirtualMachineTemplate.State.Active); + if (hypervisorType != null && !HypervisorType.Any.equals(hypervisorType)) { + sc.setParameters("hypervisorType", List.of(hypervisorType).toArray()); + } else { + sc.setParameters("hypervisorType", + availableHypervisors.stream().map(Pair::first).distinct().toArray()); + } + sc.setJoinParameters("vmTemplateJoinTemplateStoreRef", "downloadState", + List.of(VMTemplateStorageResourceAssoc.Status.DOWNLOADED, + VMTemplateStorageResourceAssoc.Status.BYPASSED).toArray()); + // order by descending order of id + List templates = listBy(sc, new Filter(VMTemplateVO.class, "id", false, null, null)); + Map, VMTemplateVO> uniqueTemplates = new HashMap<>(); + for (VMTemplateVO template : templates) { + Pair key = new Pair<>(template.getHypervisorType(), template.getArch()); + if (availableHypervisors.contains(key) && !uniqueTemplates.containsKey(key)) { + uniqueTemplates.put(key, template); + } + } + return getSortedTemplatesListWithPreferredArch(uniqueTemplates, preferredArch); + } + @Override public VMTemplateVO findRoutingTemplate(HypervisorType hType, String templateName) { SearchCriteria sc = tmpltTypeHyperSearch2.create(); @@ -618,10 +683,43 @@ public VMTemplateVO findRoutingTemplate(HypervisorType hType, String templateNam } @Override - public VMTemplateVO findLatestTemplateByTypeAndHypervisor(HypervisorType hypervisorType, TemplateType type) { + public List findRoutingTemplates(HypervisorType hType, String templateName, String preferredArch) { + SearchCriteria sc = tmpltTypeHyperSearch2.create(); + sc.setParameters("templateType", TemplateType.ROUTING); + sc.setParameters("hypervisorType", hType); + sc.setParameters("state", VirtualMachineTemplate.State.Active.toString()); + if (templateName != null) { + sc.setParameters("templateName", templateName); + } + List templates = listBy(sc, new Filter(VMTemplateVO.class, "id", false, null, 1L)); + if (CollectionUtils.isEmpty(templates)) { + sc = tmpltTypeHyperSearch2.create(); + sc.setParameters("templateType", TemplateType.SYSTEM); + sc.setParameters("hypervisorType", hType); + sc.setParameters("state", VirtualMachineTemplate.State.Active.toString()); + if (templateName != null) { + sc.setParameters("templateName", templateName); + } + templates = listBy(sc, new Filter(VMTemplateVO.class, "id", false, null, 1L)); + } + Map, VMTemplateVO> uniqueTemplates = new HashMap<>(); + for (VMTemplateVO template : templates) { + Pair key = new Pair<>(template.getHypervisorType(), template.getArch()); + if (!uniqueTemplates.containsKey(key)) { + uniqueTemplates.put(key, template); + } + } + return getSortedTemplatesListWithPreferredArch(uniqueTemplates, preferredArch); + } + + @Override + public VMTemplateVO findLatestTemplateByTypeAndHypervisorAndArch(HypervisorType hypervisorType, CPU.CPUArch arch, TemplateType type) { SearchCriteria sc = LatestTemplateByHypervisorTypeSearch.create(); sc.setParameters("hypervisorType", hypervisorType); sc.setParameters("templateType", type); + if (arch != null) { + sc.setParameters("arch", arch); + } Filter filter = new Filter(VMTemplateVO.class, "id", false, null, 1L); List templates = listBy(sc, filter); if (templates != null && !templates.isEmpty()) { diff --git a/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java b/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java index 9b0e10cbbfcc..e711c564015f 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java @@ -16,6 +16,47 @@ // under the License. package com.cloud.upgrade; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.Date; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationDaoImpl; +import org.apache.cloudstack.framework.config.impl.ConfigurationVO; +import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; +import org.apache.cloudstack.storage.datastore.db.ImageStoreDaoImpl; +import org.apache.cloudstack.storage.datastore.db.ImageStoreDetailsDao; +import org.apache.cloudstack.storage.datastore.db.ImageStoreDetailsDaoImpl; +import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; +import org.apache.cloudstack.utils.security.DigestHelper; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.ini4j.Ini; + +import com.cloud.cpu.CPU; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.ClusterDaoImpl; @@ -36,6 +77,7 @@ import com.cloud.upgrade.dao.BasicTemplateDataStoreDaoImpl; import com.cloud.user.Account; import com.cloud.utils.DateUtil; +import com.cloud.utils.HttpUtils; import com.cloud.utils.Pair; import com.cloud.utils.UriUtils; import com.cloud.utils.db.GlobalLock; @@ -46,48 +88,10 @@ import com.cloud.utils.script.Script; import com.cloud.vm.dao.VMInstanceDao; import com.cloud.vm.dao.VMInstanceDaoImpl; -import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.framework.config.dao.ConfigurationDaoImpl; -import org.apache.cloudstack.framework.config.impl.ConfigurationVO; -import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; -import org.apache.cloudstack.storage.datastore.db.ImageStoreDaoImpl; -import org.apache.cloudstack.storage.datastore.db.ImageStoreDetailsDao; -import org.apache.cloudstack.storage.datastore.db.ImageStoreDetailsDaoImpl; -import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; -import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; -import org.apache.cloudstack.utils.security.DigestHelper; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; -import org.ini4j.Ini; - -import javax.inject.Inject; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.sql.Connection; -import java.sql.Date; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; public class SystemVmTemplateRegistration { protected static Logger LOGGER = LogManager.getLogger(SystemVmTemplateRegistration.class); - private static final String MOUNT_COMMAND = "sudo mount -t nfs %s %s"; + private static final String MOUNT_COMMAND_BASE = "sudo mount -t nfs"; private static final String UMOUNT_COMMAND = "sudo umount %s"; private static final String RELATIVE_TEMPLATE_PATH = "./engine/schema/dist/systemvm-templates/"; private static final String ABSOLUTE_TEMPLATE_PATH = "/usr/share/cloudstack-management/templates/systemvm/"; @@ -102,6 +106,9 @@ public class SystemVmTemplateRegistration { private static final Integer LINUX_7_ID = 183; private static final Integer SCRIPT_TIMEOUT = 1800000; private static final Integer LOCK_WAIT_TIMEOUT = 1200; + protected static final List DOWNLOADABLE_TEMPLATE_ARCH_TYPES = Arrays.asList( + CPU.CPUArch.arm64 + ); public static String CS_MAJOR_VERSION = null; @@ -128,6 +135,8 @@ public class SystemVmTemplateRegistration { private String systemVmTemplateVersion; + private final File tempDownloadDir; + public SystemVmTemplateRegistration() { dataCenterDao = new DataCenterDaoImpl(); vmTemplateDao = new VMTemplateDaoImpl(); @@ -138,6 +147,7 @@ public SystemVmTemplateRegistration() { imageStoreDetailsDao = new ImageStoreDetailsDaoImpl(); clusterDao = new ClusterDaoImpl(); configurationDao = new ConfigurationDaoImpl(); + tempDownloadDir = new File(System.getProperty("java.io.tmpdir")); } /** @@ -149,7 +159,7 @@ public SystemVmTemplateRegistration(String systemVmTemplateVersion) { } public static String getMountCommand(String nfsVersion, String device, String dir) { - String cmd = "sudo mount -t nfs"; + String cmd = MOUNT_COMMAND_BASE; if (StringUtils.isNotBlank(nfsVersion)) { cmd = String.format("%s -o vers=%s", cmd, nfsVersion); } @@ -163,6 +173,10 @@ public String getSystemVmTemplateVersion() { return systemVmTemplateVersion; } + public File getTempDownloadDir() { + return tempDownloadDir; + } + private static class SystemVMTemplateDetails { Long id; String uuid; @@ -174,6 +188,7 @@ private static class SystemVMTemplateDetails { ImageFormat format; Integer guestOsId; Hypervisor.HypervisorType hypervisorType; + CPU.CPUArch arch; Long storeId; Long size; Long physicalSize; @@ -183,7 +198,7 @@ private static class SystemVMTemplateDetails { SystemVMTemplateDetails(String uuid, String name, Date created, String url, String checksum, ImageFormat format, Integer guestOsId, Hypervisor.HypervisorType hypervisorType, - Long storeId) { + CPU.CPUArch arch, Long storeId) { this.uuid = uuid; this.name = name; this.created = created; @@ -192,6 +207,7 @@ private static class SystemVMTemplateDetails { this.format = format; this.guestOsId = guestOsId; this.hypervisorType = hypervisorType; + this.arch = arch; this.storeId = storeId; } @@ -235,6 +251,10 @@ public Hypervisor.HypervisorType getHypervisorType() { return hypervisorType; } + public CPU.CPUArch getArch() { + return arch; + } + public Long getStoreId() { return storeId; } @@ -288,18 +308,17 @@ public void setUpdated(Date updated) { } } - public static final List hypervisorList = Arrays.asList(Hypervisor.HypervisorType.KVM, - Hypervisor.HypervisorType.VMware, - Hypervisor.HypervisorType.XenServer, - Hypervisor.HypervisorType.Hyperv, - Hypervisor.HypervisorType.LXC, - Hypervisor.HypervisorType.Ovm3 + public static final List> hypervisorList = Arrays.asList( + new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64), + new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.arm64), + new Pair<>(Hypervisor.HypervisorType.VMware, null), + new Pair<>(Hypervisor.HypervisorType.XenServer, null), + new Pair<>(Hypervisor.HypervisorType.Hyperv, null), + new Pair<>(Hypervisor.HypervisorType.LXC, null), + new Pair<>(Hypervisor.HypervisorType.Ovm3, null) ); - public static final Map NewTemplateNameList = new HashMap(); - public static final Map FileNames = new HashMap(); - public static final Map NewTemplateUrl = new HashMap(); - public static final Map NewTemplateChecksum = new HashMap(); + public static final Map NewTemplateMap = new HashMap<>(); public static final Map RouterTemplateConfigurationNames = new HashMap() { { @@ -368,55 +387,73 @@ public boolean validateIfSeeded(TemplateDataStoreVO templDataStoreVO, String url } } - public Long getRegisteredTemplateId(Pair hypervisorAndTemplateName) { - VMTemplateVO vmTemplate = vmTemplateDao.findLatestTemplateByName(hypervisorAndTemplateName.second()); - Long templateId = null; - if (vmTemplate != null) { - templateId = vmTemplate.getId(); + private static String getHypervisorArchLog(Hypervisor.HypervisorType hypervisorType, CPU.CPUArch arch) { + StringBuilder sb = new StringBuilder("hypervisor: ").append(hypervisorType.name()); + if (Hypervisor.HypervisorType.KVM.equals(hypervisorType)) { + sb.append(", arch: ").append(arch == null ? CPU.CPUArch.amd64.getType() : arch.getType()); } - return templateId; + return sb.toString(); } - private static String fetchTemplatesPath() { - String filePath = RELATIVE_TEMPLATE_PATH + METADATA_FILE_NAME; - LOGGER.debug(String.format("Looking for file [ %s ] in the classpath.", filePath)); - File metaFile = new File(filePath); - String templatePath = null; - if (metaFile.exists()) { - templatePath = RELATIVE_TEMPLATE_PATH; - } - if (templatePath == null) { - filePath = ABSOLUTE_TEMPLATE_PATH + METADATA_FILE_NAME; - metaFile = new File(filePath); - templatePath = ABSOLUTE_TEMPLATE_PATH; - LOGGER.debug(String.format("Looking for file [ %s ] in the classpath.", filePath)); - if (!metaFile.exists()) { - String errMsg = String.format("Unable to locate metadata file in your setup at %s", filePath.toString()); - LOGGER.error(errMsg); - throw new CloudRuntimeException(errMsg); - } + protected static String getHypervisorArchKey(Hypervisor.HypervisorType hypervisorType, CPU.CPUArch arch) { + if (Hypervisor.HypervisorType.KVM.equals(hypervisorType)) { + return String.format("%s-%s", hypervisorType.name().toLowerCase(), + arch == null ? CPU.CPUArch.amd64.getType() : arch.getType()); } - return templatePath; + return hypervisorType.name().toLowerCase(); } - private String getHypervisorName(String name) { - if (name.equals("xenserver")) { - return "xen"; - } - if (name.equals("ovm3")) { - return "ovm"; - } - return name; + protected static MetadataTemplateDetails getMetadataTemplateDetails(Hypervisor.HypervisorType hypervisorType, + CPU.CPUArch arch) { + return NewTemplateMap.get(getHypervisorArchKey(hypervisorType, arch)); + } + + public VMTemplateVO getRegisteredTemplate(String templateName, CPU.CPUArch arch) { + return vmTemplateDao.findLatestTemplateByName(templateName, arch); + } + private static boolean isRunningInTest() { + return "true".equalsIgnoreCase(System.getProperty("test.mode")); } - private Hypervisor.HypervisorType getHypervisorType(String hypervisor) { - if (hypervisor.equalsIgnoreCase("xen")) { - hypervisor = "xenserver"; - } else if (hypervisor.equalsIgnoreCase("ovm")) { - hypervisor = "ovm3"; + /** + * Attempts to determine the templates directory path by locating the metadata file. + *

+ * This method checks if the application is running in a test environment by invoking + * {@code isRunningInTest()}. If so, it immediately returns the {@code RELATIVE_TEMPLATE_PATH}. + *

+ *

+ * Otherwise, it creates a list of candidate paths (typically including both relative and absolute + * template paths) and iterates through them. For each candidate, it constructs the metadata file + * path by appending {@code METADATA_FILE_NAME} to {@code RELATIVE_TEMPLATE_PATH} (note: the candidate + * path is not used in the file path construction in this implementation) and checks if that file exists. + * If the metadata file exists, the candidate path is returned. + *

+ *

+ * If none of the candidate paths contain the metadata file, the method logs an error and throws a + * {@link CloudRuntimeException}. + *

+ * + * @return the path to the templates directory if the metadata file is found, or {@code RELATIVE_TEMPLATE_PATH} + * when running in a test environment. + * @throws CloudRuntimeException if the metadata file cannot be located in any of the candidate paths. + */ + private static String fetchTemplatesPath() { + if (isRunningInTest()) { + return RELATIVE_TEMPLATE_PATH; + } + List paths = Arrays.asList(RELATIVE_TEMPLATE_PATH, ABSOLUTE_TEMPLATE_PATH); + for (String path : paths) { + String filePath = path + METADATA_FILE_NAME; + LOGGER.debug("Looking for file [ {} ] in the classpath.", filePath); + File metaFile = new File(filePath); + if (metaFile.exists()) { + return path; + } } - return Hypervisor.HypervisorType.getType(hypervisor); + String errMsg = String.format("Unable to locate metadata file in your setup at %s", StringUtils.join(paths)); + LOGGER.error(errMsg); + throw new CloudRuntimeException(errMsg); } private List getEligibleZoneIds() { @@ -430,28 +467,28 @@ private List getEligibleZoneIds() { return zoneIds; } - private Pair getNfsStoreInZone(Long zoneId) { - String url = null; - Long storeId = null; + protected Pair getNfsStoreInZone(Long zoneId) { ImageStoreVO storeVO = imageStoreDao.findOneByZoneAndProtocol(zoneId, "nfs"); if (storeVO == null) { - String errMsg = String.format("Failed to fetch NFS store in zone = %s for SystemVM template registration", zoneId); + String errMsg = String.format("Failed to fetch NFS store in zone = %s for SystemVM template registration", + zoneId); LOGGER.error(errMsg); throw new CloudRuntimeException(errMsg); } - url = storeVO.getUrl(); - storeId = storeVO.getId(); + String url = storeVO.getUrl(); + Long storeId = storeVO.getId(); return new Pair<>(url, storeId); } public static void mountStore(String storeUrl, String path, String nfsVersion) { try { - if (storeUrl != null) { - URI uri = new URI(UriUtils.encodeURIComponent(storeUrl)); - String host = uri.getHost(); - String mountPath = uri.getPath(); - Script.runSimpleBashScript(getMountCommand(nfsVersion, host + ":" + mountPath, path)); + if (storeUrl == null) { + return; } + URI uri = new URI(UriUtils.encodeURIComponent(storeUrl)); + String host = uri.getHost(); + String mountPath = uri.getPath(); + Script.runSimpleBashScript(getMountCommand(nfsVersion, host + ":" + mountPath, path)); } catch (Exception e) { String msg = "NFS Store URL is not in the correct format"; LOGGER.error(msg, e); @@ -459,13 +496,6 @@ public static void mountStore(String storeUrl, String path, String nfsVersion) { } } - private List fetchAllHypervisors(Long zoneId) { - List hypervisorList = new ArrayList<>(); - List hypervisorTypes = clusterDao.getAvailableHypervisorInZone(zoneId); - hypervisorList = hypervisorTypes.stream().distinct().map(Hypervisor.HypervisorType::name).collect(Collectors.toList()); - return hypervisorList; - } - private VMTemplateVO createTemplateObjectInDB(SystemVMTemplateDetails details) { Long templateId = vmTemplateDao.getNextInSequence(Long.class, "id"); VMTemplateVO template = new VMTemplateVO(); @@ -486,6 +516,7 @@ private VMTemplateVO createTemplateObjectInDB(SystemVMTemplateDetails details) { template.setGuestOSId(details.getGuestOsId()); template.setCrossZones(true); template.setHypervisorType(details.getHypervisorType()); + template.setArch(details.getArch()); template.setState(VirtualMachineTemplate.State.Inactive); template.setDeployAsIs(false); template = vmTemplateDao.persist(template); @@ -627,16 +658,20 @@ public static void unmountStore(String filePath) { } } - private void setupTemplate(String templateName, Pair hypervisorAndTemplateName, - String destTempFolder) throws CloudRuntimeException { + private void setupTemplate(String templateName, Hypervisor.HypervisorType hypervisor, CPU.CPUArch arch, + String destTempFolder) throws CloudRuntimeException { String setupTmpltScript = Script.findScript(storageScriptsDir, "setup-sysvm-tmplt"); if (setupTmpltScript == null) { throw new CloudRuntimeException("Unable to find the createtmplt.sh"); } Script scr = new Script(setupTmpltScript, SCRIPT_TIMEOUT, LOGGER); scr.add("-u", templateName); - scr.add("-f", TEMPLATES_PATH + FileNames.get(hypervisorAndTemplateName.first())); - scr.add("-h", hypervisorAndTemplateName.first().name().toLowerCase(Locale.ROOT)); + MetadataTemplateDetails templateDetails = NewTemplateMap.get(getHypervisorArchKey(hypervisor, arch)); + String filePath = StringUtils.isNotBlank(templateDetails.getDownloadedFilePath()) ? + templateDetails.getDownloadedFilePath() : + templateDetails.getDefaultFilePath(); + scr.add("-f", filePath); + scr.add("-h", hypervisor.name().toLowerCase(Locale.ROOT)); scr.add("-d", destTempFolder); String result = scr.execute(); if (result != null) { @@ -644,17 +679,15 @@ private void setupTemplate(String templateName, Pair hypervisorAndTemplateName, - String url, String checksum, ImageFormat format, long guestOsId, - Long storeId, Long templateId, String filePath, TemplateDataStoreVO templateDataStoreVO) { - Hypervisor.HypervisorType hypervisor = hypervisorAndTemplateName.first(); + private Long performTemplateRegistrationOperations(Hypervisor.HypervisorType hypervisor, + String name, CPU.CPUArch arch, String url, String checksum, ImageFormat format, long guestOsId, + Long storeId, Long templateId, String filePath, TemplateDataStoreVO templateDataStoreVO) { String templateName = UUID.randomUUID().toString(); Date created = new Date(DateUtil.currentGMTTime().getTime()); - SystemVMTemplateDetails details = new SystemVMTemplateDetails(templateName, hypervisorAndTemplateName.second(), created, - url, checksum, format, (int) guestOsId, hypervisor, storeId); + SystemVMTemplateDetails details = new SystemVMTemplateDetails(templateName, name, created, + url, checksum, format, (int) guestOsId, hypervisor, arch, storeId); if (templateId == null) { VMTemplateVO template = createTemplateObjectInDB(details); if (template == null) { @@ -671,45 +704,44 @@ private Long performTemplateRegistrationOperations(Pair hypervisorAndTemplateName, - Pair storeUrlAndId, VMTemplateVO templateVO, - TemplateDataStoreVO templateDataStoreVO, String filePath) { - Long templateId = null; + public void registerTemplate(Hypervisor.HypervisorType hypervisor, String name, Long storeId, + VMTemplateVO templateVO, TemplateDataStoreVO templateDataStoreVO, String filePath) { try { - templateId = templateVO.getId(); - performTemplateRegistrationOperations(hypervisorAndTemplateName, templateVO.getUrl(), templateVO.getChecksum(), - templateVO.getFormat(), templateVO.getGuestOSId(), storeUrlAndId.second(), templateId, filePath, templateDataStoreVO); + performTemplateRegistrationOperations(hypervisor, name, templateVO.getArch(), templateVO.getUrl(), + templateVO.getChecksum(), templateVO.getFormat(), templateVO.getGuestOSId(), storeId, + templateVO.getId(), filePath, templateDataStoreVO); } catch (Exception e) { - String errMsg = String.format("Failed to register template for hypervisor: %s", hypervisorAndTemplateName.first()); + String errMsg = String.format("Failed to register template for hypervisor: %s", hypervisor); LOGGER.error(errMsg, e); - if (templateId != null) { - updateTemplateTablesOnFailure(templateId); - cleanupStore(templateId, filePath); - } + updateTemplateTablesOnFailure(templateVO.getId()); + cleanupStore(templateVO.getId(), filePath); throw new CloudRuntimeException(errMsg, e); } } - public void registerTemplate(Pair hypervisorAndTemplateName, Pair storeUrlAndId, String filePath) { + public void registerTemplateForNonExistingEntries(Hypervisor.HypervisorType hypervisor, CPU.CPUArch arch, + String name, Pair storeUrlAndId, String filePath) { Long templateId = null; try { - Hypervisor.HypervisorType hypervisor = hypervisorAndTemplateName.first(); - templateId = performTemplateRegistrationOperations(hypervisorAndTemplateName, NewTemplateUrl.get(hypervisor), NewTemplateChecksum.get(hypervisor), - hypervisorImageFormat.get(hypervisor), hypervisorGuestOsMap.get(hypervisor), storeUrlAndId.second(), null, filePath, null); + MetadataTemplateDetails templateDetails = getMetadataTemplateDetails(hypervisor, arch); + templateId = performTemplateRegistrationOperations(hypervisor, name, + templateDetails.getArch(), templateDetails.getUrl(), + templateDetails.getChecksum(), hypervisorImageFormat.get(hypervisor), + hypervisorGuestOsMap.get(hypervisor), storeUrlAndId.second(), null, filePath, null); Map configParams = new HashMap<>(); - configParams.put(RouterTemplateConfigurationNames.get(hypervisorAndTemplateName.first()), hypervisorAndTemplateName.second()); + configParams.put(RouterTemplateConfigurationNames.get(hypervisor), templateDetails.getName()); configParams.put("minreq.sysvmtemplate.version", getSystemVmTemplateVersion()); updateConfigurationParams(configParams); - updateSystemVMEntries(templateId, hypervisorAndTemplateName.first()); + updateSystemVMEntries(templateId, hypervisor); } catch (Exception e) { - String errMsg = String.format("Failed to register template for hypervisor: %s", hypervisorAndTemplateName.first()); + String errMsg = String.format("Failed to register template for hypervisor: %s", hypervisor); LOGGER.error(errMsg, e); if (templateId != null) { updateTemplateTablesOnFailure(templateId); @@ -719,6 +751,33 @@ public void registerTemplate(Pair hypervisorA } } + protected void validateTemplateFileForHypervisorAndArch(Hypervisor.HypervisorType hypervisor, CPU.CPUArch arch) { + MetadataTemplateDetails templateDetails = getMetadataTemplateDetails(hypervisor, arch); + File templateFile = getTemplateFile(templateDetails); + if (templateFile == null) { + throw new CloudRuntimeException("Failed to find local template file"); + } + if (isTemplateFileChecksumDifferent(templateDetails, templateFile)) { + throw new CloudRuntimeException("Checksum failed for local template file"); + } + } + + public void validateAndRegisterTemplate(Hypervisor.HypervisorType hypervisor, String name, Long storeId, + VMTemplateVO templateVO, TemplateDataStoreVO templateDataStoreVO, String filePath) { + validateTemplateFileForHypervisorAndArch(hypervisor, templateVO.getArch()); + registerTemplate(hypervisor, name, storeId, templateVO, templateDataStoreVO, filePath); + } + + public void validateAndRegisterTemplateForNonExistingEntries(Hypervisor.HypervisorType hypervisor, + CPU.CPUArch arch, String name, Pair storeUrlAndId, String filePath) { + validateTemplateFileForHypervisorAndArch(hypervisor, arch); + registerTemplateForNonExistingEntries(hypervisor, arch, name, storeUrlAndId, filePath); + } + + protected static String getMetadataFilePath() { + return METADATA_FILE; + } + /** * This method parses the metadata file consisting of the systemVM templates information * @return the version of the systemvm template that is to be used. This is done in order @@ -726,26 +785,41 @@ public void registerTemplate(Pair hypervisorA * exist a template corresponding to the current code version. */ public static String parseMetadataFile() { - try { - Ini ini = new Ini(); - ini.load(new FileReader(METADATA_FILE)); - for (Hypervisor.HypervisorType hypervisorType : hypervisorList) { - String hypervisor = hypervisorType.name().toLowerCase(Locale.ROOT); - Ini.Section section = ini.get(hypervisor); - NewTemplateNameList.put(hypervisorType, section.get("templatename")); - FileNames.put(hypervisorType, section.get("filename")); - NewTemplateChecksum.put(hypervisorType, section.get("checksum")); - NewTemplateUrl.put(hypervisorType, section.get("downloadurl")); - } - Ini.Section section = ini.get("default"); - return section.get("version"); - } catch (Exception e) { - String errMsg = String.format("Failed to parse systemVM template metadata file: %s", METADATA_FILE); + String metadataFilePath = getMetadataFilePath(); + String errMsg = String.format("Failed to parse systemVM template metadata file: %s", metadataFilePath); + final Ini ini = new Ini(); + try (FileReader reader = new FileReader(metadataFilePath)) { + ini.load(reader); + } catch (IOException e) { LOGGER.error(errMsg, e); throw new CloudRuntimeException(errMsg, e); } + if (!ini.containsKey("default")) { + errMsg = String.format("%s as unable to default section", errMsg); + LOGGER.error(errMsg); + throw new CloudRuntimeException(errMsg); + } + for (Pair hypervisorType : hypervisorList) { + String key = getHypervisorArchKey(hypervisorType.first(), hypervisorType.second()); + Ini.Section section = ini.get(key); + if (section == null) { + LOGGER.error("Failed to find details for {} in template metadata file: {}", + key, metadataFilePath); + continue; + } + NewTemplateMap.put(key, new MetadataTemplateDetails( + hypervisorType.first(), + section.get("templatename"), + section.get("filename"), + section.get("downloadurl"), + section.get("checksum"), + hypervisorType.second())); + } + Ini.Section defaultSection = ini.get("default"); + return defaultSection.get("version").trim(); } + private static void cleanupStore(Long templateId, String filePath) { String destTempFolder = filePath + PARTIAL_TEMPLATE_FOLDER + String.valueOf(templateId); try { @@ -755,31 +829,60 @@ private static void cleanupStore(Long templateId, String filePath) { } } - private void validateTemplates(Set hypervisorsInUse) { - Set hypervisors = hypervisorsInUse.stream(). - map(Hypervisor.HypervisorType::name).map(name -> name.toLowerCase(Locale.ROOT)).map(this::getHypervisorName).collect(Collectors.toSet()); - List templates = new ArrayList<>(); - for (Hypervisor.HypervisorType hypervisorType : hypervisorsInUse) { - templates.add(FileNames.get(hypervisorType)); + protected File getTemplateFile(MetadataTemplateDetails templateDetails) { + File templateFile = new File(templateDetails.getDefaultFilePath()); + if (templateFile.exists()) { + return templateFile; + } + LOGGER.debug("{} is not present", templateFile.getAbsolutePath()); + if (DOWNLOADABLE_TEMPLATE_ARCH_TYPES.contains(templateDetails.getArch()) && + StringUtils.isNotBlank(templateDetails.getUrl())) { + LOGGER.debug("Downloading the template file {} for {}", + templateDetails.getUrl(), templateDetails.getHypervisorArchLog()); + Path path = Path.of(TEMPLATES_PATH); + if (!Files.isWritable(path)) { + templateFile = new File(tempDownloadDir, templateDetails.getFilename()); + } + if (!templateFile.exists() && + !HttpUtils.downloadFileWithProgress(templateDetails.getUrl(), templateFile.getAbsolutePath(), + LOGGER)) { + return null; + } + templateDetails.setDownloadedFilePath(templateFile.getAbsolutePath()); + } + return templateFile; + } + + protected boolean isTemplateFileChecksumDifferent(MetadataTemplateDetails templateDetails, File templateFile) { + String templateChecksum = DigestHelper.calculateChecksum(templateFile); + if (!templateChecksum.equals(templateDetails.getChecksum())) { + LOGGER.error("Checksum {} for file {} does not match checksum {} from metadata", + templateChecksum, templateFile, templateDetails.getChecksum()); + return true; } + return false; + } + protected void validateTemplates(List> hypervisorsArchInUse) { boolean templatesFound = true; - for (String hypervisor : hypervisors) { - String matchedTemplate = templates.stream().filter(x -> x.contains(hypervisor)).findAny().orElse(null); + for (Pair hypervisorArch : hypervisorsArchInUse) { + MetadataTemplateDetails matchedTemplate = getMetadataTemplateDetails(hypervisorArch.first(), + hypervisorArch.second()); if (matchedTemplate == null) { templatesFound = false; break; } - - File tempFile = new File(TEMPLATES_PATH + matchedTemplate); - String templateChecksum = DigestHelper.calculateChecksum(tempFile); - if (!templateChecksum.equals(NewTemplateChecksum.get(getHypervisorType(hypervisor)))) { - LOGGER.error(String.format("Checksum mismatch: %s != %s ", templateChecksum, NewTemplateChecksum.get(getHypervisorType(hypervisor)))); + File tempFile = getTemplateFile(matchedTemplate); + if (tempFile == null) { + LOGGER.warn("Failed to download template for {}, moving ahead", + matchedTemplate.getHypervisorArchLog()); + continue; + } + if (isTemplateFileChecksumDifferent(matchedTemplate, tempFile)) { templatesFound = false; break; } } - if (!templatesFound) { String errMsg = "SystemVm template not found. Cannot upgrade system Vms"; LOGGER.error(errMsg); @@ -787,7 +890,40 @@ private void validateTemplates(Set hypervisorsInUse) } } - public void registerTemplates(Set hypervisorsInUse) { + protected void registerTemplatesForZone(long zoneId, String filePath) { + Pair storeUrlAndId = getNfsStoreInZone(zoneId); + String nfsVersion = getNfsVersion(storeUrlAndId.second()); + mountStore(storeUrlAndId.first(), filePath, nfsVersion); + List> hypervisorArchList = + clusterDao.listDistinctHypervisorsArchAcrossClusters(zoneId); + for (Pair hypervisorArch : hypervisorArchList) { + Hypervisor.HypervisorType hypervisorType = hypervisorArch.first(); + MetadataTemplateDetails templateDetails = getMetadataTemplateDetails(hypervisorType, + hypervisorArch.second()); + if (templateDetails == null) { + continue; + } + VMTemplateVO templateVO = getRegisteredTemplate(templateDetails.getName(), templateDetails.getArch()); + if (templateVO != null) { + TemplateDataStoreVO templateDataStoreVO = + templateDataStoreDao.findByStoreTemplate(storeUrlAndId.second(), templateVO.getId()); + if (templateDataStoreVO != null) { + String installPath = templateDataStoreVO.getInstallPath(); + if (validateIfSeeded(templateDataStoreVO, storeUrlAndId.first(), installPath, nfsVersion)) { + continue; + } + } + registerTemplate(hypervisorType, templateDetails.getName(), storeUrlAndId.second(), templateVO, + templateDataStoreVO, filePath); + updateRegisteredTemplateDetails(templateVO.getId(), templateDetails); + continue; + } + registerTemplateForNonExistingEntries(hypervisorType, templateDetails.getArch(), templateDetails.getName(), + storeUrlAndId, filePath); + } + } + + public void registerTemplates(List> hypervisorsArchInUse) { GlobalLock lock = GlobalLock.getInternLock("UpgradeDatabase-Lock"); try { LOGGER.info("Grabbing lock to register templates."); @@ -795,7 +931,7 @@ public void registerTemplates(Set hypervisorsInUse) { throw new CloudRuntimeException("Unable to acquire lock to register SystemVM template."); } try { - validateTemplates(hypervisorsInUse); + validateTemplates(hypervisorsArchInUse); // Perform Registration if templates not already registered Transaction.execute(new TransactionCallbackNoReturn() { @Override @@ -808,32 +944,7 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { if (filePath == null) { throw new CloudRuntimeException("Failed to create temporary file path to mount the store"); } - Pair storeUrlAndId = getNfsStoreInZone(zoneId); - String nfsVersion = getNfsVersion(storeUrlAndId.second()); - mountStore(storeUrlAndId.first(), filePath, nfsVersion); - List hypervisorList = fetchAllHypervisors(zoneId); - for (String hypervisor : hypervisorList) { - Hypervisor.HypervisorType name = Hypervisor.HypervisorType.getType(hypervisor); - String templateName = NewTemplateNameList.get(name); - Pair hypervisorAndTemplateName = new Pair(name, templateName); - Long templateId = getRegisteredTemplateId(hypervisorAndTemplateName); - if (templateId != null) { - VMTemplateVO templateVO = vmTemplateDao.findById(templateId); - TemplateDataStoreVO templateDataStoreVO = templateDataStoreDao.findByStoreTemplate(storeUrlAndId.second(), templateId); - if (templateDataStoreVO != null) { - String installPath = templateDataStoreVO.getInstallPath(); - if (validateIfSeeded(templateDataStoreVO, storeUrlAndId.first(), installPath, nfsVersion)) { - continue; - } - } - if (templateVO != null) { - registerTemplate(hypervisorAndTemplateName, storeUrlAndId, templateVO, templateDataStoreVO, filePath); - updateRegisteredTemplateDetails(templateId, hypervisorAndTemplateName); - continue; - } - } - registerTemplate(hypervisorAndTemplateName, storeUrlAndId, filePath); - } + registerTemplatesForZone(zoneId, filePath); unmountStore(filePath); } catch (Exception e) { unmountStore(filePath); @@ -851,12 +962,7 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { } } - private void updateRegisteredTemplateDetails(Long templateId, Map.Entry hypervisorAndTemplateName) { - Pair entry = new Pair<>(hypervisorAndTemplateName.getKey(), hypervisorAndTemplateName.getValue()); - updateRegisteredTemplateDetails(templateId, entry); - } - - private void updateRegisteredTemplateDetails(Long templateId, Pair hypervisorAndTemplateName) { + private void updateRegisteredTemplateDetails(Long templateId, MetadataTemplateDetails templateDetails) { VMTemplateVO templateVO = vmTemplateDao.findById(templateId); templateVO.setTemplateType(Storage.TemplateType.SYSTEM); boolean updated = vmTemplateDao.update(templateVO.getId(), templateVO); @@ -865,68 +971,81 @@ private void updateRegisteredTemplateDetails(Long templateId, Pair configParams = new HashMap<>(); - configParams.put(RouterTemplateConfigurationNames.get(hypervisorAndTemplateName.first()), hypervisorAndTemplateName.second()); + configParams.put(RouterTemplateConfigurationNames.get(hypervisorType), templateDetails.getName()); configParams.put("minreq.sysvmtemplate.version", getSystemVmTemplateVersion()); updateConfigurationParams(configParams); } - private void updateTemplateUrlAndChecksum(VMTemplateVO templateVO, Map.Entry hypervisorAndTemplateName) { - templateVO.setUrl(NewTemplateUrl.get(hypervisorAndTemplateName.getKey())); - templateVO.setChecksum(NewTemplateChecksum.get(hypervisorAndTemplateName.getKey())); + private void updateTemplateUrlAndChecksum(VMTemplateVO templateVO, MetadataTemplateDetails templateDetails) { + templateVO.setUrl(templateDetails.getUrl()); + templateVO.setChecksum(templateDetails.getChecksum()); boolean updated = vmTemplateDao.update(templateVO.getId(), templateVO); if (!updated) { - String errMsg = String.format("updateSystemVmTemplates:Exception while updating 'url' and 'checksum' for hypervisor type %s", hypervisorAndTemplateName.getKey().name()); + String errMsg = String.format("updateSystemVmTemplates:Exception while updating 'url' and 'checksum' for hypervisor type %s", templateDetails.getHypervisorType()); LOGGER.error(errMsg); throw new CloudRuntimeException(errMsg); } } + protected boolean registerOrUpdateSystemVmTemplate(MetadataTemplateDetails templateDetails, + List> hypervisorsInUse) { + LOGGER.debug("Updating System VM template for {}", templateDetails.getHypervisorArchLog()); + VMTemplateVO registeredTemplate = getRegisteredTemplate(templateDetails.getName(), templateDetails.getArch()); + // change template type to SYSTEM + if (registeredTemplate != null) { + updateRegisteredTemplateDetails(registeredTemplate.getId(), templateDetails); + } else { + boolean isHypervisorArchMatchMetadata = hypervisorsInUse.stream() + .anyMatch(p -> p.first().equals(templateDetails.getHypervisorType()) + && Objects.equals(p.second(), templateDetails.getArch())); + if (isHypervisorArchMatchMetadata) { + try { + registerTemplates(hypervisorsInUse); + return true; + } catch (final Exception e) { + throw new CloudRuntimeException(String.format("Failed to register %s templates for hypervisors: [%s]. " + + "Cannot upgrade system VMs", + getSystemVmTemplateVersion(), + StringUtils.join(hypervisorsInUse.stream() + .map(x -> getHypervisorArchKey(x.first(), x.second())) + .collect(Collectors.toList()), ",")), e); + } + } else { + LOGGER.warn("Cannot upgrade {} system VM template for {} as it is not used, not failing upgrade", + getSystemVmTemplateVersion(), templateDetails.getHypervisorArchLog()); + VMTemplateVO templateVO = vmTemplateDao.findLatestTemplateByTypeAndHypervisorAndArch( + templateDetails.getHypervisorType(), templateDetails.getArch(), Storage.TemplateType.SYSTEM); + if (templateVO != null) { + updateTemplateUrlAndChecksum(templateVO, templateDetails); + } + } + } + return false; + } + public void updateSystemVmTemplates(final Connection conn) { LOGGER.debug("Updating System Vm template IDs"); Transaction.execute(new TransactionCallbackNoReturn() { @Override public void doInTransactionWithoutResult(final TransactionStatus status) { - Set hypervisorsListInUse = new HashSet(); + List> hypervisorsInUse; try { - hypervisorsListInUse = clusterDao.getDistinctAvailableHypervisorsAcrossClusters(); - + hypervisorsInUse = clusterDao.listDistinctHypervisorsArchAcrossClusters(null); } catch (final Exception e) { - LOGGER.error("updateSystemVmTemplates: Exception caught while getting hypervisor types from clusters: " + e.getMessage()); - throw new CloudRuntimeException("updateSystemVmTemplates:Exception while getting hypervisor types from clusters", e); + throw new CloudRuntimeException("Exception while getting hypervisor types from clusters", e); } - - for (final Map.Entry hypervisorAndTemplateName : NewTemplateNameList.entrySet()) { - LOGGER.debug("Updating " + hypervisorAndTemplateName.getKey() + " System Vms"); - Long templateId = getRegisteredTemplateId(new Pair<>(hypervisorAndTemplateName.getKey(), hypervisorAndTemplateName.getValue())); + Collection templateEntries = NewTemplateMap.values(); + for (MetadataTemplateDetails templateDetails : templateEntries) { try { - // change template type to SYSTEM - if (templateId != null) { - updateRegisteredTemplateDetails(templateId, hypervisorAndTemplateName); - } else { - if (hypervisorsListInUse.contains(hypervisorAndTemplateName.getKey())) { - try { - registerTemplates(hypervisorsListInUse); - break; - } catch (final Exception e) { - throw new CloudRuntimeException(String.format("%s %s SystemVm template not found. Cannot upgrade system Vms", getSystemVmTemplateVersion(), hypervisorAndTemplateName.getKey())); - } - } else { - LOGGER.warn(String.format("%s %s SystemVm template not found. Cannot upgrade system Vms hypervisor is not used, so not failing upgrade", - getSystemVmTemplateVersion(), hypervisorAndTemplateName.getKey())); - // Update the latest template URLs for corresponding hypervisor - VMTemplateVO templateVO = vmTemplateDao.findLatestTemplateByTypeAndHypervisor(hypervisorAndTemplateName.getKey(), Storage.TemplateType.SYSTEM); - if (templateVO != null) { - updateTemplateUrlAndChecksum(templateVO, hypervisorAndTemplateName); - } - } + if (registerOrUpdateSystemVmTemplate(templateDetails, hypervisorsInUse)) { + break; } } catch (final Exception e) { - String errMsg = "updateSystemVmTemplates:Exception while getting ids of templates"; + String errMsg = "Exception while registering/updating system VM templates for hypervisors in metadata"; LOGGER.error(errMsg, e); throw new CloudRuntimeException(errMsg, e); } @@ -948,4 +1067,64 @@ public String getNfsVersion(long storeId) { } return null; } + + protected static class MetadataTemplateDetails { + private final Hypervisor.HypervisorType hypervisorType; + private final String name; + private final String filename; + private final String url; + private final String checksum; + private final CPU.CPUArch arch; + private String downloadedFilePath; + + MetadataTemplateDetails(Hypervisor.HypervisorType hypervisorType, String name, String filename, String url, + String checksum, CPU.CPUArch arch) { + this.hypervisorType = hypervisorType; + this.name = name; + this.filename = filename; + this.url = url; + this.checksum = checksum; + this.arch = arch; + } + + public Hypervisor.HypervisorType getHypervisorType() { + return hypervisorType; + } + + public String getName() { + return name; + } + + public String getFilename() { + return filename; + } + + public String getUrl() { + return url; + } + + public String getChecksum() { + return checksum; + } + + public CPU.CPUArch getArch() { + return arch; + } + + public String getDownloadedFilePath() { + return downloadedFilePath; + } + + public void setDownloadedFilePath(String downloadedFilePath) { + this.downloadedFilePath = downloadedFilePath; + } + + public String getDefaultFilePath() { + return TEMPLATES_PATH + filename; + } + + public String getHypervisorArchLog() { + return SystemVmTemplateRegistration.getHypervisorArchLog(hypervisorType, arch); + } + } } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42000to42010.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42000to42010.java index 6298e0e729a5..d442dc899042 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42000to42010.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42000to42010.java @@ -78,7 +78,7 @@ public void updateSystemVmTemplates(Connection conn) { try { systemVmTemplateRegistration.updateSystemVmTemplates(conn); } catch (Exception e) { - throw new CloudRuntimeException("Failed to find / register SystemVM template(s)"); + throw new CloudRuntimeException("Failed to find / register SystemVM template(s)", e); } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDao.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDao.java index 7aab5bbf7b31..eda4bcfdaa1f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDao.java @@ -44,7 +44,7 @@ public interface ImageStoreDao extends GenericDao { List listStoresByZoneId(long zoneId); - List listAllStoresInZone(Long zoneId, String provider, DataStoreRole role); + List listAllStoresInZoneExceptId(Long zoneId, String provider, DataStoreRole role, long storeId); List findByProtocol(String protocol); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDaoImpl.java index 84b88c215ca6..4cb40b5eaf63 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDaoImpl.java @@ -41,7 +41,7 @@ public class ImageStoreDaoImpl extends GenericDaoBase implem private SearchBuilder nameSearch; private SearchBuilder providerSearch; private SearchBuilder regionSearch; - private SearchBuilder storeSearch; + private SearchBuilder storesExceptIdSearch; private SearchBuilder protocolSearch; private SearchBuilder zoneProtocolSearch; @@ -88,11 +88,12 @@ public boolean configure(String name, Map params) throws Configu regionSearch.and("role", regionSearch.entity().getRole(), SearchCriteria.Op.EQ); regionSearch.done(); - storeSearch = createSearchBuilder(); - storeSearch.and("providerName", storeSearch.entity().getProviderName(), SearchCriteria.Op.EQ); - storeSearch.and("role", storeSearch.entity().getRole(), SearchCriteria.Op.EQ); - storeSearch.and("dataCenterId", storeSearch.entity().getDcId(), SearchCriteria.Op.EQ); - storeSearch.done(); + storesExceptIdSearch = createSearchBuilder(); + storesExceptIdSearch.and("providerName", storesExceptIdSearch.entity().getProviderName(), SearchCriteria.Op.EQ); + storesExceptIdSearch.and("role", storesExceptIdSearch.entity().getRole(), SearchCriteria.Op.EQ); + storesExceptIdSearch.and("dataCenterId", storesExceptIdSearch.entity().getDcId(), SearchCriteria.Op.EQ); + storesExceptIdSearch.and("id", storesExceptIdSearch.entity().getId(), SearchCriteria.Op.NEQ); + storesExceptIdSearch.done(); return true; } @@ -113,11 +114,12 @@ public List findByProvider(String provider) { } @Override - public List listAllStoresInZone(Long zoneId, String provider, DataStoreRole role) { - SearchCriteria sc = storeSearch.create(); + public List listAllStoresInZoneExceptId(Long zoneId, String provider, DataStoreRole role, long id) { + SearchCriteria sc = storesExceptIdSearch.create(); sc.setParameters("providerName", provider); sc.setParameters("role", role); sc.setParameters("dataCenterId", zoneId); + sc.setParameters("id", id); return listBy(sc); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/util/CPUArchConverter.java b/engine/schema/src/main/java/org/apache/cloudstack/util/CPUArchConverter.java index e278809fb96b..8e56cce739d8 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/util/CPUArchConverter.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/util/CPUArchConverter.java @@ -21,7 +21,7 @@ import javax.persistence.AttributeConverter; import javax.persistence.Converter; -@Converter +@Converter(autoApply = true) public class CPUArchConverter implements AttributeConverter { @Override diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql index 70394e8fd6d3..a12e02bcfdb8 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql @@ -58,6 +58,7 @@ select host.resource_state host_resource_state, vm_template.id template_id, vm_template.uuid template_uuid, + vm_template.arch arch, service_offering.id service_offering_id, service_offering.uuid service_offering_uuid, service_offering.name service_offering_name, diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql index 97cb7b735cfc..430031b1d975 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql @@ -83,6 +83,7 @@ SELECT `iso`.`uuid` AS `iso_uuid`, `iso`.`name` AS `iso_name`, `iso`.`display_text` AS `iso_display_text`, + `vm_template`.`arch` AS `arch`, `service_offering`.`id` AS `service_offering_id`, `service_offering`.`uuid` AS `service_offering_uuid`, `disk_offering`.`uuid` AS `disk_offering_uuid`, diff --git a/engine/schema/src/test/java/com/cloud/dc/dao/ClusterDaoImplTest.java b/engine/schema/src/test/java/com/cloud/dc/dao/ClusterDaoImplTest.java index a513809be057..f8673fa9c9fd 100644 --- a/engine/schema/src/test/java/com/cloud/dc/dao/ClusterDaoImplTest.java +++ b/engine/schema/src/test/java/com/cloud/dc/dao/ClusterDaoImplTest.java @@ -17,6 +17,7 @@ package com.cloud.dc.dao; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; @@ -36,9 +37,13 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; +import com.cloud.cpu.CPU; import com.cloud.dc.ClusterVO; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.utils.Pair; import com.cloud.utils.db.GenericSearchBuilder; import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; @RunWith(MockitoJUnitRunner.class) public class ClusterDaoImplTest { @@ -75,4 +80,39 @@ public void testListAllIdsEmptyResult() { verify(clusterDao).customSearch(genericSearchBuilder.create(), null); assertTrue(result.isEmpty()); } + + @Test + public void listDistinctHypervisorsArchAcrossClusters_WithZone() { + Long zoneId = 123L; + ClusterVO cluster1 = mock(ClusterVO.class); + when(cluster1.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.XenServer); + when(cluster1.getArch()).thenReturn(CPU.CPUArch.amd64); + ClusterVO cluster2 = mock(ClusterVO.class); + when(cluster2.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(cluster2.getArch()).thenReturn(CPU.CPUArch.arm64); + List dummyHosts = Arrays.asList(cluster1, cluster2); + doReturn(dummyHosts).when(clusterDao).search(any(SearchCriteria.class), isNull()); + List> result = clusterDao.listDistinctHypervisorsArchAcrossClusters(zoneId); + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals(Hypervisor.HypervisorType.XenServer, result.get(0).first()); + assertEquals(CPU.CPUArch.amd64, result.get(0).second()); + assertEquals(Hypervisor.HypervisorType.KVM, result.get(1).first()); + assertEquals(CPU.CPUArch.arm64, result.get(1).second()); + } + + @Test + public void listDistinctHypervisorsArchAcrossClusters_WithoutZone() { + Long zoneId = null; + ClusterVO cluster = mock(ClusterVO.class); + when(cluster.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.VMware); + when(cluster.getArch()).thenReturn(CPU.CPUArch.amd64); + List dummyHosts = Collections.singletonList(cluster); + doReturn(dummyHosts).when(clusterDao).search(any(SearchCriteria.class), isNull()); + List> result = clusterDao.listDistinctHypervisorsArchAcrossClusters(zoneId); + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(Hypervisor.HypervisorType.VMware, result.get(0).first()); + assertEquals(CPU.CPUArch.amd64, result.get(0).second()); + } } diff --git a/engine/schema/src/test/java/com/cloud/host/dao/HostDaoImplTest.java b/engine/schema/src/test/java/com/cloud/host/dao/HostDaoImplTest.java index 81163321c6b8..5df240916d28 100644 --- a/engine/schema/src/test/java/com/cloud/host/dao/HostDaoImplTest.java +++ b/engine/schema/src/test/java/com/cloud/host/dao/HostDaoImplTest.java @@ -16,6 +16,16 @@ // under the License. package com.cloud.host.dao; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.junit.Assert; @@ -26,6 +36,7 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; +import com.cloud.cpu.CPU; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.Status; @@ -52,10 +63,10 @@ public class HostDaoImplTest { public void testCountUpAndEnabledHostsInZone() { long testZoneId = 100L; hostDao.HostTypeCountSearch = mockSearchBuilder; - Mockito.when(mockSearchBuilder.create()).thenReturn(mockSearchCriteria); - Mockito.doNothing().when(mockSearchCriteria).setParameters(Mockito.anyString(), Mockito.any()); + when(mockSearchBuilder.create()).thenReturn(mockSearchCriteria); + Mockito.doNothing().when(mockSearchCriteria).setParameters(Mockito.anyString(), any()); int expected = 5; - Mockito.doReturn(expected).when(hostDao).getCount(mockSearchCriteria); + doReturn(expected).when(hostDao).getCount(mockSearchCriteria); Integer count = hostDao.countUpAndEnabledHostsInZone(testZoneId); Assert.assertSame(expected, count); Mockito.verify(mockSearchCriteria).setParameters("type", Host.Type.Routing); @@ -70,16 +81,16 @@ public void testCountAllHostsAndCPUSocketsByType() { GenericDaoBase.SumCount mockSumCount = new GenericDaoBase.SumCount(); mockSumCount.count = 10; mockSumCount.sum = 20; - HostVO host = Mockito.mock(HostVO.class); - GenericSearchBuilder sb = Mockito.mock(GenericSearchBuilder.class); - Mockito.when(sb.entity()).thenReturn(host); - Mockito.doReturn(sb).when(hostDao).createSearchBuilder(GenericDaoBase.SumCount.class); - SearchCriteria sc = Mockito.mock(SearchCriteria.class); - Mockito.when(sb.create()).thenReturn(sc); - Mockito.doReturn(List.of(mockSumCount)).when(hostDao).customSearch(Mockito.any(SearchCriteria.class), Mockito.any()); + HostVO host = mock(HostVO.class); + GenericSearchBuilder sb = mock(GenericSearchBuilder.class); + when(sb.entity()).thenReturn(host); + doReturn(sb).when(hostDao).createSearchBuilder(GenericDaoBase.SumCount.class); + SearchCriteria sc = mock(SearchCriteria.class); + when(sb.create()).thenReturn(sc); + doReturn(List.of(mockSumCount)).when(hostDao).customSearch(any(SearchCriteria.class), any()); Pair result = hostDao.countAllHostsAndCPUSocketsByType(type); - Assert.assertEquals(10, result.first().intValue()); - Assert.assertEquals(20, result.second().intValue()); + assertEquals(10, result.first().intValue()); + assertEquals(20, result.second().intValue()); Mockito.verify(sc).setParameters("type", type); } @@ -87,13 +98,13 @@ public void testCountAllHostsAndCPUSocketsByType() { public void testIsHostUp() { long testHostId = 101L; List statuses = List.of(Status.Up); - HostVO host = Mockito.mock(HostVO.class); - GenericSearchBuilder sb = Mockito.mock(GenericSearchBuilder.class); - Mockito.when(sb.entity()).thenReturn(host); - SearchCriteria sc = Mockito.mock(SearchCriteria.class); - Mockito.when(sb.create()).thenReturn(sc); - Mockito.doReturn(sb).when(hostDao).createSearchBuilder(Status.class); - Mockito.doReturn(statuses).when(hostDao).customSearch(Mockito.any(SearchCriteria.class), Mockito.any()); + HostVO host = mock(HostVO.class); + GenericSearchBuilder sb = mock(GenericSearchBuilder.class); + when(sb.entity()).thenReturn(host); + SearchCriteria sc = mock(SearchCriteria.class); + when(sb.create()).thenReturn(sc); + doReturn(sb).when(hostDao).createSearchBuilder(Status.class); + doReturn(statuses).when(hostDao).customSearch(any(SearchCriteria.class), any()); boolean result = hostDao.isHostUp(testHostId); Assert.assertTrue("Host should be up", result); Mockito.verify(sc).setParameters("id", testHostId); @@ -108,17 +119,17 @@ public void testFindHostIdsByZoneClusterResourceStateTypeAndHypervisorType() { List types = List.of(Host.Type.Routing); List hypervisorTypes = List.of(Hypervisor.HypervisorType.KVM); List mockResults = List.of(1001L, 1002L); // Mocked result - HostVO host = Mockito.mock(HostVO.class); - GenericSearchBuilder sb = Mockito.mock(GenericSearchBuilder.class); - Mockito.when(sb.entity()).thenReturn(host); - SearchCriteria sc = Mockito.mock(SearchCriteria.class); - Mockito.when(sb.create()).thenReturn(sc); - Mockito.when(sb.and()).thenReturn(sb); - Mockito.doReturn(sb).when(hostDao).createSearchBuilder(Long.class); - Mockito.doReturn(mockResults).when(hostDao).customSearch(Mockito.any(SearchCriteria.class), Mockito.any()); + HostVO host = mock(HostVO.class); + GenericSearchBuilder sb = mock(GenericSearchBuilder.class); + when(sb.entity()).thenReturn(host); + SearchCriteria sc = mock(SearchCriteria.class); + when(sb.create()).thenReturn(sc); + when(sb.and()).thenReturn(sb); + doReturn(sb).when(hostDao).createSearchBuilder(Long.class); + doReturn(mockResults).when(hostDao).customSearch(any(SearchCriteria.class), any()); List hostIds = hostDao.findHostIdsByZoneClusterResourceStateTypeAndHypervisorType( zoneId, clusterId, resourceStates, types, hypervisorTypes); - Assert.assertEquals(mockResults, hostIds); + assertEquals(mockResults, hostIds); Mockito.verify(sc).setParameters("zoneId", zoneId); Mockito.verify(sc).setParameters("clusterId", clusterId); Mockito.verify(sc).setParameters("resourceState", resourceStates.toArray()); @@ -130,15 +141,15 @@ public void testFindHostIdsByZoneClusterResourceStateTypeAndHypervisorType() { public void testListDistinctHypervisorTypes() { Long zoneId = 1L; List mockResults = List.of(Hypervisor.HypervisorType.KVM, Hypervisor.HypervisorType.XenServer); - HostVO host = Mockito.mock(HostVO.class); - GenericSearchBuilder sb = Mockito.mock(GenericSearchBuilder.class); - Mockito.when(sb.entity()).thenReturn(host); - SearchCriteria sc = Mockito.mock(SearchCriteria.class); - Mockito.when(sb.create()).thenReturn(sc); - Mockito.doReturn(sb).when(hostDao).createSearchBuilder(Hypervisor.HypervisorType.class); - Mockito.doReturn(mockResults).when(hostDao).customSearch(Mockito.any(SearchCriteria.class), Mockito.any()); + HostVO host = mock(HostVO.class); + GenericSearchBuilder sb = mock(GenericSearchBuilder.class); + when(sb.entity()).thenReturn(host); + SearchCriteria sc = mock(SearchCriteria.class); + when(sb.create()).thenReturn(sc); + doReturn(sb).when(hostDao).createSearchBuilder(Hypervisor.HypervisorType.class); + doReturn(mockResults).when(hostDao).customSearch(any(SearchCriteria.class), any()); List hypervisorTypes = hostDao.listDistinctHypervisorTypes(zoneId); - Assert.assertEquals(mockResults, hypervisorTypes); + assertEquals(mockResults, hypervisorTypes); Mockito.verify(sc).setParameters("zoneId", zoneId); Mockito.verify(sc).setParameters("type", Host.Type.Routing); } @@ -146,12 +157,12 @@ public void testListDistinctHypervisorTypes() { @Test public void testListByIds() { List ids = List.of(101L, 102L); - List mockResults = List.of(Mockito.mock(HostVO.class), Mockito.mock(HostVO.class)); + List mockResults = List.of(mock(HostVO.class), mock(HostVO.class)); hostDao.IdsSearch = mockSearchBuilder; - Mockito.when(mockSearchBuilder.create()).thenReturn(mockSearchCriteria); - Mockito.doReturn(mockResults).when(hostDao).search(Mockito.any(SearchCriteria.class), Mockito.any()); + when(mockSearchBuilder.create()).thenReturn(mockSearchCriteria); + doReturn(mockResults).when(hostDao).search(any(SearchCriteria.class), any()); List hosts = hostDao.listByIds(ids); - Assert.assertEquals(mockResults, hosts); + assertEquals(mockResults, hosts); Mockito.verify(mockSearchCriteria).setParameters("id", ids.toArray()); Mockito.verify(hostDao).search(mockSearchCriteria, null); } @@ -164,15 +175,15 @@ public void testListIdsBy() { Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; Long zoneId = 1L, podId = 2L, clusterId = 3L; List mockResults = List.of(1001L, 1002L); - HostVO host = Mockito.mock(HostVO.class); - GenericSearchBuilder sb = Mockito.mock(GenericSearchBuilder.class); - Mockito.when(sb.entity()).thenReturn(host); - SearchCriteria sc = Mockito.mock(SearchCriteria.class); - Mockito.when(sb.create()).thenReturn(sc); - Mockito.doReturn(sb).when(hostDao).createSearchBuilder(Long.class); - Mockito.doReturn(mockResults).when(hostDao).customSearch(Mockito.any(SearchCriteria.class), Mockito.any()); + HostVO host = mock(HostVO.class); + GenericSearchBuilder sb = mock(GenericSearchBuilder.class); + when(sb.entity()).thenReturn(host); + SearchCriteria sc = mock(SearchCriteria.class); + when(sb.create()).thenReturn(sc); + doReturn(sb).when(hostDao).createSearchBuilder(Long.class); + doReturn(mockResults).when(hostDao).customSearch(any(SearchCriteria.class), any()); List hostIds = hostDao.listIdsBy(type, status, resourceState, hypervisorType, zoneId, podId, clusterId); - Assert.assertEquals(mockResults, hostIds); + assertEquals(mockResults, hostIds); Mockito.verify(sc).setParameters("type", type); Mockito.verify(sc).setParameters("status", status); Mockito.verify(sc).setParameters("resourceState", resourceState); @@ -181,4 +192,39 @@ public void testListIdsBy() { Mockito.verify(sc).setParameters("podId", podId); Mockito.verify(sc).setParameters("clusterId", clusterId); } + + @Test + public void testListDistinctHypervisorArchTypes_WithZone() { + Long zoneId = 123L; + HostVO host1 = mock(HostVO.class); + when(host1.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.XenServer); + when(host1.getArch()).thenReturn(CPU.CPUArch.amd64); + HostVO host2 = mock(HostVO.class); + when(host2.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(host2.getArch()).thenReturn(CPU.CPUArch.arm64); + List dummyHosts = Arrays.asList(host1, host2); + doReturn(dummyHosts).when(hostDao).search(any(SearchCriteria.class), isNull()); + List> result = hostDao.listDistinctHypervisorArchTypes(zoneId); + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals(Hypervisor.HypervisorType.XenServer, result.get(0).first()); + assertEquals(CPU.CPUArch.amd64, result.get(0).second()); + assertEquals(Hypervisor.HypervisorType.KVM, result.get(1).first()); + assertEquals(CPU.CPUArch.arm64, result.get(1).second()); + } + + @Test + public void testListDistinctHypervisorArchTypes_WithoutZone() { + Long zoneId = null; + HostVO host1 = mock(HostVO.class); + when(host1.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.VMware); + when(host1.getArch()).thenReturn(CPU.CPUArch.amd64); + List dummyHosts = Collections.singletonList(host1); + doReturn(dummyHosts).when(hostDao).search(any(SearchCriteria.class), isNull()); + List> result = hostDao.listDistinctHypervisorArchTypes(zoneId); + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(Hypervisor.HypervisorType.VMware, result.get(0).first()); + assertEquals(CPU.CPUArch.amd64, result.get(0).second()); + } } diff --git a/engine/schema/src/test/java/com/cloud/storage/dao/VMTemplateDaoImplTest.java b/engine/schema/src/test/java/com/cloud/storage/dao/VMTemplateDaoImplTest.java new file mode 100644 index 000000000000..df0b36ebdbf6 --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/storage/dao/VMTemplateDaoImplTest.java @@ -0,0 +1,189 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.storage.dao; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.cpu.CPU; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.storage.Storage; +import com.cloud.storage.VMTemplateVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@RunWith(MockitoJUnitRunner.class) +public class VMTemplateDaoImplTest { + + @Mock + HostDao hostDao; + + @Spy + @InjectMocks + VMTemplateDaoImpl templateDao = new VMTemplateDaoImpl(); + + @Test + public void testFindLatestTemplateByName_ReturnsTemplate() { + VMTemplateVO expectedTemplate = new VMTemplateVO(); + List returnedList = Collections.singletonList(expectedTemplate); + doReturn(returnedList).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class)); + VMTemplateVO result = templateDao.findLatestTemplateByName("test", CPU.CPUArch.getDefault()); + assertNotNull("Expected a non-null template", result); + assertEquals("Expected the returned template to be the first element", expectedTemplate, result); + } + + @Test + public void testFindLatestTemplateByName_ReturnsNullWhenNoTemplateFound() { + List emptyList = Collections.emptyList(); + doReturn(emptyList).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class)); + VMTemplateVO result = templateDao.findLatestTemplateByName("test", CPU.CPUArch.getDefault()); + assertNull("Expected null when no templates are found", result); + } + + @Test + public void testFindLatestTemplateByName_NullArch() { + VMTemplateVO expectedTemplate = new VMTemplateVO(); + List returnedList = Collections.singletonList(expectedTemplate); + doReturn(returnedList).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class)); + VMTemplateVO result = templateDao.findLatestTemplateByName("test", null); + assertNotNull("Expected a non-null template even if arch is null", result); + assertEquals("Expected the returned template to be the first element", expectedTemplate, result); + } + + @Test + public void testGetSortedTemplatesListWithPreferredArch_PreferredProvided() { + VMTemplateVO templatePreferred = Mockito.mock(VMTemplateVO.class); + when(templatePreferred.getArch()).thenReturn(CPU.CPUArch.amd64); + VMTemplateVO templateOther = Mockito.mock(VMTemplateVO.class); + when(templateOther.getArch()).thenReturn(CPU.CPUArch.arm64); + + Map, VMTemplateVO> uniqueTemplates = new HashMap<>(); + uniqueTemplates.put(new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64), templatePreferred); + uniqueTemplates.put(new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.arm64), templateOther); + List sortedList = templateDao.getSortedTemplatesListWithPreferredArch(uniqueTemplates, + CPU.CPUArch.amd64.getType()); + assertEquals(2, sortedList.size()); + assertEquals(templatePreferred, sortedList.get(0)); + assertEquals(templateOther, sortedList.get(1)); + } + + @Test + public void testGetSortedTemplatesListWithPreferredArch_NoPreferred() { + VMTemplateVO template1 = Mockito.mock(VMTemplateVO.class); + when(template1.getId()).thenReturn(1L); + VMTemplateVO template2 = Mockito.mock(VMTemplateVO.class); + when(template2.getId()).thenReturn(2L); + Map, VMTemplateVO> uniqueTemplates = new HashMap<>(); + uniqueTemplates.put(new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64), template1); + uniqueTemplates.put(new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.arm64), template2); + List sortedList = templateDao.getSortedTemplatesListWithPreferredArch(uniqueTemplates, ""); + assertEquals(2, sortedList.size()); + assertEquals(template2, sortedList.get(0)); + assertEquals(template1, sortedList.get(1)); + } + + @Test + public void testFindSystemVMReadyTemplates() { + long zoneId = 1L; + Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; + String preferredArch = CPU.CPUArch.arm64.getType(); + List> availableHypervisors = new ArrayList<>(); + availableHypervisors.add(new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64)); + availableHypervisors.add(new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.arm64)); + doReturn(availableHypervisors).when(hostDao).listDistinctHypervisorArchTypes(zoneId); + VMTemplateVO template1 = Mockito.mock(VMTemplateVO.class); + when(template1.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(template1.getArch()).thenReturn(CPU.CPUArch.amd64); + VMTemplateVO template2 = Mockito.mock(VMTemplateVO.class); + when(template2.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(template2.getArch()).thenReturn(CPU.CPUArch.arm64); + List templatesFromDb = Arrays.asList(template1, template2); + doReturn(templatesFromDb).when(templateDao).listBy(any(), any()); + SearchBuilder sb = mock(SearchBuilder.class); + templateDao.readySystemTemplateSearch = sb; + when(sb.create()).thenReturn(mock(SearchCriteria.class)); + List result = templateDao.findSystemVMReadyTemplates(zoneId, hypervisorType, preferredArch); + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals(template2, result.get(0)); + assertEquals(template1, result.get(1)); + } + + @Test + public void testFindRoutingTemplates() { + Hypervisor.HypervisorType hType = Hypervisor.HypervisorType.KVM; + String templateName = "TestRouting"; + String preferredArch = CPU.CPUArch.amd64.getType(); + VMTemplateVO template = Mockito.mock(VMTemplateVO.class); + when(template.getArch()).thenReturn(CPU.CPUArch.amd64); + List templatesFromDb = Collections.singletonList(template); + doReturn(templatesFromDb).when(templateDao).listBy(any(), any()); + SearchBuilder sb = mock(SearchBuilder.class); + when(sb.create()).thenReturn(mock(SearchCriteria.class)); + templateDao.tmpltTypeHyperSearch2 = sb; + List result = templateDao.findRoutingTemplates(hType, templateName, preferredArch); + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(template, result.get(0)); + } + + @Test + public void testFindLatestTemplateByTypeAndHypervisorAndArch_Found() { + Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; + CPU.CPUArch arch = CPU.CPUArch.amd64; + Storage.TemplateType type = Storage.TemplateType.SYSTEM; + VMTemplateVO template = Mockito.mock(VMTemplateVO.class); + List templatesFromDb = Collections.singletonList(template); + doReturn(templatesFromDb).when(templateDao).listBy(any(), any()); + VMTemplateVO result = templateDao.findLatestTemplateByTypeAndHypervisorAndArch(hypervisorType, arch, type); + assertNotNull(result); + assertEquals(template, result); + } + + @Test + public void testFindLatestTemplateByTypeAndHypervisorAndArch_NotFound() { + Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; + CPU.CPUArch arch = CPU.CPUArch.x86; + Storage.TemplateType type = Storage.TemplateType.SYSTEM; + doReturn(Collections.emptyList()).when(templateDao).listBy(any(), any()); + VMTemplateVO result = templateDao.findLatestTemplateByTypeAndHypervisorAndArch(hypervisorType, arch, type); + assertNull(result); + } +} diff --git a/engine/schema/src/test/java/com/cloud/upgrade/SystemVmTemplateRegistrationTest.java b/engine/schema/src/test/java/com/cloud/upgrade/SystemVmTemplateRegistrationTest.java new file mode 100644 index 000000000000..27656fd33b0e --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/upgrade/SystemVmTemplateRegistrationTest.java @@ -0,0 +1,427 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.upgrade; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; +import org.apache.cloudstack.utils.security.DigestHelper; +import org.apache.commons.lang3.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.cpu.CPU; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.utils.HttpUtils; +import com.cloud.utils.Pair; +import com.cloud.utils.UriUtils; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.Script; + +@RunWith(MockitoJUnitRunner.class) +public class SystemVmTemplateRegistrationTest { + + @Mock + ClusterDao clusterDao; + + @Mock + VMTemplateDao vmTemplateDao; + + @Spy + @InjectMocks + SystemVmTemplateRegistration systemVmTemplateRegistration = new SystemVmTemplateRegistration(); + + private void setupMetadataFile(MockedStatic mockedStatic, String content) { + try { + String location = "metadata.ini"; + if (StringUtils.isNotBlank(content)) { + File tempFile = File.createTempFile("metadata", ".ini"); + location = tempFile.getAbsolutePath(); + Files.write(Paths.get(location), content.getBytes()); + tempFile.deleteOnExit(); + } + mockedStatic.when(SystemVmTemplateRegistration::getMetadataFilePath).thenReturn(location); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + @Test + public void test_parseMetadataFile_noFile() { + try (MockedStatic mockedStatic = + Mockito.mockStatic(SystemVmTemplateRegistration.class, Mockito.CALLS_REAL_METHODS)) { + setupMetadataFile(mockedStatic, null); + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, + SystemVmTemplateRegistration::parseMetadataFile); + assertTrue(exception.getMessage().contains("Failed to parse systemVM template metadata file")); + } + } + + @Test + public void test_parseMetadataFile_invalidContent() { + try (MockedStatic mockedStatic = + Mockito.mockStatic(SystemVmTemplateRegistration.class, Mockito.CALLS_REAL_METHODS)) { + setupMetadataFile(mockedStatic, "abc"); + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, + SystemVmTemplateRegistration::parseMetadataFile); + assertTrue(exception.getMessage().contains("Failed to parse systemVM template metadata file")); + } + } + + @Test + public void test_parseMetadataFile_success() { + String metadataFileContent = "[default]\n" + + "version = x.y.z.0\n" + + "\n" + + "[kvm-x86_64]\n" + + "templatename = systemvm-kvm-x.y.z\n" + + "checksum = abc1\n" + + "downloadurl = https://download.cloudstack.org/systemvm/x.y/systemvmtemplate-x.y.z-kvm.qcow2.bz2\n" + + "filename = systemvmtemplate-x.y.z-kvm.qcow2.bz2\n" + + "\n" + + "[kvm-aarch64]\n" + + "templatename = systemvm-kvm-x.y.z\n" + + "checksum = abc2\n" + + "downloadurl = https://download.cloudstack.org/systemvm/x.y/systemvmtemplate-x.y.z-kvm.qcow2.bz2\n" + + "filename = systemvmtemplate-x.y.z-kvm.qcow2.bz2\n" + + "\n" + + "[vmware]\n" + + "templatename = systemvm-vmware-x.y.z\n" + + "checksum = abc3\n" + + "downloadurl = https://download.cloudstack.org/systemvm/x.y/systemvmtemplate-x.y.z-vmware.ova\n" + + "filename = systemvmtemplate-x.y.z-vmware.ova\n"; + try (MockedStatic mockedStatic = + Mockito.mockStatic(SystemVmTemplateRegistration.class, Mockito.CALLS_REAL_METHODS)) { + setupMetadataFile(mockedStatic, metadataFileContent); + String version = SystemVmTemplateRegistration.parseMetadataFile(); + assertEquals("x.y.z.0", version); + } + assertNull(SystemVmTemplateRegistration.NewTemplateMap.get("xenserver")); + SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails = + SystemVmTemplateRegistration.NewTemplateMap.get("kvm-x86_64"); + assertNotNull(templateDetails); + assertEquals(CPU.CPUArch.amd64, templateDetails.getArch()); + assertEquals(Hypervisor.HypervisorType.KVM, templateDetails.getHypervisorType()); + templateDetails = + SystemVmTemplateRegistration.NewTemplateMap.get("kvm-aarch64"); + assertNotNull(templateDetails); + assertEquals(CPU.CPUArch.arm64, templateDetails.getArch()); + assertEquals(Hypervisor.HypervisorType.KVM, templateDetails.getHypervisorType()); + templateDetails = + SystemVmTemplateRegistration.NewTemplateMap.get("vmware"); + assertNotNull(templateDetails); + assertNull(templateDetails.getArch()); + assertEquals(Hypervisor.HypervisorType.VMware, templateDetails.getHypervisorType()); + } + + @Test + public void testMountStore_nullStoreUrl() throws Exception { + try (MockedStatic