Compare commits

...

25 Commits

Author SHA1 Message Date
Abhishek Kumar
24d3b3ccdd 4.20 Health Check, please don't merge this!
Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
2025-10-16 10:36:15 +02:00
Abhishek Kumar
03a4b9f4fd
server,utils: improve js interpretation functionality
Make JS interpretation functionalities configurable via a hidden config
- js.interpretation.enabled
Default value is false, making such functionalities disabled, ie, new
heuristic rules cannot be added or updated.

For JsInterpretor, use --no-java --no-syntax-extensions args and a deny-all ClassFilter.
Replace string-spliced vars with ENGINE_SCOPE Bindings, use a fresh ScriptContext per run, and compile before eval.
Use a named daemon worker with hard timeouts and capture stdout.

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
2025-10-16 09:49:36 +02:00
Abhishek Kumar
c8d44d92a7
api,server: fix entity access
Added access check for:
- createNetworkACL
- listNetworkACLs
- listResourceDetails
- listVirtualMachinesUsageHistory
- listVolumesUsageHistory

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
2025-10-16 09:49:34 +02:00
Abhishek Kumar
eee43e534f
cloudutils: fix warning, error during kvm agent installation (#11318)
* cloudutils: fix warning, error during kvm agent installation

Fixes #10379

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* Update utilities.py

---------

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
2025-10-15 20:31:00 +02:00
Wei Zhou
b82369c241
systemvm: fix duplicated "en_US.UTF-8 UTF-8" in /etc/locale.gen (#11823) 2025-10-15 11:42:24 +02:00
Wei Zhou
4327871036
Routed: fix create network exception when auto-allocation is disabled (#11624)
* Routed: fix create network exception when auto-allocation is disabled for regular users

* routed: throw InvalidParameterValueException instead of CloudRuntimeException which gives vague message to regular users
2025-10-14 13:00:33 +02:00
Rohit Yadav
6f931dbd00
agent: increase timeout for host arch retrieval (#11254) (#11822)
Cherry-picked from 44f80648a9ea818e34997416aabbcd95cb03f847

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
Co-authored-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
2025-10-14 10:53:45 +02:00
Wei Zhou
86cad79c15
importvm: fix IP address allocation on Shared networks (#11811) 2025-10-13 08:16:46 +02:00
Abhisar Sinha
4d95f08a3a
Delete template from storage pool instantly if no volume is using it (#11782) 2025-10-09 09:41:18 +02:00
dahn
309b444205
pom.xml: update jetty version (#11793)
* update jetty

* Rollback jetty-maven-plugin version in pom.xml

Co-authored-by: Rohit Yadav <rohit.yadav@shapeblue.com>

---------

Co-authored-by: Daan Hoogland <dahn@apache.org>
Co-authored-by: Wei Zhou <weizhou@apache.org>
Co-authored-by: Rohit Yadav <rohit.yadav@shapeblue.com>
2025-10-09 08:39:45 +02:00
Wei Zhou
89d2b17461
storage: change storage pool to Up state when cancel storage migration (#11773)
* storage: change storage pool to Up state when cancel storage migration

* Update 11773: connect host to shared pool after cancelling storage migration

* Update 11773: update db only

* Update 11773: skip capacity update for storpool
2025-10-08 15:34:59 +02:00
Suresh Kumar Anaparti
b143ddc405
Sanitize the rbd file cmd parameter logs during qemu-img convert (through Script) (#11801) 2025-10-08 13:55:08 +02:00
Manoj Kumar
9f20979bce
UI: Fix primary storage for datastore cluster and retain traffic labels during zone deployment (#11760) 2025-10-08 13:38:03 +02:00
dahn
270d3f9a2d
UI: Deal with crosssite api call after login (#10533) 2025-10-08 10:42:00 +02:00
Wei Zhou
314c4591ec
systemvmtemplate: Bump Debian version to 12.12.0 (#11778) 2025-10-08 10:25:36 +02:00
Daan Hoogland
aca8235960 Merge branch '4.19' into 4.20 2025-10-07 14:28:47 +02:00
Suresh Kumar Anaparti
823cb00a0a
server: do not enable the disabled local storage(s) on host connection during mgmt server / agent start (#11722) 2025-10-07 11:21:47 +02:00
Wei Zhou
963a67b816
server: add user.password.reset.smtp.useStartTLS and enabledSecurityProtocols for password reset (#11228) 2025-10-07 10:19:57 +05:30
Rene Peinthor
a208db54ea
linstor: use sparse/discard qemu-img convert on thin devices (#11787) 2025-10-06 09:10:53 +02:00
Alexandru Bagu
8e4dc0a66d
VMware: match nic mac for ip address fetch (#10641) 2025-10-04 11:49:26 +02:00
Wei Zhou
e12813de49
CKS: fix CKS creation on an existing Shared and Routed network (#11735) 2025-10-03 14:35:43 +05:30
Manoj Kumar
ca7138b3bd
server: Consider Instance in Starting state as well for allocation algorithm (#11751)
* Consider Instance in Starting state as well for allocation algorithm

* use IN instead of OR statement
2025-10-02 08:13:48 +02:00
Wei Zhou
c631d6a480
CKS: generate a random UUID as password of CKS user in project (#11639) 2025-10-01 08:47:58 +02:00
Abhisar Sinha
70af55e848
UI support for extraconfig in deploy and update instance (#11719) 2025-09-30 09:20:44 +02:00
Nicolas Vazquez
30cb8c7a82
Fix importing unmanaged instances due to incorrect internal name (#11753) 2025-09-30 09:01:07 +02:00
69 changed files with 869 additions and 296 deletions

View File

@ -2,7 +2,7 @@
[![Apache CloudStack](tools/logo/apache_cloudstack.png)](https://cloudstack.apache.org/) [![Apache CloudStack](tools/logo/apache_cloudstack.png)](https://cloudstack.apache.org/)
Apache CloudStack is open source software designed to deploy and manage large Apache CloudStack is open-source software designed to deploy and manage large
networks of virtual machines, as a highly available, highly scalable networks of virtual machines, as a highly available, highly scalable
Infrastructure as a Service (IaaS) cloud computing platform. CloudStack is used Infrastructure as a Service (IaaS) cloud computing platform. CloudStack is used
by a number of service providers to offer public cloud services, and by many by a number of service providers to offer public cloud services, and by many

View File

@ -93,7 +93,6 @@ import com.cloud.utils.nio.Link;
import com.cloud.utils.nio.NioClient; import com.cloud.utils.nio.NioClient;
import com.cloud.utils.nio.NioConnection; import com.cloud.utils.nio.NioConnection;
import com.cloud.utils.nio.Task; import com.cloud.utils.nio.Task;
import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script; import com.cloud.utils.script.Script;
/** /**
@ -598,9 +597,9 @@ public class Agent implements HandlerFactory, IAgentControl, AgentStatusUpdater
} }
protected String getAgentArch() { protected String getAgentArch() {
final Script command = new Script("/usr/bin/arch", 500, logger); String arch = Script.runSimpleBashScript(Script.getExecutableAbsolutePath("arch"), 2000);
final OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser(); logger.debug("Arch for agent: {} found: {}", _name, arch);
return command.execute(parser); return arch;
} }
@Override @Override

View File

@ -20,7 +20,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.cloud.user.UserData;
import org.apache.cloudstack.api.command.admin.cluster.ListClustersCmd; import org.apache.cloudstack.api.command.admin.cluster.ListClustersCmd;
import org.apache.cloudstack.api.command.admin.config.ListCfgGroupsByCmd; import org.apache.cloudstack.api.command.admin.config.ListCfgGroupsByCmd;
import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd; import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd;
@ -66,6 +65,7 @@ import org.apache.cloudstack.api.command.user.vm.GetVMPasswordCmd;
import org.apache.cloudstack.api.command.user.vmgroup.UpdateVMGroupCmd; import org.apache.cloudstack.api.command.user.vmgroup.UpdateVMGroupCmd;
import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.config.ConfigurationGroup; import org.apache.cloudstack.config.ConfigurationGroup;
import org.apache.cloudstack.framework.config.ConfigKey;
import com.cloud.alert.Alert; import com.cloud.alert.Alert;
import com.cloud.capacity.Capacity; import com.cloud.capacity.Capacity;
@ -85,6 +85,7 @@ import com.cloud.storage.GuestOSHypervisor;
import com.cloud.storage.GuestOsCategory; import com.cloud.storage.GuestOsCategory;
import com.cloud.storage.StoragePool; import com.cloud.storage.StoragePool;
import com.cloud.user.SSHKeyPair; import com.cloud.user.SSHKeyPair;
import com.cloud.user.UserData;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.Ternary; import com.cloud.utils.Ternary;
import com.cloud.vm.InstanceGroup; import com.cloud.vm.InstanceGroup;
@ -98,6 +99,14 @@ import com.cloud.vm.VirtualMachine.Type;
public interface ManagementService { public interface ManagementService {
static final String Name = "management-server"; static final String Name = "management-server";
ConfigKey<Boolean> JsInterpretationEnabled = new ConfigKey<>("Hidden"
, Boolean.class
, "js.interpretation.enabled"
, "false"
, "Enable/Disable all JavaScript interpretation related functionalities to create or update Javascript rules."
, false
, ConfigKey.Scope.Global);
/** /**
* returns the a map of the names/values in the configuration table * returns the a map of the names/values in the configuration table
* *
@ -481,4 +490,6 @@ public interface ManagementService {
Pair<Boolean, String> patchSystemVM(PatchSystemVMCmd cmd); Pair<Boolean, String> patchSystemVM(PatchSystemVMCmd cmd);
void checkJsInterpretationAllowedIfNeededForParameterValue(String paramName, boolean paramValue);
} }

View File

@ -18,6 +18,7 @@ package com.cloud.server;
public interface ResourceManagerUtil { public interface ResourceManagerUtil {
long getResourceId(String resourceId, ResourceTag.ResourceObjectType resourceType); long getResourceId(String resourceId, ResourceTag.ResourceObjectType resourceType);
long getResourceId(String resourceId, ResourceTag.ResourceObjectType resourceType, boolean checkAccess);
String getUuid(String resourceId, ResourceTag.ResourceObjectType resourceType); String getUuid(String resourceId, ResourceTag.ResourceObjectType resourceType);
ResourceTag.ResourceObjectType getResourceType(String resourceTypeStr); ResourceTag.ResourceObjectType getResourceType(String resourceTypeStr);
void checkResourceAccessible(Long accountId, Long domainId, String exceptionMessage); void checkResourceAccessible(Long accountId, Long domainId, String exceptionMessage);

View File

@ -26,6 +26,7 @@ public class ApiConstants {
public static final String ACTIVATION_RULE = "activationrule"; public static final String ACTIVATION_RULE = "activationrule";
public static final String ACTIVITY = "activity"; public static final String ACTIVITY = "activity";
public static final String ADAPTER_TYPE = "adaptertype"; public static final String ADAPTER_TYPE = "adaptertype";
public static final String ADDITONAL_CONFIG_ENABLED = "additionalconfigenabled";
public static final String ADDRESS = "address"; public static final String ADDRESS = "address";
public static final String ALGORITHM = "algorithm"; public static final String ALGORITHM = "algorithm";
public static final String ALIAS = "alias"; public static final String ALIAS = "alias";

View File

@ -73,6 +73,7 @@ public class ListCapabilitiesCmd extends BaseCmd {
response.setSharedFsVmMinCpuCount((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_CPU_COUNT)); response.setSharedFsVmMinCpuCount((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_CPU_COUNT));
response.setSharedFsVmMinRamSize((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_RAM_SIZE)); response.setSharedFsVmMinRamSize((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_RAM_SIZE));
response.setDynamicScalingEnabled((Boolean) capabilities.get(ApiConstants.DYNAMIC_SCALING_ENABLED)); response.setDynamicScalingEnabled((Boolean) capabilities.get(ApiConstants.DYNAMIC_SCALING_ENABLED));
response.setAdditionalConfigEnabled((Boolean) capabilities.get(ApiConstants.ADDITONAL_CONFIG_ENABLED));
response.setObjectName("capability"); response.setObjectName("capability");
response.setResponseName(getCommandName()); response.setResponseName(getCommandName());
this.setResponseObject(response); this.setResponseObject(response);

View File

@ -140,6 +140,10 @@ public class CapabilitiesResponse extends BaseResponse {
@Param(description = "true if dynamically scaling for instances is enabled", since = "4.21.0") @Param(description = "true if dynamically scaling for instances is enabled", since = "4.21.0")
private Boolean dynamicScalingEnabled; private Boolean dynamicScalingEnabled;
@SerializedName(ApiConstants.ADDITONAL_CONFIG_ENABLED)
@Param(description = "true if additional configurations or extraconfig can be passed to Instances", since = "4.20.2")
private Boolean additionalConfigEnabled;
public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) { public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) {
this.securityGroupsEnabled = securityGroupsEnabled; this.securityGroupsEnabled = securityGroupsEnabled;
} }
@ -255,4 +259,8 @@ public class CapabilitiesResponse extends BaseResponse {
public void setDynamicScalingEnabled(Boolean dynamicScalingEnabled) { public void setDynamicScalingEnabled(Boolean dynamicScalingEnabled) {
this.dynamicScalingEnabled = dynamicScalingEnabled; this.dynamicScalingEnabled = dynamicScalingEnabled;
} }
public void setAdditionalConfigEnabled(Boolean additionalConfigEnabled) {
this.additionalConfigEnabled = additionalConfigEnabled;
}
} }

View File

@ -294,6 +294,8 @@ public interface StorageManager extends StorageService {
Answer sendToPool(StoragePool pool, long[] hostIdsToTryFirst, Command cmd) throws StorageUnavailableException; Answer sendToPool(StoragePool pool, long[] hostIdsToTryFirst, Command cmd) throws StorageUnavailableException;
void updateStoragePoolHostVOAndBytes(StoragePool pool, long hostId, ModifyStoragePoolAnswer mspAnswer);
CapacityVO getSecondaryStorageUsedStats(Long hostId, Long zoneId); CapacityVO getSecondaryStorageUsedStats(Long hostId, Long zoneId);
CapacityVO getStoragePoolUsedStats(Long poolId, Long clusterId, Long podId, Long zoneId); CapacityVO getStoragePoolUsedStats(Long poolId, Long clusterId, Long podId, Long zoneId);

View File

@ -56,6 +56,13 @@ public interface TemplateManager {
+ "will validate if the provided URL is resolvable during the register of templates/ISOs before persisting them in the database.", + "will validate if the provided URL is resolvable during the register of templates/ISOs before persisting them in the database.",
true); true);
ConfigKey<Boolean> TemplateDeleteFromPrimaryStorage = new ConfigKey<Boolean>("Advanced",
Boolean.class,
"template.delete.from.primary.storage", "true",
"Template when deleted will be instantly deleted from the Primary Storage",
true,
ConfigKey.Scope.Global);
static final String VMWARE_TOOLS_ISO = "vmware-tools.iso"; static final String VMWARE_TOOLS_ISO = "vmware-tools.iso";
static final String XS_TOOLS_ISO = "xs-tools.iso"; static final String XS_TOOLS_ISO = "xs-tools.iso";
@ -103,6 +110,8 @@ public interface TemplateManager {
*/ */
List<VMTemplateStoragePoolVO> getUnusedTemplatesInPool(StoragePoolVO pool); List<VMTemplateStoragePoolVO> getUnusedTemplatesInPool(StoragePoolVO pool);
void evictTemplateFromStoragePoolsForZones(Long templateId, List<Long> zoneId);
/** /**
* Deletes a template in the specified storage pool. * Deletes a template in the specified storage pool.
* *

View File

@ -4772,6 +4772,18 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
} }
}); });
if (selectedIp != null && GuestType.Shared.equals(network.getGuestType())) {
IPAddressVO ipAddressVO = _ipAddressDao.findByIpAndSourceNetworkId(network.getId(), selectedIp);
if (ipAddressVO != null && IpAddress.State.Free.equals(ipAddressVO.getState())) {
ipAddressVO.setState(IPAddressVO.State.Allocated);
ipAddressVO.setAllocatedTime(new Date());
Account account = _accountDao.findById(vm.getAccountId());
ipAddressVO.setAllocatedInDomainId(account.getDomainId());
ipAddressVO.setAllocatedToAccountId(account.getId());
_ipAddressDao.update(ipAddressVO.getId(), ipAddressVO);
}
}
final Integer networkRate = _networkModel.getNetworkRate(network.getId(), vm.getId()); final Integer networkRate = _networkModel.getNetworkRate(network.getId(), vm.getId());
final NicProfile vmNic = new NicProfile(vo, network, vo.getBroadcastUri(), vo.getIsolationUri(), networkRate, _networkModel.isSecurityGroupSupportedInNetwork(network), final NicProfile vmNic = new NicProfile(vo, network, vo.getBroadcastUri(), vo.getIsolationUri(), networkRate, _networkModel.isSecurityGroupSupportedInNetwork(network),
_networkModel.getNetworkTag(vm.getHypervisorType(), network)); _networkModel.getNetworkTag(vm.getHypervisorType(), network));
@ -4783,15 +4795,15 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
if (network.getGuestType() == GuestType.L2) { if (network.getGuestType() == GuestType.L2) {
return null; return null;
} }
return dataCenter.getNetworkType() == NetworkType.Basic ? return GuestType.Shared.equals(network.getGuestType()) ?
getSelectedIpForNicImportOnBasicZone(ipAddresses.getIp4Address(), network, dataCenter): getSelectedIpForNicImportOnSharedNetwork(ipAddresses.getIp4Address(), network, dataCenter):
_ipAddrMgr.acquireGuestIpAddress(network, ipAddresses.getIp4Address()); _ipAddrMgr.acquireGuestIpAddress(network, ipAddresses.getIp4Address());
} }
protected String getSelectedIpForNicImportOnBasicZone(String requestedIp, Network network, DataCenter dataCenter) { protected String getSelectedIpForNicImportOnSharedNetwork(String requestedIp, Network network, DataCenter dataCenter) {
IPAddressVO ipAddressVO = StringUtils.isBlank(requestedIp) ? IPAddressVO ipAddressVO = StringUtils.isBlank(requestedIp) ?
_ipAddressDao.findBySourceNetworkIdAndDatacenterIdAndState(network.getId(), dataCenter.getId(), IpAddress.State.Free): _ipAddressDao.findBySourceNetworkIdAndDatacenterIdAndState(network.getId(), dataCenter.getId(), IpAddress.State.Free):
_ipAddressDao.findByIp(requestedIp); _ipAddressDao.findByIpAndSourceNetworkId(network.getId(), requestedIp);
if (ipAddressVO == null || ipAddressVO.getState() != IpAddress.State.Free) { if (ipAddressVO == null || ipAddressVO.getState() != IpAddress.State.Free) {
String msg = String.format("Cannot find a free IP to assign to VM NIC on network %s", network.getName()); String msg = String.format("Cannot find a free IP to assign to VM NIC on network %s", network.getName());
logger.error(msg); logger.error(msg);

View File

@ -822,7 +822,7 @@ public class NetworkOrchestratorTest extends TestCase {
Mockito.when(network.getId()).thenReturn(networkId); Mockito.when(network.getId()).thenReturn(networkId);
Mockito.when(dataCenter.getId()).thenReturn(dataCenterId); Mockito.when(dataCenter.getId()).thenReturn(dataCenterId);
Mockito.when(ipAddresses.getIp4Address()).thenReturn(requestedIp); Mockito.when(ipAddresses.getIp4Address()).thenReturn(requestedIp);
Mockito.when(testOrchestrator._ipAddressDao.findByIp(requestedIp)).thenReturn(ipAddressVO); Mockito.when(testOrchestrator._ipAddressDao.findByIpAndSourceNetworkId(networkId, requestedIp)).thenReturn(ipAddressVO);
String ipAddress = testOrchestrator.getSelectedIpForNicImport(network, dataCenter, ipAddresses); String ipAddress = testOrchestrator.getSelectedIpForNicImport(network, dataCenter, ipAddresses);
Assert.assertEquals(requestedIp, ipAddress); Assert.assertEquals(requestedIp, ipAddress);
} }

View File

@ -35,6 +35,8 @@ public interface VMTemplatePoolDao extends GenericDao<VMTemplateStoragePoolVO, L
List<VMTemplateStoragePoolVO> listByPoolIdAndState(long poolId, ObjectInDataStoreStateMachine.State state); List<VMTemplateStoragePoolVO> listByPoolIdAndState(long poolId, ObjectInDataStoreStateMachine.State state);
List<VMTemplateStoragePoolVO> listByPoolIdsAndTemplate(List<Long> poolIds, Long templateId);
List<VMTemplateStoragePoolVO> listByTemplateStatus(long templateId, VMTemplateStoragePoolVO.Status downloadState); List<VMTemplateStoragePoolVO> listByTemplateStatus(long templateId, VMTemplateStoragePoolVO.Status downloadState);
List<VMTemplateStoragePoolVO> listByTemplateStatus(long templateId, VMTemplateStoragePoolVO.Status downloadState, long poolId); List<VMTemplateStoragePoolVO> listByTemplateStatus(long templateId, VMTemplateStoragePoolVO.Status downloadState, long poolId);

View File

@ -150,6 +150,16 @@ public class VMTemplatePoolDaoImpl extends GenericDaoBase<VMTemplateStoragePoolV
return findOneIncludingRemovedBy(sc); return findOneIncludingRemovedBy(sc);
} }
@Override
public List<VMTemplateStoragePoolVO> listByPoolIdsAndTemplate(List<Long> poolIds, Long templateId) {
SearchCriteria<VMTemplateStoragePoolVO> sc = PoolTemplateSearch.create();
if (CollectionUtils.isNotEmpty(poolIds)) {
sc.setParameters("pool_id", poolIds.toArray());
}
sc.setParameters("template_id", templateId);
return listBy(sc);
}
@Override @Override
public List<VMTemplateStoragePoolVO> listByTemplateStatus(long templateId, VMTemplateStoragePoolVO.Status downloadState) { public List<VMTemplateStoragePoolVO> listByTemplateStatus(long templateId, VMTemplateStoragePoolVO.Status downloadState) {
SearchCriteria<VMTemplateStoragePoolVO> sc = TemplateStatusSearch.create(); SearchCriteria<VMTemplateStoragePoolVO> sc = TemplateStatusSearch.create();

View File

@ -116,17 +116,17 @@ public class VMInstanceDaoImpl extends GenericDaoBase<VMInstanceVO, Long> implem
protected Attribute _updateTimeAttr; protected Attribute _updateTimeAttr;
private static final String ORDER_CLUSTERS_NUMBER_OF_VMS_FOR_ACCOUNT_PART1 = "SELECT host.cluster_id, SUM(IF(vm.state='Running' AND vm.account_id = ?, 1, 0)) " + private static final String ORDER_CLUSTERS_NUMBER_OF_VMS_FOR_ACCOUNT_PART1 = "SELECT host.cluster_id, SUM(IF(vm.state IN ('Running', 'Starting') AND vm.account_id = ?, 1, 0)) " +
"FROM `cloud`.`host` host LEFT JOIN `cloud`.`vm_instance` vm ON host.id = vm.host_id WHERE "; "FROM `cloud`.`host` host LEFT JOIN `cloud`.`vm_instance` vm ON host.id = vm.host_id WHERE ";
private static final String ORDER_CLUSTERS_NUMBER_OF_VMS_FOR_ACCOUNT_PART2 = " AND host.type = 'Routing' AND host.removed is null GROUP BY host.cluster_id " + private static final String ORDER_CLUSTERS_NUMBER_OF_VMS_FOR_ACCOUNT_PART2 = " AND host.type = 'Routing' AND host.removed is null GROUP BY host.cluster_id " +
"ORDER BY 2 ASC "; "ORDER BY 2 ASC ";
private static final String ORDER_PODS_NUMBER_OF_VMS_FOR_ACCOUNT = "SELECT pod.id, SUM(IF(vm.state='Running' AND vm.account_id = ?, 1, 0)) FROM `cloud`.`" + private static final String ORDER_PODS_NUMBER_OF_VMS_FOR_ACCOUNT = "SELECT pod.id, SUM(IF(vm.state IN ('Running', 'Starting') AND vm.account_id = ?, 1, 0)) FROM `cloud`.`" +
"host_pod_ref` pod LEFT JOIN `cloud`.`vm_instance` vm ON pod.id = vm.pod_id WHERE pod.data_center_id = ? AND pod.removed is null " "host_pod_ref` pod LEFT JOIN `cloud`.`vm_instance` vm ON pod.id = vm.pod_id WHERE pod.data_center_id = ? AND pod.removed is null "
+ " GROUP BY pod.id ORDER BY 2 ASC "; + " GROUP BY pod.id ORDER BY 2 ASC ";
private static final String ORDER_HOSTS_NUMBER_OF_VMS_FOR_ACCOUNT = private static final String ORDER_HOSTS_NUMBER_OF_VMS_FOR_ACCOUNT =
"SELECT host.id, SUM(IF(vm.state='Running' AND vm.account_id = ?, 1, 0)) FROM `cloud`.`host` host LEFT JOIN `cloud`.`vm_instance` vm ON host.id = vm.host_id " + "SELECT host.id, SUM(IF(vm.state IN ('Running', 'Starting') AND vm.account_id = ?, 1, 0)) FROM `cloud`.`host` host LEFT JOIN `cloud`.`vm_instance` vm ON host.id = vm.host_id " +
"WHERE host.data_center_id = ? AND host.type = 'Routing' AND host.removed is null "; "WHERE host.data_center_id = ? AND host.type = 'Routing' AND host.removed is null ";
private static final String ORDER_HOSTS_NUMBER_OF_VMS_FOR_ACCOUNT_PART2 = " GROUP BY host.id ORDER BY 2 ASC "; private static final String ORDER_HOSTS_NUMBER_OF_VMS_FOR_ACCOUNT_PART2 = " GROUP BY host.id ORDER BY 2 ASC ";

View File

@ -154,4 +154,6 @@ public interface PrimaryDataStoreDao extends GenericDao<StoragePoolVO, Long> {
String keyword, Filter searchFilter); String keyword, Filter searchFilter);
List<StoragePoolVO> listByIds(List<Long> ids); List<StoragePoolVO> listByIds(List<Long> ids);
List<StoragePoolVO> listByDataCenterIds(List<Long> dataCenterIds);
} }

View File

@ -63,6 +63,7 @@ public class PrimaryDataStoreDaoImpl extends GenericDaoBase<StoragePoolVO, Long>
private final GenericSearchBuilder<StoragePoolVO, Long> StatusCountSearch; private final GenericSearchBuilder<StoragePoolVO, Long> StatusCountSearch;
private final SearchBuilder<StoragePoolVO> ClustersSearch; private final SearchBuilder<StoragePoolVO> ClustersSearch;
private final SearchBuilder<StoragePoolVO> IdsSearch; private final SearchBuilder<StoragePoolVO> IdsSearch;
private final SearchBuilder<StoragePoolVO> DcsSearch;
@Inject @Inject
private StoragePoolDetailsDao _detailsDao; private StoragePoolDetailsDao _detailsDao;
@ -155,6 +156,9 @@ public class PrimaryDataStoreDaoImpl extends GenericDaoBase<StoragePoolVO, Long>
IdsSearch.and("ids", IdsSearch.entity().getId(), SearchCriteria.Op.IN); IdsSearch.and("ids", IdsSearch.entity().getId(), SearchCriteria.Op.IN);
IdsSearch.done(); IdsSearch.done();
DcsSearch = createSearchBuilder();
DcsSearch.and("dataCenterId", DcsSearch.entity().getDataCenterId(), SearchCriteria.Op.IN);
DcsSearch.done();
} }
@Override @Override
@ -733,6 +737,16 @@ public class PrimaryDataStoreDaoImpl extends GenericDaoBase<StoragePoolVO, Long>
return listBy(sc); return listBy(sc);
} }
@Override
public List<StoragePoolVO> listByDataCenterIds(List<Long> dataCenterIds) {
if (CollectionUtils.isEmpty(dataCenterIds)) {
return Collections.emptyList();
}
SearchCriteria<StoragePoolVO> sc = DcsSearch.create();
sc.setParameters("dataCenterId", dataCenterIds.toArray());
return listBy(sc);
}
private SearchCriteria<StoragePoolVO> createStoragePoolSearchCriteria(Long storagePoolId, String storagePoolName, private SearchCriteria<StoragePoolVO> createStoragePoolSearchCriteria(Long storagePoolId, String storagePoolName,
Long zoneId, String path, Long podId, Long clusterId, String address, ScopeType scopeType, Long zoneId, String path, Long podId, Long clusterId, String address, ScopeType scopeType,
StoragePoolStatus status, String keyword) { StoragePoolStatus status, String keyword) {

View File

@ -159,7 +159,9 @@ public class PrimaryDataStoreHelper {
pool.setScope(scope.getScopeType()); pool.setScope(scope.getScopeType());
pool.setUsedBytes(existingInfo.getCapacityBytes() - existingInfo.getAvailableBytes()); pool.setUsedBytes(existingInfo.getCapacityBytes() - existingInfo.getAvailableBytes());
pool.setCapacityBytes(existingInfo.getCapacityBytes()); pool.setCapacityBytes(existingInfo.getCapacityBytes());
pool.setStatus(StoragePoolStatus.Up); if (pool.getStatus() != StoragePoolStatus.Disabled) {
pool.setStatus(StoragePoolStatus.Up);
}
this.dataStoreDao.update(pool.getId(), pool); this.dataStoreDao.update(pool.getId(), pool);
this.storageMgr.createCapacityEntry(pool, Capacity.CAPACITY_TYPE_LOCAL_STORAGE, pool.getUsedBytes()); this.storageMgr.createCapacityEntry(pool, Capacity.CAPACITY_TYPE_LOCAL_STORAGE, pool.getUsedBytes());
return dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary); return dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary);

View File

@ -32,7 +32,6 @@ import java.util.stream.Collectors;
import javax.inject.Inject; import javax.inject.Inject;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import com.cloud.user.Account;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.quota.activationrule.presetvariables.GenericPresetVariable; import org.apache.cloudstack.quota.activationrule.presetvariables.GenericPresetVariable;
import org.apache.cloudstack.quota.activationrule.presetvariables.PresetVariableHelper; import org.apache.cloudstack.quota.activationrule.presetvariables.PresetVariableHelper;
@ -62,6 +61,7 @@ import org.springframework.stereotype.Component;
import com.cloud.usage.UsageVO; import com.cloud.usage.UsageVO;
import com.cloud.usage.dao.UsageDao; import com.cloud.usage.dao.UsageDao;
import com.cloud.user.Account;
import com.cloud.user.AccountVO; import com.cloud.user.AccountVO;
import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.AccountDao;
import com.cloud.utils.DateUtil; import com.cloud.utils.DateUtil;
@ -467,7 +467,7 @@ public class QuotaManagerImpl extends ManagerBase implements QuotaManager {
} }
jsInterpreter.injectStringVariable("resourceType", presetVariables.getResourceType()); jsInterpreter.injectVariable("resourceType", presetVariables.getResourceType());
jsInterpreter.injectVariable("value", presetVariables.getValue().toString()); jsInterpreter.injectVariable("value", presetVariables.getValue().toString());
jsInterpreter.injectVariable("zone", presetVariables.getZone().toString()); jsInterpreter.injectVariable("zone", presetVariables.getZone().toString());
} }

View File

@ -270,7 +270,7 @@ public class QuotaManagerImplTest {
Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("account"), Mockito.anyString()); Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("account"), Mockito.anyString());
Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("domain"), Mockito.anyString()); Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("domain"), Mockito.anyString());
Mockito.verify(jsInterpreterMock, Mockito.never()).injectVariable(Mockito.eq("project"), Mockito.anyString()); Mockito.verify(jsInterpreterMock, Mockito.never()).injectVariable(Mockito.eq("project"), Mockito.anyString());
Mockito.verify(jsInterpreterMock).injectStringVariable(Mockito.eq("resourceType"), Mockito.anyString()); Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("resourceType"), Mockito.anyString());
Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("value"), Mockito.anyString()); Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("value"), Mockito.anyString());
Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("zone"), Mockito.anyString()); Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("zone"), Mockito.anyString());
} }
@ -291,7 +291,7 @@ public class QuotaManagerImplTest {
Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("account"), Mockito.anyString()); Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("account"), Mockito.anyString());
Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("domain"), Mockito.anyString()); Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("domain"), Mockito.anyString());
Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("project"), Mockito.anyString()); Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("project"), Mockito.anyString());
Mockito.verify(jsInterpreterMock).injectStringVariable(Mockito.eq("resourceType"), Mockito.anyString()); Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("resourceType"), Mockito.anyString());
Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("value"), Mockito.anyString()); Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("value"), Mockito.anyString());
Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("zone"), Mockito.anyString()); Mockito.verify(jsInterpreterMock).injectVariable(Mockito.eq("zone"), Mockito.anyString());
} }

View File

@ -39,7 +39,6 @@ import java.util.stream.Collectors;
import javax.inject.Inject; import javax.inject.Inject;
import com.cloud.utils.DateUtil;
import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.QuotaBalanceCmd; import org.apache.cloudstack.api.command.QuotaBalanceCmd;
@ -70,8 +69,8 @@ import org.apache.cloudstack.quota.dao.QuotaCreditsDao;
import org.apache.cloudstack.quota.dao.QuotaEmailConfigurationDao; import org.apache.cloudstack.quota.dao.QuotaEmailConfigurationDao;
import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao; import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao;
import org.apache.cloudstack.quota.dao.QuotaTariffDao; import org.apache.cloudstack.quota.dao.QuotaTariffDao;
import org.apache.cloudstack.quota.vo.QuotaAccountVO;
import org.apache.cloudstack.quota.dao.QuotaUsageDao; import org.apache.cloudstack.quota.dao.QuotaUsageDao;
import org.apache.cloudstack.quota.vo.QuotaAccountVO;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO; import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
import org.apache.cloudstack.quota.vo.QuotaCreditsVO; import org.apache.cloudstack.quota.vo.QuotaCreditsVO;
import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO;
@ -79,26 +78,28 @@ import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO;
import org.apache.cloudstack.quota.vo.QuotaTariffVO; import org.apache.cloudstack.quota.vo.QuotaTariffVO;
import org.apache.cloudstack.quota.vo.QuotaUsageVO; import org.apache.cloudstack.quota.vo.QuotaUsageVO;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.cloud.domain.DomainVO; import com.cloud.domain.DomainVO;
import com.cloud.domain.dao.DomainDao; import com.cloud.domain.dao.DomainDao;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.user.Account; import com.cloud.user.Account;
import com.cloud.user.AccountManager; import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO; import com.cloud.user.AccountVO;
import com.cloud.user.User; import com.cloud.user.User;
import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.AccountDao;
import com.cloud.user.dao.UserDao; import com.cloud.user.dao.UserDao;
import com.cloud.utils.DateUtil;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter; import com.cloud.utils.db.Filter;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
@Component @Component
public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
@ -139,6 +140,12 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
@Inject @Inject
private ApiDiscoveryService apiDiscoveryService; private ApiDiscoveryService apiDiscoveryService;
protected void checkActivationRulesAllowed(String activationRule) {
if (!_quotaService.isJsInterpretationEnabled() && StringUtils.isNotEmpty(activationRule)) {
throw new PermissionDeniedException("Quota Tariff Activation Rule cannot be set, as Javascript interpretation is disabled in the configuration.");
}
}
@Override @Override
public QuotaTariffResponse createQuotaTariffResponse(QuotaTariffVO tariff, boolean returnActivationRule) { public QuotaTariffResponse createQuotaTariffResponse(QuotaTariffVO tariff, boolean returnActivationRule) {
final QuotaTariffResponse response = new QuotaTariffResponse(); final QuotaTariffResponse response = new QuotaTariffResponse();
@ -440,6 +447,8 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
throw new InvalidParameterValueException(String.format("There is no quota tariffs with name [%s].", name)); throw new InvalidParameterValueException(String.format("There is no quota tariffs with name [%s].", name));
} }
checkActivationRulesAllowed(activationRule);
Date currentQuotaTariffStartDate = currentQuotaTariff.getEffectiveOn(); Date currentQuotaTariffStartDate = currentQuotaTariff.getEffectiveOn();
currentQuotaTariff.setRemoved(now); currentQuotaTariff.setRemoved(now);
@ -696,6 +705,8 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
throw new InvalidParameterValueException(String.format("A quota tariff with name [%s] already exist.", name)); throw new InvalidParameterValueException(String.format("A quota tariff with name [%s] already exist.", name));
} }
checkActivationRulesAllowed(activationRule);
if (startDate.compareTo(now) < 0) { if (startDate.compareTo(now) < 0) {
throw new InvalidParameterValueException(String.format("The value passed as Quota tariff's start date is in the past: [%s]. " + throw new InvalidParameterValueException(String.format("The value passed as Quota tariff's start date is in the past: [%s]. " +
"Please, inform a date in the future or do not pass the parameter to use the current date and time.", startDate)); "Please, inform a date in the future or do not pass the parameter to use the current date and time.", startDate));

View File

@ -16,15 +16,15 @@
//under the License. //under the License.
package org.apache.cloudstack.quota; package org.apache.cloudstack.quota;
import com.cloud.user.AccountVO; import java.math.BigDecimal;
import com.cloud.utils.component.PluggableService; import java.util.Date;
import java.util.List;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO; import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
import org.apache.cloudstack.quota.vo.QuotaUsageVO; import org.apache.cloudstack.quota.vo.QuotaUsageVO;
import java.math.BigDecimal; import com.cloud.user.AccountVO;
import java.util.Date; import com.cloud.utils.component.PluggableService;
import java.util.List;
public interface QuotaService extends PluggableService { public interface QuotaService extends PluggableService {
@ -40,4 +40,6 @@ public interface QuotaService extends PluggableService {
boolean saveQuotaAccount(AccountVO account, BigDecimal aggrUsage, Date endDate); boolean saveQuotaAccount(AccountVO account, BigDecimal aggrUsage, Date endDate);
boolean isJsInterpretationEnabled();
} }

View File

@ -60,6 +60,7 @@ import com.cloud.configuration.Config;
import com.cloud.domain.dao.DomainDao; import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.PermissionDeniedException;
import com.cloud.server.ManagementService;
import com.cloud.user.Account; import com.cloud.user.Account;
import com.cloud.user.AccountVO; import com.cloud.user.AccountVO;
import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.AccountDao;
@ -86,6 +87,8 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi
private TimeZone _usageTimezone; private TimeZone _usageTimezone;
private boolean jsInterpretationEnabled = false;
public QuotaServiceImpl() { public QuotaServiceImpl() {
super(); super();
} }
@ -97,6 +100,8 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi
String timeZoneStr = ObjectUtils.defaultIfNull(_configDao.getValue(Config.UsageAggregationTimezone.toString()), "GMT"); String timeZoneStr = ObjectUtils.defaultIfNull(_configDao.getValue(Config.UsageAggregationTimezone.toString()), "GMT");
_usageTimezone = TimeZone.getTimeZone(timeZoneStr); _usageTimezone = TimeZone.getTimeZone(timeZoneStr);
jsInterpretationEnabled = ManagementService.JsInterpretationEnabled.value();
return true; return true;
} }
@ -284,4 +289,8 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi
} }
} }
@Override
public boolean isJsInterpretationEnabled() {
return jsInterpretationEnabled;
}
} }

View File

@ -58,7 +58,7 @@ public class KVMHostInfo {
private long reservedMemory; private long reservedMemory;
private long overCommitMemory; private long overCommitMemory;
private List<String> capabilities = new ArrayList<>(); private List<String> capabilities = new ArrayList<>();
private static String cpuArchCommand = "/usr/bin/arch"; private static String cpuArchRetrieveExecutable = "arch";
private static List<String> cpuInfoFreqFileNames = List.of("/sys/devices/system/cpu/cpu0/cpufreq/base_frequency","/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq"); private static List<String> cpuInfoFreqFileNames = List.of("/sys/devices/system/cpu/cpu0/cpufreq/base_frequency","/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq");
public KVMHostInfo(long reservedMemory, long overCommitMemory, long manualSpeed, int reservedCpus) { public KVMHostInfo(long reservedMemory, long overCommitMemory, long manualSpeed, int reservedCpus) {
@ -248,6 +248,6 @@ public class KVMHostInfo {
private String getCPUArchFromCommand() { private String getCPUArchFromCommand() {
LOGGER.info("Fetching host CPU arch"); LOGGER.info("Fetching host CPU arch");
return Script.runSimpleBashScript(cpuArchCommand); return Script.runSimpleBashScript(Script.getExecutableAbsolutePath(cpuArchRetrieveExecutable));
} }
} }

View File

@ -5837,11 +5837,20 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
if (toolsStatus == VirtualMachineToolsStatus.TOOLS_NOT_INSTALLED) { if (toolsStatus == VirtualMachineToolsStatus.TOOLS_NOT_INSTALLED) {
details += "Vmware tools not installed."; details += "Vmware tools not installed.";
} else { } else {
ip = guestInfo.getIpAddress(); var normalizedMac = cmd.getMacAddress().replaceAll("-", ":");
if (ip != null) { for(var guestInfoNic : guestInfo.getNet()) {
result = true; var normalizedNicMac = guestInfoNic.getMacAddress().replaceAll("-", ":");
if (!result && normalizedNicMac.equalsIgnoreCase(normalizedMac)) {
result = true;
details = null;
for (var ipAddr : guestInfoNic.getIpAddress()) {
if (NetUtils.isValidIp4(ipAddr) && (cmd.getVmNetworkCidr() == null || NetUtils.isIpWithInCidrRange(ipAddr, cmd.getVmNetworkCidr()))) {
details = ipAddr;
}
}
break;
}
} }
details = ip;
} }
} else { } else {
details += "VM " + vmName + " no longer exists on vSphere host: " + hyperHost.getHyperHostName(); details += "VM " + vmName + " no longer exists on vSphere host: " + hyperHost.getHyperHostName();

View File

@ -381,6 +381,9 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
logger.warn("Unable to find the network with ID: {} passed for the Kubernetes cluster", networkId); logger.warn("Unable to find the network with ID: {} passed for the Kubernetes cluster", networkId);
return false; return false;
} }
if (isDirectAccess(network)) {
return true;
}
networkOffering = networkOfferingDao.findById(network.getNetworkOfferingId()); networkOffering = networkOfferingDao.findById(network.getNetworkOfferingId());
if (networkOffering == null) { if (networkOffering == null) {
logger.warn("Unable to find the network offering of the network: {} ({}) to be used for provisioning Kubernetes cluster", network.getName(), network.getUuid()); logger.warn("Unable to find the network offering of the network: {} ({}) to be used for provisioning Kubernetes cluster", network.getName(), network.getUuid());
@ -1551,7 +1554,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
try { try {
Role role = getProjectKubernetesAccountRole(); Role role = getProjectKubernetesAccountRole();
UserAccount userAccount = accountService.createUserAccount(accountName, UserAccount userAccount = accountService.createUserAccount(accountName,
UuidUtils.first(UUID.randomUUID().toString()), PROJECT_KUBERNETES_ACCOUNT_FIRST_NAME, UUID.randomUUID().toString(), PROJECT_KUBERNETES_ACCOUNT_FIRST_NAME,
PROJECT_KUBERNETES_ACCOUNT_LAST_NAME, null, null, accountName, Account.Type.NORMAL, role.getId(), PROJECT_KUBERNETES_ACCOUNT_LAST_NAME, null, null, accountName, Account.Type.NORMAL, role.getId(),
project.getDomainId(), null, null, null, null, User.Source.NATIVE); project.getDomainId(), null, null, null, null, User.Source.NATIVE);
projectManager.assignAccountToProject(project, userAccount.getAccountId(), ProjectAccount.Role.Regular, projectManager.assignAccountToProject(project, userAccount.getAccountId(), ProjectAccount.Role.Regular,

View File

@ -22,6 +22,7 @@ import static com.cloud.utils.NumbersUtil.toReadableSize;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -32,8 +33,6 @@ import java.util.stream.Collectors;
import javax.inject.Inject; import javax.inject.Inject;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import com.cloud.dc.ClusterVO;
import com.cloud.utils.Ternary;
import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ListClustersMetricsCmd; import org.apache.cloudstack.api.ListClustersMetricsCmd;
import org.apache.cloudstack.api.ListDbMetricsCmd; import org.apache.cloudstack.api.ListDbMetricsCmd;
@ -100,6 +99,7 @@ import com.cloud.capacity.CapacityManager;
import com.cloud.capacity.dao.CapacityDao; import com.cloud.capacity.dao.CapacityDao;
import com.cloud.capacity.dao.CapacityDaoImpl; import com.cloud.capacity.dao.CapacityDaoImpl;
import com.cloud.cluster.dao.ManagementServerHostDao; import com.cloud.cluster.dao.ManagementServerHostDao;
import com.cloud.dc.ClusterVO;
import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenter;
import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.ClusterDao;
import com.cloud.dc.dao.DataCenterDao; import com.cloud.dc.dao.DataCenterDao;
@ -112,6 +112,7 @@ import com.cloud.host.Status;
import com.cloud.host.dao.HostDao; import com.cloud.host.dao.HostDao;
import com.cloud.network.router.VirtualRouter; import com.cloud.network.router.VirtualRouter;
import com.cloud.org.Cluster; import com.cloud.org.Cluster;
import com.cloud.projects.Project;
import com.cloud.server.DbStatsCollection; import com.cloud.server.DbStatsCollection;
import com.cloud.server.ManagementServerHostStats; import com.cloud.server.ManagementServerHostStats;
import com.cloud.server.StatsCollector; import com.cloud.server.StatsCollector;
@ -124,6 +125,7 @@ import com.cloud.usage.dao.UsageJobDao;
import com.cloud.user.Account; import com.cloud.user.Account;
import com.cloud.user.AccountManager; import com.cloud.user.AccountManager;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.db.DbProperties; import com.cloud.utils.db.DbProperties;
import com.cloud.utils.db.DbUtil; import com.cloud.utils.db.DbUtil;
import com.cloud.utils.db.Filter; import com.cloud.utils.db.Filter;
@ -184,6 +186,10 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements
private static Gson gson = new Gson(); private static Gson gson = new Gson();
private final List<Account.Type> AccountTypesWithRecursiveUsageAccess = Arrays.asList(
Account.Type.ADMIN, Account.Type.DOMAIN_ADMIN, Account.Type.READ_ONLY_ADMIN
);
protected MetricsServiceImpl() { protected MetricsServiceImpl() {
super(); super();
} }
@ -245,17 +251,30 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements
* @return the list of VMs. * @return the list of VMs.
*/ */
protected Pair<List<UserVmVO>, Integer> searchForUserVmsInternal(ListVMsUsageHistoryCmd cmd) { protected Pair<List<UserVmVO>, Integer> searchForUserVmsInternal(ListVMsUsageHistoryCmd cmd) {
final Long id = cmd.getId();
Account caller = CallContext.current().getCallingAccount();
List<Long> permittedAccounts = new ArrayList<>();
boolean recursive = AccountTypesWithRecursiveUsageAccess.contains(caller.getType());
Ternary<Long, Boolean, Project.ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(null, recursive, null);
accountMgr.buildACLSearchParameters(caller, id, null, null, permittedAccounts, domainIdRecursiveListProject, true, false);
Long domainId = domainIdRecursiveListProject.first();
Boolean isRecursive = domainIdRecursiveListProject.second();
Project.ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
Filter searchFilter = new Filter(UserVmVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal()); Filter searchFilter = new Filter(UserVmVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal());
List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds()); List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds());
String name = cmd.getName(); String name = cmd.getName();
String keyword = cmd.getKeyword(); String keyword = cmd.getKeyword();
SearchBuilder<UserVmVO> sb = userVmDao.createSearchBuilder(); SearchBuilder<UserVmVO> sb = userVmDao.createSearchBuilder();
sb.select(null, SearchCriteria.Func.DISTINCT, sb.entity().getId()); // select distinct
accountMgr.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
sb.and("idIN", sb.entity().getId(), SearchCriteria.Op.IN); sb.and("idIN", sb.entity().getId(), SearchCriteria.Op.IN);
sb.and("displayName", sb.entity().getDisplayName(), SearchCriteria.Op.LIKE); sb.and("displayName", sb.entity().getDisplayName(), SearchCriteria.Op.LIKE);
sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ); sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
SearchCriteria<UserVmVO> sc = sb.create(); SearchCriteria<UserVmVO> sc = sb.create();
accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
if (CollectionUtils.isNotEmpty(ids)) { if (CollectionUtils.isNotEmpty(ids)) {
sc.setParameters("idIN", ids.toArray()); sc.setParameters("idIN", ids.toArray());
} }
@ -269,7 +288,14 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements
sc.addAnd("displayName", SearchCriteria.Op.SC, ssc); sc.addAnd("displayName", SearchCriteria.Op.SC, ssc);
} }
return userVmDao.searchAndCount(sc, searchFilter); Pair<List<UserVmVO>, Integer> uniqueVmPair = userVmDao.searchAndCount(sc, searchFilter);
Integer count = uniqueVmPair.second();
if (count == 0) {
return new Pair<>(new ArrayList<>(), count);
}
List<Long> vmIds = uniqueVmPair.first().stream().map(UserVmVO::getId).collect(Collectors.toList());
List<UserVmVO> vms = userVmDao.listByIds(vmIds);
return new Pair<>(vms, count);
} }
/** /**
@ -331,17 +357,49 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements
* @return the list of VMs. * @return the list of VMs.
*/ */
protected Pair<List<VolumeVO>, Integer> searchForVolumesInternal(ListVolumesUsageHistoryCmd cmd) { protected Pair<List<VolumeVO>, Integer> searchForVolumesInternal(ListVolumesUsageHistoryCmd cmd) {
final Long id = cmd.getId();
Account caller = CallContext.current().getCallingAccount();
List<Long> permittedAccounts = new ArrayList<>();
boolean recursive = AccountTypesWithRecursiveUsageAccess.contains(caller.getType());
Ternary<Long, Boolean, Project.ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(null, recursive, null);
accountMgr.buildACLSearchParameters(caller, id, null, null, permittedAccounts, domainIdRecursiveListProject, true, false);
Long domainId = domainIdRecursiveListProject.first();
Boolean isRecursive = domainIdRecursiveListProject.second();
Project.ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
Filter searchFilter = new Filter(VolumeVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal()); Filter searchFilter = new Filter(VolumeVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal());
List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds()); List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds());
String name = cmd.getName(); String name = cmd.getName();
String keyword = cmd.getKeyword(); String keyword = cmd.getKeyword();
SearchBuilder<VolumeVO> sb = volumeDao.createSearchBuilder(); SearchBuilder<VolumeVO> sb = volumeDao.createSearchBuilder();
sb.select(null, SearchCriteria.Func.DISTINCT, sb.entity().getId()); // select distinct
accountMgr.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
sb.and("idIN", sb.entity().getId(), SearchCriteria.Op.IN); sb.and("idIN", sb.entity().getId(), SearchCriteria.Op.IN);
sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ); sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ); sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
boolean shouldListSystemVmVolumes = accountMgr.isRootAdmin(CallContext.current().getCallingAccountId());
List<Long> vmIds = new ArrayList<>();
if (!shouldListSystemVmVolumes) {
SearchBuilder<UserVmVO> vmSearch = userVmDao.createSearchBuilder();
vmSearch.select(null, SearchCriteria.Func.DISTINCT, vmSearch.entity().getId());
accountMgr.buildACLSearchBuilder(vmSearch, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
SearchCriteria<UserVmVO> vmSc = vmSearch.create();
accountMgr.buildACLSearchCriteria(vmSc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
List<UserVmVO> vms = userVmDao.search(vmSc, null);
vmIds = vms.stream().map(UserVmVO::getId).collect(Collectors.toList());
if (vmIds.isEmpty()) {
sb.and("instanceIdNull", sb.entity().getInstanceId(), SearchCriteria.Op.NULL);
} else {
sb.and().op("instanceIdNull", sb.entity().getInstanceId(), SearchCriteria.Op.NULL);
sb.or("instanceIds", sb.entity().getInstanceId(), SearchCriteria.Op.IN);
sb.cp();
}
}
SearchCriteria<VolumeVO> sc = sb.create(); SearchCriteria<VolumeVO> sc = sb.create();
accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
if (CollectionUtils.isNotEmpty(ids)) { if (CollectionUtils.isNotEmpty(ids)) {
sc.setParameters("idIN", ids.toArray()); sc.setParameters("idIN", ids.toArray());
} }
@ -354,8 +412,18 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements
ssc.addOr("state", SearchCriteria.Op.EQ, keyword); ssc.addOr("state", SearchCriteria.Op.EQ, keyword);
sc.addAnd("name", SearchCriteria.Op.SC, ssc); sc.addAnd("name", SearchCriteria.Op.SC, ssc);
} }
if (!shouldListSystemVmVolumes && CollectionUtils.isNotEmpty(vmIds)) {
sc.setParameters("instanceIds", vmIds.toArray());
}
return volumeDao.searchAndCount(sc, searchFilter); Pair<List<VolumeVO>, Integer> uniqueVolumePair = volumeDao.searchAndCount(sc, searchFilter);
Integer count = uniqueVolumePair.second();
if (count == 0) {
return new Pair<>(new ArrayList<>(), count);
}
List<Long> volumeIds = uniqueVolumePair.first().stream().map(VolumeVO::getId).collect(Collectors.toList());
List<VolumeVO> volumes = volumeDao.listByIds(volumeIds);
return new Pair<>(volumes, count);
} }
/** /**

View File

@ -19,13 +19,16 @@ package org.apache.cloudstack.metrics;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.cloudstack.api.ListVMsUsageHistoryCmd; import org.apache.cloudstack.api.ListVMsUsageHistoryCmd;
import org.apache.cloudstack.api.ListVolumesUsageHistoryCmd;
import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.response.VmMetricsStatsResponse; import org.apache.cloudstack.response.VmMetricsStatsResponse;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.junit.Assert; import org.junit.Assert;
@ -35,12 +38,18 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor; import org.mockito.Captor;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.Spy; import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.InvalidParameterValueException;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria;
import com.cloud.vm.UserVmVO; import com.cloud.vm.UserVmVO;
@ -75,6 +84,12 @@ public class MetricsServiceImplTest {
@Mock @Mock
VmStatsDao vmStatsDaoMock; VmStatsDao vmStatsDaoMock;
@Mock
AccountManager accountManager;
@Mock
VolumeDao volumeDao;
@Captor @Captor
ArgumentCaptor<String> stringCaptor1, stringCaptor2; ArgumentCaptor<String> stringCaptor1, stringCaptor2;
@ -95,12 +110,26 @@ public class MetricsServiceImplTest {
@Mock @Mock
VmStatsVO vmStatsVOMock; VmStatsVO vmStatsVOMock;
@Mock
Account mockAccount;
@Mock
ListVolumesUsageHistoryCmd listVolumesUsageHistoryCmdMock;
@Mock
VolumeVO volumeVOMock;
@Mock
SearchBuilder<VolumeVO> volumeSearchBuilderMock;
@Mock
SearchCriteria<VolumeVO> volumeSearchCriteriaMock;
@Mock
Filter filterMock;
private void prepareSearchCriteriaWhenUseSetParameters() { private void prepareSearchCriteriaWhenUseSetParameters() {
Mockito.doNothing().when(scMock).setParameters(Mockito.anyString(), Mockito.any()); Mockito.doNothing().when(scMock).setParameters(Mockito.anyString(), Mockito.any());
} }
private void preparesearchForUserVmsInternalTest() { private void preparesearchForUserVmsInternalTest() {
Mockito.when(mockAccount.getType()).thenReturn(Account.Type.NORMAL);
expectedVmListAndCounter = new Pair<>(Arrays.asList(userVmVOMock), 1); expectedVmListAndCounter = new Pair<>(Arrays.asList(userVmVOMock), 1);
Mockito.doReturn(1L).when(listVMsUsageHistoryCmdMock).getStartIndex(); Mockito.doReturn(1L).when(listVMsUsageHistoryCmdMock).getStartIndex();
@ -111,8 +140,10 @@ public class MetricsServiceImplTest {
Mockito.doReturn(userVmVOMock).when(sbMock).entity(); Mockito.doReturn(userVmVOMock).when(sbMock).entity();
Mockito.doReturn(scMock).when(sbMock).create(); Mockito.doReturn(scMock).when(sbMock).create();
Mockito.doReturn(new Pair<List<UserVmVO>, Integer>(Arrays.asList(userVmVOMock), 1)) Mockito.doReturn(expectedVmListAndCounter)
.when(userVmDaoMock).searchAndCount(Mockito.any(), Mockito.any()); .when(userVmDaoMock).searchAndCount(Mockito.any(), Mockito.any());
Mockito.doReturn(expectedVmListAndCounter.first())
.when(userVmDaoMock).listByIds(Mockito.anyList());
} }
@Test @Test
@ -124,12 +155,17 @@ public class MetricsServiceImplTest {
Mockito.doReturn(null).when(listVMsUsageHistoryCmdMock).getName(); Mockito.doReturn(null).when(listVMsUsageHistoryCmdMock).getName();
Mockito.doReturn(null).when(listVMsUsageHistoryCmdMock).getKeyword(); Mockito.doReturn(null).when(listVMsUsageHistoryCmdMock).getKeyword();
Pair<List<UserVmVO>, Integer> result = spy.searchForUserVmsInternal(listVMsUsageHistoryCmdMock); try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
CallContext callContextMock = Mockito.mock(CallContext.class);
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
Mockito.when(callContextMock.getCallingAccount()).thenReturn(mockAccount);
Pair<List<UserVmVO>, Integer> result = spy.searchForUserVmsInternal(listVMsUsageHistoryCmdMock);
Mockito.verify(scMock).setParameters(stringCaptor1.capture(), objectArrayCaptor.capture()); Mockito.verify(scMock).setParameters(stringCaptor1.capture(), objectArrayCaptor.capture());
Assert.assertEquals("idIN", stringCaptor1.getValue()); Assert.assertEquals("idIN", stringCaptor1.getValue());
Assert.assertEquals(fakeVmId1, objectArrayCaptor.getAllValues().get(0)[0]); Assert.assertEquals(fakeVmId1, objectArrayCaptor.getAllValues().get(0)[0]);
Assert.assertEquals(expectedVmListAndCounter, result); Assert.assertEquals(expectedVmListAndCounter, result);
}
} }
@Test @Test
@ -141,13 +177,17 @@ public class MetricsServiceImplTest {
Mockito.doReturn(expected).when(listVMsUsageHistoryCmdMock).getIds(); Mockito.doReturn(expected).when(listVMsUsageHistoryCmdMock).getIds();
Mockito.doReturn(null).when(listVMsUsageHistoryCmdMock).getName(); Mockito.doReturn(null).when(listVMsUsageHistoryCmdMock).getName();
Mockito.doReturn(null).when(listVMsUsageHistoryCmdMock).getKeyword(); Mockito.doReturn(null).when(listVMsUsageHistoryCmdMock).getKeyword();
try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
CallContext callContextMock = Mockito.mock(CallContext.class);
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
Mockito.when(callContextMock.getCallingAccount()).thenReturn(mockAccount);
Pair<List<UserVmVO>, Integer> result = spy.searchForUserVmsInternal(listVMsUsageHistoryCmdMock);
Pair<List<UserVmVO>, Integer> result = spy.searchForUserVmsInternal(listVMsUsageHistoryCmdMock); Mockito.verify(scMock).setParameters(stringCaptor1.capture(), objectArrayCaptor.capture());
Assert.assertEquals("idIN", stringCaptor1.getValue());
Mockito.verify(scMock).setParameters(stringCaptor1.capture(), objectArrayCaptor.capture()); Assert.assertArrayEquals(expected.toArray(), objectArrayCaptor.getAllValues().get(0));
Assert.assertEquals("idIN", stringCaptor1.getValue()); Assert.assertEquals(expectedVmListAndCounter, result);
Assert.assertArrayEquals(expected.toArray(), objectArrayCaptor.getAllValues().get(0)); }
Assert.assertEquals(expectedVmListAndCounter, result);
} }
@Test @Test
@ -159,12 +199,17 @@ public class MetricsServiceImplTest {
Mockito.doReturn("fakeName").when(listVMsUsageHistoryCmdMock).getName(); Mockito.doReturn("fakeName").when(listVMsUsageHistoryCmdMock).getName();
Mockito.doReturn(null).when(listVMsUsageHistoryCmdMock).getKeyword(); Mockito.doReturn(null).when(listVMsUsageHistoryCmdMock).getKeyword();
Pair<List<UserVmVO>, Integer> result = spy.searchForUserVmsInternal(listVMsUsageHistoryCmdMock); try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
CallContext callContextMock = Mockito.mock(CallContext.class);
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
Mockito.when(callContextMock.getCallingAccount()).thenReturn(mockAccount);
Pair<List<UserVmVO>, Integer> result = spy.searchForUserVmsInternal(listVMsUsageHistoryCmdMock);
Mockito.verify(scMock).setParameters(stringCaptor1.capture(), objectArrayCaptor.capture()); Mockito.verify(scMock).setParameters(stringCaptor1.capture(), objectArrayCaptor.capture());
Assert.assertEquals("displayName", stringCaptor1.getValue()); Assert.assertEquals("displayName", stringCaptor1.getValue());
Assert.assertEquals("%fakeName%", objectArrayCaptor.getValue()[0]); Assert.assertEquals("%fakeName%", objectArrayCaptor.getValue()[0]);
Assert.assertEquals(expectedVmListAndCounter, result); Assert.assertEquals(expectedVmListAndCounter, result);
}
} }
@Test @Test
@ -177,16 +222,21 @@ public class MetricsServiceImplTest {
Mockito.doReturn(null).when(listVMsUsageHistoryCmdMock).getName(); Mockito.doReturn(null).when(listVMsUsageHistoryCmdMock).getName();
Mockito.doReturn("fakeKeyword").when(listVMsUsageHistoryCmdMock).getKeyword(); Mockito.doReturn("fakeKeyword").when(listVMsUsageHistoryCmdMock).getKeyword();
Pair<List<UserVmVO>, Integer> result = spy.searchForUserVmsInternal(listVMsUsageHistoryCmdMock); try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
CallContext callContextMock = Mockito.mock(CallContext.class);
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
Mockito.when(callContextMock.getCallingAccount()).thenReturn(mockAccount);
Pair<List<UserVmVO>, Integer> result = spy.searchForUserVmsInternal(listVMsUsageHistoryCmdMock);
Mockito.verify(scMock, Mockito.times(2)).addOr(stringCaptor1.capture(), opCaptor.capture(), objectArrayCaptor.capture()); Mockito.verify(scMock, Mockito.times(2)).addOr(stringCaptor1.capture(), opCaptor.capture(), objectArrayCaptor.capture());
List<String> conditions = stringCaptor1.getAllValues(); List<String> conditions = stringCaptor1.getAllValues();
List<Object[]> params = objectArrayCaptor.getAllValues(); List<Object[]> params = objectArrayCaptor.getAllValues();
Assert.assertEquals("displayName", conditions.get(0)); Assert.assertEquals("displayName", conditions.get(0));
Assert.assertEquals("state", conditions.get(1)); Assert.assertEquals("state", conditions.get(1));
Assert.assertEquals("%fakeKeyword%", params.get(0)[0]); Assert.assertEquals("%fakeKeyword%", params.get(0)[0]);
Assert.assertEquals("fakeKeyword", params.get(1)[0]); Assert.assertEquals("fakeKeyword", params.get(1)[0]);
Assert.assertEquals(expectedVmListAndCounter, result); Assert.assertEquals(expectedVmListAndCounter, result);
}
} }
@Test @Test
@ -317,4 +367,57 @@ public class MetricsServiceImplTest {
spy.createStatsResponse(Arrays.asList(vmStatsVOMock)); spy.createStatsResponse(Arrays.asList(vmStatsVOMock));
} }
@Test
public void searchForVolumesInternalWithValidParameters() {
Mockito.doReturn(null).when(listVolumesUsageHistoryCmdMock).getId();
Mockito.doReturn(Arrays.asList(1L, 2L)).when(listVolumesUsageHistoryCmdMock).getIds();
Mockito.doReturn("volumeName").when(listVolumesUsageHistoryCmdMock).getName();
Mockito.doReturn("keyword").when(listVolumesUsageHistoryCmdMock).getKeyword();
Mockito.doReturn(volumeSearchBuilderMock).when(volumeDao).createSearchBuilder();
Mockito.doReturn(volumeVOMock).when(volumeSearchBuilderMock).entity();
SearchBuilder vmSearchBuilderMock = Mockito.mock(SearchBuilder.class);
Mockito.doReturn(vmSearchBuilderMock).when(userVmDaoMock).createSearchBuilder();
Mockito.doReturn(userVmVOMock).when(vmSearchBuilderMock).entity();
Mockito.doReturn(volumeSearchCriteriaMock).when(volumeSearchBuilderMock).create();
Mockito.doReturn(volumeSearchCriteriaMock).when(volumeDao).createSearchCriteria();
Mockito.doReturn(new Pair<>(Arrays.asList(volumeVOMock), 1)).when(volumeDao).searchAndCount(Mockito.any(), Mockito.any());
Mockito.doReturn(Arrays.asList(volumeVOMock)).when(volumeDao).listByIds(Mockito.anyList());
try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
CallContext callContextMock = Mockito.mock(CallContext.class);
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
Mockito.when(callContextMock.getCallingAccount()).thenReturn(mockAccount);
Pair<List<VolumeVO>, Integer> result = spy.searchForVolumesInternal(listVolumesUsageHistoryCmdMock);
Assert.assertNotNull(result);
Assert.assertEquals(1, result.second().intValue());
Assert.assertEquals(volumeVOMock, result.first().get(0));
}
}
@Test
public void searchForVolumesInternalWithValidParametersNoItem() {
Mockito.doReturn(1L).when(listVolumesUsageHistoryCmdMock).getId();
Mockito.doReturn(volumeSearchBuilderMock).when(volumeDao).createSearchBuilder();
Mockito.doReturn(volumeVOMock).when(volumeSearchBuilderMock).entity();
Mockito.doReturn(volumeSearchCriteriaMock).when(volumeSearchBuilderMock).create();
Mockito.doReturn(new Pair<>(Collections.emptyList(), 0)).when(volumeDao).searchAndCount(Mockito.any(), Mockito.any());
try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
CallContext callContextMock = Mockito.mock(CallContext.class);
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
Mockito.when(callContextMock.getCallingAccount()).thenReturn(mockAccount);
Mockito.when(callContextMock.getCallingAccountId()).thenReturn(1L);
Mockito.when(accountManager.isRootAdmin(1L)).thenReturn(true);
Mockito.when(mockAccount.getType()).thenReturn(Account.Type.ADMIN);
Pair<List<VolumeVO>, Integer> result = spy.searchForVolumesInternal(listVolumesUsageHistoryCmdMock);
Assert.assertNotNull(result);
Assert.assertEquals(0, result.second().intValue());
}
}
} }

View File

@ -448,8 +448,8 @@ public class CloudStackPrimaryDataStoreLifeCycleImpl extends BasePrimaryDataStor
@Override @Override
public boolean cancelMaintain(DataStore store) { public boolean cancelMaintain(DataStore store) {
storagePoolAutmation.cancelMaintain(store);
dataStoreHelper.cancelMaintain(store); dataStoreHelper.cancelMaintain(store);
storagePoolAutmation.cancelMaintain(store);
return true; return true;
} }
@ -500,7 +500,7 @@ public class CloudStackPrimaryDataStoreLifeCycleImpl extends BasePrimaryDataStor
@Override @Override
public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) { public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) {
DataStore dataStore = dataStoreHelper.attachHost(store, scope, existingInfo); DataStore dataStore = dataStoreHelper.attachHost(store, scope, existingInfo);
if(existingInfo.getCapacityBytes() == 0){ if (existingInfo.getCapacityBytes() == 0) {
try { try {
storageMgr.connectHostToSharedPool(hostDao.findById(scope.getScopeId()), dataStore.getId()); storageMgr.connectHostToSharedPool(hostDao.findById(scope.getScopeId()), dataStore.getId());
} catch (StorageUnavailableException ex) { } catch (StorageUnavailableException ex) {

View File

@ -5,6 +5,12 @@ All notable changes to Linstor CloudStack plugin will be documented in this file
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2025-10-03]
### Changed
- Revert qcow2 snapshot now use sparse/discard options to convert on thin devices.
## [2025-08-05] ## [2025-08-05]
### Fixed ### Fixed

View File

@ -26,24 +26,39 @@ import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
import com.cloud.resource.CommandWrapper; import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper; import com.cloud.resource.ResourceWrapper;
import com.cloud.storage.Storage; import com.cloud.storage.Storage;
import com.cloud.utils.script.Script;
import org.apache.cloudstack.storage.command.CopyCmdAnswer; import org.apache.cloudstack.storage.command.CopyCmdAnswer;
import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
import org.apache.cloudstack.storage.to.SnapshotObjectTO; import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO; import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.cloudstack.utils.qemu.QemuImg; import org.apache.cloudstack.utils.qemu.QemuImg;
import org.apache.cloudstack.utils.qemu.QemuImgException; import org.apache.cloudstack.utils.qemu.QemuImgException;
import org.apache.cloudstack.utils.qemu.QemuImgFile; import org.apache.cloudstack.utils.qemu.QemuImgFile;
import org.joda.time.Duration;
import org.libvirt.LibvirtException; import org.libvirt.LibvirtException;
@ResourceWrapper(handles = LinstorRevertBackupSnapshotCommand.class) @ResourceWrapper(handles = LinstorRevertBackupSnapshotCommand.class)
public final class LinstorRevertBackupSnapshotCommandWrapper public final class LinstorRevertBackupSnapshotCommandWrapper
extends CommandWrapper<LinstorRevertBackupSnapshotCommand, CopyCmdAnswer, LibvirtComputingResource> extends CommandWrapper<LinstorRevertBackupSnapshotCommand, CopyCmdAnswer, LibvirtComputingResource>
{ {
private void convertQCow2ToRAW(final String srcPath, final String dstPath, int waitMilliSeconds)
private void convertQCow2ToRAW(
KVMStoragePool pool, final String srcPath, final String dstUuid, int waitMilliSeconds)
throws LibvirtException, QemuImgException throws LibvirtException, QemuImgException
{ {
final String dstPath = pool.getPhysicalDisk(dstUuid).getPath();
final QemuImgFile srcQemuFile = new QemuImgFile( final QemuImgFile srcQemuFile = new QemuImgFile(
srcPath, QemuImg.PhysicalDiskFormat.QCOW2); srcPath, QemuImg.PhysicalDiskFormat.QCOW2);
final QemuImg qemu = new QemuImg(waitMilliSeconds); boolean zeroedDevice = LinstorUtil.resourceSupportZeroBlocks(pool, LinstorUtil.RSC_PREFIX + dstUuid);
if (zeroedDevice)
{
// blockdiscard the device to ensure the device is filled with zeroes
Script blkDiscardScript = new Script("blkdiscard", Duration.millis(waitMilliSeconds));
blkDiscardScript.add("-f");
blkDiscardScript.add(dstPath);
blkDiscardScript.execute();
}
final QemuImg qemu = new QemuImg(waitMilliSeconds, zeroedDevice, true);
final QemuImgFile dstFile = new QemuImgFile(dstPath, QemuImg.PhysicalDiskFormat.RAW); final QemuImgFile dstFile = new QemuImgFile(dstPath, QemuImg.PhysicalDiskFormat.RAW);
qemu.convert(srcQemuFile, dstFile); qemu.convert(srcQemuFile, dstFile);
} }
@ -70,8 +85,9 @@ public final class LinstorRevertBackupSnapshotCommandWrapper
srcDataStore.getUrl() + File.separator + srcFile.getParent()); srcDataStore.getUrl() + File.separator + srcFile.getParent());
convertQCow2ToRAW( convertQCow2ToRAW(
linstorPool,
secondaryPool.getLocalPath() + File.separator + srcFile.getName(), secondaryPool.getLocalPath() + File.separator + srcFile.getName(),
linstorPool.getPhysicalDisk(dst.getPath()).getPath(), dst.getPath(),
cmd.getWaitInMillSeconds()); cmd.getWaitInMillSeconds());
final VolumeObjectTO dstVolume = new VolumeObjectTO(); final VolumeObjectTO dstVolume = new VolumeObjectTO();

View File

@ -30,7 +30,6 @@ import javax.annotation.Nonnull;
import com.cloud.storage.Storage; import com.cloud.storage.Storage;
import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.Script; import com.cloud.utils.script.Script;
import org.apache.cloudstack.storage.datastore.util.LinstorUtil; import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
import org.apache.cloudstack.utils.qemu.QemuImg; import org.apache.cloudstack.utils.qemu.QemuImg;
import org.apache.cloudstack.utils.qemu.QemuImgException; import org.apache.cloudstack.utils.qemu.QemuImgException;
@ -57,7 +56,6 @@ import com.linbit.linstor.api.model.ResourceGroupSpawn;
import com.linbit.linstor.api.model.ResourceMakeAvailable; import com.linbit.linstor.api.model.ResourceMakeAvailable;
import com.linbit.linstor.api.model.ResourceWithVolumes; import com.linbit.linstor.api.model.ResourceWithVolumes;
import com.linbit.linstor.api.model.StoragePool; import com.linbit.linstor.api.model.StoragePool;
import com.linbit.linstor.api.model.Volume;
import com.linbit.linstor.api.model.VolumeDefinition; import com.linbit.linstor.api.model.VolumeDefinition;
import java.io.File; import java.io.File;
@ -573,40 +571,6 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
return copyPhysicalDisk(disk, name, destPool, timeout, null, null, null); return copyPhysicalDisk(disk, name, destPool, timeout, null, null, null);
} }
/**
* Checks if all diskful resource are on a zeroed block device.
* @param destPool Linstor pool to use
* @param resName Linstor resource name
* @return true if all resources are on a provider with zeroed blocks.
*/
private boolean resourceSupportZeroBlocks(KVMStoragePool destPool, String resName) {
final DevelopersApi api = getLinstorAPI(destPool);
try {
List<ResourceWithVolumes> resWithVols = api.viewResources(
Collections.emptyList(),
Collections.singletonList(resName),
Collections.emptyList(),
Collections.emptyList(),
null,
null);
if (resWithVols != null) {
return resWithVols.stream()
.allMatch(res -> {
Volume vol0 = res.getVolumes().get(0);
return vol0 != null && (vol0.getProviderKind() == ProviderKind.LVM_THIN ||
vol0.getProviderKind() == ProviderKind.ZFS ||
vol0.getProviderKind() == ProviderKind.ZFS_THIN ||
vol0.getProviderKind() == ProviderKind.DISKLESS);
} );
}
} catch (ApiException apiExc) {
logger.error(apiExc.getMessage());
}
return false;
}
/** /**
* Checks if the given disk is the SystemVM template, by checking its properties file in the same directory. * Checks if the given disk is the SystemVM template, by checking its properties file in the same directory.
* The initial systemvm template resource isn't created on the management server, but * The initial systemvm template resource isn't created on the management server, but
@ -677,7 +641,7 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
destFile.setFormat(dstDisk.getFormat()); destFile.setFormat(dstDisk.getFormat());
destFile.setSize(disk.getVirtualSize()); destFile.setSize(disk.getVirtualSize());
boolean zeroedDevice = resourceSupportZeroBlocks(destPools, getLinstorRscName(name)); boolean zeroedDevice = LinstorUtil.resourceSupportZeroBlocks(destPools, getLinstorRscName(name));
try { try {
final QemuImg qemu = new QemuImg(timeout, zeroedDevice, true); final QemuImg qemu = new QemuImg(timeout, zeroedDevice, true);
qemu.convert(srcFile, destFile); qemu.convert(srcFile, destFile);

View File

@ -42,6 +42,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -431,4 +432,37 @@ public class LinstorUtil {
public static boolean isRscDiskless(ResourceWithVolumes rsc) { public static boolean isRscDiskless(ResourceWithVolumes rsc) {
return rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS); return rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS);
} }
/**
* Checks if all diskful resource are on a zeroed block device.
* @param pool Linstor pool to use
* @param resName Linstor resource name
* @return true if all resources are on a provider with zeroed blocks.
*/
public static boolean resourceSupportZeroBlocks(KVMStoragePool pool, String resName) {
final DevelopersApi api = getLinstorAPI(pool.getSourceHost());
try {
List<ResourceWithVolumes> resWithVols = api.viewResources(
Collections.emptyList(),
Collections.singletonList(resName),
Collections.emptyList(),
Collections.emptyList(),
null,
null);
if (resWithVols != null) {
return resWithVols.stream()
.allMatch(res -> {
Volume vol0 = res.getVolumes().get(0);
return vol0 != null && (vol0.getProviderKind() == ProviderKind.LVM_THIN ||
vol0.getProviderKind() == ProviderKind.ZFS ||
vol0.getProviderKind() == ProviderKind.ZFS_THIN ||
vol0.getProviderKind() == ProviderKind.DISKLESS);
} );
}
} catch (ApiException apiExc) {
LOGGER.error(apiExc.getMessage());
}
return false;
}
} }

View File

@ -158,7 +158,7 @@
<cs.jakarta.xml.bind.version>2.3.3</cs.jakarta.xml.bind.version> <cs.jakarta.xml.bind.version>2.3.3</cs.jakarta.xml.bind.version>
<cs.jaxws.version>2.3.7</cs.jaxws.version> <cs.jaxws.version>2.3.7</cs.jaxws.version>
<cs.jersey-client.version>2.26</cs.jersey-client.version> <cs.jersey-client.version>2.26</cs.jersey-client.version>
<cs.jetty.version>9.4.51.v20230217</cs.jetty.version> <cs.jetty.version>9.4.58.v20250814</cs.jetty.version>
<cs.jetty-maven-plugin.version>9.4.27.v20200227</cs.jetty-maven-plugin.version> <cs.jetty-maven-plugin.version>9.4.27.v20200227</cs.jetty-maven-plugin.version>
<cs.jna.version>5.5.0</cs.jna.version> <cs.jna.version>5.5.0</cs.jna.version>
<cs.joda-time.version>2.12.5</cs.joda-time.version> <cs.joda-time.version>2.12.5</cs.joda-time.version>

View File

@ -68,14 +68,14 @@ class configFileOps:
for entry in self.entries: for entry in self.entries:
if entry.op == "add": if entry.op == "add":
if entry.separator == "=": if entry.separator == "=":
matchString = "^\ *" + entry.name + ".*" matchString = r"^\ *" + entry.name + ".*"
elif entry.separator == " ": elif entry.separator == " ":
matchString = "^\ *" + entry.name + "\ *" + entry.value matchString = r"^\ *" + entry.name + r"\ *" + entry.value
else: else:
if entry.separator == "=": if entry.separator == "=":
matchString = "^\ *" + entry.name + "\ *=\ *" + entry.value matchString = r"^\ *" + entry.name + r"\ *=\ *" + entry.value
else: else:
matchString = "^\ *" + entry.name + "\ *" + entry.value matchString = r"^\ *" + entry.name + r"\ *" + entry.value
match = re.match(matchString, line) match = re.match(matchString, line)
if match is not None: if match is not None:

View File

@ -45,8 +45,11 @@ class networkConfig:
if not cmd.isSuccess(): if not cmd.isSuccess():
logging.debug("Failed to get default route") logging.debug("Failed to get default route")
raise CloudRuntimeException("Failed to get default route") raise CloudRuntimeException("Failed to get default route")
output = cmd.getStdout().strip()
result = cmd.getStdout().split(" ") result = output.split(" ")
if len(result) < 2:
logging.debug("Output for the default route incomplete: %s. It should have been '<GATEWAY> <DEVICE>'" % output)
raise CloudRuntimeException("Output for the default route incomplete")
gateway = result[0] gateway = result[0]
dev = result[1] dev = result[1]
@ -150,10 +153,10 @@ class networkConfig:
if line.find("HWaddr") != -1: if line.find("HWaddr") != -1:
macAddr = line.split("HWaddr ")[1].strip(" ") macAddr = line.split("HWaddr ")[1].strip(" ")
elif line.find("inet ") != -1: elif line.find("inet ") != -1:
m = re.search("addr:(.*)\ *Bcast:(.*)\ *Mask:(.*)", line) m = re.search(r"addr:([^\s]+)\s*Bcast:([^\s]+)\s*Mask:([^\s]+)", line)
if m is not None: if m is not None:
ipAddr = m.group(1).rstrip(" ") ipAddr = m.group(1).strip()
netmask = m.group(3).rstrip(" ") netmask = m.group(3).strip()
if networkConfig.isBridgePort(dev): if networkConfig.isBridgePort(dev):
type = "brport" type = "brport"

View File

@ -63,7 +63,7 @@ class bash:
return self.stdout.decode('utf-8').strip('\n') return self.stdout.decode('utf-8').strip('\n')
def getLines(self): def getLines(self):
return self.stdout.decode('utf-8').strip('\n') return self.stdout.decode('utf-8').strip('\n').split('\n')
def getStderr(self): def getStderr(self):
return self.stderr.decode('utf-8').strip('\n') return self.stderr.decode('utf-8').strip('\n')

View File

@ -5335,7 +5335,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
//Validation - 1.3 //Validation - 1.3
if (resourceIdStr != null) { if (resourceIdStr != null) {
resourceId = resourceManagerUtil.getResourceId(resourceIdStr, resourceType); resourceId = resourceManagerUtil.getResourceId(resourceIdStr, resourceType, true);
} }
List<? extends ResourceDetail> detailList = new ArrayList<>(); List<? extends ResourceDetail> detailList = new ArrayList<>();

View File

@ -483,6 +483,8 @@ public class NetworkACLServiceImpl extends ManagerBase implements NetworkACLServ
throw new InvalidParameterValueException("Cannot create Network ACL Item. ACL Id or network Id is required"); throw new InvalidParameterValueException("Cannot create Network ACL Item. ACL Id or network Id is required");
} }
Network network = networkModel.getNetwork(createNetworkACLCmd.getNetworkId()); Network network = networkModel.getNetwork(createNetworkACLCmd.getNetworkId());
Account caller = CallContext.current().getCallingAccount();
_accountMgr.checkAccess(caller, null, true, network);
if (network.getVpcId() == null) { if (network.getVpcId() == null) {
throw new InvalidParameterValueException("Network: " + network.getUuid() + " does not belong to VPC"); throw new InvalidParameterValueException("Network: " + network.getUuid() + " does not belong to VPC");
} }
@ -744,6 +746,7 @@ public class NetworkACLServiceImpl extends ManagerBase implements NetworkACLServ
if (networkId != null) { if (networkId != null) {
final Network network = _networkDao.findById(networkId); final Network network = _networkDao.findById(networkId);
_accountMgr.checkAccess(caller, null, true, network);
aclId = network.getNetworkACLId(); aclId = network.getNetworkACLId();
if (aclId == null) { if (aclId == null) {
// No aclId associated with the network. // No aclId associated with the network.

View File

@ -154,6 +154,7 @@ import com.cloud.org.Cluster;
import com.cloud.org.Grouping; import com.cloud.org.Grouping;
import com.cloud.org.Managed; import com.cloud.org.Managed;
import com.cloud.serializer.GsonHelper; import com.cloud.serializer.GsonHelper;
import com.cloud.server.ManagementService;
import com.cloud.service.ServiceOfferingVO; import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.service.dao.ServiceOfferingDetailsDao;
@ -271,6 +272,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
private ServiceOfferingDetailsDao _serviceOfferingDetailsDao; private ServiceOfferingDetailsDao _serviceOfferingDetailsDao;
@Inject @Inject
private UserVmManager userVmManager; private UserVmManager userVmManager;
@Inject
ManagementService managementService;
private List<? extends Discoverer> _discoverers; private List<? extends Discoverer> _discoverers;
@ -1936,6 +1939,9 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
@Override @Override
public Host updateHost(final UpdateHostCmd cmd) throws NoTransitionException { public Host updateHost(final UpdateHostCmd cmd) throws NoTransitionException {
managementService.checkJsInterpretationAllowedIfNeededForParameterValue(ApiConstants.IS_TAG_A_RULE,
Boolean.TRUE.equals(cmd.getIsTagARule()));
return updateHost(cmd.getId(), cmd.getName(), cmd.getOsCategoryId(), return updateHost(cmd.getId(), cmd.getName(), cmd.getOsCategoryId(),
cmd.getAllocationState(), cmd.getUrl(), cmd.getHostTags(), cmd.getIsTagARule(), cmd.getAnnotation(), false); cmd.getAllocationState(), cmd.getUrl(), cmd.getHostTags(), cmd.getIsTagARule(), cmd.getAnnotation(), false);
} }

View File

@ -1040,6 +1040,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
protected List<DeploymentPlanner> _planners; protected List<DeploymentPlanner> _planners;
private boolean jsInterpretationEnabled = false;
private final List<HypervisorType> supportedHypervisors = new ArrayList<>(); private final List<HypervisorType> supportedHypervisors = new ArrayList<>();
public List<DeploymentPlanner> getPlanners() { public List<DeploymentPlanner> getPlanners() {
@ -1126,6 +1128,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
supportedHypervisors.add(HypervisorType.KVM); supportedHypervisors.add(HypervisorType.KVM);
supportedHypervisors.add(HypervisorType.XenServer); supportedHypervisors.add(HypervisorType.XenServer);
jsInterpretationEnabled = JsInterpretationEnabled.value();
return true; return true;
} }
@ -4022,8 +4026,10 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(ListGuestVlansCmd.class); cmdList.add(ListGuestVlansCmd.class);
cmdList.add(AssignVolumeCmd.class); cmdList.add(AssignVolumeCmd.class);
cmdList.add(ListSecondaryStorageSelectorsCmd.class); cmdList.add(ListSecondaryStorageSelectorsCmd.class);
cmdList.add(CreateSecondaryStorageSelectorCmd.class); if (jsInterpretationEnabled) {
cmdList.add(UpdateSecondaryStorageSelectorCmd.class); cmdList.add(CreateSecondaryStorageSelectorCmd.class);
cmdList.add(UpdateSecondaryStorageSelectorCmd.class);
}
cmdList.add(RemoveSecondaryStorageSelectorCmd.class); cmdList.add(RemoveSecondaryStorageSelectorCmd.class);
cmdList.add(ListAffectedVmsForStorageScopeChangeCmd.class); cmdList.add(ListAffectedVmsForStorageScopeChangeCmd.class);
@ -4066,7 +4072,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
@Override @Override
public ConfigKey<?>[] getConfigKeys() { public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {vmPasswordLength, sshKeyLength, humanReadableSizes, customCsIdentifier}; return new ConfigKey<?>[] {vmPasswordLength, sshKeyLength, humanReadableSizes, customCsIdentifier,
JsInterpretationEnabled};
} }
protected class EventPurgeTask extends ManagedContextRunnable { protected class EventPurgeTask extends ManagedContextRunnable {
@ -4535,6 +4542,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
} }
capabilities.put(ApiConstants.SHAREDFSVM_MIN_CPU_COUNT, fsVmMinCpu); capabilities.put(ApiConstants.SHAREDFSVM_MIN_CPU_COUNT, fsVmMinCpu);
capabilities.put(ApiConstants.SHAREDFSVM_MIN_RAM_SIZE, fsVmMinRam); capabilities.put(ApiConstants.SHAREDFSVM_MIN_RAM_SIZE, fsVmMinRam);
capabilities.put(ApiConstants.ADDITONAL_CONFIG_ENABLED, UserVmManager.EnableAdditionalVmConfig.valueIn(caller.getId()));
return capabilities; return capabilities;
} }
@ -5521,4 +5530,13 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
_lockControllerListener = lockControllerListener; _lockControllerListener = lockControllerListener;
} }
@Override
public void checkJsInterpretationAllowedIfNeededForParameterValue(String paramName, boolean paramValue) {
if (!paramValue || jsInterpretationEnabled) {
return;
}
throw new InvalidParameterValueException(String.format(
"The parameter %s cannot be set to true as JS interpretation is disabled",
paramName));
}
} }

View File

@ -213,6 +213,7 @@ import com.cloud.org.Grouping.AllocationState;
import com.cloud.resource.ResourceState; import com.cloud.resource.ResourceState;
import com.cloud.server.ConfigurationServer; import com.cloud.server.ConfigurationServer;
import com.cloud.server.ManagementServer; import com.cloud.server.ManagementServer;
import com.cloud.server.ManagementService;
import com.cloud.server.StatsCollector; import com.cloud.server.StatsCollector;
import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.service.dao.ServiceOfferingDetailsDao;
import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.Storage.ImageFormat;
@ -398,6 +399,8 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
ConfigurationDao configurationDao; ConfigurationDao configurationDao;
@Inject @Inject
private ImageStoreDetailsUtil imageStoreDetailsUtil; private ImageStoreDetailsUtil imageStoreDetailsUtil;
@Inject
ManagementService managementService;
protected List<StoragePoolDiscoverer> _discoverers; protected List<StoragePoolDiscoverer> _discoverers;
@ -1015,6 +1018,9 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
throw new PermissionDeniedException(String.format("Cannot perform this operation, Zone is currently disabled: %s", zone)); throw new PermissionDeniedException(String.format("Cannot perform this operation, Zone is currently disabled: %s", zone));
} }
managementService.checkJsInterpretationAllowedIfNeededForParameterValue(ApiConstants.IS_TAG_A_RULE,
Boolean.TRUE.equals(cmd.isTagARule()));
Map<String, Object> params = new HashMap<>(); Map<String, Object> params = new HashMap<>();
params.put("zoneId", zone.getId()); params.put("zoneId", zone.getId());
params.put("clusterId", clusterId); params.put("clusterId", clusterId);
@ -1197,6 +1203,9 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
// Input validation // Input validation
Long id = cmd.getId(); Long id = cmd.getId();
managementService.checkJsInterpretationAllowedIfNeededForParameterValue(ApiConstants.IS_TAG_A_RULE,
Boolean.TRUE.equals(cmd.isTagARule()));
StoragePoolVO pool = _storagePoolDao.findById(id); StoragePoolVO pool = _storagePoolDao.findById(id);
if (pool == null) { if (pool == null) {
throw new IllegalArgumentException("Unable to find storage pool with ID: " + id); throw new IllegalArgumentException("Unable to find storage pool with ID: " + id);
@ -2688,7 +2697,8 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
} }
private void updateStoragePoolHostVOAndBytes(StoragePool pool, long hostId, ModifyStoragePoolAnswer mspAnswer) { @Override
public void updateStoragePoolHostVOAndBytes(StoragePool pool, long hostId, ModifyStoragePoolAnswer mspAnswer) {
StoragePoolHostVO poolHost = _storagePoolHostDao.findByPoolHost(pool.getId(), hostId); StoragePoolHostVO poolHost = _storagePoolHostDao.findByPoolHost(pool.getId(), hostId);
if (poolHost == null) { if (poolHost == null) {
poolHost = new StoragePoolHostVO(pool.getId(), hostId, mspAnswer.getPoolInfo().getLocalPath().replaceAll("//", "/")); poolHost = new StoragePoolHostVO(pool.getId(), hostId, mspAnswer.getPoolInfo().getLocalPath().replaceAll("//", "/"));
@ -2698,8 +2708,10 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
} }
StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId()); StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId());
poolVO.setUsedBytes(mspAnswer.getPoolInfo().getCapacityBytes() - mspAnswer.getPoolInfo().getAvailableBytes()); if (!Storage.StoragePoolType.StorPool.equals(poolVO.getPoolType())) {
poolVO.setCapacityBytes(mspAnswer.getPoolInfo().getCapacityBytes()); poolVO.setUsedBytes(mspAnswer.getPoolInfo().getCapacityBytes() - mspAnswer.getPoolInfo().getAvailableBytes());
poolVO.setCapacityBytes(mspAnswer.getPoolInfo().getCapacityBytes());
}
_storagePoolDao.update(pool.getId(), poolVO); _storagePoolDao.update(pool.getId(), poolVO);
} }

View File

@ -346,6 +346,7 @@ public class StoragePoolAutomationImpl implements StoragePoolAutomation {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("ModifyStoragePool add succeeded"); logger.debug("ModifyStoragePool add succeeded");
} }
storageManager.updateStoragePoolHostVOAndBytes(pool, host.getId(), (ModifyStoragePoolAnswer) answer);
if (pool.getPoolType() == Storage.StoragePoolType.DatastoreCluster) { if (pool.getPoolType() == Storage.StoragePoolType.DatastoreCluster) {
logger.debug("Started synchronising datastore cluster storage pool {} with vCenter", pool); logger.debug("Started synchronising datastore cluster storage pool {} with vCenter", pool);
storageManager.syncDatastoreClusterStoragePool(pool.getId(), ((ModifyStoragePoolAnswer) answer).getDatastoreClusterChildren(), host.getId()); storageManager.syncDatastoreClusterStoragePool(pool.getId(), ((ModifyStoragePoolAnswer) answer).getDatastoreClusterChildren(), host.getId());

View File

@ -22,6 +22,7 @@ import java.util.Objects;
import javax.inject.Inject; import javax.inject.Inject;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity; import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.context.CallContext;
@ -128,6 +129,11 @@ public class ResourceManagerUtilImpl implements ResourceManagerUtil {
@Override @Override
public long getResourceId(String resourceId, ResourceTag.ResourceObjectType resourceType) { public long getResourceId(String resourceId, ResourceTag.ResourceObjectType resourceType) {
return getResourceId(resourceId, resourceType, false);
}
@Override
public long getResourceId(String resourceId, ResourceTag.ResourceObjectType resourceType, boolean checkAccess) {
Class<?> clazz = s_typeMap.get(resourceType); Class<?> clazz = s_typeMap.get(resourceType);
Object entity = entityMgr.findByUuid(clazz, resourceId); Object entity = entityMgr.findByUuid(clazz, resourceId);
if (entity != null) { if (entity != null) {
@ -138,6 +144,11 @@ public class ResourceManagerUtilImpl implements ResourceManagerUtil {
} }
entity = entityMgr.findById(clazz, resourceId); entity = entityMgr.findById(clazz, resourceId);
if (entity != null) { if (entity != null) {
if (checkAccess && entity instanceof ControlledEntity) {
ControlledEntity controlledEntity = (ControlledEntity)entity;
Account caller = CallContext.current().getCallingAccount();
accountMgr.checkAccess(caller, null, false, controlledEntity);
}
return ((InternalIdentity)entity).getId(); return ((InternalIdentity)entity).getId();
} }
throw new InvalidParameterValueException("Unable to find resource by id " + resourceId + " and type " + resourceType); throw new InvalidParameterValueException("Unable to find resource by id " + resourceId + " and type " + resourceType);

View File

@ -627,7 +627,7 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
boolean dataDiskDeletetionResult = true; boolean dataDiskDeletetionResult = true;
List<VMTemplateVO> dataDiskTemplates = templateDao.listByParentTemplatetId(template.getId()); List<VMTemplateVO> dataDiskTemplates = templateDao.listByParentTemplatetId(template.getId());
if (dataDiskTemplates != null && dataDiskTemplates.size() > 0) { if (CollectionUtils.isNotEmpty(dataDiskTemplates)) {
logger.info("Template: {} has Datadisk template(s) associated with it. Delete Datadisk templates before deleting the template", template); logger.info("Template: {} has Datadisk template(s) associated with it. Delete Datadisk templates before deleting the template", template);
for (VMTemplateVO dataDiskTemplate : dataDiskTemplates) { for (VMTemplateVO dataDiskTemplate : dataDiskTemplates) {
logger.info("Delete Datadisk template: {} from image store: {}", dataDiskTemplate, imageStore); logger.info("Delete Datadisk template: {} from image store: {}", dataDiskTemplate, imageStore);
@ -693,6 +693,9 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
if (success) { if (success) {
if ((imageStores != null && imageStores.size() > 1) && (profile.getZoneIdList() != null)) { if ((imageStores != null && imageStores.size() > 1) && (profile.getZoneIdList() != null)) {
//if template is stored in more than one image stores, and the zone id is not null, then don't delete other templates. //if template is stored in more than one image stores, and the zone id is not null, then don't delete other templates.
if (templateMgr.TemplateDeleteFromPrimaryStorage.value()) {
templateMgr.evictTemplateFromStoragePoolsForZones(template.getId(), profile.getZoneIdList());
}
return cleanupTemplate(template, success); return cleanupTemplate(template, success);
} }
@ -705,7 +708,7 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
// find all eligible image stores for this template // find all eligible image stores for this template
List<DataStore> iStores = templateMgr.getImageStoreByTemplate(template.getId(), null); List<DataStore> iStores = templateMgr.getImageStoreByTemplate(template.getId(), null);
if (iStores == null || iStores.size() == 0) { if (CollectionUtils.isEmpty(iStores)) {
// remove any references from template_zone_ref // remove any references from template_zone_ref
List<VMTemplateZoneVO> templateZones = templateZoneDao.listByTemplateId(template.getId()); List<VMTemplateZoneVO> templateZones = templateZoneDao.listByTemplateId(template.getId());
if (templateZones != null) { if (templateZones != null) {
@ -726,6 +729,10 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
} }
if (templateMgr.TemplateDeleteFromPrimaryStorage.value()) {
templateMgr.evictTemplateFromStoragePoolsForZones(template.getId(), profile.getZoneIdList());
}
// remove its related ACL permission // remove its related ACL permission
Pair<Class<?>, Long> templateClassForId = new Pair<>(VirtualMachineTemplate.class, template.getId()); Pair<Class<?>, Long> templateClassForId = new Pair<>(VirtualMachineTemplate.class, template.getId());
_messageBus.publish(_name, EntityManager.MESSAGE_REMOVE_ENTITY_EVENT, PublishScope.LOCAL, templateClassForId); _messageBus.publish(_name, EntityManager.MESSAGE_REMOVE_ENTITY_EVENT, PublishScope.LOCAL, templateClassForId);

View File

@ -1024,33 +1024,53 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
return adapter.delete(new TemplateProfile(userId, template, zoneId)); return adapter.delete(new TemplateProfile(userId, template, zoneId));
} }
private Boolean templateIsUnusedInPool(VMTemplateStoragePoolVO templatePoolVO) {
VMTemplateVO template = _tmpltDao.findByIdIncludingRemoved(templatePoolVO.getTemplateId());
// If this is a routing template, consider it in use
if (template.getTemplateType() == TemplateType.SYSTEM) {
return false;
}
// If the template is not yet downloaded to the pool, consider it in use
if (templatePoolVO.getDownloadState() != Status.DOWNLOADED) {
return false;
}
if (template.getFormat() == ImageFormat.ISO || _volumeDao.isAnyVolumeActivelyUsingTemplateOnPool(template.getId(), templatePoolVO.getPoolId())) {
return false;
}
return true;
}
@Override @Override
public List<VMTemplateStoragePoolVO> getUnusedTemplatesInPool(StoragePoolVO pool) { public List<VMTemplateStoragePoolVO> getUnusedTemplatesInPool(StoragePoolVO pool) {
List<VMTemplateStoragePoolVO> unusedTemplatesInPool = new ArrayList<VMTemplateStoragePoolVO>(); List<VMTemplateStoragePoolVO> unusedTemplatesInPool = new ArrayList<VMTemplateStoragePoolVO>();
List<VMTemplateStoragePoolVO> allTemplatesInPool = _tmpltPoolDao.listByPoolId(pool.getId()); List<VMTemplateStoragePoolVO> allTemplatesInPool = _tmpltPoolDao.listByPoolId(pool.getId());
for (VMTemplateStoragePoolVO templatePoolVO : allTemplatesInPool) { for (VMTemplateStoragePoolVO templatePoolVO : allTemplatesInPool) {
VMTemplateVO template = _tmpltDao.findByIdIncludingRemoved(templatePoolVO.getTemplateId()); if (templateIsUnusedInPool(templatePoolVO)) {
// If this is a routing template, consider it in use
if (template.getTemplateType() == TemplateType.SYSTEM) {
continue;
}
// If the template is not yet downloaded to the pool, consider it in
// use
if (templatePoolVO.getDownloadState() != Status.DOWNLOADED) {
continue;
}
if (template.getFormat() != ImageFormat.ISO && !_volumeDao.isAnyVolumeActivelyUsingTemplateOnPool(template.getId(), pool.getId())) {
unusedTemplatesInPool.add(templatePoolVO); unusedTemplatesInPool.add(templatePoolVO);
} }
} }
return unusedTemplatesInPool; return unusedTemplatesInPool;
} }
@Override
public void evictTemplateFromStoragePoolsForZones(Long templateId, List<Long> zoneIds) {
List<Long> poolIds = new ArrayList<>();
if (CollectionUtils.isNotEmpty(zoneIds)) {
List<StoragePoolVO> pools = _poolDao.listByDataCenterIds(zoneIds);
poolIds = pools.stream().map(StoragePoolVO::getId).collect(Collectors.toList());
}
List<VMTemplateStoragePoolVO> templateStoragePoolVOS = _tmpltPoolDao.listByPoolIdsAndTemplate(poolIds, templateId);
for (VMTemplateStoragePoolVO templateStoragePoolVO: templateStoragePoolVOS) {
if (templateIsUnusedInPool(templateStoragePoolVO)) {
evictTemplateFromStoragePool(templateStoragePoolVO);
}
}
}
@Override @Override
@DB @DB
public void evictTemplateFromStoragePool(VMTemplateStoragePoolVO templatePoolVO) { public void evictTemplateFromStoragePool(VMTemplateStoragePoolVO templatePoolVO) {
@ -2368,7 +2388,10 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
@Override @Override
public ConfigKey<?>[] getConfigKeys() { public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {AllowPublicUserTemplates, TemplatePreloaderPoolSize, ValidateUrlIsResolvableBeforeRegisteringTemplate}; return new ConfigKey<?>[] {AllowPublicUserTemplates,
TemplatePreloaderPoolSize,
ValidateUrlIsResolvableBeforeRegisteringTemplate,
TemplateDeleteFromPrimaryStorage};
} }
public List<TemplateAdapter> getTemplateAdapters() { public List<TemplateAdapter> getTemplateAdapters() {

View File

@ -2747,7 +2747,10 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
logger.debug("Creating user: " + userName + ", accountId: " + accountId + " timezone:" + timezone); logger.debug("Creating user: " + userName + ", accountId: " + accountId + " timezone:" + timezone);
} }
passwordPolicy.verifyIfPasswordCompliesWithPasswordPolicies(password, userName, getAccount(accountId).getDomainId()); Account callingAccount = getCurrentCallingAccount();
if (callingAccount.getId() != Account.ACCOUNT_ID_SYSTEM) {
passwordPolicy.verifyIfPasswordCompliesWithPasswordPolicies(password, userName, getAccount(accountId).getDomainId());
}
String encodedPassword = null; String encodedPassword = null;
for (UserAuthenticator authenticator : _userPasswordEncoders) { for (UserAuthenticator authenticator : _userPasswordEncoders) {

View File

@ -83,6 +83,15 @@ public interface UserVmManager extends UserVmService {
"If set to true, tags specified in `resource.limit.host.tags` are also included in vm.strict.host.tags.", "If set to true, tags specified in `resource.limit.host.tags` are also included in vm.strict.host.tags.",
true); true);
ConfigKey<Boolean> EnableAdditionalVmConfig = new ConfigKey<>(
"Advanced",
Boolean.class,
"enable.additional.vm.configuration",
"false",
"allow additional arbitrary configuration to vm",
true,
ConfigKey.Scope.Account);
static final int MAX_USER_DATA_LENGTH_BYTES = 2048; static final int MAX_USER_DATA_LENGTH_BYTES = 2048;
public static final String CKS_NODE = "cksnode"; public static final String CKS_NODE = "cksnode";

View File

@ -670,9 +670,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
private static final ConfigKey<Boolean> AllowDeployVmIfGivenHostFails = new ConfigKey<Boolean>("Advanced", Boolean.class, "allow.deploy.vm.if.deploy.on.given.host.fails", "false", private static final ConfigKey<Boolean> AllowDeployVmIfGivenHostFails = new ConfigKey<Boolean>("Advanced", Boolean.class, "allow.deploy.vm.if.deploy.on.given.host.fails", "false",
"allow vm to deploy on different host if vm fails to deploy on the given host ", true); "allow vm to deploy on different host if vm fails to deploy on the given host ", true);
private static final ConfigKey<Boolean> EnableAdditionalVmConfig = new ConfigKey<>("Advanced", Boolean.class,
"enable.additional.vm.configuration", "false", "allow additional arbitrary configuration to vm", true, ConfigKey.Scope.Account);
private static final ConfigKey<String> KvmAdditionalConfigAllowList = new ConfigKey<>(String.class, private static final ConfigKey<String> KvmAdditionalConfigAllowList = new ConfigKey<>(String.class,
"allow.additional.vm.configuration.list.kvm", "Advanced", "", "Comma separated list of allowed additional configuration options.", true, ConfigKey.Scope.Account, null, null, EnableAdditionalVmConfig.key(), null, null, ConfigKey.Kind.CSV, null); "allow.additional.vm.configuration.list.kvm", "Advanced", "", "Comma separated list of allowed additional configuration options.", true, ConfigKey.Scope.Account, null, null, EnableAdditionalVmConfig.key(), null, null, ConfigKey.Kind.CSV, null);
@ -756,7 +753,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
String networkCidr; String networkCidr;
String macAddress; String macAddress;
public VmIpAddrFetchThread(long vmId, long nicId, String instanceName, boolean windows, Long hostId, String networkCidr, String macAddress) { public VmIpAddrFetchThread(long vmId, String vmUuid, long nicId, String instanceName, boolean windows, Long hostId, String networkCidr, String macAddress) {
this.vmId = vmId; this.vmId = vmId;
this.vmUuid = vmUuid; this.vmUuid = vmUuid;
this.nicId = nicId; this.nicId = nicId;
@ -778,8 +775,13 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
Answer answer = _agentMgr.send(hostId, cmd); Answer answer = _agentMgr.send(hostId, cmd);
if (answer.getResult()) { if (answer.getResult()) {
String vmIp = answer.getDetails(); String vmIp = answer.getDetails();
if (vmIp == null) {
if (NetUtils.isValidIp4(vmIp)) { // we got a valid response and the NIC does not have an IP assigned, as such we will update the database with null
if (nic.getIPv4Address() != null) {
nic.setIPv4Address(null);
_nicDao.update(nicId, nic);
}
} else if (NetUtils.isValidIp4(vmIp)) {
// set this vm ip addr in vm nic. // set this vm ip addr in vm nic.
if (nic != null) { if (nic != null) {
nic.setIPv4Address(vmIp); nic.setIPv4Address(vmIp);
@ -794,12 +796,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
} }
} }
} else { } else {
//previously vm has ip and nic table has ip address. After vm restart or stop/start // since no changes are being done, we should not decrement IP usage
//if vm doesnot get the ip then set the ip in nic table to null decrementCount = false;
if (nic.getIPv4Address() != null) {
nic.setIPv4Address(null);
_nicDao.update(nicId, nic);
}
if (answer.getDetails() != null) { if (answer.getDetails() != null) {
logger.debug("Failed to get vm ip for Vm [id: {}, uuid: {}, name: {}], details: {}", logger.debug("Failed to get vm ip for Vm [id: {}, uuid: {}, name: {}], details: {}",
vmId, vmUuid, vmName, answer.getDetails()); vmId, vmUuid, vmName, answer.getDetails());
@ -2696,7 +2694,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
VirtualMachineProfile vmProfile = new VirtualMachineProfileImpl(userVm); VirtualMachineProfile vmProfile = new VirtualMachineProfileImpl(userVm);
VirtualMachine vm = vmProfile.getVirtualMachine(); VirtualMachine vm = vmProfile.getVirtualMachine();
boolean isWindows = _guestOSCategoryDao.findById(_guestOSDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows"); boolean isWindows = _guestOSCategoryDao.findById(_guestOSDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows");
_vmIpFetchThreadExecutor.execute(new VmIpAddrFetchThread(vmId, nicId, vmInstance.getInstanceName(),
_vmIpFetchThreadExecutor.execute(new VmIpAddrFetchThread(vmId, vmInstance.getUuid(), nicId, vmInstance.getInstanceName(),
isWindows, vm.getHostId(), network.getCidr(), nicVo.getMacAddress())); isWindows, vm.getHostId(), network.getCidr(), nicVo.getMacAddress()));
} }
@ -6280,7 +6279,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
protected void persistExtraConfigVmware(String decodedUrl, UserVm vm) { protected void persistExtraConfigVmware(String decodedUrl, UserVm vm) {
boolean isValidConfig = isValidKeyValuePair(decodedUrl); boolean isValidConfig = isValidKeyValuePair(decodedUrl);
if (isValidConfig) { if (isValidConfig) {
String[] extraConfigs = decodedUrl.split("\\r?\\n"); String[] extraConfigs = decodedUrl.split("\\r?\\n+");
for (String cfg : extraConfigs) { for (String cfg : extraConfigs) {
// Validate cfg against unsupported operations set by admin here // Validate cfg against unsupported operations set by admin here
String[] allowedKeyList = VmwareAdditionalConfigAllowList.value().split(","); String[] allowedKeyList = VmwareAdditionalConfigAllowList.value().split(",");
@ -6308,7 +6307,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
protected void persistExtraConfigXenServer(String decodedUrl, UserVm vm) { protected void persistExtraConfigXenServer(String decodedUrl, UserVm vm) {
boolean isValidConfig = isValidKeyValuePair(decodedUrl); boolean isValidConfig = isValidKeyValuePair(decodedUrl);
if (isValidConfig) { if (isValidConfig) {
String[] extraConfigs = decodedUrl.split("\\r?\\n"); String[] extraConfigs = decodedUrl.split("\\r?\\n+");
int i = 1; int i = 1;
String extraConfigKey = ApiConstants.EXTRA_CONFIG + "-"; String extraConfigKey = ApiConstants.EXTRA_CONFIG + "-";
for (String cfg : extraConfigs) { for (String cfg : extraConfigs) {
@ -6388,8 +6387,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
// validate config against denied cfg commands // validate config against denied cfg commands
validateKvmExtraConfig(decodedUrl, vm.getAccountId()); validateKvmExtraConfig(decodedUrl, vm.getAccountId());
String[] extraConfigs = decodedUrl.split("\n\n"); String[] extraConfigs = decodedUrl.split("\n\n");
int i = 1;
for (String cfg : extraConfigs) { for (String cfg : extraConfigs) {
int i = 1;
String[] cfgParts = cfg.split("\n"); String[] cfgParts = cfg.split("\n");
String extraConfigKey = ApiConstants.EXTRA_CONFIG; String extraConfigKey = ApiConstants.EXTRA_CONFIG;
String extraConfigValue; String extraConfigValue;

View File

@ -607,10 +607,20 @@ public class RoutedIpv4ManagerImpl extends ComponentLifecycleBase implements Rou
} }
protected Ipv4GuestSubnetNetworkMap getOrCreateIpv4SubnetForGuestNetworkOrVpcInternal(Integer cidrSize, Long ownerDomainId, Long ownerAccountId, Long zoneId) { protected Ipv4GuestSubnetNetworkMap getOrCreateIpv4SubnetForGuestNetworkOrVpcInternal(Integer cidrSize, Long ownerDomainId, Long ownerAccountId, Long zoneId) {
validateNetworkCidrSize(ownerAccountId, cidrSize); validateNetworkCidrSize(cidrSize);
List<DataCenterIpv4GuestSubnetVO> subnets = getZoneSubnetsForAccount(ownerDomainId, ownerAccountId, zoneId); List<DataCenterIpv4GuestSubnetVO> subnets = getZoneSubnetsForAccount(ownerDomainId, ownerAccountId, zoneId);
for (DataCenterIpv4GuestSubnetVO subnet : subnets) { for (DataCenterIpv4GuestSubnetVO subnet : subnets) {
Ipv4GuestSubnetNetworkMap result = getOrCreateIpv4SubnetForGuestNetworkOrVpcInternal(cidrSize, subnet); Ipv4GuestSubnetNetworkMap result = getIpv4SubnetForGuestNetworkOrVpcInternal(cidrSize, subnet);
if (result != null) {
return result;
}
}
Boolean isAutoAllocationEnabled = RoutedIPv4NetworkCidrAutoAllocationEnabled.valueIn(ownerAccountId);
if (!Boolean.TRUE.equals(isAutoAllocationEnabled)) {
throw new InvalidParameterValueException("CIDR auto-allocation is disabled for this account");
}
for (DataCenterIpv4GuestSubnetVO subnet : subnets) {
Ipv4GuestSubnetNetworkMap result = createIpv4SubnetForGuestNetworkOrVpcInternal(cidrSize, subnet);
if (result != null) { if (result != null) {
return result; return result;
} }
@ -618,11 +628,11 @@ public class RoutedIpv4ManagerImpl extends ComponentLifecycleBase implements Rou
return null; return null;
} }
protected Ipv4GuestSubnetNetworkMap getOrCreateIpv4SubnetForGuestNetworkOrVpcInternal(Integer cidrSize, DataCenterIpv4GuestSubnetVO subnet) { protected Ipv4GuestSubnetNetworkMap getIpv4SubnetForGuestNetworkOrVpcInternal(Integer cidrSize, DataCenterIpv4GuestSubnetVO subnet) {
Ipv4GuestSubnetNetworkMap map = ipv4GuestSubnetNetworkMapDao.findFirstAvailable(subnet.getId(), cidrSize); return ipv4GuestSubnetNetworkMapDao.findFirstAvailable(subnet.getId(), cidrSize);
if (map != null) { }
return map;
} protected Ipv4GuestSubnetNetworkMap createIpv4SubnetForGuestNetworkOrVpcInternal(Integer cidrSize, DataCenterIpv4GuestSubnetVO subnet) {
try { try {
return createIpv4SubnetFromParentSubnet(subnet, cidrSize); return createIpv4SubnetFromParentSubnet(subnet, cidrSize);
} catch (Exception ex) { } catch (Exception ex) {
@ -631,6 +641,14 @@ public class RoutedIpv4ManagerImpl extends ComponentLifecycleBase implements Rou
return null; return null;
} }
protected Ipv4GuestSubnetNetworkMap getOrCreateIpv4SubnetForGuestNetworkOrVpcInternal(Integer cidrSize, DataCenterIpv4GuestSubnetVO subnet) {
Ipv4GuestSubnetNetworkMap map = getIpv4SubnetForGuestNetworkOrVpcInternal(cidrSize, subnet);
if (map != null) {
return map;
}
return createIpv4SubnetForGuestNetworkOrVpcInternal(cidrSize, subnet);
}
protected void getOrCreateIpv4SubnetForGuestNetworkOrVpcInternal(String networkCidr, Long ownerDomainId, Long ownerAccountId, Long zoneId) { protected void getOrCreateIpv4SubnetForGuestNetworkOrVpcInternal(String networkCidr, Long ownerDomainId, Long ownerAccountId, Long zoneId) {
Ipv4GuestSubnetNetworkMapVO subnetMap = ipv4GuestSubnetNetworkMapDao.findBySubnet(networkCidr); Ipv4GuestSubnetNetworkMapVO subnetMap = ipv4GuestSubnetNetworkMapDao.findBySubnet(networkCidr);
if (subnetMap != null) { if (subnetMap != null) {
@ -693,13 +711,9 @@ public class RoutedIpv4ManagerImpl extends ComponentLifecycleBase implements Rou
} }
} }
private void validateNetworkCidrSize(long accountId, Integer networkCidrSize) { private void validateNetworkCidrSize(Integer networkCidrSize) {
if (networkCidrSize == null) { if (networkCidrSize == null) {
throw new CloudRuntimeException("network/vpc CidrSize is null"); throw new InvalidParameterValueException("network/vpc CidrSize is null");
}
Boolean isAutoAllocationEnabled = RoutedIPv4NetworkCidrAutoAllocationEnabled.valueIn(accountId);
if (!Boolean.TRUE.equals(isAutoAllocationEnabled)) {
throw new CloudRuntimeException("CIDR auto-allocation is disabled for this account");
} }
} }
@ -755,7 +769,7 @@ public class RoutedIpv4ManagerImpl extends ComponentLifecycleBase implements Rou
// Allocate a subnet automatically // Allocate a subnet automatically
String networkCidr = getFreeNetworkCidr(subnetsInFreeIpRanges, networkCidrSize); String networkCidr = getFreeNetworkCidr(subnetsInFreeIpRanges, networkCidrSize);
if (networkCidr == null) { if (networkCidr == null) {
throw new CloudRuntimeException("Failed to automatically allocate a subnet with specified cidrsize"); throw new InvalidParameterValueException("Failed to automatically allocate a subnet with specified cidrsize");
} }
return networkCidr; return networkCidr;
} }

View File

@ -55,6 +55,17 @@ public interface UserPasswordResetManager {
"Use auth in the SMTP server for sending emails for resetting password for ACS users", "Use auth in the SMTP server for sending emails for resetting password for ACS users",
false, ConfigKey.Scope.Global); false, ConfigKey.Scope.Global);
ConfigKey<Boolean> UserPasswordResetSMTPUseStartTLS = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED,
Boolean.class, "user.password.reset.smtp.useStartTLS", "false",
"If set to true and if we enable security via user.password.reset.smtp.useAuth, this will enable StartTLS to secure the connection.",
true,
ConfigKey.Scope.Global);
ConfigKey<String> UserPasswordResetSMTPEnabledSecurityProtocols = new ConfigKey<String>(ConfigKey.CATEGORY_ADVANCED,
String.class, "user.password.reset.smtp.enabledSecurityProtocols", "",
"White-space separated security protocols; ex: \"TLSv1 TLSv1.1\". Supported protocols: SSLv2Hello, SSLv3, TLSv1, TLSv1.1 and TLSv1.2",
true, ConfigKey.Kind.WhitespaceSeparatedListWithOptions, "SSLv2Hello,SSLv3,TLSv1,TLSv1.1,TLSv1.2");
ConfigKey<String> UserPasswordResetSMTPUsername = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, ConfigKey<String> UserPasswordResetSMTPUsername = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED,
String.class, "user.password.reset.smtp.username", null, String.class, "user.password.reset.smtp.username", null,
"Username for SMTP server for sending emails for resetting password for ACS users", "Username for SMTP server for sending emails for resetting password for ACS users",

View File

@ -93,6 +93,8 @@ public class UserPasswordResetManagerImpl extends ManagerBase implements UserPas
UserPasswordResetSMTPHost, UserPasswordResetSMTPHost,
UserPasswordResetSMTPPort, UserPasswordResetSMTPPort,
UserPasswordResetSMTPUseAuth, UserPasswordResetSMTPUseAuth,
UserPasswordResetSMTPUseStartTLS,
UserPasswordResetSMTPEnabledSecurityProtocols,
UserPasswordResetSMTPUsername, UserPasswordResetSMTPUsername,
UserPasswordResetSMTPPassword, UserPasswordResetSMTPPassword,
PasswordResetMailTemplate PasswordResetMailTemplate
@ -106,6 +108,8 @@ public class UserPasswordResetManagerImpl extends ManagerBase implements UserPas
Boolean useAuth = UserPasswordResetSMTPUseAuth.value(); Boolean useAuth = UserPasswordResetSMTPUseAuth.value();
String username = UserPasswordResetSMTPUsername.value(); String username = UserPasswordResetSMTPUsername.value();
String password = UserPasswordResetSMTPPassword.value(); String password = UserPasswordResetSMTPPassword.value();
Boolean useStartTLS = UserPasswordResetSMTPUseStartTLS.value();
String enabledSecurityProtocols = UserPasswordResetSMTPEnabledSecurityProtocols.value();
if (!StringUtils.isEmpty(smtpHost) && smtpPort != null && smtpPort > 0) { if (!StringUtils.isEmpty(smtpHost) && smtpPort != null && smtpPort > 0) {
String namespace = "password.reset.smtp"; String namespace = "password.reset.smtp";
@ -117,6 +121,8 @@ public class UserPasswordResetManagerImpl extends ManagerBase implements UserPas
configs.put(getKey(namespace, SMTPMailSender.CONFIG_USE_AUTH), useAuth.toString()); configs.put(getKey(namespace, SMTPMailSender.CONFIG_USE_AUTH), useAuth.toString());
configs.put(getKey(namespace, SMTPMailSender.CONFIG_USERNAME), username); configs.put(getKey(namespace, SMTPMailSender.CONFIG_USERNAME), username);
configs.put(getKey(namespace, SMTPMailSender.CONFIG_PASSWORD), password); configs.put(getKey(namespace, SMTPMailSender.CONFIG_PASSWORD), password);
configs.put(getKey(namespace, SMTPMailSender.CONFIG_USE_STARTTLS), useStartTLS.toString());
configs.put(getKey(namespace, SMTPMailSender.CONFIG_ENABLED_SECURITY_PROTOCOLS), enabledSecurityProtocols);
mailSender = new SMTPMailSender(configs, namespace); mailSender = new SMTPMailSender(configs, namespace);
} }

View File

@ -71,7 +71,6 @@ import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.Hypervisor;
import com.cloud.hypervisor.HypervisorGuru; import com.cloud.hypervisor.HypervisorGuru;
import com.cloud.hypervisor.HypervisorGuruManager; import com.cloud.hypervisor.HypervisorGuruManager;
import com.cloud.network.IpAddressManager;
import com.cloud.network.Network; import com.cloud.network.Network;
import com.cloud.network.NetworkModel; import com.cloud.network.NetworkModel;
import com.cloud.network.Networks; import com.cloud.network.Networks;
@ -271,8 +270,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
@Inject @Inject
private PhysicalNetworkDao physicalNetworkDao; private PhysicalNetworkDao physicalNetworkDao;
@Inject @Inject
private IpAddressManager ipAddressManager;
@Inject
private StoragePoolHostDao storagePoolHostDao; private StoragePoolHostDao storagePoolHostDao;
@Inject @Inject
private HypervisorGuruManager hypervisorGuruManager; private HypervisorGuruManager hypervisorGuruManager;
@ -1128,7 +1125,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
} }
String internalCSName = unmanagedInstance.getInternalCSName(); String internalCSName = unmanagedInstance.getInternalCSName();
if (StringUtils.isEmpty(instanceNameInternal)) { if (StringUtils.isEmpty(internalCSName)) {
internalCSName = instanceNameInternal; internalCSName = instanceNameInternal;
} }
Map<String, String> allDetails = new HashMap<>(details); Map<String, String> allDetails = new HashMap<>(details);
@ -2613,10 +2610,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
} }
String macAddress = networkModel.getNextAvailableMacAddressInNetwork(networkId); String macAddress = networkModel.getNextAvailableMacAddressInNetwork(networkId);
String ipAddress = null;
if (network.getGuestType() != Network.GuestType.L2) { String ipAddress = network.getGuestType() != Network.GuestType.L2 ? "auto" : null;
ipAddress = ipAddressManager.acquireGuestIpAddress(network, null);
}
Network.IpAddresses requestedIpPair = new Network.IpAddresses(ipAddress, null, macAddress); Network.IpAddresses requestedIpPair = new Network.IpAddresses(ipAddress, null, macAddress);

View File

@ -545,12 +545,12 @@ public class RoutedIpv4ManagerImplTest {
DataCenterIpv4GuestSubnetVO subnet3 = Mockito.mock(DataCenterIpv4GuestSubnetVO.class); DataCenterIpv4GuestSubnetVO subnet3 = Mockito.mock(DataCenterIpv4GuestSubnetVO.class);
when(dataCenterIpv4GuestSubnetDao.listNonDedicatedByDataCenterId(zoneId)).thenReturn(Arrays.asList(subnet3)); when(dataCenterIpv4GuestSubnetDao.listNonDedicatedByDataCenterId(zoneId)).thenReturn(Arrays.asList(subnet3));
doReturn(null).doReturn(null).doReturn(ipv4GuestSubnetNetworkMap).when(routedIpv4Manager).getOrCreateIpv4SubnetForGuestNetworkOrVpcInternal(eq(cidrSize), any()); doReturn(null).doReturn(null).doReturn(ipv4GuestSubnetNetworkMap).when(routedIpv4Manager).getIpv4SubnetForGuestNetworkOrVpcInternal(eq(cidrSize), any());
Ipv4GuestSubnetNetworkMap result = routedIpv4Manager.getOrCreateIpv4SubnetForGuestNetworkOrVpcInternal(cidrSize, domainId, accountId, zoneId); Ipv4GuestSubnetNetworkMap result = routedIpv4Manager.getOrCreateIpv4SubnetForGuestNetworkOrVpcInternal(cidrSize, domainId, accountId, zoneId);
Assert.assertEquals(ipv4GuestSubnetNetworkMap, result); Assert.assertEquals(ipv4GuestSubnetNetworkMap, result);
verify(routedIpv4Manager, times(3)).getOrCreateIpv4SubnetForGuestNetworkOrVpcInternal(eq(cidrSize), any()); verify(routedIpv4Manager, times(3)).getIpv4SubnetForGuestNetworkOrVpcInternal(eq(cidrSize), any());
} }
@Test @Test

View File

@ -22,13 +22,15 @@ set -x
function configure_locale() { function configure_locale() {
grep LANG=en_US.UTF-8 /etc/default/locale && \ grep LANG=en_US.UTF-8 /etc/default/locale && \
grep LC_ALL=en_US.UTF-8 /etc/default/locale && \ grep LC_ALL=en_US.UTF-8 /etc/default/locale && \
grep "en_US.UTF-8 UTF-8" /etc/locale.gen && grep "^en_US.UTF-8 UTF-8" /etc/locale.gen &&
return return
cat >> /etc/default/locale << EOF cat >> /etc/default/locale << EOF
LANG=en_US.UTF-8 LANG=en_US.UTF-8
LC_ALL=en_US.UTF-8 LC_ALL=en_US.UTF-8
EOF EOF
grep "^en_US.UTF-8 UTF-8" /etc/locale.gen || \
cat >> /etc/locale.gen << EOF cat >> /etc/locale.gen << EOF
en_US.UTF-8 UTF-8 en_US.UTF-8 UTF-8
EOF EOF

View File

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

View File

@ -31,8 +31,8 @@
"format": "qcow2", "format": "qcow2",
"headless": true, "headless": true,
"http_directory": "http", "http_directory": "http",
"iso_checksum": "sha512:892cf1185a214d16ff62a18c6b89cdcd58719647c99916f6214bfca6f9915275d727b666c0b8fbf022c425ef18647e9759974abf7fc440431c39b50c296a98d3", "iso_checksum": "sha512:55ab206cd8b0da2898767c3eb6ab5ebef101e3925ec91b3b5f0a286136195b7072588f6ac2d059c545c6938978704ae78cd18d7d9d2a86a7380e46ce27ee4e7b",
"iso_url": "https://cdimage.debian.org/mirror/cdimage/archive/12.11.0/arm64/iso-cd/debian-12.11.0-arm64-netinst.iso", "iso_url": "https://cdimage.debian.org/mirror/cdimage/archive/12.12.0/arm64/iso-cd/debian-12.12.0-arm64-netinst.iso",
"net_device": "virtio-net", "net_device": "virtio-net",
"output_directory": "../dist", "output_directory": "../dist",
"qemu_binary": "qemu-system-aarch64", "qemu_binary": "qemu-system-aarch64",

View File

@ -27,8 +27,8 @@
"format": "qcow2", "format": "qcow2",
"headless": true, "headless": true,
"http_directory": "http", "http_directory": "http",
"iso_checksum": "sha512:0921d8b297c63ac458d8a06f87cd4c353f751eb5fe30fd0d839ca09c0833d1d9934b02ee14bbd0c0ec4f8917dde793957801ae1af3c8122cdf28dde8f3c3e0da", "iso_checksum": "sha512:c93055182057dd19a334260671c7e10880541b7721ad9c8df87be47e0a11d5bbf85018350ff224ff6a5f6a68320b07e95d539cef9dc020c93966bfaa86d4b2ce",
"iso_url": "https://cdimage.debian.org/mirror/cdimage/archive/12.11.0/amd64/iso-cd/debian-12.11.0-amd64-netinst.iso", "iso_url": "https://cdimage.debian.org/mirror/cdimage/archive/12.12.0/amd64/iso-cd/debian-12.12.0-amd64-netinst.iso",
"net_device": "virtio-net", "net_device": "virtio-net",
"output_directory": "../dist", "output_directory": "../dist",
"qemuargs": [ "qemuargs": [

View File

@ -97,5 +97,6 @@
"basicZoneEnabled": true, "basicZoneEnabled": true,
"multipleServer": false, "multipleServer": false,
"allowSettingTheme": true, "allowSettingTheme": true,
"docHelpMappings": {} "docHelpMappings": {},
"notifyLatestCSVersion": true
} }

View File

@ -975,6 +975,8 @@
"label.externalid": "External Id", "label.externalid": "External Id",
"label.externalloadbalanceripaddress": "External load balancer IP address.", "label.externalloadbalanceripaddress": "External load balancer IP address.",
"label.extra": "Extra arguments", "label.extra": "Extra arguments",
"label.extraconfig": "Additional Configuration",
"label.extraconfig.tooltip": "Additional configuration parameters (extraconfig) to pass to the instance in plain text",
"label.f5": "F5", "label.f5": "F5",
"label.f5.ip.loadbalancer": "F5 BIG-IP load balancer.", "label.f5.ip.loadbalancer": "F5 BIG-IP load balancer.",
"label.failed": "Failed", "label.failed": "Failed",

View File

@ -317,7 +317,6 @@ const user = {
const result = response.listusersresponse.user[0] const result = response.listusersresponse.user[0]
commit('SET_INFO', result) commit('SET_INFO', result)
commit('SET_NAME', result.firstname + ' ' + result.lastname) commit('SET_NAME', result.firstname + ' ' + result.lastname)
store.dispatch('SetCsLatestVersion', result.rolename)
resolve(cachedApis) resolve(cachedApis)
}).catch(error => { }).catch(error => {
reject(error) reject(error)
@ -564,6 +563,9 @@ const user = {
commit('SET_DOMAIN_STORE', domainStore) commit('SET_DOMAIN_STORE', domainStore)
}, },
SetCsLatestVersion ({ commit }, rolename) { SetCsLatestVersion ({ commit }, rolename) {
if (!vueProps.$config.notifyLatestCSVersion) {
return
}
const lastFetchTs = store.getters.latestVersion?.fetchedTs ? store.getters.latestVersion.fetchedTs : 0 const lastFetchTs = store.getters.latestVersion?.fetchedTs ? store.getters.latestVersion.fetchedTs : 0
if (rolename === 'Root Admin' && (+new Date() - lastFetchTs) > 24 * 60 * 60 * 1000) { if (rolename === 'Root Admin' && (+new Date() - lastFetchTs) > 24 * 60 * 60 * 1000) {
axios.get( axios.get(

View File

@ -724,6 +724,12 @@
</div> </div>
</a-card> </a-card>
</a-form-item> </a-form-item>
<a-form-item v-if="extraConfigEnabled" name="extraconfig" ref="extraconfig">
<template #label>
<tooltip-label :title="$t('label.extraconfig')" :tooltip="$t('label.extraconfig.tooltip')"/>
</template>
<a-textarea v-model:value="form.extraconfig"/>
</a-form-item>
<a-form-item :label="$t('label.affinity.groups')"> <a-form-item :label="$t('label.affinity.groups')">
<affinity-group-selection <affinity-group-selection
:items="options.affinityGroups" :items="options.affinityGroups"
@ -1418,6 +1424,9 @@ export default {
dynamicScalingVmConfigValue () { dynamicScalingVmConfigValue () {
return this.$store.getters.features.dynamicscalingenabled return this.$store.getters.features.dynamicscalingenabled
}, },
extraConfigEnabled () {
return this.$store.getters.features.additionalconfigenabled
},
isCustomizedDiskIOPS () { isCustomizedDiskIOPS () {
return this.diskSelected?.iscustomizediops || false return this.diskSelected?.iscustomizediops || false
}, },
@ -2054,6 +2063,9 @@ export default {
if (isUserdataAllowed && values.userdata && values.userdata.length > 0) { if (isUserdataAllowed && values.userdata && values.userdata.length > 0) {
deployVmData.userdata = this.$toBase64AndURIEncoded(values.userdata) deployVmData.userdata = this.$toBase64AndURIEncoded(values.userdata)
} }
if (values.extraconfig && values.extraconfig.length > 0) {
deployVmData.extraconfig = encodeURIComponent(values.extraconfig)
}
// step 2: select template/iso // step 2: select template/iso
if (this.tabKey === 'templateid') { if (this.tabKey === 'templateid') {
deployVmData.templateid = values.templateid deployVmData.templateid = values.templateid

View File

@ -91,6 +91,12 @@
<a-textarea v-model:value="form.userdata"> <a-textarea v-model:value="form.userdata">
</a-textarea> </a-textarea>
</a-form-item> </a-form-item>
<a-form-item v-if="extraConfigEnabled">
<template #label>
<tooltip-label :title="$t('label.extraconfig')" :tooltip="$t('label.extraconfig.tooltip')"/>
</template>
<a-textarea v-model:value="form.extraconfig"/>
</a-form-item>
<a-form-item ref="securitygroupids" name="securitygroupids" :label="$t('label.security.groups')" v-if="securityGroupsEnabled"> <a-form-item ref="securitygroupids" name="securitygroupids" :label="$t('label.security.groups')" v-if="securityGroupsEnabled">
<a-select <a-select
mode="multiple" mode="multiple"
@ -167,6 +173,19 @@ export default {
} }
} }
}, },
computed: {
extraConfigEnabled () {
return this.$store.getters.features.additionalconfigenabled
},
combinedExtraConfig () {
if (!this.extraConfigEnabled || !this.resource.details) return ''
const configs = Object.keys(this.resource.details)
.filter(key => key.startsWith('extraconfig-'))
.map(key => this.resource.details[key] || '')
.filter(val => val.trim())
return configs.join('\n\n')
}
},
beforeCreate () { beforeCreate () {
this.apiParams = this.$getApiParams('updateVirtualMachine') this.apiParams = this.$getApiParams('updateVirtualMachine')
}, },
@ -185,7 +204,8 @@ export default {
deleteprotection: this.resource.deleteprotection, deleteprotection: this.resource.deleteprotection,
group: this.resource.group, group: this.resource.group,
userdata: '', userdata: '',
haenable: this.resource.haenable haenable: this.resource.haenable,
extraconfig: this.combinedExtraConfig
}) })
this.rules = reactive({}) this.rules = reactive({})
}, },
@ -342,6 +362,9 @@ export default {
if (values.userdata && values.userdata.length > 0) { if (values.userdata && values.userdata.length > 0) {
params.userdata = this.$toBase64AndURIEncoded(values.userdata) params.userdata = this.$toBase64AndURIEncoded(values.userdata)
} }
if (values.extraconfig && values.extraconfig.length > 0) {
params.extraconfig = encodeURIComponent(values.extraconfig)
}
this.loading = true this.loading = true
api('updateVirtualMachine', {}, 'POST', params).then(json => { api('updateVirtualMachine', {}, 'POST', params).then(json => {

View File

@ -396,7 +396,7 @@ export default {
placeHolder: 'message.error.server', placeHolder: 'message.error.server',
required: true, required: true,
display: { display: {
primaryStorageProtocol: ['nfs', 'iscsi', 'gluster', 'SMB', 'Linstor'] primaryStorageProtocol: ['nfs', 'iscsi', 'gluster', 'SMB', 'Linstor', 'datastorecluster', 'vmfs']
} }
}, },
{ {

View File

@ -1510,10 +1510,10 @@ export default {
} }
path += '/' + this.prefillContent.primaryStorageVmfsDatastore path += '/' + this.prefillContent.primaryStorageVmfsDatastore
if (protocol === 'vmfs') { if (protocol === 'vmfs') {
url = this.vmfsURL('dummy', path) url = this.vmfsURL(server, path)
} }
if (protocol === 'datastorecluster') { if (protocol === 'datastorecluster') {
url = this.datastoreclusterURL('dummy', path) url = this.datastoreclusterURL(server, path)
} }
} else if (protocol === 'iscsi') { } else if (protocol === 'iscsi') {
let iqn = this.prefillContent?.primaryStorageTargetIQN || '' let iqn = this.prefillContent?.primaryStorageTargetIQN || ''

View File

@ -413,7 +413,7 @@ export default {
for (const index in net.traffics) { for (const index in net.traffics) {
if (this.hypervisor === 'VMware') { if (this.hypervisor === 'VMware') {
delete this.physicalNetworks[idx].traffics[index].label delete this.physicalNetworks[idx].traffics[index].label
} else { } else if (!net.traffics[index].label) {
this.physicalNetworks[idx].traffics[index].label = '' this.physicalNetworks[idx].traffics[index].label = ''
} }
const traffic = net.traffics[index] const traffic = net.traffics[index]

View File

@ -29,6 +29,7 @@ import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@ -157,13 +158,7 @@ public class Script implements Callable<String> {
boolean obscureParam = false; boolean obscureParam = false;
for (int i = 0; i < command.length; i++) { for (int i = 0; i < command.length; i++) {
String cmd = command[i]; String cmd = command[i];
if (StringUtils.isNotEmpty(cmd) && cmd.startsWith("vi://")) { if (sanitizeViCmdParameter(cmd, builder) || sanitizeRbdFileFormatCmdParameter(cmd, builder)) {
String[] tokens = cmd.split("@");
if (tokens.length >= 2) {
builder.append("vi://").append("******@").append(tokens[1]).append(" ");
} else {
builder.append("vi://").append("******").append(" ");
}
continue; continue;
} }
if (obscureParam) { if (obscureParam) {
@ -181,6 +176,41 @@ public class Script implements Callable<String> {
return builder.toString(); return builder.toString();
} }
private boolean sanitizeViCmdParameter(String cmd, StringBuilder builder) {
if (StringUtils.isEmpty(cmd) || !cmd.startsWith("vi://")) {
return false;
}
String[] tokens = cmd.split("@");
if (tokens.length >= 2) {
builder.append("vi://").append("******@").append(tokens[1]).append(" ");
} else {
builder.append("vi://").append("******").append(" ");
}
return true;
}
private boolean sanitizeRbdFileFormatCmdParameter(String cmd, StringBuilder builder) {
if (StringUtils.isEmpty(cmd) || !cmd.startsWith("rbd:") || !cmd.contains("key=")) {
return false;
}
String[] tokens = cmd.split("key=");
if (tokens.length != 2) {
return false;
}
String tokenWithKey = tokens[1];
String[] options = tokenWithKey.split(":");
if (options.length > 1) {
String optionsAfterKey = String.join(":", Arrays.copyOfRange(options, 1, options.length));
builder.append(tokens[0]).append("key=").append("******").append(":").append(optionsAfterKey).append(" ");
} else {
builder.append(tokens[0]).append("key=").append("******").append(" ");
}
return true;
}
public long getTimeout() { public long getTimeout() {
return _timeout; return _timeout;
} }

View File

@ -19,31 +19,52 @@ package org.apache.cloudstack.utils.jsinterpreter;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import org.apache.commons.collections.MapUtils; import javax.script.Bindings;
import org.apache.logging.log4j.Logger; import javax.script.Compilable;
import org.apache.logging.log4j.LogManager; import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import javax.script.SimpleScriptContext;
import com.cloud.utils.exception.CloudRuntimeException; import org.apache.commons.collections.MapUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openjdk.nashorn.api.scripting.ClassFilter;
import org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory; import org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory;
import javax.script.ScriptEngine; import com.cloud.utils.exception.CloudRuntimeException;
/** /**
* A class to execute JavaScript scripts, with the possibility to inject context to the scripts. * Executes JavaScript with strong restrictions to mitigate RCE risks.
* - Disables Java interop via --no-java AND a deny-all ClassFilter
* - Disables Nashorn syntax extensions
* - Uses Bindings instead of string-splicing variables
* - Fresh ScriptContext per execution, with timeout on a daemon worker
*/ */
public class JsInterpreter implements Closeable { public class JsInterpreter implements Closeable {
protected Logger logger = LogManager.getLogger(JsInterpreter.class); protected Logger logger = LogManager.getLogger(JsInterpreter.class);
protected static final List<String> RESTRICTED_TOKENS = Arrays.asList( "engine", "context", "factory",
"Java", "java", "Packages"," javax", "load", "loadWithNewGlobal", "print", "factory", "getClass",
"runCommand", "Runtime", "exec", "ProcessBuilder", "Thread", "thread", "Threads", "Class", "class");
protected ScriptEngine interpreter; protected ScriptEngine interpreter;
protected String interpreterName; protected String interpreterName;
private final String injectingLogMessage = "Injecting variable [%s] with value [%s] into the JS interpreter."; private final String injectingLogMessage = "Injecting variable [%s] with value [%s] into the JS interpreter.";
@ -51,21 +72,40 @@ public class JsInterpreter implements Closeable {
private TimeUnit defaultTimeUnit = TimeUnit.MILLISECONDS; private TimeUnit defaultTimeUnit = TimeUnit.MILLISECONDS;
private long timeout; private long timeout;
private String timeoutDefaultMessage; private String timeoutDefaultMessage;
protected Map<String, String> variables = new LinkedHashMap<>();
// Store variables as Objects; they go into Bindings (no code splicing)
protected Map<String, Object> variables = new LinkedHashMap<>();
/** Deny-all filter: no Java class is visible from scripts. */
static final class DenyAllClassFilter implements ClassFilter {
@Override public boolean exposeToScripts(String className) { return false; }
}
/** /**
* Constructor created exclusively for unit testing. * Constructor created exclusively for unit testing.
*/ */
protected JsInterpreter() { protected JsInterpreter() { }
}
public JsInterpreter(long timeout) { public JsInterpreter(long timeout) {
this.timeout = timeout; this.timeout = timeout;
this.timeoutDefaultMessage = String.format("Timeout (in milliseconds) defined in the global setting [quota.activationrule.timeout]: [%s].", this.timeout); this.timeoutDefaultMessage = String.format(
"Timeout (in milliseconds) defined in the global setting [quota.activationrule.timeout]: [%s].", this.timeout);
if (System.getProperty("nashorn.args") == null) {
System.setProperty("nashorn.args", "--no-java --no-syntax-extensions");
}
this.executor = new ThreadPoolExecutor(
1, 1, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
r -> {
Thread t = new Thread(r, "JsInterpreter-worker");
t.setDaemon(true);
return t;
}
);
executor = Executors.newSingleThreadExecutor();
NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
this.interpreterName = factory.getEngineName(); this.interpreterName = factory.getEngineName();
logger.trace(String.format("Initiating JS interpreter: %s.", interpreterName)); logger.trace(String.format("Initiating JS interpreter: %s.", interpreterName));
@ -73,49 +113,53 @@ public class JsInterpreter implements Closeable {
} }
protected void setScriptEngineDisablingJavaLanguage(NashornScriptEngineFactory factory) { protected void setScriptEngineDisablingJavaLanguage(NashornScriptEngineFactory factory) {
interpreter = factory.getScriptEngine("--no-java"); String[] opts = new String[] {
"--no-java",
"--no-syntax-extensions",
};
interpreter = factory.getScriptEngine(
opts,
JsInterpreter.class.getClassLoader(),
new DenyAllClassFilter()
);
} }
/** /** Discards the current variables map and create a new one. */
* Discards the current variables map and create a new one.
*/
public void discardCurrentVariables() { public void discardCurrentVariables() {
logger.trace("Discarding current variables map and creating a new one."); logger.trace("Discarding current variables map and creating a new one.");
variables = new LinkedHashMap<>(); variables = new LinkedHashMap<>();
} }
/** /**
* Adds the parameters to a Map that will be converted to JS variables right before executing the script. * Adds a variable that will be exposed via ENGINE_SCOPE bindings.
* @param key The name of the variable. * Safe against code injection (no string concatenation).
* @param value The value of the variable.
*/ */
public void injectVariable(String key, String value) { public void injectVariable(String key, Object value) {
logger.trace(String.format(injectingLogMessage, key, value)); if (key == null) return;
logger.trace(String.format(injectingLogMessage, key, String.valueOf(value)));
variables.put(key, value); variables.put(key, value);
} }
/** /**
* Adds the parameter, surrounded by double quotes, to a Map that will be converted to a JS variable right before executing the script. * @deprecated Not needed when using Bindings; kept for source compatibility.
* @param key The name of the variable. * Prefer {@link #injectVariable(String, Object)}.
* @param value The value of the variable.
*/ */
@Deprecated
public void injectStringVariable(String key, String value) { public void injectStringVariable(String key, String value) {
if (value == null) { if (value == null) {
logger.trace(String.format("Not injecting [%s] because its value is null.", key)); logger.trace(String.format("Not injecting [%s] because its value is null.", key));
return; return;
} }
value = String.format("\"%s\"", value); injectVariable(key, value);
logger.trace(String.format(injectingLogMessage, key, value));
variables.put(key, value);
} }
/** /**
* Injects the variables to the script and execute it. * Injects the variables via Bindings and executes the script with a fresh context.
* @param script Code to be executed. * @param script Code to be executed.
* @return The result of the executed script. * @return The result of the executed script.
*/ */
public Object executeScript(String script) { public Object executeScript(String script) {
script = addVariablesToScript(script); Objects.requireNonNull(script, "script");
logger.debug(String.format("Executing script [%s].", script)); logger.debug(String.format("Executing script [%s].", script));
@ -126,43 +170,60 @@ public class JsInterpreter implements Closeable {
} }
protected Object executeScriptInThread(String script) { protected Object executeScriptInThread(String script) {
Callable<Object> task = () -> interpreter.eval(script); final Callable<Object> task = () -> {
final SimpleScriptContext ctx = new SimpleScriptContext();
Future<Object> future = executor.submit(task); final Bindings engineBindings = new SimpleBindings();
if (MapUtils.isNotEmpty(variables)) {
engineBindings.putAll(variables);
}
for (String token : RESTRICTED_TOKENS) {
engineBindings.put(token, null);
}
ctx.setBindings(engineBindings, ScriptContext.ENGINE_SCOPE);
final StringWriter out = new StringWriter();
ctx.setWriter(out);
try {
final CompiledScript compiled = ((Compilable) interpreter).compile(script);
Object result = compiled.eval(ctx);
if (out.getBuffer().length() > 0) {
logger.info("Script produced output on stdout: [{}]", out);
}
return result;
} catch (ScriptException se) {
String msg = se.getMessage() == null ? "Script error" : se.getMessage();
throw new ScriptException("Script error: " + msg, se.getFileName(), se.getLineNumber(), se.getColumnNumber());
}
};
final Future<Object> future = executor.submit(task);
try { try {
return future.get(this.timeout, defaultTimeUnit); return future.get(this.timeout, defaultTimeUnit);
} catch (TimeoutException | InterruptedException | ExecutionException e) { } catch (TimeoutException e) {
String message = String.format("Unable to execute script [%s] due to [%s]", script, e.getMessage()); String message = String.format(
"Execution of script [%s] took too long and timed out. %s", script, timeoutDefaultMessage);
if (e instanceof TimeoutException) {
message = String.format("Execution of script [%s] took too long and timed out. %s", script, timeoutDefaultMessage);
}
logger.error(message, e); logger.error(message, e);
throw new CloudRuntimeException(message, e); throw new CloudRuntimeException(message, e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
String message = String.format("Execution of script [%s] was interrupted.", script);
logger.error(message, e);
throw new CloudRuntimeException(message, e);
} catch (ExecutionException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
String message = String.format("Unable to execute script [%s] due to [%s]", script, cause.getMessage());
logger.error(message, cause);
throw new CloudRuntimeException(message, cause);
} finally { } finally {
future.cancel(true); future.cancel(true);
} }
} }
protected String addVariablesToScript(String script) {
if (MapUtils.isEmpty(variables)) {
logger.trace(String.format("There is no variables to add to script [%s]. Returning.", script));
return script;
}
String variablesToString = "";
for (Map.Entry<String, String> variable : variables.entrySet()) {
variablesToString = String.format("%s %s = %s;", variablesToString, variable.getKey(), variable.getValue());
}
return String.format("%s %s", variablesToString, script);
}
@Override @Override
public void close() throws IOException { public void close() throws IOException {
executor.shutdown(); executor.shutdownNow();
} }
} }

View File

@ -16,12 +16,12 @@
//under the License. //under the License.
package org.apache.cloudstack.utils.jsinterpreter; package org.apache.cloudstack.utils.jsinterpreter;
import com.cloud.utils.exception.CloudRuntimeException; import java.io.IOException;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.io.IOException; import com.cloud.utils.exception.CloudRuntimeException;
public class TagAsRuleHelper { public class TagAsRuleHelper {
@ -32,7 +32,6 @@ public class TagAsRuleHelper {
public static boolean interpretTagAsRule(String rule, String tags, long timeout) { public static boolean interpretTagAsRule(String rule, String tags, long timeout) {
String script = PARSE_TAGS + rule; String script = PARSE_TAGS + rule;
tags = String.format("'%s'", StringEscapeUtils.escapeEcmaScript(tags));
try (JsInterpreter jsInterpreter = new JsInterpreter(timeout)) { try (JsInterpreter jsInterpreter = new JsInterpreter(timeout)) {
jsInterpreter.injectVariable("tags", tags); jsInterpreter.injectVariable("tags", tags);
Object scriptReturn = jsInterpreter.executeScript(script); Object scriptReturn = jsInterpreter.executeScript(script);

View File

@ -20,6 +20,7 @@ package org.apache.cloudstack.utils.jsinterpreter;
import java.io.IOException; import java.io.IOException;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -27,7 +28,8 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import com.cloud.utils.exception.CloudRuntimeException; import javax.script.ScriptEngine;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -36,9 +38,10 @@ import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.Spy; import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import org.openjdk.nashorn.api.scripting.ClassFilter;
import org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory; import org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory;
import javax.script.ScriptEngine; import com.cloud.utils.exception.CloudRuntimeException;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class JsInterpreterTest { public class JsInterpreterTest {
@ -61,30 +64,6 @@ public class JsInterpreterTest {
Assert.assertTrue(executor.isShutdown()); Assert.assertTrue(executor.isShutdown());
} }
@Test
public void addVariablesToScriptTestVariablesMapIsEmptyReturnScript() {
String script = "a + b";
jsInterpreterSpy.variables = new LinkedHashMap<>();
String result = jsInterpreterSpy.addVariablesToScript(script);
Assert.assertEquals(script, result);
}
@Test
public void addVariablesToScriptTestVariablesMapIsNotEmptyInjectVariableToScript() {
String script = "a + b";
String var1 = "{test: \"test\"}";
jsInterpreterSpy.injectVariable("var1", var1);
jsInterpreterSpy.injectVariable("var2", var1);
String expected = String.format(" var1 = %s; var2 = %s; %s", var1, var1, script);
String result = jsInterpreterSpy.addVariablesToScript(script);
Assert.assertEquals(expected, result);
}
@Test @Test
public void executeScriptTestReturnResultOfScriptExecution() { public void executeScriptTestReturnResultOfScriptExecution() {
String script = "5"; String script = "5";
@ -154,7 +133,7 @@ public class JsInterpreterTest {
@Test @Test
public void discardCurrentVariablesTestInstantiateNewMap() { public void discardCurrentVariablesTestInstantiateNewMap() {
Map<String, String> variables = new LinkedHashMap<>(); Map<String, Object> variables = new LinkedHashMap<>();
variables.put("a", "b"); variables.put("a", "b");
variables.put("b", null); variables.put("b", null);
@ -170,12 +149,14 @@ public class JsInterpreterTest {
NashornScriptEngineFactory nashornScriptEngineFactoryMock = Mockito.spy(NashornScriptEngineFactory.class); NashornScriptEngineFactory nashornScriptEngineFactoryMock = Mockito.spy(NashornScriptEngineFactory.class);
ScriptEngine scriptEngineMock = Mockito.mock(ScriptEngine.class); ScriptEngine scriptEngineMock = Mockito.mock(ScriptEngine.class);
Mockito.doReturn(scriptEngineMock).when(nashornScriptEngineFactoryMock).getScriptEngine(Mockito.anyString()); Mockito.doReturn(scriptEngineMock).when(nashornScriptEngineFactoryMock).getScriptEngine(Mockito.any(),
Mockito.any(ClassLoader.class), Mockito.any(ClassFilter.class));
jsInterpreterSpy.setScriptEngineDisablingJavaLanguage(nashornScriptEngineFactoryMock); jsInterpreterSpy.setScriptEngineDisablingJavaLanguage(nashornScriptEngineFactoryMock);
Assert.assertEquals(scriptEngineMock, jsInterpreterSpy.interpreter); Assert.assertEquals(scriptEngineMock, jsInterpreterSpy.interpreter);
Mockito.verify(nashornScriptEngineFactoryMock).getScriptEngine("--no-java"); Mockito.verify(nashornScriptEngineFactoryMock).getScriptEngine(Mockito.any(),
Mockito.any(ClassLoader.class), Mockito.any(ClassFilter.class));
} }
@Test @Test
@ -193,6 +174,46 @@ public class JsInterpreterTest {
jsInterpreterSpy.injectStringVariable("a", "b"); jsInterpreterSpy.injectStringVariable("a", "b");
Assert.assertEquals(jsInterpreterSpy.variables.get("a"), "\"b\""); Assert.assertEquals(jsInterpreterSpy.variables.get("a"), "b");
}
@Test
public void executeScriptTestValidScriptShouldPassWithMixedVariables() {
try (JsInterpreter jsInterpreter = new JsInterpreter(1000)) {
jsInterpreter.injectVariable("x", 10);
jsInterpreter.injectVariable("y", "hello");
jsInterpreter.injectVariable("z", true);
String validScript = "var result = x + (z ? 1 : 0); y + '-' + result;";
Object result = jsInterpreter.executeScript(validScript);
Assert.assertEquals("hello-11", result);
} catch (IOException exception) {
Assert.fail("IOException not expected here");
}
}
private void runMaliciousScriptFileTest(String script, String filename) {
try (JsInterpreter jsInterpreter = new JsInterpreter(1000)) {
jsInterpreter.executeScript(script);
} catch (CloudRuntimeException ex) {
Assert.assertTrue(ex.getMessage().contains("Unable to execute script"));
java.io.File f = new java.io.File(filename);
Assert.assertFalse(f.exists());
} catch (IOException exception) {
Assert.fail("IOException not expected here");
}
}
@Test
public void executeScriptTestMaliciousScriptShouldThrowException1() {
String filename = "/tmp/hack1-" + UUID.randomUUID();
String maliciousScript = "Java.type('java.lang.Runtime').getRuntime().exec('touch " + filename + "')";
runMaliciousScriptFileTest(maliciousScript, filename);
}
@Test
public void executeScriptTestMaliciousScriptShouldThrowException2() {
String filename = "/tmp/hack2-" + UUID.randomUUID();
String maliciousScript = "var e=this.engine.getFactory().getScriptEngine('-Dnashorn.args=--no-java=False'); e.eval(\"java.lang.Runtime.getRuntime().exec(['/bin/bash','-c','touch " + filename + "']);\");";
runMaliciousScriptFileTest(maliciousScript, filename);
} }
} }