Merge branch 'main' of https://github.com/apache/cloudstack into clone-edit-existing-offerings

This commit is contained in:
Pearl Dsilva 2026-01-21 08:29:05 -05:00
commit 6e68f80106
236 changed files with 11737 additions and 1973 deletions

View File

@ -26,3 +26,5 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "daily"
cooldown:
default-days: 7

View File

@ -33,9 +33,11 @@ jobs:
stale-issue-message: 'This issue is stale because it has been open for 120 days with no activity. It may be removed by administrators of this project at any time. Remove the stale label or comment to request for removal of it to prevent this.'
stale-pr-message: 'This PR is stale because it has been open for 120 days with no activity. It may be removed by administrators of this project at any time. Remove the stale label or comment to request for removal of it to prevent this.'
close-issue-message: 'This issue was closed because it has been stale for 120 days with no activity.'
close-pr-message: 'This PR was closed because it has been stale for 120 days with no activity.'
close-pr-message: 'This PR was closed because it has been stale for 240 days with no activity.'
stale-issue-label: 'no-issue-activity'
stale-pr-label: 'no-pr-activity'
days-before-stale: 120
days-before-close: -1
days-before-pr-close: 240
exempt-issue-labels: 'gsoc,good-first-issue,long-term-plan'
exempt-pr-labels: 'status:ready-for-merge,status:needs-testing,status:on-hold'

View File

@ -62,6 +62,16 @@ repos:
- .github/workflows/license-templates/LICENSE.txt
- --fuzzy-match-generates-todo
exclude: ^(CHANGES|ISSUE_TEMPLATE|PULL_REQUEST_TEMPLATE)\.md$|^ui/docs/(full|smoke)-test-plan\.template\.md$
- id: insert-license
name: add license for all properties files
description: automatically adds a licence header to all properties files that don't have a license header
files: \.properties$
args:
- --comment-style
- '|#|'
- --license-filepath
- .github/workflows/license-templates/LICENSE.txt
- --fuzzy-match-generates-todo
- id: insert-license
name: add license for all Shell files
description: automatically adds a licence header to all Shell files that don't have a license header

View File

@ -504,6 +504,7 @@ public class EventTypes {
public static final String EVENT_S2S_VPN_CUSTOMER_GATEWAY_CREATE = "VPN.S2S.CUSTOMER.GATEWAY.CREATE";
public static final String EVENT_S2S_VPN_CUSTOMER_GATEWAY_DELETE = "VPN.S2S.CUSTOMER.GATEWAY.DELETE";
public static final String EVENT_S2S_VPN_CUSTOMER_GATEWAY_UPDATE = "VPN.S2S.CUSTOMER.GATEWAY.UPDATE";
public static final String EVENT_S2S_VPN_GATEWAY_OBSOLETE_PARAMS = "VPN.S2S.GATEWAY.OBSOLETE.PARAMS";
public static final String EVENT_S2S_VPN_CONNECTION_CREATE = "VPN.S2S.CONNECTION.CREATE";
public static final String EVENT_S2S_VPN_CONNECTION_DELETE = "VPN.S2S.CONNECTION.DELETE";
public static final String EVENT_S2S_VPN_CONNECTION_RESET = "VPN.S2S.CONNECTION.RESET";
@ -1153,6 +1154,7 @@ public class EventTypes {
entityEventDetails.put(EVENT_S2S_VPN_CUSTOMER_GATEWAY_CREATE, Site2SiteCustomerGateway.class);
entityEventDetails.put(EVENT_S2S_VPN_CUSTOMER_GATEWAY_DELETE, Site2SiteCustomerGateway.class);
entityEventDetails.put(EVENT_S2S_VPN_CUSTOMER_GATEWAY_UPDATE, Site2SiteCustomerGateway.class);
entityEventDetails.put(EVENT_S2S_VPN_GATEWAY_OBSOLETE_PARAMS, Site2SiteCustomerGateway.class);
entityEventDetails.put(EVENT_S2S_VPN_CONNECTION_CREATE, Site2SiteVpnConnection.class);
entityEventDetails.put(EVENT_S2S_VPN_CONNECTION_DELETE, Site2SiteVpnConnection.class);
entityEventDetails.put(EVENT_S2S_VPN_CONNECTION_RESET, Site2SiteVpnConnection.class);

View File

@ -125,6 +125,10 @@ public interface NetworkModel {
*/
String getNextAvailableMacAddressInNetwork(long networkConfigurationId) throws InsufficientAddressCapacityException;
String getUniqueMacAddress(long macAddress, long networkId, long datacenterId) throws InsufficientAddressCapacityException;
boolean isMACUnique(String mac, long networkId);
PublicIpAddress getPublicIpAddress(long ipAddressId);
List<? extends Vlan> listPodVlans(long podId);
@ -364,4 +368,8 @@ public interface NetworkModel {
boolean checkSecurityGroupSupportForNetwork(Account account, DataCenter zone, List<Long> networkIds,
List<Long> securityGroupsIds);
default long getMacIdentifier(Long dataCenterId) {
return 0;
}
}

View File

@ -36,6 +36,7 @@ import com.cloud.offering.DiskOffering;
import com.cloud.offering.NetworkOffering;
import com.cloud.offering.ServiceOffering;
import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
import org.apache.cloudstack.backup.BackupOffering;
public interface AccountService {
@ -115,6 +116,8 @@ public interface AccountService {
void checkAccess(Account account, VpcOffering vof, DataCenter zone) throws PermissionDeniedException;
void checkAccess(Account account, BackupOffering bof) throws PermissionDeniedException;
void checkAccess(User user, ControlledEntity entity);
void checkAccess(Account account, AccessType accessType, boolean sameOwner, String apiName, ControlledEntity... entities) throws PermissionDeniedException;

View File

@ -55,6 +55,9 @@ public interface VmDetailConstants {
String NIC_MULTIQUEUE_NUMBER = "nic.multiqueue.number";
String NIC_PACKED_VIRTQUEUES_ENABLED = "nic.packed.virtqueues.enabled";
// KVM specific, disk controllers
String KVM_SKIP_FORCE_DISK_CONTROLLER = "skip.force.disk.controller";
// Mac OSX guest specific (internal)
String SMC_PRESENT = "smc.present";
String FIRMWARE = "firmware";

View File

@ -27,6 +27,8 @@ import com.cloud.user.Account;
import com.cloud.user.User;
import com.cloud.utils.component.Adapter;
import org.apache.cloudstack.backup.BackupOffering;
/**
* SecurityChecker checks the ownership and access control to objects within
*/
@ -145,4 +147,6 @@ public interface SecurityChecker extends Adapter {
boolean checkAccess(Account account, NetworkOffering nof, DataCenter zone) throws PermissionDeniedException;
boolean checkAccess(Account account, VpcOffering vof, DataCenter zone) throws PermissionDeniedException;
boolean checkAccess(Account account, BackupOffering bof) throws PermissionDeniedException;
}

View File

@ -74,6 +74,7 @@ public interface AlertService {
public static final AlertType ALERT_TYPE_VR_PUBLIC_IFACE_MTU = new AlertType((short)32, "ALERT.VR.PUBLIC.IFACE.MTU", true);
public static final AlertType ALERT_TYPE_VR_PRIVATE_IFACE_MTU = new AlertType((short)32, "ALERT.VR.PRIVATE.IFACE.MTU", true);
public static final AlertType ALERT_TYPE_EXTENSION_PATH_NOT_READY = new AlertType((short)33, "ALERT.TYPE.EXTENSION.PATH.NOT.READY", true);
public static final AlertType ALERT_TYPE_VPN_GATEWAY_OBSOLETE_PARAMETERS = new AlertType((short)34, "ALERT.S2S.VPN.GATEWAY.OBSOLETE.PARAMETERS", true);
public static final AlertType ALERT_TYPE_BACKUP_STORAGE = new AlertType(Capacity.CAPACITY_TYPE_BACKUP_STORAGE, "ALERT.STORAGE.BACKUP", true);
public static final AlertType ALERT_TYPE_OBJECT_STORAGE = new AlertType(Capacity.CAPACITY_TYPE_OBJECT_STORAGE, "ALERT.STORAGE.OBJECT", true);

View File

@ -375,6 +375,7 @@ public class ApiConstants {
public static final String MAC_ADDRESS = "macaddress";
public static final String MAC_ADDRESSES = "macaddresses";
public static final String MANUAL_UPGRADE = "manualupgrade";
public static final String MATCH_TYPE = "matchtype";
public static final String MAX = "max";
public static final String MAX_SNAPS = "maxsnaps";
public static final String MAX_BACKUPS = "maxbackups";
@ -1364,6 +1365,10 @@ public class ApiConstants {
public static final String RECURSIVE_DOMAINS = "recursivedomains";
public static final String VPN_CUSTOMER_GATEWAY_PARAMETERS = "vpncustomergatewayparameters";
public static final String OBSOLETE_PARAMETERS = "obsoleteparameters";
public static final String EXCLUDED_PARAMETERS = "excludedparameters";
/**
* This enum specifies IO Drivers, each option controls specific policies on I/O.
* Qemu guests support "threads" and "native" options Since 0.8.8 ; "io_uring" is supported Since 6.3.0 (QEMU 5.0).

View File

@ -25,7 +25,7 @@ import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.context.CallContext;
public abstract class BaseBackupListCmd extends BaseListCmd {
public abstract class BaseBackupListCmd extends BaseListAccountResourcesCmd {
protected void setupResponseBackupOfferingsList(final List<BackupOffering> offerings, final Integer count) {
final ListResponse<BackupOfferingResponse> response = new ListResponse<>();

View File

@ -27,6 +27,7 @@ import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.BackupOfferingResponse;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.backup.BackupManager;
import org.apache.cloudstack.backup.BackupOffering;
@ -40,6 +41,11 @@ import com.cloud.exception.NetworkRuleConflictException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.commons.collections.CollectionUtils;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@APICommand(name = "importBackupOffering",
description = "Imports a backup offering using a backup provider",
@ -76,6 +82,13 @@ public class ImportBackupOfferingCmd extends BaseAsyncCmd {
description = "Whether users are allowed to create adhoc backups and backup schedules", required = true)
private Boolean userDrivenBackups;
@Parameter(name = ApiConstants.DOMAIN_ID,
type = CommandType.LIST,
collectionType = CommandType.UUID,
entityType = DomainResponse.class,
description = "the ID of the containing domain(s), null for public offerings")
private List<Long> domainIds;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -100,6 +113,15 @@ public class ImportBackupOfferingCmd extends BaseAsyncCmd {
return userDrivenBackups == null ? false : userDrivenBackups;
}
public List<Long> getDomainIds() {
if (CollectionUtils.isNotEmpty(domainIds)) {
Set<Long> set = new LinkedHashSet<>(domainIds);
domainIds.clear();
domainIds.addAll(set);
}
return domainIds;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -25,19 +25,24 @@ import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver;
import org.apache.cloudstack.api.response.BackupOfferingResponse;
import org.apache.cloudstack.backup.BackupManager;
import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.Account;
import com.cloud.utils.exception.CloudRuntimeException;
import java.util.List;
import java.util.function.LongFunction;
@APICommand(name = "updateBackupOffering", description = "Updates a backup offering.", responseObject = BackupOfferingResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.16.0")
public class UpdateBackupOfferingCmd extends BaseCmd {
public class UpdateBackupOfferingCmd extends BaseCmd implements DomainAndZoneIdResolver {
@Inject
private BackupManager backupManager;
@ -57,6 +62,13 @@ public class UpdateBackupOfferingCmd extends BaseCmd {
@Parameter(name = ApiConstants.ALLOW_USER_DRIVEN_BACKUPS, type = CommandType.BOOLEAN, description = "Whether to allow user driven backups or not")
private Boolean allowUserDrivenBackups;
@Parameter(name = ApiConstants.DOMAIN_ID,
type = CommandType.STRING,
description = "the ID of the containing domain(s) as comma separated string, public for public offerings",
since = "4.23.0",
length = 4096)
private String domainIds;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -82,7 +94,7 @@ public class UpdateBackupOfferingCmd extends BaseCmd {
@Override
public void execute() {
try {
if (StringUtils.isAllEmpty(getName(), getDescription()) && getAllowUserDrivenBackups() == null) {
if (StringUtils.isAllEmpty(getName(), getDescription()) && getAllowUserDrivenBackups() == null && CollectionUtils.isEmpty(getDomainIds())) {
throw new InvalidParameterValueException(String.format("Can't update Backup Offering [id: %s] because there are no parameters to be updated, at least one of the",
"following should be informed: name, description or allowUserDrivenBackups.", id));
}
@ -103,6 +115,18 @@ public class UpdateBackupOfferingCmd extends BaseCmd {
}
}
public List<Long> getDomainIds() {
// backupManager may be null in unit tests where the command is spied without injection.
// Avoid creating a method reference to a null receiver which causes NPE. When backupManager
// is null, pass null as the defaultDomainsProvider so resolveDomainIds will simply return
// an empty list or parse the explicit domainIds string.
LongFunction<List<Long>> defaultDomainsProvider = null;
if (backupManager != null) {
defaultDomainsProvider = backupManager::getBackupOfferingDomains;
}
return resolveDomainIds(domainIds, id, defaultDomainsProvider, "backup offering");
}
@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;

View File

@ -16,7 +16,6 @@
// under the License.
package org.apache.cloudstack.api.command.admin.network;
import java.util.ArrayList;
import java.util.List;
import org.apache.cloudstack.api.APICommand;
@ -26,18 +25,16 @@ import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver;
import org.apache.cloudstack.api.response.NetworkOfferingResponse;
import org.apache.commons.lang3.StringUtils;
import com.cloud.dc.DataCenter;
import com.cloud.domain.Domain;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.offering.NetworkOffering;
import com.cloud.user.Account;
@APICommand(name = "updateNetworkOffering", description = "Updates a network offering.", responseObject = NetworkOfferingResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class UpdateNetworkOfferingCmd extends BaseCmd {
public class UpdateNetworkOfferingCmd extends BaseCmd implements DomainAndZoneIdResolver {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
@ -129,63 +126,11 @@ public class UpdateNetworkOfferingCmd extends BaseCmd {
}
public List<Long> getDomainIds() {
List<Long> validDomainIds = new ArrayList<>();
if (StringUtils.isNotEmpty(domainIds)) {
if (domainIds.contains(",")) {
String[] domains = domainIds.split(",");
for (String domain : domains) {
Domain validDomain = _entityMgr.findByUuid(Domain.class, domain.trim());
if (validDomain != null) {
validDomainIds.add(validDomain.getId());
} else {
throw new InvalidParameterValueException("Failed to create network offering because invalid domain has been specified.");
}
}
} else {
domainIds = domainIds.trim();
if (!domainIds.matches("public")) {
Domain validDomain = _entityMgr.findByUuid(Domain.class, domainIds.trim());
if (validDomain != null) {
validDomainIds.add(validDomain.getId());
} else {
throw new InvalidParameterValueException("Failed to create network offering because invalid domain has been specified.");
}
}
}
} else {
validDomainIds.addAll(_configService.getNetworkOfferingDomains(id));
}
return validDomainIds;
return resolveDomainIds(domainIds, id, _configService::getNetworkOfferingDomains, "network offering");
}
public List<Long> getZoneIds() {
List<Long> validZoneIds = new ArrayList<>();
if (StringUtils.isNotEmpty(zoneIds)) {
if (zoneIds.contains(",")) {
String[] zones = zoneIds.split(",");
for (String zone : zones) {
DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zone.trim());
if (validZone != null) {
validZoneIds.add(validZone.getId());
} else {
throw new InvalidParameterValueException("Failed to create network offering because invalid zone has been specified.");
}
}
} else {
zoneIds = zoneIds.trim();
if (!zoneIds.matches("all")) {
DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zoneIds.trim());
if (validZone != null) {
validZoneIds.add(validZone.getId());
} else {
throw new InvalidParameterValueException("Failed to create network offering because invalid zone has been specified.");
}
}
}
} else {
validZoneIds.addAll(_configService.getNetworkOfferingZones(id));
}
return validZoneIds;
return resolveZoneIds(zoneIds, id, _configService::getNetworkOfferingZones, "network offering");
}
/////////////////////////////////////////////////////

View File

@ -16,7 +16,6 @@
// under the License.
package org.apache.cloudstack.api.command.admin.offering;
import java.util.ArrayList;
import java.util.List;
import com.cloud.offering.DiskOffering.State;
@ -27,19 +26,18 @@ import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver;
import org.apache.cloudstack.api.response.DiskOfferingResponse;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringUtils;
import com.cloud.dc.DataCenter;
import com.cloud.domain.Domain;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.offering.DiskOffering;
import com.cloud.user.Account;
@APICommand(name = "updateDiskOffering", description = "Updates a disk offering.", responseObject = DiskOfferingResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class UpdateDiskOfferingCmd extends BaseCmd {
public class UpdateDiskOfferingCmd extends BaseCmd implements DomainAndZoneIdResolver {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
@ -151,63 +149,11 @@ public class UpdateDiskOfferingCmd extends BaseCmd {
}
public List<Long> getDomainIds() {
List<Long> validDomainIds = new ArrayList<>();
if (StringUtils.isNotEmpty(domainIds)) {
if (domainIds.contains(",")) {
String[] domains = domainIds.split(",");
for (String domain : domains) {
Domain validDomain = _entityMgr.findByUuid(Domain.class, domain.trim());
if (validDomain != null) {
validDomainIds.add(validDomain.getId());
} else {
throw new InvalidParameterValueException("Failed to create disk offering because invalid domain has been specified.");
}
}
} else {
domainIds = domainIds.trim();
if (!domainIds.matches("public")) {
Domain validDomain = _entityMgr.findByUuid(Domain.class, domainIds.trim());
if (validDomain != null) {
validDomainIds.add(validDomain.getId());
} else {
throw new InvalidParameterValueException("Failed to create disk offering because invalid domain has been specified.");
}
}
}
} else {
validDomainIds.addAll(_configService.getDiskOfferingDomains(id));
}
return validDomainIds;
return resolveDomainIds(domainIds, id, _configService::getDiskOfferingDomains, "disk offering");
}
public List<Long> getZoneIds() {
List<Long> validZoneIds = new ArrayList<>();
if (StringUtils.isNotEmpty(zoneIds)) {
if (zoneIds.contains(",")) {
String[] zones = zoneIds.split(",");
for (String zone : zones) {
DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zone.trim());
if (validZone != null) {
validZoneIds.add(validZone.getId());
} else {
throw new InvalidParameterValueException("Failed to create disk offering because invalid zone has been specified.");
}
}
} else {
zoneIds = zoneIds.trim();
if (!zoneIds.matches("all")) {
DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zoneIds.trim());
if (validZone != null) {
validZoneIds.add(validZone.getId());
} else {
throw new InvalidParameterValueException("Failed to create disk offering because invalid zone has been specified.");
}
}
}
} else {
validZoneIds.addAll(_configService.getDiskOfferingZones(id));
}
return validZoneIds;
return resolveZoneIds(zoneIds, id, _configService::getDiskOfferingZones, "disk offering");
}
public String getTags() {

View File

@ -16,7 +16,6 @@
// under the License.
package org.apache.cloudstack.api.command.admin.offering;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -28,19 +27,18 @@ import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringUtils;
import com.cloud.dc.DataCenter;
import com.cloud.domain.Domain;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.offering.ServiceOffering;
import com.cloud.user.Account;
@APICommand(name = "updateServiceOffering", description = "Updates a service offering.", responseObject = ServiceOfferingResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class UpdateServiceOfferingCmd extends BaseCmd {
public class UpdateServiceOfferingCmd extends BaseCmd implements DomainAndZoneIdResolver {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
@ -130,63 +128,11 @@ public class UpdateServiceOfferingCmd extends BaseCmd {
}
public List<Long> getDomainIds() {
List<Long> validDomainIds = new ArrayList<>();
if (StringUtils.isNotEmpty(domainIds)) {
if (domainIds.contains(",")) {
String[] domains = domainIds.split(",");
for (String domain : domains) {
Domain validDomain = _entityMgr.findByUuid(Domain.class, domain.trim());
if (validDomain != null) {
validDomainIds.add(validDomain.getId());
} else {
throw new InvalidParameterValueException("Failed to create service offering because invalid domain has been specified.");
}
}
} else {
domainIds = domainIds.trim();
if (!domainIds.matches("public")) {
Domain validDomain = _entityMgr.findByUuid(Domain.class, domainIds.trim());
if (validDomain != null) {
validDomainIds.add(validDomain.getId());
} else {
throw new InvalidParameterValueException("Failed to create service offering because invalid domain has been specified.");
}
}
}
} else {
validDomainIds.addAll(_configService.getServiceOfferingDomains(id));
}
return validDomainIds;
return resolveDomainIds(domainIds, id, _configService::getServiceOfferingDomains, "service offering");
}
public List<Long> getZoneIds() {
List<Long> validZoneIds = new ArrayList<>();
if (StringUtils.isNotEmpty(zoneIds)) {
if (zoneIds.contains(",")) {
String[] zones = zoneIds.split(",");
for (String zone : zones) {
DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zone.trim());
if (validZone != null) {
validZoneIds.add(validZone.getId());
} else {
throw new InvalidParameterValueException("Failed to create service offering because invalid zone has been specified.");
}
}
} else {
zoneIds = zoneIds.trim();
if (!zoneIds.matches("all")) {
DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zoneIds.trim());
if (validZone != null) {
validZoneIds.add(validZone.getId());
} else {
throw new InvalidParameterValueException("Failed to create service offering because invalid zone has been specified.");
}
}
}
} else {
validZoneIds.addAll(_configService.getServiceOfferingZones(id));
}
return validZoneIds;
return resolveZoneIds(zoneIds, id, _configService::getServiceOfferingZones, "service offering");
}
public String getStorageTags() {

View File

@ -78,12 +78,12 @@ public class DisableUserCmd extends BaseAsyncCmd {
@Override
public String getEventDescription() {
return "disabling user: " + getId();
return "disabling user: " + this._uuidMgr.getUuid(User.class, getId());
}
@Override
public void execute() {
CallContext.current().setEventDetails("UserId: " + getId());
CallContext.current().setEventDetails("User ID: " + this._uuidMgr.getUuid(User.class, getId()));
UserAccount user = _regionService.disableUser(this);
if (user != null) {

View File

@ -16,7 +16,6 @@
// under the License.
package org.apache.cloudstack.api.command.admin.vpc;
import java.util.ArrayList;
import java.util.List;
import org.apache.cloudstack.api.APICommand;
@ -26,19 +25,16 @@ import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver;
import org.apache.cloudstack.api.response.VpcOfferingResponse;
import org.apache.commons.lang3.StringUtils;
import com.cloud.dc.DataCenter;
import com.cloud.domain.Domain;
import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.network.vpc.VpcOffering;
import com.cloud.user.Account;
@APICommand(name = "updateVPCOffering", description = "Updates VPC offering", responseObject = VpcOfferingResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class UpdateVPCOfferingCmd extends BaseAsyncCmd {
public class UpdateVPCOfferingCmd extends BaseAsyncCmd implements DomainAndZoneIdResolver {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
@ -92,63 +88,11 @@ public class UpdateVPCOfferingCmd extends BaseAsyncCmd {
}
public List<Long> getDomainIds() {
List<Long> validDomainIds = new ArrayList<>();
if (StringUtils.isNotEmpty(domainIds)) {
if (domainIds.contains(",")) {
String[] domains = domainIds.split(",");
for (String domain : domains) {
Domain validDomain = _entityMgr.findByUuid(Domain.class, domain.trim());
if (validDomain != null) {
validDomainIds.add(validDomain.getId());
} else {
throw new InvalidParameterValueException("Failed to create VPC offering because invalid domain has been specified.");
}
}
} else {
domainIds = domainIds.trim();
if (!domainIds.matches("public")) {
Domain validDomain = _entityMgr.findByUuid(Domain.class, domainIds.trim());
if (validDomain != null) {
validDomainIds.add(validDomain.getId());
} else {
throw new InvalidParameterValueException("Failed to create VPC offering because invalid domain has been specified.");
}
}
}
} else {
validDomainIds.addAll(_vpcProvSvc.getVpcOfferingDomains(id));
}
return validDomainIds;
return resolveDomainIds(domainIds, id, _vpcProvSvc::getVpcOfferingDomains, "VPC offering");
}
public List<Long> getZoneIds() {
List<Long> validZoneIds = new ArrayList<>();
if (StringUtils.isNotEmpty(zoneIds)) {
if (zoneIds.contains(",")) {
String[] zones = zoneIds.split(",");
for (String zone : zones) {
DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zone.trim());
if (validZone != null) {
validZoneIds.add(validZone.getId());
} else {
throw new InvalidParameterValueException("Failed to create VPC offering because invalid zone has been specified.");
}
}
} else {
zoneIds = zoneIds.trim();
if (!zoneIds.matches("all")) {
DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zoneIds.trim());
if (validZone != null) {
validZoneIds.add(validZone.getId());
} else {
throw new InvalidParameterValueException("Failed to create VPC offering because invalid zone has been specified.");
}
}
}
} else {
validZoneIds.addAll(_vpcProvSvc.getVpcOfferingZones(id));
}
return validZoneIds;
return resolveZoneIds(zoneIds, id, _vpcProvSvc::getVpcOfferingZones, "VPC offering");
}
public Integer getSortKey() {

View File

@ -0,0 +1,114 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.offering;
import java.util.ArrayList;
import java.util.List;
import java.util.function.LongFunction;
import com.cloud.dc.DataCenter;
import com.cloud.domain.Domain;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Helper for commands that accept a domainIds or zoneIds string and need to
* resolve them to lists of IDs, falling back to an offering-specific
* default provider.
*/
public interface DomainAndZoneIdResolver {
/**
* Parse the provided domainIds string and return a list of domain IDs.
* If domainIds is empty, the defaultDomainsProvider will be invoked with the
* provided resource id to obtain the current domains.
*/
default List<Long> resolveDomainIds(final String domainIds, final Long id, final LongFunction<List<Long>> defaultDomainsProvider, final String resourceTypeName) {
final List<Long> validDomainIds = new ArrayList<>();
final BaseCmd base = (BaseCmd) this;
final Logger logger = LogManager.getLogger(base.getClass());
if (StringUtils.isEmpty(domainIds)) {
if (defaultDomainsProvider != null) {
final List<Long> defaults = defaultDomainsProvider.apply(id);
if (defaults != null) {
validDomainIds.addAll(defaults);
}
}
return validDomainIds;
}
final String[] domains = domainIds.split(",");
final String type = (resourceTypeName == null || resourceTypeName.isEmpty()) ? "offering" : resourceTypeName;
for (String domain : domains) {
final String trimmed = domain == null ? "" : domain.trim();
if (trimmed.isEmpty() || "public".equalsIgnoreCase(trimmed)) {
continue;
}
final Domain validDomain = base._entityMgr.findByUuid(Domain.class, trimmed);
if (validDomain == null) {
logger.warn("Invalid domain specified for {}", type);
throw new InvalidParameterValueException("Failed to create " + type + " because invalid domain has been specified.");
}
validDomainIds.add(validDomain.getId());
}
return validDomainIds;
}
/**
* Parse the provided zoneIds string and return a list of zone IDs.
* If zoneIds is empty, the defaultZonesProvider will be invoked with the
* provided resource id to obtain the current zones.
*/
default List<Long> resolveZoneIds(final String zoneIds, final Long id, final LongFunction<List<Long>> defaultZonesProvider, final String resourceTypeName) {
final List<Long> validZoneIds = new ArrayList<>();
final BaseCmd base = (BaseCmd) this;
final Logger logger = LogManager.getLogger(base.getClass());
if (StringUtils.isEmpty(zoneIds)) {
if (defaultZonesProvider != null) {
final List<Long> defaults = defaultZonesProvider.apply(id);
if (defaults != null) {
validZoneIds.addAll(defaults);
}
}
return validZoneIds;
}
final String[] zones = zoneIds.split(",");
final String type = (resourceTypeName == null || resourceTypeName.isEmpty()) ? "offering" : resourceTypeName;
for (String zone : zones) {
final String trimmed = zone == null ? "" : zone.trim();
if (trimmed.isEmpty() || "all".equalsIgnoreCase(trimmed)) {
continue;
}
final DataCenter validZone = base._entityMgr.findByUuid(DataCenter.class, trimmed);
if (validZone == null) {
logger.warn("Invalid zone specified for {}: {}", type, trimmed);
throw new InvalidParameterValueException("Failed to create " + type + " because invalid zone has been specified.");
}
validZoneIds.add(validZone.getId());
}
return validZoneIds;
}
}

View File

@ -19,6 +19,7 @@ package org.apache.cloudstack.api.command.user.backup;
import javax.inject.Inject;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
@ -138,7 +139,8 @@ public class CreateBackupCmd extends BaseAsyncCreateCmd {
@Override
public String getEventDescription() {
return "Creating backup for Instance " + vmId;
String vmUuid = _uuidMgr.getUuid(VirtualMachine.class, getVmId());
return "Creating backup for Instance " + vmUuid;
}
@Override

View File

@ -26,7 +26,6 @@ import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.BackupResponse;
import org.apache.cloudstack.api.response.BackupScheduleResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.backup.BackupManager;
@ -38,7 +37,7 @@ import com.cloud.utils.exception.CloudRuntimeException;
@APICommand(name = "createBackupSchedule",
description = "Creates a User-defined Instance backup schedule",
responseObject = BackupResponse.class, since = "4.14.0",
responseObject = BackupScheduleResponse.class, since = "4.14.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class CreateBackupScheduleCmd extends BaseCmd {

View File

@ -28,6 +28,7 @@ import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.BackupResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.backup.Backup;
import org.apache.cloudstack.backup.BackupManager;
import org.apache.cloudstack.context.CallContext;
import org.apache.commons.lang3.BooleanUtils;
@ -111,6 +112,7 @@ public class DeleteBackupCmd extends BaseAsyncCmd {
@Override
public String getEventDescription() {
return "Deleting backup ID " + backupId;
String backupUuid = _uuidMgr.getUuid(Backup.class, getId());
return "Deleting backup ID " + backupUuid;
}
}

View File

@ -28,6 +28,7 @@ import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.api.response.BackupResponse;
import org.apache.cloudstack.backup.Backup;
import org.apache.cloudstack.backup.BackupManager;
import org.apache.cloudstack.context.CallContext;
@ -99,6 +100,7 @@ public class RestoreBackupCmd extends BaseAsyncCmd {
@Override
public String getEventDescription() {
return "Restoring Instance from backup: " + backupId;
String backupUuid = _uuidMgr.getUuid(Backup.class, getBackupId());
return "Restoring Instance from backup: " + backupUuid;
}
}

View File

@ -17,6 +17,7 @@
package org.apache.cloudstack.api.command.user.backup.repository;
import com.cloud.utils.StringUtils;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
@ -100,7 +101,7 @@ public class AddBackupRepositoryCmd extends BaseCmd {
}
public String getMountOptions() {
return mountOptions == null ? "" : mountOptions;
return StringUtils.isBlank(mountOptions) ? "" : mountOptions;
}
public Long getZoneId() {

View File

@ -21,7 +21,9 @@ import java.util.Map;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.CapabilitiesResponse;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.config.ApiServiceConfiguration;
import com.cloud.user.Account;
@ -30,12 +32,22 @@ import com.cloud.user.Account;
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class ListCapabilitiesCmd extends BaseCmd {
@Parameter(name = ApiConstants.DOMAIN_ID,
type = CommandType.UUID,
entityType = DomainResponse.class,
description = "the domain for listing capabilities.",
since = "4.23.0")
private Long domainId;
@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;
}
public Long getDomainId() {
return domainId;
}
@Override
public void execute() {
Map<String, Object> capabilities = _mgr.listCapabilities(this);
@ -76,6 +88,10 @@ public class ListCapabilitiesCmd extends BaseCmd {
response.setExtensionsPath((String)capabilities.get(ApiConstants.EXTENSIONS_PATH));
response.setDynamicScalingEnabled((Boolean) capabilities.get(ApiConstants.DYNAMIC_SCALING_ENABLED));
response.setAdditionalConfigEnabled((Boolean) capabilities.get(ApiConstants.ADDITONAL_CONFIG_ENABLED));
if (capabilities.containsKey(ApiConstants.VPN_CUSTOMER_GATEWAY_PARAMETERS)) {
Map<String, Object> vpnCustomerGatewayParameters = (Map<String, Object>) capabilities.get(ApiConstants.VPN_CUSTOMER_GATEWAY_PARAMETERS);
response.setVpnCustomerGatewayParameters(vpnCustomerGatewayParameters);
}
response.setObjectName("capability");
response.setResponseName(getCommandName());
this.setResponseObject(response);

View File

@ -97,7 +97,11 @@ public class CopySnapshotCmd extends BaseAsyncCmd implements UserCmd {
"The snapshot will always be made available in the zone in which the volume is present. Currently supported for StorPool only")
protected List<Long> storagePoolIds;
@Parameter (name = ApiConstants.USE_STORAGE_REPLICATION, type=CommandType.BOOLEAN, required = false, since = "4.21.0", description = "This parameter enables the option the snapshot to be copied to supported primary storage")
@Parameter (name = ApiConstants.USE_STORAGE_REPLICATION,
type=CommandType.BOOLEAN,
since = "4.21.0",
description = "Enables the snapshot to be copied to the supported primary storages when the config 'use.storage.replication' is set to true for the storage or globally. " +
"This is supported only for StorPool storage for now.")
protected Boolean useStorageReplication;
/////////////////////////////////////////////////////

View File

@ -112,7 +112,10 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd {
since = "4.21.0")
protected List<Long> storagePoolIds;
@Parameter (name = ApiConstants.USE_STORAGE_REPLICATION, type=CommandType.BOOLEAN, required = false, description = "This parameter enables the option the snapshot to be copied to supported primary storage")
@Parameter (name = ApiConstants.USE_STORAGE_REPLICATION,
type=CommandType.BOOLEAN,
description = "Enables the snapshot to be copied to the supported primary storages when the config 'use.storage.replication' is set to true for the storage or globally. " +
"This is supported only for StorPool storage for now.")
protected Boolean useStorageReplication;
private String syncObjectType = BaseAsyncCmd.snapshotHostSyncObject;

View File

@ -94,7 +94,11 @@ public class CreateSnapshotPolicyCmd extends BaseCmd {
since = "4.21.0")
protected List<Long> storagePoolIds;
@Parameter (name = ApiConstants.USE_STORAGE_REPLICATION, type=CommandType.BOOLEAN, required = false, since = "4.21.0", description = "This parameter enables the option the snapshot to be copied to supported primary storage")
@Parameter (name = ApiConstants.USE_STORAGE_REPLICATION,
type=CommandType.BOOLEAN,
since = "4.21.0",
description = "Enables the snapshot to be copied to the supported primary storages when the config 'use.storage.replication' is set to true for the storage or globally. " +
"This is supported only for StorPool storage for now.")
protected Boolean useStorageReplication;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////

View File

@ -61,6 +61,16 @@ public class BackupOfferingResponse extends BaseResponse {
@Param(description = "Zone name")
private String zoneName;
@SerializedName(ApiConstants.DOMAIN_ID)
@Param(description = "the domain ID(s) this backup offering belongs to.",
since = "4.23.0")
private String domainId;
@SerializedName(ApiConstants.DOMAIN)
@Param(description = "the domain name(s) this backup offering belongs to.",
since = "4.23.0")
private String domain;
@SerializedName(ApiConstants.CROSS_ZONE_INSTANCE_CREATION)
@Param(description = "the backups with this offering can be used to create Instances on all Zones", since = "4.22.0")
private Boolean crossZoneInstanceCreation;
@ -108,4 +118,13 @@ public class BackupOfferingResponse extends BaseResponse {
public void setCreated(Date created) {
this.created = created;
}
public void setDomainId(String domainId) {
this.domainId = domainId;
}
public void setDomain(String domain) {
this.domain = domain;
}
}

View File

@ -57,6 +57,10 @@ public class BackupRepositoryResponse extends BaseResponse {
@Param(description = "backup type")
private String type;
@SerializedName(ApiConstants.MOUNT_OPTIONS)
@Param(description = "mount options", since = "4.22.1")
private String mountOptions;
@SerializedName(ApiConstants.CAPACITY_BYTES)
@Param(description = "capacity of the backup repository")
private Long capacityBytes;
@ -128,6 +132,14 @@ public class BackupRepositoryResponse extends BaseResponse {
this.type = type;
}
public String getMountOptions() {
return mountOptions;
}
public void setMountOptions(String mountOptions) {
this.mountOptions = mountOptions;
}
public Long getCapacityBytes() {
return capacityBytes;
}

View File

@ -16,6 +16,8 @@
// under the License.
package org.apache.cloudstack.api.response;
import java.util.Map;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
@ -153,6 +155,10 @@ public class CapabilitiesResponse extends BaseResponse {
@Param(description = "true if additional configurations or extraconfig can be passed to Instances", since = "4.20.2")
private Boolean additionalConfigEnabled;
@SerializedName(ApiConstants.VPN_CUSTOMER_GATEWAY_PARAMETERS)
@Param(description = "Excluded and obsolete VPN customer gateway cryptographic parameters")
private Map<String, Object> vpnCustomerGatewayParameters;
public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) {
this.securityGroupsEnabled = securityGroupsEnabled;
}
@ -280,4 +286,8 @@ public class CapabilitiesResponse extends BaseResponse {
public void setAdditionalConfigEnabled(Boolean additionalConfigEnabled) {
this.additionalConfigEnabled = additionalConfigEnabled;
}
public void setVpnCustomerGatewayParameters(Map<String, Object> vpnCustomerGatewayParameters) {
this.vpnCustomerGatewayParameters = vpnCustomerGatewayParameters;
}
}

View File

@ -114,6 +114,14 @@ public class Site2SiteCustomerGatewayResponse extends BaseResponseWithAnnotation
@Param(description = "Which IKE Version to use, one of ike (autoselect), IKEv1, or IKEv2. Defaults to ike")
private String ikeVersion;
@SerializedName(ApiConstants.OBSOLETE_PARAMETERS)
@Param(description = "Contains the list of obsolete/insecure cryptographic parameters that the vpn customer gateway is using.", since = "4.23.0")
private String obsoleteParameters;
@SerializedName(ApiConstants.EXCLUDED_PARAMETERS)
@Param(description = "Contains the list of excluded/not allowed cryptographic parameters that the vpn customer gateway is using.", since = "4.23.0")
private String excludedParameters;
public void setId(String id) {
this.id = id;
}
@ -202,4 +210,12 @@ public class Site2SiteCustomerGatewayResponse extends BaseResponseWithAnnotation
this.domainPath = domainPath;
}
public void setContainsObsoleteParameters(String obsoleteParameters) {
this.obsoleteParameters = obsoleteParameters;
}
public void setContainsExcludedParameters(String excludedParameters) {
this.excludedParameters = excludedParameters;
}
}

View File

@ -137,6 +137,8 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer
*/
BackupOffering importBackupOffering(final ImportBackupOfferingCmd cmd);
List<Long> getBackupOfferingDomains(final Long offeringId);
/**
* Clone an existing backup offering with updated values
* @param cmd clone backup offering cmd

View File

@ -32,5 +32,4 @@ public interface BackupRepositoryService {
BackupRepository updateBackupRepository(UpdateBackupRepositoryCmd cmd);
boolean deleteBackupRepository(DeleteBackupRepositoryCmd cmd);
Pair<List<BackupRepository>, Integer> listBackupRepositories(ListBackupRepositoriesCmd cmd);
}

View File

@ -0,0 +1,149 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.offering;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.LongFunction;
import com.cloud.dc.DataCenter;
import com.cloud.domain.Domain;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.NetworkRuleConflictException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.utils.db.EntityManager;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.ServerApiException;
import org.junit.Assert;
import org.junit.Test;
public class DomainAndZoneIdResolverTest {
static class TestCmd extends BaseCmd implements DomainAndZoneIdResolver {
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
// No implementation needed for tests
}
@Override
public String getCommandName() {
return "test";
}
@Override
public long getEntityOwnerId() {
return 1L;
}
}
private void setEntityMgr(final BaseCmd cmd, final EntityManager entityMgr) throws Exception {
Field f = BaseCmd.class.getDeclaredField("_entityMgr");
f.setAccessible(true);
f.set(cmd, entityMgr);
}
@Test
public void resolveDomainIds_usesDefaultProviderWhenEmpty() {
TestCmd cmd = new TestCmd();
final LongFunction<List<Long>> defaultsProvider = id -> Arrays.asList(100L, 200L);
List<Long> result = cmd.resolveDomainIds("", 42L, defaultsProvider, "offering");
Assert.assertEquals(Arrays.asList(100L, 200L), result);
}
@Test
public void resolveDomainIds_resolvesValidUuids() throws Exception {
TestCmd cmd = new TestCmd();
EntityManager em = mock(EntityManager.class);
setEntityMgr(cmd, em);
Domain d1 = mock(Domain.class);
when(d1.getId()).thenReturn(10L);
Domain d2 = mock(Domain.class);
when(d2.getId()).thenReturn(20L);
when(em.findByUuid(Domain.class, "uuid1")).thenReturn(d1);
when(em.findByUuid(Domain.class, "uuid2")).thenReturn(d2);
List<Long> ids = cmd.resolveDomainIds("uuid1, public, uuid2", null, null, "template");
Assert.assertEquals(Arrays.asList(10L, 20L), ids);
}
@Test
public void resolveDomainIds_invalidUuid_throws() throws Exception {
TestCmd cmd = new TestCmd();
EntityManager em = mock(EntityManager.class);
setEntityMgr(cmd, em);
when(em.findByUuid(Domain.class, "bad-uuid")).thenReturn(null);
Assert.assertThrows(InvalidParameterValueException.class,
() -> cmd.resolveDomainIds("bad-uuid", null, null, "offering"));
}
@Test
public void resolveZoneIds_usesDefaultProviderWhenEmpty() {
TestCmd cmd = new TestCmd();
final LongFunction<List<Long>> defaultsProvider = id -> Collections.singletonList(300L);
List<Long> result = cmd.resolveZoneIds("", 99L, defaultsProvider, "offering");
Assert.assertEquals(Collections.singletonList(300L), result);
}
@Test
public void resolveZoneIds_resolvesValidUuids() throws Exception {
TestCmd cmd = new TestCmd();
EntityManager em = mock(EntityManager.class);
setEntityMgr(cmd, em);
DataCenter z1 = mock(DataCenter.class);
when(z1.getId()).thenReturn(30L);
DataCenter z2 = mock(DataCenter.class);
when(z2.getId()).thenReturn(40L);
when(em.findByUuid(DataCenter.class, "zone-1")).thenReturn(z1);
when(em.findByUuid(DataCenter.class, "zone-2")).thenReturn(z2);
List<Long> ids = cmd.resolveZoneIds("zone-1, all, zone-2", null, null, "service");
Assert.assertEquals(Arrays.asList(30L, 40L), ids);
}
@Test
public void resolveZoneIds_invalidUuid_throws() throws Exception {
TestCmd cmd = new TestCmd();
EntityManager em = mock(EntityManager.class);
setEntityMgr(cmd, em);
when(em.findByUuid(DataCenter.class, "bad-zone")).thenReturn(null);
Assert.assertThrows(InvalidParameterValueException.class,
() -> cmd.resolveZoneIds("bad-zone", null, null, "offering"));
}
}

View File

@ -36,6 +36,106 @@ from cloudutils.cloudException import CloudRuntimeException, CloudInternalExcept
from cloudutils.globalEnv import globalEnv
from cloudutils.serviceConfigServer import cloudManagementConfig
from optparse import OptionParser
import urllib.request
import configparser
import hashlib
SYSTEMVM_TEMPLATES_PATH = "/usr/share/cloudstack-management/templates/systemvm"
SYSTEMVM_TEMPLATES_METADATA_FILE = SYSTEMVM_TEMPLATES_PATH + "/metadata.ini"
def verify_sha512_checksum(file_path, expected_checksum):
sha512 = hashlib.sha512()
try:
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
sha512.update(chunk)
return sha512.hexdigest().lower() == expected_checksum.lower()
except Exception as e:
print(f"Failed to verify checksum for {file_path}: {e}")
return False
def download_file(url, dest_path, chunk_size=8 * 1024 * 1024):
"""
Downloads a file from the given URL to the specified destination path in chunks.
"""
try:
with urllib.request.urlopen(url) as response:
total_size = response.length if response.length else None
downloaded = 0
try:
with open(dest_path, 'wb') as out_file:
while True:
chunk = response.read(chunk_size)
if not chunk:
break
out_file.write(chunk)
downloaded += len(chunk)
if total_size:
print(f"Downloaded {downloaded / (1024 * 1024):.2f}MB of {total_size / (1024 * 1024):.2f}MB", end='\r')
except PermissionError as pe:
print(f"Permission denied: {dest_path}")
raise
print(f"\nDownloaded file from {url} to {dest_path}")
except Exception as e:
print(f"Failed to download file: {e}")
raise
def download_template_if_needed(template, url, filename, checksum):
dest_path = os.path.join(SYSTEMVM_TEMPLATES_PATH, filename)
if os.path.exists(dest_path):
if checksum and verify_sha512_checksum(dest_path, checksum):
print(f"{template} System VM template already exists at {dest_path} with valid checksum, skipping download.")
return
else:
print(f"{template} System VM template at {dest_path} has invalid or missing checksum, re-downloading...")
else:
print(f"Downloading {template} System VM template from {url} to {dest_path}...")
try:
download_file(url, dest_path)
#After download, verify checksum if provided
if checksum:
if verify_sha512_checksum(dest_path, checksum):
print(f"{template} System VM template downloaded and verified successfully.")
else:
print(f"ERROR: Checksum verification failed for {template} System VM template after download.")
except Exception as e:
print(f"ERROR: Failed to download {template} System VM template: {e}")
def collect_template_metadata(selected_templates, options):
template_metadata_list = []
if not os.path.exists(SYSTEMVM_TEMPLATES_METADATA_FILE):
print(f"ERROR: System VM templates metadata file not found at {SYSTEMVM_TEMPLATES_METADATA_FILE}, cannot download templates.")
sys.exit(1)
config = configparser.ConfigParser()
config.read(SYSTEMVM_TEMPLATES_METADATA_FILE)
template_repo_url = None
if options.systemvm_templates_repository:
if "default" in config and "downloadrepository" in config["default"]:
template_repo_url = config["default"]["downloadrepository"].strip()
if not template_repo_url:
print("ERROR: downloadrepository value is empty in metadata file, cannot use --systemvm-template-repository option.")
sys.exit(1)
for template in selected_templates:
if template in config:
url = config[template].get("downloadurl")
filename = config[template].get("filename")
checksum = config[template].get("checksum")
if url and filename:
if template_repo_url:
url = url.replace(template_repo_url, options.systemvm_templates_repository)
template_metadata_list.append({
"template": template,
"url": url,
"filename": filename,
"checksum": checksum
})
else:
print(f"ERROR: URL or filename not found for {template} System VM template in metadata.")
sys.exit(1)
else:
print(f"ERROR: No metadata found for {template} System VM template.")
sys.exit(1)
return template_metadata_list
if __name__ == '__main__':
initLoging("@MSLOGDIR@/setupManagement.log")
@ -45,6 +145,16 @@ if __name__ == '__main__':
parser.add_option("--https", action="store_true", dest="https", help="Enable HTTPs connection of management server")
parser.add_option("--tomcat7", action="store_true", dest="tomcat7", help="Depreciated option, don't use it")
parser.add_option("--no-start", action="store_true", dest="nostart", help="Do not start management server after successful configuration")
parser.add_option(
"--systemvm-templates",
dest="systemvm_templates",
help="Specify System VM templates to download: all, kvm-aarch64, kvm-x86_64, xenserver, vmware or comma-separated list of hypervisor combinations (e.g., kvm-x86_64,xenserver). Default is kvm-x86_64.",
)
parser.add_option(
"--systemvm-templates-repository",
dest="systemvm_templates_repository",
help="Specify the URL to download System VM templates from."
)
(options, args) = parser.parse_args()
if options.https:
glbEnv.svrMode = "HttpsServer"
@ -53,6 +163,34 @@ if __name__ == '__main__':
if options.nostart:
glbEnv.noStart = True
available_templates = ["kvm-aarch64", "kvm-x86_64", "xenserver", "vmware"]
templates_arg = options.systemvm_templates
selected_templates = ["kvm-x86_64"]
if templates_arg:
templates_list = [t.strip().lower() for t in templates_arg.split(",")]
if "all" in templates_list:
if len(templates_list) > 1:
print("WARNING: 'all' specified for System VM templates, ignoring other specified templates.")
selected_templates = available_templates
else:
invalid_templates = []
for t in templates_list:
if t in available_templates:
if t not in selected_templates:
selected_templates.append(t)
else:
if t not in invalid_templates:
invalid_templates.append(t)
if invalid_templates:
print(f"ERROR: Invalid System VM template names provided: {', '.join(invalid_templates)}")
sys.exit(1)
print(f"Selected systemvm templates to download: {', '.join(selected_templates) if selected_templates else 'None'}")
template_metadata_list = []
if selected_templates:
template_metadata_list = collect_template_metadata(selected_templates, options)
glbEnv.mode = "Server"
print("Starting to configure CloudStack Management Server:")
@ -74,3 +212,6 @@ if __name__ == '__main__':
syscfg.restore()
except:
pass
for meta in template_metadata_list:
download_template_if_needed(meta["template"], meta["url"], meta["filename"], meta["checksum"])

View File

@ -62,3 +62,8 @@ extensions.deployment.mode=@EXTENSIONSDEPLOYMENTMODE@
# Thread pool configuration
#threads.min=10
#threads.max=500
# The URL prefix for the system VM templates repository. When downloading system VM templates, the server replaces the
# `downloadrepository` key value from the metadata file in template URLs. If not specified, the original template URL
# will be used for download.
# system.vm.templates.download.repository=http://download.cloudstack.org/systemvm/

View File

@ -151,7 +151,7 @@ public class HttpTemplateDownloader extends ManagedContextRunnable implements Te
client.getParams().setAuthenticationPreemptive(true);
Credentials defaultcreds = new UsernamePasswordCredentials(user, password);
client.getState().setCredentials(new AuthScope(hostAndPort.first(), hostAndPort.second(), AuthScope.ANY_REALM), defaultcreds);
logger.info("Added username=" + user + ", password=" + password + "for host " + hostAndPort.first() + ":" + hostAndPort.second());
logger.info("Added username={}, password=****** for host {}:{}", user, hostAndPort.first(), hostAndPort.second());
} else {
logger.info("No credentials configured for host=" + hostAndPort.first() + ":" + hostAndPort.second());
}

View File

@ -35,9 +35,9 @@ public interface ObjectInDataStoreStateMachine extends StateObject<ObjectInDataS
Failed("Failed to download Template"),
Hidden("The object is hidden from the user");
String _description;
final String _description;
private State(String description) {
State(String description) {
_description = description;
}
@ -50,7 +50,7 @@ public interface ObjectInDataStoreStateMachine extends StateObject<ObjectInDataS
CreateRequested,
CreateOnlyRequested,
DestroyRequested,
OperationSuccessed,
OperationSucceeded,
OperationFailed,
CopyRequested,
CopyingRequested,

View File

@ -30,6 +30,8 @@ public interface SnapshotDataFactory {
SnapshotInfo getSnapshot(long snapshotId, long storeId, DataStoreRole role);
SnapshotInfo getSnapshotIncludingRemoved(long snapshotId, long storeId, DataStoreRole role);
SnapshotInfo getSnapshotWithRoleAndZone(long snapshotId, DataStoreRole role, long zoneId);
SnapshotInfo getSnapshotOnPrimaryStore(long snapshotId);

View File

@ -40,7 +40,11 @@ public class PublicIp implements PublicIpAddress {
}
public static PublicIp createFromAddrAndVlan(IPAddressVO addr, VlanVO vlan) {
return new PublicIp(addr, vlan, NetUtils.createSequenceBasedMacAddress(addr.getMacAddress(), NetworkModel.MACIdentifier.value()));
long macIdentifier = NetworkModel.MACIdentifier.valueIn(addr.getDataCenterId());
if (macIdentifier == 0) {
macIdentifier = addr.getDataCenterId();
}
return new PublicIp(addr, vlan, NetUtils.createSequenceBasedMacAddress(addr.getMacAddress(), macIdentifier));
}
@Override
@ -279,5 +283,4 @@ public class PublicIp implements PublicIpAddress {
public boolean isForRouter() {
return _addr.isForRouter();
}
}

View File

@ -3053,7 +3053,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
}
protected void migrate(final VMInstanceVO vm, final long srcHostId, final DeployDestination dest) throws ResourceUnavailableException, ConcurrentOperationException {
logger.info("Migrating {} to {}", vm, dest);
logger.info("Start preparing migration of the VM: {} to {}", vm, dest);
final long dstHostId = dest.getHost().getId();
final Host fromHost = _hostDao.findById(srcHostId);
if (fromHost == null) {
@ -3118,9 +3118,11 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
if (pfma == null || !pfma.getResult()) {
final String details = pfma != null ? pfma.getDetails() : "null answer returned";
final String msg = "Unable to prepare for migration due to " + details;
logger.error("Failed to prepare destination host {} for migration of VM {} : {}", dstHostId, vm.getInstanceName(), details);
pfma = null;
throw new AgentUnavailableException(msg, dstHostId);
}
logger.debug("Successfully prepared destination host {} for migration of VM {} ", dstHostId, vm.getInstanceName());
} catch (final OperationTimedoutException e1) {
throw new AgentUnavailableException("Operation timed out", dstHostId);
} finally {
@ -3141,18 +3143,23 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
volumeMgr.release(vm.getId(), dstHostId);
}
logger.info("Migration cancelled because state has changed: {}", vm);
throw new ConcurrentOperationException("Migration cancelled because state has changed: " + vm);
String msg = "Migration cancelled because state has changed: " + vm;
logger.warn(msg);
throw new ConcurrentOperationException(msg);
}
} catch (final NoTransitionException e1) {
_networkMgr.rollbackNicForMigration(vmSrc, profile);
volumeMgr.release(vm.getId(), dstHostId);
logger.info("Migration cancelled because {}", e1.getMessage());
String msg = String.format("Migration cancelled for VM %s due to state transition failure: %s",
vm.getInstanceName(), e1.getMessage());
logger.warn(msg, e1);
throw new ConcurrentOperationException("Migration cancelled because " + e1.getMessage());
} catch (final CloudRuntimeException e2) {
_networkMgr.rollbackNicForMigration(vmSrc, profile);
volumeMgr.release(vm.getId(), dstHostId);
logger.info("Migration cancelled because {}", e2.getMessage());
String msg = String.format("Migration cancelled for VM %s due to runtime exception: %s",
vm.getInstanceName(), e2.getMessage());
logger.error(msg, e2);
work.setStep(Step.Done);
_workDao.update(work.getId(), work);
try {
@ -3172,8 +3179,12 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
final Answer ma = _agentMgr.send(vm.getLastHostId(), mc);
if (ma == null || !ma.getResult()) {
final String details = ma != null ? ma.getDetails() : "null answer returned";
String msg = String.format("Migration command failed for VM %s on source host id=%s to destination host %s: %s",
vm.getInstanceName(), vm.getLastHostId(), dstHostId, details);
logger.error(msg);
throw new CloudRuntimeException(details);
}
logger.info("Migration command successful for VM {}", vm.getInstanceName());
} catch (final OperationTimedoutException e) {
boolean success = false;
if (HypervisorType.KVM.equals(vm.getHypervisorType())) {
@ -3210,7 +3221,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
try {
if (!checkVmOnHost(vm, dstHostId)) {
logger.error("Unable to complete migration for {}", vm);
logger.error("Migration verification failed for VM {} : VM not found on destination host {} ", vm.getInstanceName(), dstHostId);
try {
_agentMgr.send(srcHostId, new Commands(cleanup(vm, dpdkInterfaceMapping)), null);
} catch (final AgentUnavailableException e) {
@ -3225,7 +3236,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
migrated = true;
} finally {
if (!migrated) {
logger.info("Migration was unsuccessful. Cleaning up: {}", vm);
logger.info("Migration was unsuccessful. Cleaning up: {}", vm);
_networkMgr.rollbackNicForMigration(vmSrc, profile);
volumeMgr.release(vm.getId(), dstHostId);
// deallocate GPU devices for the VM on the destination host
@ -3237,7 +3248,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
try {
_agentMgr.send(dstHostId, new Commands(cleanup(vm, dpdkInterfaceMapping)), null);
} catch (final AgentUnavailableException ae) {
logger.warn("Looks like the destination Host is unavailable for cleanup", ae);
logger.warn("Destination host {} unavailable for cleanup after failed migration of VM {}", dstHostId, vm.getInstanceName(), ae);
}
_networkMgr.setHypervisorHostname(profile, dest, false);
try {
@ -3246,6 +3257,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
logger.warn(e.getMessage());
}
} else {
logger.info("Migration completed successfully for VM %s" + vm);
_networkMgr.commitNicForMigration(vmSrc, profile);
volumeMgr.release(vm.getId(), srcHostId);
// deallocate GPU devices for the VM on the src host after migration is complete
@ -3276,6 +3288,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
migrateCommand.setVlanToPersistenceMap(vlanToPersistenceMap);
}
logger.debug("Setting auto convergence to: {}", StorageManager.KvmAutoConvergence.value());
migrateCommand.setAutoConvergence(StorageManager.KvmAutoConvergence.value());
migrateCommand.setHostGuid(destination.getHost().getGuid());

View File

@ -1283,7 +1283,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
nicProfile.setIPv4Gateway(ipv4Gateway);
nicProfile.setIPv4Netmask(ipv4Netmask);
if (nicProfile.getMacAddress() == null) {
if (nicProfile.getMacAddress() == null || !_networkModel.isMACUnique(nicProfile.getMacAddress(), network.getId())) {
try {
String macAddress = _networkModel.getNextAvailableMacAddressInNetwork(network.getId());
nicProfile.setMacAddress(macAddress);

View File

@ -396,6 +396,7 @@ public class NetworkOrchestratorTest extends TestCase {
when(testOrchestrator._ipAddressDao.acquireInLockTable(Mockito.anyLong())).thenReturn(ipVoSpy);
when(testOrchestrator._ipAddressDao.update(Mockito.anyLong(), Mockito.any(IPAddressVO.class))).thenReturn(true);
when(testOrchestrator._ipAddressDao.releaseFromLockTable(Mockito.anyLong())).thenReturn(true);
when(testOrchestrator._networkModel.isMACUnique(Mockito.anyString(), Mockito.anyLong())).thenReturn(true);
try {
when(testOrchestrator._networkModel.getNextAvailableMacAddressInNetwork(Mockito.anyLong())).thenReturn(macAddress);
} catch (InsufficientAddressCapacityException e) {

View File

@ -213,6 +213,8 @@ public class CapacityDaoImpl extends GenericDaoBase<CapacityVO, Long> implements
private static final String LEFT_JOIN_VM_TEMPLATE = "LEFT JOIN vm_template ON vm_template.id = vi.vm_template_id ";
private static final String STORAGE_POOLS_WITH_CHILDREN = "SELECT DISTINCT parent FROM storage_pool WHERE parent != 0 AND removed IS NULL";
public CapacityDaoImpl() {
_hostIdTypeSearch = createSearchBuilder();
_hostIdTypeSearch.and("hostId", _hostIdTypeSearch.entity().getHostOrPoolId(), SearchCriteria.Op.EQ);
@ -379,6 +381,11 @@ public class CapacityDaoImpl extends GenericDaoBase<CapacityVO, Long> implements
finalQuery.append(" AND capacity_type = ?");
resourceIdList.add(capacityType.longValue());
}
// Exclude storage pools with children from capacity calculations to avoid double counting
finalQuery.append(" AND NOT (capacity.capacity_type = ").append(Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED)
.append(" AND capacity.host_id IN (").append(STORAGE_POOLS_WITH_CHILDREN).append("))");
if (CollectionUtils.isNotEmpty(hostIds)) {
finalQuery.append(String.format(" AND capacity.host_id IN (%s)", StringUtils.join(hostIds, ",")));
if (capacityType == null) {
@ -541,6 +548,10 @@ public class CapacityDaoImpl extends GenericDaoBase<CapacityVO, Long> implements
StringBuilder sql = new StringBuilder(LIST_CAPACITY_GROUP_BY_CAPACITY_PART1);
List<Long> resourceIdList = new ArrayList<Long>();
// Exclude storage pools with children from capacity calculations to avoid double counting
sql.append(" AND NOT (capacity.capacity_type = ").append(Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED)
.append(" AND capacity.host_id IN (").append(STORAGE_POOLS_WITH_CHILDREN).append("))");
if (zoneId != null) {
sql.append(" AND capacity.data_center_id = ?");
resourceIdList.add(zoneId);

View File

@ -31,7 +31,8 @@ public class DataCenterDetailsDaoImpl extends ResourceDetailsDaoBase<DataCenterD
private final SearchBuilder<DataCenterDetailVO> DetailSearch;
DataCenterDetailsDaoImpl() {
public DataCenterDetailsDaoImpl() {
super();
DetailSearch = createSearchBuilder();
DetailSearch.and("zoneId", DetailSearch.entity().getResourceId(), SearchCriteria.Op.EQ);
DetailSearch.and("name", DetailSearch.entity().getName(), SearchCriteria.Op.EQ);

View File

@ -462,8 +462,8 @@ public class NetworkDaoImpl extends GenericDaoBase<NetworkVO, Long>implements Ne
public String getNextAvailableMacAddress(final long networkConfigId, Integer zoneMacIdentifier) {
final SequenceFetcher fetch = SequenceFetcher.getInstance();
long seq = fetch.getNextSequence(Long.class, _tgMacAddress, networkConfigId);
if(zoneMacIdentifier != null && zoneMacIdentifier.intValue() != 0 ){
seq = seq | _prefix << 40 | (long)zoneMacIdentifier << 32 | networkConfigId << 16 & 0x00000000ffff0000l;
if (zoneMacIdentifier != null && zoneMacIdentifier != 0) {
seq = seq | _prefix << 40 | (long)zoneMacIdentifier << 32 | networkConfigId << 16 & 0x00000000ffff0000L;
}
return NetUtils.long2Mac(seq);
}

View File

@ -47,6 +47,8 @@ public interface SnapshotDao extends GenericDao<SnapshotVO, Long>, StateDao<Snap
List<SnapshotVO> listAllByStatus(Snapshot.State... status);
List<SnapshotVO> listAllByStatusIncludingRemoved(Snapshot.State... status);
void updateVolumeIds(long oldVolId, long newVolId);
List<SnapshotVO> listByStatusNotIn(long volumeId, Snapshot.State... status);

View File

@ -265,6 +265,13 @@ public class SnapshotDaoImpl extends GenericDaoBase<SnapshotVO, Long> implements
return listBy(sc, null);
}
@Override
public List<SnapshotVO> listAllByStatusIncludingRemoved(Snapshot.State... status) {
SearchCriteria<SnapshotVO> sc = StatusSearch.create();
sc.setParameters("status", (Object[])status);
return listIncludingRemovedBy(sc, null);
}
@Override
public List<SnapshotVO> listByIds(Object... ids) {
SearchCriteria<SnapshotVO> sc = snapshotIdsSearch.create();

View File

@ -94,7 +94,7 @@ public interface VMTemplateDao extends GenericDao<VMTemplateVO, Long>, StateDao<
List<VMTemplateVO> listByParentTemplatetId(long parentTemplatetId);
VMTemplateVO findLatestTemplateByName(String name, CPU.CPUArch arch);
VMTemplateVO findLatestTemplateByName(String name, HypervisorType hypervisorType, CPU.CPUArch arch);
List<VMTemplateVO> findTemplatesLinkedToUserdata(long userdataId);
@ -103,4 +103,7 @@ public interface VMTemplateDao extends GenericDao<VMTemplateVO, Long>, StateDao<
List<Long> listIdsByTemplateTag(String tag);
List<Long> listIdsByExtensionId(long extensionId);
VMTemplateVO findActiveSystemTemplateByHypervisorArchAndUrlPath(HypervisorType hypervisorType,
CPU.CPUArch arch, String urlPathSuffix);
}

View File

@ -245,13 +245,17 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
@Override
public VMTemplateVO findLatestTemplateByName(String name, CPU.CPUArch arch) {
public VMTemplateVO findLatestTemplateByName(String name, HypervisorType hypervisorType, CPU.CPUArch arch) {
SearchBuilder<VMTemplateVO> sb = createSearchBuilder();
sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
sb.and("hypervisorType", sb.entity().getHypervisorType(), SearchCriteria.Op.EQ);
sb.and("arch", sb.entity().getArch(), SearchCriteria.Op.EQ);
sb.done();
SearchCriteria<VMTemplateVO> sc = sb.create();
sc.setParameters("name", name);
if (hypervisorType != null) {
sc.setParameters("hypervisorType", hypervisorType);
}
if (arch != null) {
sc.setParameters("arch", arch);
}
@ -850,6 +854,37 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
return customSearch(sc, null);
}
@Override
public VMTemplateVO findActiveSystemTemplateByHypervisorArchAndUrlPath(HypervisorType hypervisorType,
CPU.CPUArch arch, String urlPathSuffix) {
if (StringUtils.isBlank(urlPathSuffix)) {
return null;
}
SearchBuilder<VMTemplateVO> sb = createSearchBuilder();
sb.and("templateType", sb.entity().getTemplateType(), SearchCriteria.Op.EQ);
sb.and("hypervisorType", sb.entity().getHypervisorType(), SearchCriteria.Op.EQ);
sb.and("arch", sb.entity().getArch(), SearchCriteria.Op.EQ);
sb.and("urlPathSuffix", sb.entity().getUrl(), SearchCriteria.Op.LIKE);
sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
sb.done();
SearchCriteria<VMTemplateVO> sc = sb.create();
sc.setParameters("templateType", TemplateType.SYSTEM);
if (hypervisorType != null) {
sc.setParameters("hypervisorType", hypervisorType);
}
if (arch != null) {
sc.setParameters("arch", arch);
}
sc.setParameters("urlPathSuffix", "%" + urlPathSuffix);
sc.setParameters("state", VirtualMachineTemplate.State.Active);
Filter filter = new Filter(VMTemplateVO.class, "id", false, null, 1L);
List<VMTemplateVO> templates = listBy(sc, filter);
if (CollectionUtils.isNotEmpty(templates)) {
return templates.get(0);
}
return null;
}
@Override
public boolean updateState(
com.cloud.template.VirtualMachineTemplate.State currentState,

View File

@ -163,5 +163,7 @@ public interface VolumeDao extends GenericDao<VolumeVO, Long>, StateDao<Volume.S
VolumeVO findOneByIScsiName(String iScsiName);
int getVolumeCountByOfferingId(long diskOfferingId);
VolumeVO findByLastIdAndState(long lastVolumeId, Volume.State...states);
}

View File

@ -78,6 +78,7 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
protected GenericSearchBuilder<VolumeVO, SumCount> primaryStorageSearch2;
protected GenericSearchBuilder<VolumeVO, SumCount> secondaryStorageSearch;
private final SearchBuilder<VolumeVO> poolAndPathSearch;
final GenericSearchBuilder<VolumeVO, Integer> CountByOfferingId;
@Inject
ReservationDao reservationDao;
@ -506,6 +507,11 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
poolAndPathSearch.and("poolId", poolAndPathSearch.entity().getPoolId(), Op.EQ);
poolAndPathSearch.and("path", poolAndPathSearch.entity().getPath(), Op.EQ);
poolAndPathSearch.done();
CountByOfferingId = createSearchBuilder(Integer.class);
CountByOfferingId.select(null, Func.COUNT, CountByOfferingId.entity().getId());
CountByOfferingId.and("diskOfferingId", CountByOfferingId.entity().getDiskOfferingId(), Op.EQ);
CountByOfferingId.done();
}
@Override
@ -914,6 +920,14 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
return findOneIncludingRemovedBy(sc);
}
@Override
public int getVolumeCountByOfferingId(long diskOfferingId) {
SearchCriteria<Integer> sc = CountByOfferingId.create();
sc.setParameters("diskOfferingId", diskOfferingId);
List<Integer> results = customSearch(sc, null);
return results.get(0);
}
@Override
public VolumeVO findByLastIdAndState(long lastVolumeId, State ...states) {
QueryBuilder<VolumeVO> sc = QueryBuilder.create(VolumeVO.class);

View File

@ -99,7 +99,7 @@ public class DatabaseCreator {
String username = dbProperties.getProperty(String.format("db.%s.username", database));
String password = dbProperties.getProperty(String.format("db.%s.password", database));
String dbName = dbProperties.getProperty(String.format("db.%s.name", database));
System.out.println(String.format("========> Initializing database=%s with host=%s port=%s username=%s password=%s", dbName, host, port, username, password));
System.out.println(String.format("========> Initializing database=%s with host=%s port=%s username=%s password=******", dbName, host, port, username));
List<String> queries = new ArrayList<String>();
queries.add(String.format("drop database if exists `%s`", dbName));

View File

@ -52,7 +52,7 @@ public class UsageDaoImpl extends GenericDaoBase<UsageVO, Long> implements Usage
private static final String DELETE_ALL = "DELETE FROM cloud_usage";
private static final String DELETE_ALL_BY_ACCOUNTID = "DELETE FROM cloud_usage WHERE account_id = ?";
private static final String DELETE_ALL_BY_INTERVAL = "DELETE FROM cloud_usage WHERE end_date < DATE_SUB(CURRENT_DATE(), INTERVAL ? DAY)";
private static final String INSERT_ACCOUNT = "INSERT INTO cloud_usage.account (id, account_name, type, role_id, domain_id, removed, cleanup_needed) VALUES (?,?,?,?,?,?,?)";
private static final String INSERT_ACCOUNT = "INSERT INTO cloud_usage.account (id, account_name, uuid, type, role_id, domain_id, removed, cleanup_needed) VALUES (?,?,?,?,?,?,?,?)";
private static final String INSERT_USER_STATS = "INSERT INTO cloud_usage.user_statistics (id, data_center_id, account_id, public_ip_address, device_id, device_type, network_id, net_bytes_received,"
+ " net_bytes_sent, current_bytes_received, current_bytes_sent, agg_bytes_received, agg_bytes_sent) VALUES (?,?,?,?,?,?,?,?,?,?, ?, ?, ?)";
@ -129,25 +129,26 @@ public class UsageDaoImpl extends GenericDaoBase<UsageVO, Long> implements Usage
for (AccountVO acct : accounts) {
pstmt.setLong(1, acct.getId());
pstmt.setString(2, acct.getAccountName());
pstmt.setInt(3, acct.getType().ordinal());
pstmt.setString(3, acct.getUuid());
pstmt.setInt(4, acct.getType().ordinal());
//prevent autoboxing NPE by defaulting to User role
if(acct.getRoleId() == null){
pstmt.setLong(4, RoleType.User.getId());
pstmt.setLong(5, RoleType.User.getId());
}else{
pstmt.setLong(4, acct.getRoleId());
pstmt.setLong(5, acct.getRoleId());
}
pstmt.setLong(5, acct.getDomainId());
pstmt.setLong(6, acct.getDomainId());
Date removed = acct.getRemoved();
if (removed == null) {
pstmt.setString(6, null);
pstmt.setString(7, null);
} else {
pstmt.setString(6, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), acct.getRemoved()));
pstmt.setString(7, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), acct.getRemoved()));
}
pstmt.setBoolean(7, acct.getNeedsCleanup());
pstmt.setBoolean(8, acct.getNeedsCleanup());
pstmt.addBatch();
}

View File

@ -95,7 +95,7 @@ public interface NicDao extends GenericDao<NicVO, Long> {
List<NicVO> listByVmIdAndKeyword(long instanceId, String keyword);
NicVO findByMacAddress(String macAddress);
NicVO findByMacAddress(String macAddress, long networkId);
NicVO findByNetworkIdAndMacAddressIncludingRemoved(long networkId, String mac);

View File

@ -420,9 +420,10 @@ public class NicDaoImpl extends GenericDaoBase<NicVO, Long> implements NicDao {
}
@Override
public NicVO findByMacAddress(String macAddress) {
public NicVO findByMacAddress(String macAddress, long networkId) {
SearchCriteria<NicVO> sc = AllFieldsSearch.create();
sc.setParameters("macAddress", macAddress);
sc.setParameters("network", networkId);
return findOneBy(sc);
}

View File

@ -187,5 +187,9 @@ public interface VMInstanceDao extends GenericDao<VMInstanceVO, Long>, StateDao<
Map<String, Long> getNameIdMapForVmIds(Collection<Long> ids);
int getVmCountByOfferingId(Long serviceOfferingId);
int getVmCountByOfferingNotInDomain(Long serviceOfferingId, List<Long> domainIds);
List<VMInstanceVO> listByIdsIncludingRemoved(List<Long> ids);
}

View File

@ -104,6 +104,8 @@ public class VMInstanceDaoImpl extends GenericDaoBase<VMInstanceVO, Long> implem
protected SearchBuilder<VMInstanceVO> LastHostAndStatesSearch;
protected SearchBuilder<VMInstanceVO> VmsNotInClusterUsingPool;
protected SearchBuilder<VMInstanceVO> IdsPowerStateSelectSearch;
GenericSearchBuilder<VMInstanceVO, Integer> CountByOfferingId;
GenericSearchBuilder<VMInstanceVO, Integer> CountUserVmNotInDomain;
@Inject
ResourceTagDao tagsDao;
@ -354,6 +356,18 @@ public class VMInstanceDaoImpl extends GenericDaoBase<VMInstanceVO, Long> implem
IdsPowerStateSelectSearch.entity().getPowerStateUpdateCount(),
IdsPowerStateSelectSearch.entity().getPowerStateUpdateTime());
IdsPowerStateSelectSearch.done();
CountByOfferingId = createSearchBuilder(Integer.class);
CountByOfferingId.select(null, Func.COUNT, CountByOfferingId.entity().getId());
CountByOfferingId.and("serviceOfferingId", CountByOfferingId.entity().getServiceOfferingId(), Op.EQ);
CountByOfferingId.done();
CountUserVmNotInDomain = createSearchBuilder(Integer.class);
CountUserVmNotInDomain.select(null, Func.COUNT, CountUserVmNotInDomain.entity().getId());
CountUserVmNotInDomain.and("serviceOfferingId", CountUserVmNotInDomain.entity().getServiceOfferingId(), Op.EQ);
CountUserVmNotInDomain.and("domainIdsNotIn", CountUserVmNotInDomain.entity().getDomainId(), Op.NIN);
CountUserVmNotInDomain.done();
}
@Override
@ -1247,6 +1261,29 @@ public class VMInstanceDaoImpl extends GenericDaoBase<VMInstanceVO, Long> implem
.collect(Collectors.toMap(VMInstanceVO::getInstanceName, VMInstanceVO::getId));
}
@Override
public int getVmCountByOfferingId(Long serviceOfferingId) {
if (serviceOfferingId == null) {
return 0;
}
SearchCriteria<Integer> sc = CountByOfferingId.create();
sc.setParameters("serviceOfferingId", serviceOfferingId);
List<Integer> count = customSearch(sc, null);
return count.get(0);
}
@Override
public int getVmCountByOfferingNotInDomain(Long serviceOfferingId, List<Long> domainIds) {
if (serviceOfferingId == null || CollectionUtils.isEmpty(domainIds)) {
return 0;
}
SearchCriteria<Integer> sc = CountUserVmNotInDomain.create();
sc.setParameters("serviceOfferingId", serviceOfferingId);
sc.setParameters("domainIdsNotIn", domainIds.toArray());
List<Integer> count = customSearch(sc, null);
return count.get(0);
}
@Override
public List<VMInstanceVO> listByIdsIncludingRemoved(List<Long> ids) {
SearchBuilder<VMInstanceVO> idsSearch = createSearchBuilder();

View File

@ -0,0 +1,86 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.backup;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.cloudstack.api.ResourceDetail;
@Entity
@Table(name = "backup_offering_details")
public class BackupOfferingDetailsVO implements ResourceDetail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private long id;
@Column(name = "backup_offering_id")
private long resourceId;
@Column(name = "name")
private String name;
@Column(name = "value")
private String value;
@Column(name = "display")
private boolean display = true;
protected BackupOfferingDetailsVO() {
}
public BackupOfferingDetailsVO(long backupOfferingId, String name, String value, boolean display) {
this.resourceId = backupOfferingId;
this.name = name;
this.value = value;
this.display = display;
}
@Override
public long getResourceId() {
return resourceId;
}
public void setResourceId(long backupOfferingId) {
this.resourceId = backupOfferingId;
}
@Override
public String getName() {
return name;
}
@Override
public String getValue() {
return value;
}
@Override
public long getId() {
return id;
}
@Override
public boolean isDisplay() {
return display;
}
}

View File

@ -17,6 +17,8 @@
package org.apache.cloudstack.backup;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import java.util.Date;
import java.util.UUID;
@ -131,4 +133,9 @@ public class BackupOfferingVO implements BackupOffering {
public Date getCreated() {
return created;
}
@Override
public String toString() {
return String.format("Backup offering %s.", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "name", "uuid"));
}
}

View File

@ -20,6 +20,8 @@ package org.apache.cloudstack.backup.dao;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import com.cloud.domain.DomainVO;
import com.cloud.domain.dao.DomainDao;
import org.apache.cloudstack.api.response.BackupOfferingResponse;
import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.backup.BackupOfferingVO;
@ -30,10 +32,16 @@ import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import java.util.List;
public class BackupOfferingDaoImpl extends GenericDaoBase<BackupOfferingVO, Long> implements BackupOfferingDao {
@Inject
DataCenterDao dataCenterDao;
@Inject
BackupOfferingDetailsDao backupOfferingDetailsDao;
@Inject
DomainDao domainDao;
private SearchBuilder<BackupOfferingVO> backupPoliciesSearch;
@ -51,8 +59,9 @@ public class BackupOfferingDaoImpl extends GenericDaoBase<BackupOfferingVO, Long
@Override
public BackupOfferingResponse newBackupOfferingResponse(BackupOffering offering, Boolean crossZoneInstanceCreation) {
DataCenterVO zone = dataCenterDao.findById(offering.getZoneId());
DataCenterVO zone = dataCenterDao.findById(offering.getZoneId());
List<Long> domainIds = backupOfferingDetailsDao.findDomainIds(offering.getId());
BackupOfferingResponse response = new BackupOfferingResponse();
response.setId(offering.getUuid());
response.setName(offering.getName());
@ -64,6 +73,18 @@ public class BackupOfferingDaoImpl extends GenericDaoBase<BackupOfferingVO, Long
response.setZoneId(zone.getUuid());
response.setZoneName(zone.getName());
}
if (domainIds != null && !domainIds.isEmpty()) {
String domainUUIDs = domainIds.stream().map(Long::valueOf).map(domainId -> {
DomainVO domain = domainDao.findById(domainId);
return domain != null ? domain.getUuid() : "";
}).filter(name -> !name.isEmpty()).reduce((a, b) -> a + "," + b).orElse("");
String domainNames = domainIds.stream().map(Long::valueOf).map(domainId -> {
DomainVO domain = domainDao.findById(domainId);
return domain != null ? domain.getName() : "";
}).filter(name -> !name.isEmpty()).reduce((a, b) -> a + "," + b).orElse("");
response.setDomain(domainNames);
response.setDomainId(domainUUIDs);
}
if (crossZoneInstanceCreation) {
response.setCrossZoneInstanceCreation(true);
}

View File

@ -0,0 +1,32 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.backup.dao;
import java.util.List;
import org.apache.cloudstack.backup.BackupOfferingDetailsVO;
import org.apache.cloudstack.resourcedetail.ResourceDetailsDao;
import com.cloud.utils.db.GenericDao;
public interface BackupOfferingDetailsDao extends GenericDao<BackupOfferingDetailsVO, Long>, ResourceDetailsDao<BackupOfferingDetailsVO> {
List<Long> findDomainIds(final long resourceId);
List<Long> findZoneIds(final long resourceId);
String getDetail(Long backupOfferingId, String key);
List<Long> findOfferingIdsByDomainIds(List<Long> domainIds);
void updateBackupOfferingDomainIdsDetail(long backupOfferingId, List<Long> filteredDomainIds);
}

View File

@ -0,0 +1,101 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.backup.dao;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.backup.BackupOfferingDetailsVO;
import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase;
import org.springframework.stereotype.Component;
@Component
public class BackupOfferingDetailsDaoImpl extends ResourceDetailsDaoBase<BackupOfferingDetailsVO> implements BackupOfferingDetailsDao {
@Override
public void addDetail(long resourceId, String key, String value, boolean display) {
super.addDetail(new BackupOfferingDetailsVO(resourceId, key, value, display));
}
@Override
public List<Long> findDomainIds(long resourceId) {
final List<Long> domainIds = new ArrayList<>();
for (final BackupOfferingDetailsVO detail: findDetails(resourceId, ApiConstants.DOMAIN_ID)) {
final Long domainId = Long.valueOf(detail.getValue());
if (domainId > 0) {
domainIds.add(domainId);
}
}
return domainIds;
}
@Override
public List<Long> findZoneIds(long resourceId) {
final List<Long> zoneIds = new ArrayList<>();
for (final BackupOfferingDetailsVO detail: findDetails(resourceId, ApiConstants.ZONE_ID)) {
final Long zoneId = Long.valueOf(detail.getValue());
if (zoneId > 0) {
zoneIds.add(zoneId);
}
}
return zoneIds;
}
@Override
public String getDetail(Long backupOfferingId, String key) {
String detailValue = null;
BackupOfferingDetailsVO backupOfferingDetail = findDetail(backupOfferingId, key);
if (backupOfferingDetail != null) {
detailValue = backupOfferingDetail.getValue();
}
return detailValue;
}
@Override
public List<Long> findOfferingIdsByDomainIds(List<Long> domainIds) {
Object[] dIds = domainIds.stream().map(s -> String.valueOf(s)).collect(Collectors.toList()).toArray();
return findResourceIdsByNameAndValueIn("domainid", dIds);
}
@DB
@Override
public void updateBackupOfferingDomainIdsDetail(long backupOfferingId, List<Long> filteredDomainIds) {
SearchBuilder<BackupOfferingDetailsVO> sb = createSearchBuilder();
List<BackupOfferingDetailsVO> detailsVO = new ArrayList<>();
sb.and("offeringId", sb.entity().getResourceId(), SearchCriteria.Op.EQ);
sb.and("detailName", sb.entity().getName(), SearchCriteria.Op.EQ);
sb.done();
SearchCriteria<BackupOfferingDetailsVO> sc = sb.create();
sc.setParameters("offeringId", String.valueOf(backupOfferingId));
sc.setParameters("detailName", ApiConstants.DOMAIN_ID);
remove(sc);
for (Long domainId : filteredDomainIds) {
detailsVO.add(new BackupOfferingDetailsVO(backupOfferingId, ApiConstants.DOMAIN_ID, String.valueOf(domainId), false));
}
if (!detailsVO.isEmpty()) {
for (BackupOfferingDetailsVO detailVO : detailsVO) {
persist(detailVO);
}
}
}
}

View File

@ -76,6 +76,8 @@ StateDao<ObjectInDataStoreStateMachine.State, ObjectInDataStoreStateMachine.Even
List<SnapshotDataStoreVO> findBySnapshotId(long snapshotId);
List<SnapshotDataStoreVO> findBySnapshotIdWithNonDestroyedState(long snapshotId);
void duplicateCacheRecordsOnRegionStore(long storeId);
// delete the snapshot entry on primary data store to make sure that next snapshot will be full snapshot

View File

@ -464,6 +464,13 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
@Override
public List<SnapshotDataStoreVO> findBySnapshotId(long snapshotId) {
SearchCriteria<SnapshotDataStoreVO> sc = searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq.create();
sc.setParameters(SNAPSHOT_ID, snapshotId);
return listBy(sc);
}
@Override
public List<SnapshotDataStoreVO> findBySnapshotIdWithNonDestroyedState(long snapshotId) {
SearchCriteria<SnapshotDataStoreVO> sc = idStateNinSearch.create();
sc.setParameters(SNAPSHOT_ID, snapshotId);
sc.setParameters(STATE, State.Destroyed.name());

View File

@ -71,6 +71,7 @@
<bean id="NetworkDaoImpl" class="org.apache.cloudstack.quota.dao.NetworkDaoImpl" />
<bean id="VpcDaoImpl" class="org.apache.cloudstack.quota.dao.VpcDaoImpl" />
<bean id="volumeDaoImpl" class="com.cloud.storage.dao.VolumeDaoImpl" />
<bean id="reservationDao" class="org.apache.cloudstack.reservation.dao.ReservationDaoImpl" />
<bean id="reservationDao" class="org.apache.cloudstack.reservation.dao.ReservationDaoImpl" />
<bean id="backupOfferingDaoImpl" class="org.apache.cloudstack.backup.dao.BackupOfferingDaoImpl" />
<bean id="backupOfferingDetailsDaoImpl" class="org.apache.cloudstack.backup.dao.BackupOfferingDetailsDaoImpl" />
</beans>

View File

@ -0,0 +1,28 @@
-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
-- regarding copyright ownership. The ASF licenses this file
-- to you under the Apache License, Version 2.0 (the
-- "License"); you may not use this file except in compliance
-- with the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing,
-- software distributed under the License is distributed on an
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.
-- in cloud
DROP PROCEDURE IF EXISTS `cloud`.`IDEMPOTENT_DROP_COLUMN`;
-- Error 1091: Can't DROP column; check that column/key exists
CREATE PROCEDURE `cloud`.`IDEMPOTENT_DROP_COLUMN` (
IN in_table_name VARCHAR(200),
IN in_column_name VARCHAR(200)
)
BEGIN
DECLARE CONTINUE HANDLER FOR 1091 BEGIN END; SET @ddl = CONCAT('ALTER TABLE ', in_table_name, ' DROP COLUMN ', in_column_name); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; END;

View File

@ -301,7 +301,7 @@ CALL `cloud`.`IDEMPOTENT_DROP_FOREIGN_KEY`('cloud.service_offering','fk_service_
CALL `cloud`.`IDEMPOTENT_ADD_FOREIGN_KEY`('cloud.service_offering', 'fk_service_offering__vgpu_profile_id', '(vgpu_profile_id)', '`vgpu_profile`(`id`)');
-- Netris Plugin
CREATE TABLE `cloud`.`netris_providers` (
CREATE TABLE IF NOT EXISTS `cloud`.`netris_providers` (
`id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
`uuid` varchar(40),
`zone_id` bigint unsigned NOT NULL COMMENT 'Zone ID',
@ -321,11 +321,11 @@ CREATE TABLE `cloud`.`netris_providers` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Drop the Tungsten and NSX columns from the network offerings (replaced by checking the provider on the ntwk_offering_service_map table)
ALTER TABLE `cloud`.`network_offerings` DROP COLUMN `for_tungsten`;
ALTER TABLE `cloud`.`network_offerings` DROP COLUMN `for_nsx`;
CALL `cloud`.`IDEMPOTENT_DROP_COLUMN`('cloud.network_offerings', 'for_tungsten');
CALL `cloud`.`IDEMPOTENT_DROP_COLUMN`('cloud.network_offerings', 'for_nsx');
-- Drop the Tungsten and NSX columns from the VPC offerings (replaced by checking the provider on the vpc_offering_service_map table)
ALTER TABLE `cloud`.`vpc_offerings` DROP COLUMN `for_nsx`;
CALL `cloud`.`IDEMPOTENT_DROP_COLUMN`('cloud.vpc_offerings', 'for_nsx');
-- Add next_hop to the static_routes table
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.static_routes', 'next_hop', 'varchar(50) COMMENT "next hop of the static route" AFTER `vpc_gateway_id`');

View File

@ -19,7 +19,33 @@
-- Schema upgrade from 4.22.1.0 to 4.23.0.0
--;
CREATE TABLE `cloud`.`backup_offering_details` (
`id` bigint unsigned NOT NULL auto_increment,
`backup_offering_id` bigint unsigned NOT NULL COMMENT 'Backup offering id',
`name` varchar(255) NOT NULL,
`value` varchar(1024) NOT NULL,
`display` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Should detail be displayed to the end user',
PRIMARY KEY (`id`),
CONSTRAINT `fk_offering_details__backup_offering_id` FOREIGN KEY `fk_offering_details__backup_offering_id`(`backup_offering_id`) REFERENCES `backup_offering`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Update value to random for the config 'vm.allocation.algorithm' or 'volume.allocation.algorithm' if configured as userconcentratedpod_random
-- Update value to firstfit for the config 'vm.allocation.algorithm' or 'volume.allocation.algorithm' if configured as userconcentratedpod_firstfit
UPDATE `cloud`.`configuration` SET value='random' WHERE name IN ('vm.allocation.algorithm', 'volume.allocation.algorithm') AND value='userconcentratedpod_random';
UPDATE `cloud`.`configuration` SET value='firstfit' WHERE name IN ('vm.allocation.algorithm', 'volume.allocation.algorithm') AND value='userconcentratedpod_firstfit';
-- Create webhook_filter table
DROP TABLE IF EXISTS `cloud`.`webhook_filter`;
CREATE TABLE IF NOT EXISTS `cloud`.`webhook_filter` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id of the webhook filter',
`uuid` varchar(255) COMMENT 'uuid of the webhook filter',
`webhook_id` bigint unsigned NOT NULL COMMENT 'id of the webhook',
`type` varchar(20) COMMENT 'type of the filter',
`mode` varchar(20) COMMENT 'mode of the filter',
`match_type` varchar(20) COMMENT 'match type of the filter',
`value` varchar(256) NOT NULL COMMENT 'value of the filter used for matching',
`created` datetime NOT NULL COMMENT 'date created',
PRIMARY KEY (`id`),
INDEX `i_webhook_filter__webhook_id`(`webhook_id`),
CONSTRAINT `fk_webhook_filter__webhook_id` FOREIGN KEY(`webhook_id`) REFERENCES `webhook`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -76,7 +76,8 @@ public class VMTemplateDaoImplTest {
VMTemplateVO expectedTemplate = new VMTemplateVO();
List<VMTemplateVO> returnedList = Collections.singletonList(expectedTemplate);
doReturn(returnedList).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class));
VMTemplateVO result = templateDao.findLatestTemplateByName("test", CPU.CPUArch.getDefault());
VMTemplateVO result = templateDao.findLatestTemplateByName("test", Hypervisor.HypervisorType.KVM,
CPU.CPUArch.getDefault());
assertNotNull("Expected a non-null template", result);
assertEquals("Expected the returned template to be the first element", expectedTemplate, result);
}
@ -85,7 +86,8 @@ public class VMTemplateDaoImplTest {
public void testFindLatestTemplateByName_ReturnsNullWhenNoTemplateFound() {
List<VMTemplateVO> emptyList = Collections.emptyList();
doReturn(emptyList).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class));
VMTemplateVO result = templateDao.findLatestTemplateByName("test", CPU.CPUArch.getDefault());
VMTemplateVO result = templateDao.findLatestTemplateByName("test", Hypervisor.HypervisorType.VMware,
CPU.CPUArch.getDefault());
assertNull("Expected null when no templates are found", result);
}
@ -94,7 +96,8 @@ public class VMTemplateDaoImplTest {
VMTemplateVO expectedTemplate = new VMTemplateVO();
List<VMTemplateVO> returnedList = Collections.singletonList(expectedTemplate);
doReturn(returnedList).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class));
VMTemplateVO result = templateDao.findLatestTemplateByName("test", null);
VMTemplateVO result = templateDao.findLatestTemplateByName("test", Hypervisor.HypervisorType.XenServer,
null);
assertNotNull("Expected a non-null template even if arch is null", result);
assertEquals("Expected the returned template to be the first element", expectedTemplate, result);
}
@ -337,4 +340,82 @@ public class VMTemplateDaoImplTest {
VMTemplateVO readyTemplate = templateDao.findSystemVMReadyTemplate(zoneId, Hypervisor.HypervisorType.KVM, CPU.CPUArch.arm64.getType());
Assert.assertEquals(CPU.CPUArch.arm64, readyTemplate.getArch());
}
@Test
public void findActiveSystemTemplateByHypervisorArchAndUrlPath_ReturnsTemplate() {
VMTemplateVO expectedTemplate = mock(VMTemplateVO.class);
SearchBuilder<VMTemplateVO> sb = mock(SearchBuilder.class);
when(sb.entity()).thenReturn(expectedTemplate);
SearchCriteria<VMTemplateVO>sc = mock(SearchCriteria.class);
when(sb.create()).thenReturn(sc);
when(templateDao.createSearchBuilder()).thenReturn(sb);
List<VMTemplateVO> templates = Collections.singletonList(expectedTemplate);
doReturn(templates).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class));
VMTemplateVO result = templateDao.findActiveSystemTemplateByHypervisorArchAndUrlPath(
Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64, "testPath");
assertNotNull(result);
assertEquals(expectedTemplate, result);
}
@Test
public void findActiveSystemTemplateByHypervisorArchAndUrlPath_ReturnsNullWhenNoTemplatesFound() {
VMTemplateVO template = mock(VMTemplateVO.class);
SearchBuilder<VMTemplateVO> sb = mock(SearchBuilder.class);
when(sb.entity()).thenReturn(template);
SearchCriteria<VMTemplateVO>sc = mock(SearchCriteria.class);
when(sb.create()).thenReturn(sc);
when(templateDao.createSearchBuilder()).thenReturn(sb);
doReturn(Collections.emptyList()).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class));
VMTemplateVO result = templateDao.findActiveSystemTemplateByHypervisorArchAndUrlPath(
Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64, "testPath");
assertNull(result);
}
@Test
public void findActiveSystemTemplateByHypervisorArchAndUrlPath_NullHypervisor() {
VMTemplateVO expectedTemplate = mock(VMTemplateVO.class);
SearchBuilder<VMTemplateVO> sb = mock(SearchBuilder.class);
when(sb.entity()).thenReturn(expectedTemplate);
SearchCriteria<VMTemplateVO>sc = mock(SearchCriteria.class);
when(sb.create()).thenReturn(sc);
when(templateDao.createSearchBuilder()).thenReturn(sb);
List<VMTemplateVO> templates = Collections.singletonList(expectedTemplate);
doReturn(templates).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class));
VMTemplateVO result = templateDao.findActiveSystemTemplateByHypervisorArchAndUrlPath(
null, CPU.CPUArch.amd64, "testPath");
assertNotNull(result);
assertEquals(expectedTemplate, result);
}
@Test
public void findActiveSystemTemplateByHypervisorArchAndUrlPath_NullArch() {
VMTemplateVO expectedTemplate = mock(VMTemplateVO.class);
SearchBuilder<VMTemplateVO> sb = mock(SearchBuilder.class);
when(sb.entity()).thenReturn(expectedTemplate);
SearchCriteria<VMTemplateVO>sc = mock(SearchCriteria.class);
when(sb.create()).thenReturn(sc);
when(templateDao.createSearchBuilder()).thenReturn(sb);
List<VMTemplateVO> templates = Collections.singletonList(expectedTemplate);
doReturn(templates).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class));
VMTemplateVO result = templateDao.findActiveSystemTemplateByHypervisorArchAndUrlPath(
Hypervisor.HypervisorType.KVM, null, "testPath");
assertNotNull(result);
assertEquals(expectedTemplate, result);
}
@Test
public void findActiveSystemTemplateByHypervisorArchAndUrlPath_EmptyUrlPathSuffix() {
VMTemplateVO result = templateDao.findActiveSystemTemplateByHypervisorArchAndUrlPath(
Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64, "");
assertNull(result);
}
}

View File

@ -0,0 +1,251 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.backup.dao;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.backup.BackupOfferingDetailsVO;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.utils.db.SearchCriteria;
@RunWith(MockitoJUnitRunner.class)
public class BackupOfferingDetailsDaoImplTest {
@Spy
@InjectMocks
private BackupOfferingDetailsDaoImpl backupOfferingDetailsDao;
private static final long RESOURCE_ID = 1L;
private static final long OFFERING_ID = 100L;
private static final String TEST_KEY = "testKey";
private static final String TEST_VALUE = "testValue";
@Test
public void testAddDetail() {
BackupOfferingDetailsVO detailVO = new BackupOfferingDetailsVO(RESOURCE_ID, TEST_KEY, TEST_VALUE, true);
Assert.assertEquals("Resource ID should match", RESOURCE_ID, detailVO.getResourceId());
Assert.assertEquals("Detail name/key should match", TEST_KEY, detailVO.getName());
Assert.assertEquals("Detail value should match", TEST_VALUE, detailVO.getValue());
Assert.assertTrue("Display flag should be true", detailVO.isDisplay());
BackupOfferingDetailsVO detailVOHidden = new BackupOfferingDetailsVO(RESOURCE_ID, "hiddenKey", "hiddenValue", false);
Assert.assertFalse("Display flag should be false", detailVOHidden.isDisplay());
}
@Test
public void testFindDomainIdsWithMultipleDomains() {
List<BackupOfferingDetailsVO> mockDetails = Arrays.asList(
createDetailVO(RESOURCE_ID, ApiConstants.DOMAIN_ID, "1", false),
createDetailVO(RESOURCE_ID, ApiConstants.DOMAIN_ID, "2", false),
createDetailVO(RESOURCE_ID, ApiConstants.DOMAIN_ID, "3", false)
);
Mockito.doReturn(mockDetails).when(backupOfferingDetailsDao)
.findDetails(RESOURCE_ID, ApiConstants.DOMAIN_ID);
List<Long> domainIds = backupOfferingDetailsDao.findDomainIds(RESOURCE_ID);
Assert.assertNotNull(domainIds);
Assert.assertEquals(3, domainIds.size());
Assert.assertEquals(Arrays.asList(1L, 2L, 3L), domainIds);
}
@Test
public void testFindDomainIdsWithEmptyList() {
Mockito.doReturn(Collections.emptyList()).when(backupOfferingDetailsDao)
.findDetails(RESOURCE_ID, ApiConstants.DOMAIN_ID);
List<Long> domainIds = backupOfferingDetailsDao.findDomainIds(RESOURCE_ID);
Assert.assertNotNull(domainIds);
Assert.assertTrue(domainIds.isEmpty());
}
@Test
public void testFindDomainIdsExcludesZeroOrNegativeValues() {
List<BackupOfferingDetailsVO> mockDetails = Arrays.asList(
createDetailVO(RESOURCE_ID, ApiConstants.DOMAIN_ID, "1", false),
createDetailVO(RESOURCE_ID, ApiConstants.DOMAIN_ID, "0", false),
createDetailVO(RESOURCE_ID, ApiConstants.DOMAIN_ID, "-1", false),
createDetailVO(RESOURCE_ID, ApiConstants.DOMAIN_ID, "2", false)
);
Mockito.doReturn(mockDetails).when(backupOfferingDetailsDao)
.findDetails(RESOURCE_ID, ApiConstants.DOMAIN_ID);
List<Long> domainIds = backupOfferingDetailsDao.findDomainIds(RESOURCE_ID);
Assert.assertNotNull(domainIds);
Assert.assertEquals(2, domainIds.size());
Assert.assertEquals(Arrays.asList(1L, 2L), domainIds);
}
@Test
public void testFindZoneIdsWithMultipleZones() {
List<BackupOfferingDetailsVO> mockDetails = Arrays.asList(
createDetailVO(RESOURCE_ID, ApiConstants.ZONE_ID, "10", false),
createDetailVO(RESOURCE_ID, ApiConstants.ZONE_ID, "20", false),
createDetailVO(RESOURCE_ID, ApiConstants.ZONE_ID, "30", false)
);
Mockito.doReturn(mockDetails).when(backupOfferingDetailsDao)
.findDetails(RESOURCE_ID, ApiConstants.ZONE_ID);
List<Long> zoneIds = backupOfferingDetailsDao.findZoneIds(RESOURCE_ID);
Assert.assertNotNull(zoneIds);
Assert.assertEquals(3, zoneIds.size());
Assert.assertEquals(Arrays.asList(10L, 20L, 30L), zoneIds);
}
@Test
public void testFindZoneIdsWithEmptyList() {
Mockito.doReturn(Collections.emptyList()).when(backupOfferingDetailsDao)
.findDetails(RESOURCE_ID, ApiConstants.ZONE_ID);
List<Long> zoneIds = backupOfferingDetailsDao.findZoneIds(RESOURCE_ID);
Assert.assertNotNull(zoneIds);
Assert.assertTrue(zoneIds.isEmpty());
}
@Test
public void testFindZoneIdsExcludesZeroOrNegativeValues() {
List<BackupOfferingDetailsVO> mockDetails = Arrays.asList(
createDetailVO(RESOURCE_ID, ApiConstants.ZONE_ID, "10", false),
createDetailVO(RESOURCE_ID, ApiConstants.ZONE_ID, "0", false),
createDetailVO(RESOURCE_ID, ApiConstants.ZONE_ID, "-5", false),
createDetailVO(RESOURCE_ID, ApiConstants.ZONE_ID, "20", false)
);
Mockito.doReturn(mockDetails).when(backupOfferingDetailsDao)
.findDetails(RESOURCE_ID, ApiConstants.ZONE_ID);
List<Long> zoneIds = backupOfferingDetailsDao.findZoneIds(RESOURCE_ID);
Assert.assertNotNull(zoneIds);
Assert.assertEquals(2, zoneIds.size());
Assert.assertEquals(Arrays.asList(10L, 20L), zoneIds);
}
@Test
public void testGetDetailWhenDetailExists() {
BackupOfferingDetailsVO mockDetail = createDetailVO(OFFERING_ID, TEST_KEY, TEST_VALUE, true);
Mockito.doReturn(mockDetail).when(backupOfferingDetailsDao)
.findDetail(OFFERING_ID, TEST_KEY);
String detailValue = backupOfferingDetailsDao.getDetail(OFFERING_ID, TEST_KEY);
Assert.assertNotNull(detailValue);
Assert.assertEquals(TEST_VALUE, detailValue);
}
@Test
public void testGetDetailWhenDetailDoesNotExist() {
Mockito.doReturn(null).when(backupOfferingDetailsDao)
.findDetail(OFFERING_ID, TEST_KEY);
String detailValue = backupOfferingDetailsDao.getDetail(OFFERING_ID, TEST_KEY);
Assert.assertNull(detailValue);
}
@Test
public void testFindOfferingIdsByDomainIds() {
List<Long> domainIds = Arrays.asList(1L, 2L, 3L);
List<Long> expectedOfferingIds = Arrays.asList(100L, 101L, 102L);
Mockito.doReturn(expectedOfferingIds).when(backupOfferingDetailsDao)
.findResourceIdsByNameAndValueIn(Mockito.eq("domainid"), Mockito.any(Object[].class));
List<Long> offeringIds = backupOfferingDetailsDao.findOfferingIdsByDomainIds(domainIds);
Assert.assertNotNull(offeringIds);
Assert.assertEquals(expectedOfferingIds, offeringIds);
Mockito.verify(backupOfferingDetailsDao).findResourceIdsByNameAndValueIn(
Mockito.eq("domainid"), Mockito.any(Object[].class));
}
@Test
public void testFindOfferingIdsByDomainIdsWithEmptyList() {
List<Long> domainIds = Collections.emptyList();
List<Long> expectedOfferingIds = Collections.emptyList();
Mockito.doReturn(expectedOfferingIds).when(backupOfferingDetailsDao)
.findResourceIdsByNameAndValueIn(Mockito.eq("domainid"), Mockito.any(Object[].class));
List<Long> offeringIds = backupOfferingDetailsDao.findOfferingIdsByDomainIds(domainIds);
Assert.assertNotNull(offeringIds);
Assert.assertTrue(offeringIds.isEmpty());
}
@Test
@SuppressWarnings("unchecked")
public void testUpdateBackupOfferingDomainIdsDetail() {
List<Long> newDomainIds = Arrays.asList(1L, 2L, 3L);
Mockito.doReturn(0).when(backupOfferingDetailsDao).remove(Mockito.any(SearchCriteria.class));
Mockito.doReturn(null).when(backupOfferingDetailsDao).persist(Mockito.any(BackupOfferingDetailsVO.class));
backupOfferingDetailsDao.updateBackupOfferingDomainIdsDetail(OFFERING_ID, newDomainIds);
Mockito.verify(backupOfferingDetailsDao, Mockito.times(3)).persist(Mockito.any(BackupOfferingDetailsVO.class));
}
@Test
@SuppressWarnings("unchecked")
public void testUpdateBackupOfferingDomainIdsDetailWithEmptyList() {
List<Long> emptyDomainIds = Collections.emptyList();
Mockito.doReturn(0).when(backupOfferingDetailsDao).remove(Mockito.any(SearchCriteria.class));
backupOfferingDetailsDao.updateBackupOfferingDomainIdsDetail(OFFERING_ID, emptyDomainIds);
Mockito.verify(backupOfferingDetailsDao, Mockito.never()).persist(Mockito.any(BackupOfferingDetailsVO.class));
}
@Test
@SuppressWarnings("unchecked")
public void testUpdateBackupOfferingDomainIdsDetailWithSingleDomain() {
List<Long> singleDomainId = Collections.singletonList(5L);
Mockito.doReturn(0).when(backupOfferingDetailsDao).remove(Mockito.any(SearchCriteria.class));
Mockito.doReturn(null).when(backupOfferingDetailsDao).persist(Mockito.any(BackupOfferingDetailsVO.class));
backupOfferingDetailsDao.updateBackupOfferingDomainIdsDetail(OFFERING_ID, singleDomainId);
Mockito.verify(backupOfferingDetailsDao, Mockito.times(1)).persist(Mockito.any(BackupOfferingDetailsVO.class));
}
private BackupOfferingDetailsVO createDetailVO(long resourceId, String name, String value, boolean display) {
return new BackupOfferingDetailsVO(resourceId, name, value, display);
}
}

View File

@ -27,6 +27,7 @@ function getTemplateVersion() {
export CS_VERSION="${subversion1}"."${subversion2}"
export CS_MINOR_VERSION="${minorversion}"
export VERSION="${CS_VERSION}.${CS_MINOR_VERSION}"
export CS_SYSTEMTEMPLATE_REPO="https://download.cloudstack.org/systemvm/"
}
function getGenericName() {
@ -63,7 +64,7 @@ function getChecksum() {
function createMetadataFile() {
local fileData=$(cat "$SOURCEFILE")
echo -e "["default"]\nversion = $VERSION.${securityversion}\n" >> "$METADATAFILE"
echo -e "["default"]\nversion = $VERSION.${securityversion}\ndownloadrepository = $CS_SYSTEMTEMPLATE_REPO\n" >> "$METADATAFILE"
for template in "${templates[@]}"
do
section="${template%%:*}"
@ -82,13 +83,21 @@ function createMetadataFile() {
declare -a templates
getTemplateVersion $1
templates=( "kvm-x86_64:https://download.cloudstack.org/systemvm/${CS_VERSION}/systemvmtemplate-$VERSION-x86_64-kvm.qcow2.bz2"
"kvm-aarch64:https://download.cloudstack.org/systemvm/${CS_VERSION}/systemvmtemplate-$VERSION-aarch64-kvm.qcow2.bz2"
"vmware:https://download.cloudstack.org/systemvm/${CS_VERSION}/systemvmtemplate-$VERSION-x86_64-vmware.ova"
"xenserver:https://download.cloudstack.org/systemvm/$CS_VERSION/systemvmtemplate-$VERSION-x86_64-xen.vhd.bz2"
"hyperv:https://download.cloudstack.org/systemvm/$CS_VERSION/systemvmtemplate-$VERSION-x86_64-hyperv.vhd.zip"
"lxc:https://download.cloudstack.org/systemvm/$CS_VERSION/systemvmtemplate-$VERSION-x86_64-kvm.qcow2.bz2"
"ovm3:https://download.cloudstack.org/systemvm/$CS_VERSION/systemvmtemplate-$VERSION-x86_64-ovm.raw.bz2" )
declare -A template_specs=(
[kvm-x86_64]="x86_64-kvm.qcow2.bz2"
[kvm-aarch64]="aarch64-kvm.qcow2.bz2"
[vmware]="x86_64-vmware.ova"
[xenserver]="x86_64-xen.vhd.bz2"
[hyperv]="x86_64-hyperv.vhd.zip"
[lxc]="x86_64-kvm.qcow2.bz2"
[ovm3]="x86_64-ovm.raw.bz2"
)
templates=()
for key in "${!template_specs[@]}"; do
url="${CS_SYSTEMTEMPLATE_REPO}/${CS_VERSION}/systemvmtemplate-$VERSION-${template_specs[$key]}"
templates+=("$key:$url")
done
PARENTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )/dist/systemvm-templates/"
mkdir -p "$PARENTPATH"

View File

@ -323,7 +323,7 @@ public class StorageCacheManagerImpl implements StorageCacheManager, Manager {
if (result.isFailed()) {
objOnCacheStore.processEvent(Event.OperationFailed);
} else {
objOnCacheStore.processEvent(Event.OperationSuccessed, result.getAnswer());
objOnCacheStore.processEvent(Event.OperationSucceeded, result.getAnswer());
objOnCacheStore.incRefCount();
return objOnCacheStore;
}

View File

@ -401,7 +401,7 @@ public class AncientDataMotionStrategy implements DataMotionStrategy {
return answer;
}
objOnImageStore.processEvent(Event.OperationSuccessed, answer);
objOnImageStore.processEvent(Event.OperationSucceeded, answer);
objOnImageStore.processEvent(Event.CopyingRequested);
@ -432,7 +432,7 @@ public class AncientDataMotionStrategy implements DataMotionStrategy {
throw e;
}
objOnImageStore.processEvent(Event.OperationSuccessed);
objOnImageStore.processEvent(Event.OperationSucceeded);
deleteVolumeOnSecondaryStore(objOnImageStore);
return answer;
} else {

View File

@ -1058,7 +1058,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
//submit processEvent
if (StringUtils.isEmpty(errMsg)) {
snapshotInfo.processEvent(Event.OperationSuccessed);
snapshotInfo.processEvent(Event.OperationSucceeded);
} else {
snapshotInfo.processEvent(Event.OperationFailed);
}
@ -1221,7 +1221,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
// command to copy this data from cache to secondary storage. We
// then clean up the cache.
destOnStore.processEvent(Event.OperationSuccessed, copyCmdAnswer);
destOnStore.processEvent(Event.OperationSucceeded, copyCmdAnswer);
CopyCommand cmd = new CopyCommand(destOnStore.getTO(), destData.getTO(), primaryStorageDownloadWait,
VirtualMachineManager.ExecuteInSequence.value());
@ -1271,7 +1271,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
try {
if (StringUtils.isEmpty(errMsg)) {
snapshotInfo.processEvent(Event.OperationSuccessed);
snapshotInfo.processEvent(Event.OperationSucceeded);
}
else {
snapshotInfo.processEvent(Event.OperationFailed);
@ -1404,7 +1404,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
try {
if (StringUtils.isEmpty(errMsg)) {
snapshotInfo.processEvent(Event.OperationSuccessed);
snapshotInfo.processEvent(Event.OperationSucceeded);
}
else {
snapshotInfo.processEvent(Event.OperationFailed);
@ -2366,7 +2366,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
_volumeDao.update(volumeVO.getId(), volumeVO);
_volumeService.copyPoliciesBetweenVolumesAndDestroySourceVolumeAfterMigration(Event.OperationSuccessed, null, srcVolumeInfo, destVolumeInfo, false);
_volumeService.copyPoliciesBetweenVolumesAndDestroySourceVolumeAfterMigration(Event.OperationSucceeded, null, srcVolumeInfo, destVolumeInfo, false);
// Update the volume ID for snapshots on secondary storage
if (!_snapshotDao.listByVolumeId(srcVolumeInfo.getId()).isEmpty()) {
@ -2704,7 +2704,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
try {
if (StringUtils.isEmpty(errMsg)) {
volumeInfo.processEvent(Event.OperationSuccessed);
volumeInfo.processEvent(Event.OperationSucceeded);
}
else {
volumeInfo.processEvent(Event.OperationFailed);

View File

@ -257,9 +257,9 @@ public class SecondaryStorageServiceImpl implements SecondaryStorageService {
}
} else {
if (destData instanceof VolumeInfo) {
((VolumeInfo) destData).processEventOnly(ObjectInDataStoreStateMachine.Event.OperationSuccessed, answer);
((VolumeInfo) destData).processEventOnly(ObjectInDataStoreStateMachine.Event.OperationSucceeded, answer);
} else {
destData.processEvent(ObjectInDataStoreStateMachine.Event.OperationSuccessed, answer);
destData.processEvent(ObjectInDataStoreStateMachine.Event.OperationSucceeded, answer);
}
updateDataObject(srcData, destData);
logger.debug("Deleting source data");

View File

@ -671,7 +671,7 @@ public class TemplateServiceImpl implements TemplateService {
TemplateApiResult res = new TemplateApiResult(tmplt);
if (result.isSuccess()) {
logger.info("Copied template [{}] to image store [{}].", tmplt.getUniqueName(), tmplt.getDataStore().getName());
tmplt.processEvent(Event.OperationSuccessed, result.getAnswer());
tmplt.processEvent(Event.OperationSucceeded, result.getAnswer());
publishTemplateCreation(tmplt);
} else {
logger.warn("Failed to copy template [{}] to image store [{}].", tmplt.getUniqueName(), tmplt.getDataStore().getName());
@ -824,7 +824,7 @@ public class TemplateServiceImpl implements TemplateService {
}
try {
template.processEvent(ObjectInDataStoreStateMachine.Event.OperationSuccessed);
template.processEvent(ObjectInDataStoreStateMachine.Event.OperationSucceeded);
} catch (Exception e) {
result.setResult(e.toString());
if (parentCallback != null) {
@ -1033,7 +1033,7 @@ public class TemplateServiceImpl implements TemplateService {
CommandResult result = callback.getResult();
TemplateObject vo = context.getTemplate();
if (result.isSuccess()) {
vo.processEvent(Event.OperationSuccessed);
vo.processEvent(Event.OperationSucceeded);
} else {
vo.processEvent(Event.OperationFailed);
}
@ -1093,7 +1093,7 @@ public class TemplateServiceImpl implements TemplateService {
// no change to existing template_store_ref, will try to re-sync later if other call triggers this sync operation, like copy template
} else {
// this will update install path properly, next time it will not sync anymore.
destTemplate.processEvent(Event.OperationSuccessed, result.getAnswer());
destTemplate.processEvent(Event.OperationSucceeded, result.getAnswer());
}
future.complete(res);
} catch (Exception e) {
@ -1273,7 +1273,7 @@ public class TemplateServiceImpl implements TemplateService {
res.setResult(result.getResult());
destTemplate.processEvent(Event.OperationFailed);
} else {
destTemplate.processEvent(Event.OperationSuccessed, result.getAnswer());
destTemplate.processEvent(Event.OperationSucceeded, result.getAnswer());
}
future.complete(res);
} catch (Exception e) {
@ -1298,7 +1298,7 @@ public class TemplateServiceImpl implements TemplateService {
res.setResult(result.getResult());
destTemplate.processEvent(Event.OperationFailed);
} else {
destTemplate.processEvent(Event.OperationSuccessed, result.getAnswer());
destTemplate.processEvent(Event.OperationSucceeded, result.getAnswer());
}
future.complete(res);
} catch (Exception e) {
@ -1384,7 +1384,7 @@ public class TemplateServiceImpl implements TemplateService {
TemplateApiResult dataDiskTemplateResult = new TemplateApiResult((TemplateObject)dataDiskTemplate);
try {
if (result.isSuccess()) {
dataDiskTemplate.processEvent(Event.OperationSuccessed, result.getAnswer());
dataDiskTemplate.processEvent(Event.OperationSucceeded, result.getAnswer());
} else {
dataDiskTemplate.processEvent(Event.OperationFailed);
dataDiskTemplateResult.setResult(result.getResult());

View File

@ -269,7 +269,7 @@ public class SnapshotTest extends CloudStackTestNGBase {
to.setSize(1000L);
CopyCmdAnswer answer = new CopyCmdAnswer(to);
templateOnStore.processEvent(Event.CreateOnlyRequested);
templateOnStore.processEvent(Event.OperationSuccessed, answer);
templateOnStore.processEvent(Event.OperationSucceeded, answer);
}

View File

@ -244,7 +244,7 @@ public class VolumeTest extends CloudStackTestNGBase {
to.setSize(100L);
CopyCmdAnswer answer = new CopyCmdAnswer(to);
templateOnStore.processEvent(Event.CreateOnlyRequested);
templateOnStore.processEvent(Event.OperationSuccessed, answer);
templateOnStore.processEvent(Event.OperationSucceeded, answer);
}

View File

@ -247,7 +247,7 @@ public class VolumeTestVmware extends CloudStackTestNGBase {
to.setPath(this.getImageInstallPath());
CopyCmdAnswer answer = new CopyCmdAnswer(to);
templateOnStore.processEvent(Event.CreateOnlyRequested);
templateOnStore.processEvent(Event.OperationSuccessed, answer);
templateOnStore.processEvent(Event.OperationSucceeded, answer);
}

View File

@ -148,7 +148,7 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
CreateObjectAnswer createSnapshotAnswer = new CreateObjectAnswer(snapTO);
snapshotOnImageStore.processEvent(Event.OperationSuccessed, createSnapshotAnswer);
snapshotOnImageStore.processEvent(Event.OperationSucceeded, createSnapshotAnswer);
SnapshotObject snapObj = castSnapshotInfoToSnapshotObject(snapshot);
try {
snapObj.processEvent(Snapshot.Event.OperationNotPerformed);
@ -303,7 +303,7 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
}
if (Snapshot.State.Error.equals(snapshotVO.getState())) {
List<SnapshotDataStoreVO> storeRefs = snapshotStoreDao.findBySnapshotId(snapshotId);
List<SnapshotDataStoreVO> storeRefs = snapshotStoreDao.findBySnapshotIdWithNonDestroyedState(snapshotId);
List<Long> deletedRefs = new ArrayList<>();
for (SnapshotDataStoreVO ref : storeRefs) {
boolean refZoneIdMatch = false;

View File

@ -94,7 +94,7 @@ public class SnapshotDataFactoryImpl implements SnapshotDataFactory {
if (snapshot == null) { //snapshot may have been removed;
return new ArrayList<>();
}
List<SnapshotDataStoreVO> allSnapshotsAndDataStore = snapshotStoreDao.findBySnapshotId(snapshotId);
List<SnapshotDataStoreVO> allSnapshotsAndDataStore = snapshotStoreDao.findBySnapshotIdWithNonDestroyedState(snapshotId);
if (CollectionUtils.isEmpty(allSnapshotsAndDataStore)) {
return new ArrayList<>();
}
@ -118,7 +118,23 @@ public class SnapshotDataFactoryImpl implements SnapshotDataFactory {
if (snapshot == null) {
return null;
}
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findByStoreSnapshot(role, storeId, snapshotId);
return getSnapshotOnStore(snapshot, storeId, role);
}
@Override
public SnapshotInfo getSnapshotIncludingRemoved(long snapshotId, long storeId, DataStoreRole role) {
SnapshotVO snapshot = snapshotDao.findByIdIncludingRemoved(snapshotId);
if (snapshot == null) {
return null;
}
return getSnapshotOnStore(snapshot, storeId, role);
}
private SnapshotInfo getSnapshotOnStore(SnapshotVO snapshot, long storeId, DataStoreRole role) {
if (snapshot == null) {
return null;
}
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findByStoreSnapshot(role, storeId, snapshot.getId());
if (snapshotStore == null) {
return null;
}
@ -207,7 +223,7 @@ public class SnapshotDataFactoryImpl implements SnapshotDataFactory {
@Override
public void updateOperationFailed(long snapshotId) throws NoTransitionException {
List<SnapshotDataStoreVO> snapshotStoreRefs = snapshotStoreDao.findBySnapshotId(snapshotId);
List<SnapshotDataStoreVO> snapshotStoreRefs = snapshotStoreDao.findBySnapshotIdWithNonDestroyedState(snapshotId);
for (SnapshotDataStoreVO snapshotStoreRef : snapshotStoreRefs) {
SnapshotInfo snapshotInfo = getSnapshot(snapshotStoreRef.getSnapshotId(), snapshotStoreRef.getDataStoreId(), snapshotStoreRef.getRole());
if (snapshotInfo != null) {

View File

@ -227,7 +227,7 @@ public class SnapshotServiceImpl implements SnapshotService {
}
try {
snapshot.processEvent(Event.OperationSuccessed, result.getAnswer());
snapshot.processEvent(Event.OperationSucceeded, result.getAnswer());
snapshot.processEvent(Snapshot.Event.OperationSucceeded);
} catch (Exception e) {
logger.debug("Failed to create snapshot: ", e);
@ -474,8 +474,7 @@ public class SnapshotServiceImpl implements SnapshotService {
if (res.isFailed()) {
throw new CloudRuntimeException(res.getResult());
}
SnapshotInfo destSnapshot = res.getSnapshot();
return destSnapshot;
return res.getSnapshot();
} catch (InterruptedException e) {
logger.debug("failed copy snapshot", e);
throw new CloudRuntimeException("Failed to copy snapshot", e);
@ -483,7 +482,6 @@ public class SnapshotServiceImpl implements SnapshotService {
logger.debug("Failed to copy snapshot", e);
throw new CloudRuntimeException("Failed to copy snapshot", e);
}
}
protected Void copySnapshotAsyncCallback(AsyncCallbackDispatcher<SnapshotServiceImpl, CopyCommandResult> callback, CopySnapshotContext<CommandResult> context) {
@ -515,7 +513,7 @@ public class SnapshotServiceImpl implements SnapshotService {
try {
CopyCmdAnswer copyCmdAnswer = (CopyCmdAnswer)result.getAnswer();
destSnapshot.processEvent(Event.OperationSuccessed, copyCmdAnswer);
destSnapshot.processEvent(Event.OperationSucceeded, copyCmdAnswer);
srcSnapshot.processEvent(Snapshot.Event.OperationSucceeded);
snapResult = new SnapshotResult(_snapshotFactory.getSnapshot(destSnapshot.getId(), destSnapshot.getDataStore()), copyCmdAnswer);
future.complete(snapResult);
@ -540,7 +538,7 @@ public class SnapshotServiceImpl implements SnapshotService {
}
try {
Answer answer = result.getAnswer();
destSnapshot.processEvent(Event.OperationSuccessed);
destSnapshot.processEvent(Event.OperationSucceeded);
snapResult = new SnapshotResult(_snapshotFactory.getSnapshot(destSnapshot.getId(), destSnapshot.getDataStore()), answer);
future.complete(snapResult);
} catch (Exception e) {
@ -571,7 +569,6 @@ public class SnapshotServiceImpl implements SnapshotService {
}
protected Void deleteSnapshotCallback(AsyncCallbackDispatcher<SnapshotServiceImpl, CommandResult> callback, DeleteSnapshotContext<CommandResult> context) {
CommandResult result = callback.getResult();
AsyncCallFuture<SnapshotResult> future = context.future;
SnapshotInfo snapshot = context.snapshot;
@ -583,7 +580,7 @@ public class SnapshotServiceImpl implements SnapshotService {
res = new SnapshotResult(context.snapshot, null);
res.setResult(result.getResult());
} else {
snapshot.processEvent(ObjectInDataStoreStateMachine.Event.OperationSuccessed);
snapshot.processEvent(ObjectInDataStoreStateMachine.Event.OperationSucceeded);
res = new SnapshotResult(context.snapshot, null);
}
} catch (Exception e) {
@ -729,7 +726,7 @@ public class SnapshotServiceImpl implements SnapshotService {
if (snapshot != null) {
if (snapshot.getState() != Snapshot.State.BackedUp) {
List<SnapshotDataStoreVO> snapshotDataStoreVOs = _snapshotStoreDao.findBySnapshotId(snapshotId);
List<SnapshotDataStoreVO> snapshotDataStoreVOs = _snapshotStoreDao.findBySnapshotIdWithNonDestroyedState(snapshotId);
for (SnapshotDataStoreVO snapshotDataStoreVO : snapshotDataStoreVOs) {
logger.debug("Remove snapshot {}, status {} on snapshot_store_ref table with id: {}", snapshot, snapshotDataStoreVO.getState(), snapshotDataStoreVO.getId());
@ -803,7 +800,7 @@ public class SnapshotServiceImpl implements SnapshotService {
// no change to existing snapshot_store_ref, will try to re-sync later if other call triggers this sync operation
} else {
// this will update install path properly, next time it will not sync anymore.
destSnapshot.processEvent(Event.OperationSuccessed, result.getAnswer());
destSnapshot.processEvent(Event.OperationSucceeded, result.getAnswer());
}
future.complete(res);
} catch (Exception e) {
@ -833,8 +830,7 @@ public class SnapshotServiceImpl implements SnapshotService {
try {
SnapshotObject srcSnapshot = (SnapshotObject)snapshot;
srcSnapshot.processEvent(Event.DestroyRequested);
srcSnapshot.processEvent(Event.OperationSuccessed);
srcSnapshot.processEvent(Event.OperationSucceeded);
srcSnapshot.processEvent(Snapshot.Event.OperationFailed);
_snapshotDetailsDao.removeDetail(srcSnapshot.getId(), AsyncJob.Constants.MS_ID);
@ -845,7 +841,6 @@ public class SnapshotServiceImpl implements SnapshotService {
}
}
});
}
@Override

View File

@ -541,7 +541,6 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
logger.warn("Failed to clean up snapshot '" + snapshot.getId() + "' on primary storage: " + e.getMessage());
}
}
}
private VMSnapshot takeHypervisorSnapshot(VolumeInfo volumeInfo) {

View File

@ -178,7 +178,7 @@ public class DataObjectManagerImpl implements DataObjectManager {
}
try {
objectInDataStoreMgr.update(objInStrore, ObjectInDataStoreStateMachine.Event.OperationSuccessed);
objectInDataStoreMgr.update(objInStrore, ObjectInDataStoreStateMachine.Event.OperationSucceeded);
} catch (NoTransitionException e) {
try {
objectInDataStoreMgr.update(objInStrore, ObjectInDataStoreStateMachine.Event.OperationFailed);
@ -267,7 +267,7 @@ public class DataObjectManagerImpl implements DataObjectManager {
}
try {
objectInDataStoreMgr.update(destObj, ObjectInDataStoreStateMachine.Event.OperationSuccessed);
objectInDataStoreMgr.update(destObj, ObjectInDataStoreStateMachine.Event.OperationSucceeded);
} catch (NoTransitionException e) {
logger.debug("Failed to update copying state: ", e);
try {
@ -341,7 +341,7 @@ public class DataObjectManagerImpl implements DataObjectManager {
} else {
try {
objectInDataStoreMgr.update(destObj, Event.OperationSuccessed);
objectInDataStoreMgr.update(destObj, Event.OperationSucceeded);
} catch (NoTransitionException e) {
logger.debug("delete failed", e);
} catch (ConcurrentOperationException e) {
@ -365,7 +365,7 @@ public class DataObjectManagerImpl implements DataObjectManager {
event = ObjectInDataStoreStateMachine.Event.CreateRequested;
objectInDataStoreMgr.update(objInStore, event);
objectInDataStoreMgr.update(objInStore, ObjectInDataStoreStateMachine.Event.OperationSuccessed);
objectInDataStoreMgr.update(objInStore, ObjectInDataStoreStateMachine.Event.OperationSucceeded);
} catch (NoTransitionException e) {
logger.debug("Failed to update state", e);
throw new CloudRuntimeException("Failed to update state", e);

View File

@ -99,27 +99,30 @@ public class ObjectInDataStoreManagerImpl implements ObjectInDataStoreManager {
stateMachines.addTransition(State.Allocated, Event.CreateOnlyRequested, State.Creating);
stateMachines.addTransition(State.Allocated, Event.DestroyRequested, State.Destroying);
stateMachines.addTransition(State.Allocated, Event.OperationFailed, State.Failed);
stateMachines.addTransition(State.Allocated, Event.OperationSuccessed, State.Ready);
stateMachines.addTransition(State.Allocated, Event.OperationSucceeded, State.Ready);
stateMachines.addTransition(State.Creating, Event.OperationFailed, State.Allocated);
stateMachines.addTransition(State.Creating, Event.OperationSuccessed, State.Ready);
stateMachines.addTransition(State.Creating, Event.OperationSucceeded, State.Ready);
stateMachines.addTransition(State.Ready, Event.CopyingRequested, State.Copying);
stateMachines.addTransition(State.Copying, Event.OperationSuccessed, State.Ready);
stateMachines.addTransition(State.Copying, Event.OperationSucceeded, State.Ready);
stateMachines.addTransition(State.Copying, Event.OperationFailed, State.Ready);
stateMachines.addTransition(State.Ready, Event.DestroyRequested, State.Destroying);
stateMachines.addTransition(State.Destroying, Event.DestroyRequested, State.Destroying);
stateMachines.addTransition(State.Destroying, Event.OperationSuccessed, State.Destroyed);
stateMachines.addTransition(State.Destroying, Event.OperationSucceeded, State.Destroyed);
stateMachines.addTransition(State.Destroying, Event.OperationFailed, State.Destroying);
stateMachines.addTransition(State.Destroyed, Event.DestroyRequested, State.Destroyed);
stateMachines.addTransition(State.Destroyed, Event.OperationSucceeded, State.Destroyed);
stateMachines.addTransition(State.Destroyed, Event.OperationFailed, State.Destroyed);
stateMachines.addTransition(State.Failed, Event.DestroyRequested, State.Destroying);
// TODO: further investigate why an extra event is sent when it is
// already Ready for DownloadListener
stateMachines.addTransition(State.Ready, Event.OperationSuccessed, State.Ready);
stateMachines.addTransition(State.Ready, Event.OperationSucceeded, State.Ready);
// State transitions for data object migration
stateMachines.addTransition(State.Ready, Event.MigrateDataRequested, State.Migrating);
stateMachines.addTransition(State.Ready, Event.CopyRequested, State.Copying);
stateMachines.addTransition(State.Allocated, Event.MigrateDataRequested, State.Migrating);
stateMachines.addTransition(State.Migrating, Event.MigrationFailed, State.Failed);
stateMachines.addTransition(State.Migrating, Event.MigrationSucceeded, State.Destroyed);
stateMachines.addTransition(State.Migrating, Event.OperationSuccessed, State.Ready);
stateMachines.addTransition(State.Migrating, Event.OperationSucceeded, State.Ready);
stateMachines.addTransition(State.Migrating, Event.OperationFailed, State.Ready);
stateMachines.addTransition(State.Hidden, Event.DestroyRequested, State.Destroying);
}
@ -360,9 +363,7 @@ public class ObjectInDataStoreManagerImpl implements ObjectInDataStoreManager {
break;
}
} else if (data.getType() == DataObjectType.TEMPLATE && data.getDataStore().getRole() == DataStoreRole.Primary) {
result = this.stateMachines.transitTo(obj, event, null, templatePoolDao);
} else if (data.getType() == DataObjectType.SNAPSHOT && data.getDataStore().getRole() == DataStoreRole.Primary) {
result = this.stateMachines.transitTo(obj, event, null, snapshotDataStoreDao);
} else {
@ -422,7 +423,5 @@ public class ObjectInDataStoreManagerImpl implements ObjectInDataStoreManager {
}
return vo;
}
}

View File

@ -427,7 +427,7 @@ public class VolumeObject implements VolumeInfo {
}
mapOfEvents.put(ObjectInDataStoreStateMachine.Event.DestroyRequested, Volume.Event.DestroyRequested);
mapOfEvents.put(ObjectInDataStoreStateMachine.Event.ExpungeRequested, Volume.Event.ExpungingRequested);
mapOfEvents.put(ObjectInDataStoreStateMachine.Event.OperationSuccessed, Volume.Event.OperationSucceeded);
mapOfEvents.put(ObjectInDataStoreStateMachine.Event.OperationSucceeded, Volume.Event.OperationSucceeded);
mapOfEvents.put(ObjectInDataStoreStateMachine.Event.MigrationCopySucceeded, Volume.Event.MigrationCopySucceeded);
mapOfEvents.put(ObjectInDataStoreStateMachine.Event.OperationFailed, Volume.Event.OperationFailed);
mapOfEvents.put(ObjectInDataStoreStateMachine.Event.MigrationCopyFailed, Volume.Event.MigrationCopyFailed);

View File

@ -324,7 +324,7 @@ public class VolumeServiceImpl implements VolumeService {
DataObject vo = context.getVolume();
String errMsg = null;
if (result.isSuccess()) {
vo.processEvent(Event.OperationSuccessed, result.getAnswer());
vo.processEvent(Event.OperationSucceeded, result.getAnswer());
} else {
vo.processEvent(Event.OperationFailed);
errMsg = result.getResult();
@ -485,7 +485,7 @@ public class VolumeServiceImpl implements VolumeService {
VolumeApiResult apiResult = new VolumeApiResult(vo);
try {
if (result.isSuccess()) {
vo.processEvent(Event.OperationSuccessed);
vo.processEvent(Event.OperationSucceeded);
if (vo.getPassphraseId() != null) {
vo.deletePassphrase();
@ -758,7 +758,7 @@ public class VolumeServiceImpl implements VolumeService {
if (result.isSuccess()) {
((TemplateObject)templateOnPrimaryStoreObj).setInstallPath(result.getPath());
templateOnPrimaryStoreObj.processEvent(Event.OperationSuccessed, result.getAnswer());
templateOnPrimaryStoreObj.processEvent(Event.OperationSucceeded, result.getAnswer());
} else {
templateOnPrimaryStoreObj.processEvent(Event.OperationFailed);
}
@ -778,7 +778,7 @@ public class VolumeServiceImpl implements VolumeService {
DataObject templateOnPrimaryStoreObj = context.destObj;
if (result.isSuccess()) {
templateOnPrimaryStoreObj.processEvent(Event.OperationSuccessed, result.getAnswer());
templateOnPrimaryStoreObj.processEvent(Event.OperationSucceeded, result.getAnswer());
} else {
templateOnPrimaryStoreObj.processEvent(Event.OperationFailed);
}
@ -802,7 +802,7 @@ public class VolumeServiceImpl implements VolumeService {
return null;
}
templateOnPrimaryStoreObj.processEvent(Event.OperationSuccessed, result.getAnswer());
templateOnPrimaryStoreObj.processEvent(Event.OperationSucceeded, result.getAnswer());
createVolumeFromBaseImageAsync(context.volume, templateOnPrimaryStoreObj, context.dataStore, future);
return null;
}
@ -853,7 +853,7 @@ public class VolumeServiceImpl implements VolumeService {
String deployAsIsConfiguration = context.deployAsIsConfiguration;
if (result.isSuccess()) {
vo.processEvent(Event.OperationSuccessed, result.getAnswer());
vo.processEvent(Event.OperationSucceeded, result.getAnswer());
} else {
vo.processEvent(Event.OperationFailed);
@ -908,7 +908,7 @@ public class VolumeServiceImpl implements VolumeService {
}
volDao.update(volume.getId(), volume);
vo.processEvent(Event.OperationSuccessed);
vo.processEvent(Event.OperationSucceeded);
} else {
volResult.setResult(result.getResult());
@ -972,7 +972,7 @@ public class VolumeServiceImpl implements VolumeService {
throw new CloudRuntimeException(String.format("Unable to create template %s on primary storage %s: %s", templateOnPrimary.getImage(), destPrimaryDataStore, errMesg));
}
templateOnPrimary.processEvent(Event.OperationSuccessed);
templateOnPrimary.processEvent(Event.OperationSucceeded);
} catch (Throwable e) {
logger.debug("Failed to create template volume on storage", e);
@ -1445,7 +1445,7 @@ public class VolumeServiceImpl implements VolumeService {
logger.debug("Failed to prepare ready bypassed template: {} on primary storage: {}", srcTemplateInfo, templateOnPrimary);
throw new CloudRuntimeException(String.format("Failed to prepare ready bypassed template: %s on primary storage: %s", srcTemplateInfo, templateOnPrimary));
}
templateOnPrimary.processEvent(Event.OperationSuccessed);
templateOnPrimary.processEvent(Event.OperationSucceeded);
return templateOnPrimaryNow;
} finally {
revokeAccess(templateOnPrimary, destHost, destPrimaryDataStore);
@ -1700,7 +1700,7 @@ public class VolumeServiceImpl implements VolumeService {
apiResult.setResult(result.getResult());
event = Event.OperationFailed;
} else {
event = Event.OperationSuccessed;
event = Event.OperationSucceeded;
}
try {
@ -1806,8 +1806,8 @@ public class VolumeServiceImpl implements VolumeService {
return null;
}
srcVolume.processEvent(Event.OperationSuccessed);
destVolume.processEvent(Event.OperationSuccessed, result.getAnswer());
srcVolume.processEvent(Event.OperationSucceeded);
destVolume.processEvent(Event.OperationSucceeded, result.getAnswer());
srcVolume.getDataStore().delete(srcVolume);
future.complete(res);
} catch (Exception e) {
@ -1857,8 +1857,8 @@ public class VolumeServiceImpl implements VolumeService {
res.setResult(result.getResult());
future.complete(res);
} else {
srcVolume.processEvent(Event.OperationSuccessed); // back to Ready state in Volume table
destVolume.processEventOnly(Event.OperationSuccessed, result.getAnswer());
srcVolume.processEvent(Event.OperationSucceeded); // back to Ready state in Volume table
destVolume.processEventOnly(Event.OperationSucceeded, result.getAnswer());
future.complete(res);
}
} catch (Exception e) {
@ -1940,7 +1940,7 @@ public class VolumeServiceImpl implements VolumeService {
if (destVolume.getStoragePoolType() == StoragePoolType.PowerFlex) {
logger.info("Dest volume {} can be removed", destVolume);
destVolume.processEvent(Event.ExpungeRequested);
destVolume.processEvent(Event.OperationSuccessed);
destVolume.processEvent(Event.OperationSucceeded);
volDao.remove(destVolume.getId());
future.complete(res);
return null;
@ -1973,7 +1973,7 @@ public class VolumeServiceImpl implements VolumeService {
protected boolean destroySourceVolumeAfterMigration(Event destinationEvent, Answer destinationEventAnswer, VolumeInfo sourceVolume,
VolumeInfo destinationVolume, boolean retryExpungeVolumeAsync) {
sourceVolume.processEvent(Event.OperationSuccessed);
sourceVolume.processEvent(Event.OperationSucceeded);
destinationVolume.processEvent(destinationEvent, destinationEventAnswer);
VolumeVO sourceVolumeVo = ((VolumeObject) sourceVolume).getVolume();
@ -1989,7 +1989,7 @@ public class VolumeServiceImpl implements VolumeService {
if (sourceVolume.getStoragePoolType() == StoragePoolType.PowerFlex) {
logger.info("Source volume {} can be removed.", sourceVolumeVo);
sourceVolume.processEvent(Event.ExpungeRequested);
sourceVolume.processEvent(Event.OperationSuccessed);
sourceVolume.processEvent(Event.OperationSucceeded);
volDao.remove(sourceVolume.getId());
return true;
}
@ -2084,7 +2084,7 @@ public class VolumeServiceImpl implements VolumeService {
logger.debug("Failed to create dest volume {}, volume can be removed", destVolume);
destroyVolume(destVolume.getId());
destVolume.processEvent(Event.ExpungeRequested);
destVolume.processEvent(Event.OperationSuccessed);
destVolume.processEvent(Event.OperationSucceeded);
volDao.remove(destVolume.getId());
throw new CloudRuntimeException("Creation of a dest volume failed: " + createVolumeResult.getResult());
}
@ -2173,7 +2173,7 @@ public class VolumeServiceImpl implements VolumeService {
logger.debug("failed to clean up managed volume on storage", e);
}
} else {
srcVolume.processEvent(Event.OperationSuccessed);
srcVolume.processEvent(Event.OperationSucceeded);
destVolume.processEvent(Event.MigrationCopySucceeded, result.getAnswer());
volDao.updateUuid(srcVolume.getId(), destVolume.getId());
volDao.detachVolume(srcVolume.getId());
@ -2293,7 +2293,7 @@ public class VolumeServiceImpl implements VolumeService {
srcVolume.processEvent(Event.OperationFailed);
future.complete(res);
} else {
srcVolume.processEvent(Event.OperationSuccessed);
srcVolume.processEvent(Event.OperationSucceeded);
if (srcVolume.getStoragePoolType() == StoragePoolType.PowerFlex) {
future.complete(res);
return null;
@ -2380,7 +2380,7 @@ public class VolumeServiceImpl implements VolumeService {
for (Map.Entry<VolumeInfo, DataStore> entry : volumeToPool.entrySet()) {
VolumeInfo volume = entry.getKey();
snapshotMgr.cleanupSnapshotsByVolume(volume.getId());
volume.processEvent(Event.OperationSuccessed);
volume.processEvent(Event.OperationSucceeded);
}
future.complete(res);
}
@ -2448,7 +2448,7 @@ public class VolumeServiceImpl implements VolumeService {
}
} else {
vo.processEvent(Event.OperationSuccessed, result.getAnswer());
vo.processEvent(Event.OperationSucceeded, result.getAnswer());
if (vo.getSize() != null) {
// publish usage events
@ -2568,7 +2568,7 @@ public class VolumeServiceImpl implements VolumeService {
}
try {
volume.processEvent(Event.OperationSuccessed);
volume.processEvent(Event.OperationSucceeded);
} catch (Exception e) {
logger.debug("Failed to change volume state (after resize success)", e);
}
@ -2657,7 +2657,7 @@ public class VolumeServiceImpl implements VolumeService {
if (volume.getState() == State.NotUploaded || volume.getState() == State.UploadInProgress) {
VolumeObject volObj = (VolumeObject)volFactory.getVolume(volume.getId());
volObj.processEvent(Event.OperationSuccessed);
volObj.processEvent(Event.OperationSucceeded);
}
if (volInfo.getSize() > 0) {

View File

@ -156,7 +156,7 @@ public class VolumeObjectTest extends TestCase{
expectedResult.put(ObjectInDataStoreStateMachine.Event.MigrationRequested, Volume.Event.CopyRequested);
expectedResult.put(ObjectInDataStoreStateMachine.Event.DestroyRequested, Volume.Event.DestroyRequested);
expectedResult.put(ObjectInDataStoreStateMachine.Event.ExpungeRequested, Volume.Event.ExpungingRequested);
expectedResult.put(ObjectInDataStoreStateMachine.Event.OperationSuccessed, Volume.Event.OperationSucceeded);
expectedResult.put(ObjectInDataStoreStateMachine.Event.OperationSucceeded, Volume.Event.OperationSucceeded);
expectedResult.put(ObjectInDataStoreStateMachine.Event.MigrationCopySucceeded, Volume.Event.MigrationCopySucceeded);
expectedResult.put(ObjectInDataStoreStateMachine.Event.OperationFailed, Volume.Event.OperationFailed);
expectedResult.put(ObjectInDataStoreStateMachine.Event.MigrationCopyFailed, Volume.Event.MigrationCopyFailed);
@ -177,7 +177,7 @@ public class VolumeObjectTest extends TestCase{
expectedResult.put(ObjectInDataStoreStateMachine.Event.MigrationCopyRequested, Volume.Event.MigrationCopyRequested);
expectedResult.put(ObjectInDataStoreStateMachine.Event.DestroyRequested, Volume.Event.DestroyRequested);
expectedResult.put(ObjectInDataStoreStateMachine.Event.ExpungeRequested, Volume.Event.ExpungingRequested);
expectedResult.put(ObjectInDataStoreStateMachine.Event.OperationSuccessed, Volume.Event.OperationSucceeded);
expectedResult.put(ObjectInDataStoreStateMachine.Event.OperationSucceeded, Volume.Event.OperationSucceeded);
expectedResult.put(ObjectInDataStoreStateMachine.Event.MigrationCopySucceeded, Volume.Event.MigrationCopySucceeded);
expectedResult.put(ObjectInDataStoreStateMachine.Event.OperationFailed, Volume.Event.OperationFailed);
expectedResult.put(ObjectInDataStoreStateMachine.Event.MigrationCopyFailed, Volume.Event.MigrationCopyFailed);

View File

@ -66,7 +66,7 @@ public class ConfigKeyScheduledExecutionWrapper implements Runnable {
this.unit = unit;
}
protected ConfigKeyScheduledExecutionWrapper(ScheduledExecutorService executorService, Runnable command,
public ConfigKeyScheduledExecutionWrapper(ScheduledExecutorService executorService, Runnable command,
ConfigKey<?> configKey, int enableIntervalSeconds, TimeUnit unit) {
validateArgs(executorService, command, configKey);
this.executorService = executorService;

View File

@ -24,8 +24,8 @@ import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
public interface Webhook extends ControlledEntity, Identity, InternalIdentity {
public static final long ID_DUMMY = 0L;
public static final String NAME_DUMMY = "Test";
long ID_DUMMY = 0L;
String NAME_DUMMY = "Test";
enum State {
Enabled, Disabled;
};

View File

@ -18,14 +18,18 @@
package org.apache.cloudstack.mom.webhook;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.mom.webhook.api.command.user.AddWebhookFilterCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.CreateWebhookCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.DeleteWebhookCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.DeleteWebhookDeliveryCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.DeleteWebhookFilterCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.ExecuteWebhookDeliveryCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.ListWebhookDeliveriesCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.ListWebhookFiltersCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.ListWebhooksCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.UpdateWebhookCmd;
import org.apache.cloudstack.mom.webhook.api.response.WebhookDeliveryResponse;
import org.apache.cloudstack.mom.webhook.api.response.WebhookFilterResponse;
import org.apache.cloudstack.mom.webhook.api.response.WebhookResponse;
import com.cloud.utils.component.PluggableService;
@ -41,4 +45,7 @@ public interface WebhookApiService extends PluggableService {
ListResponse<WebhookDeliveryResponse> listWebhookDeliveries(ListWebhookDeliveriesCmd cmd);
int deleteWebhookDelivery(DeleteWebhookDeliveryCmd cmd) throws CloudRuntimeException;
WebhookDeliveryResponse executeWebhookDelivery(ExecuteWebhookDeliveryCmd cmd) throws CloudRuntimeException;
ListResponse<WebhookFilterResponse> listWebhookFilters(ListWebhookFiltersCmd cmd) throws CloudRuntimeException;
WebhookFilterResponse addWebhookFilter(AddWebhookFilterCmd cmd) throws CloudRuntimeException;
int deleteWebhookFilter(DeleteWebhookFilterCmd cmd) throws CloudRuntimeException;
}

View File

@ -29,23 +29,30 @@ import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.mom.webhook.api.command.user.AddWebhookFilterCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.CreateWebhookCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.DeleteWebhookCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.DeleteWebhookDeliveryCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.DeleteWebhookFilterCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.ExecuteWebhookDeliveryCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.ListWebhookDeliveriesCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.ListWebhookFiltersCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.ListWebhooksCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.UpdateWebhookCmd;
import org.apache.cloudstack.mom.webhook.api.response.WebhookDeliveryResponse;
import org.apache.cloudstack.mom.webhook.api.response.WebhookFilterResponse;
import org.apache.cloudstack.mom.webhook.api.response.WebhookResponse;
import org.apache.cloudstack.mom.webhook.dao.WebhookDao;
import org.apache.cloudstack.mom.webhook.dao.WebhookDeliveryDao;
import org.apache.cloudstack.mom.webhook.dao.WebhookDeliveryJoinDao;
import org.apache.cloudstack.mom.webhook.dao.WebhookFilterDao;
import org.apache.cloudstack.mom.webhook.dao.WebhookJoinDao;
import org.apache.cloudstack.mom.webhook.vo.WebhookDeliveryJoinVO;
import org.apache.cloudstack.mom.webhook.vo.WebhookDeliveryVO;
import org.apache.cloudstack.mom.webhook.vo.WebhookFilterVO;
import org.apache.cloudstack.mom.webhook.vo.WebhookJoinVO;
import org.apache.cloudstack.mom.webhook.vo.WebhookVO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
@ -59,6 +66,7 @@ import com.cloud.exception.PermissionDeniedException;
import com.cloud.projects.Project;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.utils.EnumUtils;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.UriUtils;
@ -84,6 +92,8 @@ public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiServ
@Inject
WebhookDeliveryJoinDao webhookDeliveryJoinDao;
@Inject
WebhookFilterDao webhookFilterDao;
@Inject
ManagementServerHostDao managementServerHostDao;
@Inject
WebhookService webhookService;
@ -225,6 +235,25 @@ public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiServ
throw new InvalidParameterValueException(error);
}
protected WebhookFilterResponse createWebhookFilterResponse(WebhookFilter webhookFilter, WebhookVO webhookVO) {
WebhookFilterResponse response = new WebhookFilterResponse();
response.setObjectName("webhookfilter");
response.setId(webhookFilter.getUuid());
if (webhookVO == null) {
webhookVO = webhookDao.findById(webhookFilter.getWebhookId());
}
if (webhookVO != null) {
response.setWebhookId(webhookVO.getUuid());
response.setWebhookName(webhookVO.getName());
}
response.setType(webhookFilter.getType().toString());
response.setMode(webhookFilter.getMode().toString());
response.setMatchType(webhookFilter.getMatchType().toString());
response.setValue(webhookFilter.getValue());
response.setCreated(webhookFilter.getCreated());
return response;
}
@Override
public ListResponse<WebhookResponse> listWebhooks(ListWebhooksCmd cmd) {
final CallContext ctx = CallContext.current();
@ -234,6 +263,25 @@ public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiServ
final String name = cmd.getName();
final String keyword = cmd.getKeyword();
final String scopeStr = cmd.getScope();
Webhook.Scope scope = null;
if (StringUtils.isNotEmpty(scopeStr)) {
scope = EnumUtils.getEnumIgnoreCase(Webhook.Scope.class, scopeStr);
if (scope == null) {
throw new InvalidParameterValueException("Invalid scope specified");
}
}
if ((Webhook.Scope.Global.equals(scope) && !Account.Type.ADMIN.equals(caller.getType())) ||
(Webhook.Scope.Domain.equals(scope) &&
!List.of(Account.Type.ADMIN, Account.Type.DOMAIN_ADMIN).contains(caller.getType()))) {
throw new InvalidParameterValueException(String.format("Scope %s can not be specified", scope));
}
Webhook.State state = null;
if (StringUtils.isNotEmpty(stateStr)) {
state = EnumUtils.getEnumIgnoreCase(Webhook.State.class, stateStr);
if (state == null) {
throw new InvalidParameterValueException("Invalid state specified");
}
}
List<WebhookResponse> responsesList = new ArrayList<>();
List<Long> permittedAccounts = new ArrayList<>();
Ternary<Long, Boolean, Project.ListProjectResourcesCriteria> domainIdRecursiveListProject =
@ -258,27 +306,6 @@ public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiServ
SearchCriteria<WebhookJoinVO> sc = sb.create();
accountManager.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts,
listProjectResourcesCriteria);
Webhook.Scope scope = null;
if (StringUtils.isNotEmpty(scopeStr)) {
try {
scope = Webhook.Scope.valueOf(scopeStr);
} catch (IllegalArgumentException iae) {
throw new InvalidParameterValueException("Invalid scope specified");
}
}
if ((Webhook.Scope.Global.equals(scope) && !Account.Type.ADMIN.equals(caller.getType())) ||
(Webhook.Scope.Domain.equals(scope) &&
!List.of(Account.Type.ADMIN, Account.Type.DOMAIN_ADMIN).contains(caller.getType()))) {
throw new InvalidParameterValueException(String.format("Scope %s can not be specified", scope));
}
Webhook.State state = null;
if (StringUtils.isNotEmpty(stateStr)) {
try {
state = Webhook.State.valueOf(stateStr);
} catch (IllegalArgumentException iae) {
throw new InvalidParameterValueException("Invalid state specified");
}
}
if (scope != null) {
sc.setParameters("scope", scope.name());
}
@ -316,9 +343,8 @@ public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiServ
final String stateStr = cmd.getState();
Webhook.Scope scope = Webhook.Scope.Local;
if (StringUtils.isNotEmpty(scopeStr)) {
try {
scope = Webhook.Scope.valueOf(scopeStr);
} catch (IllegalArgumentException iae) {
scope = EnumUtils.getEnumIgnoreCase(Webhook.Scope.class, scopeStr);
if (scope == null) {
throw new InvalidParameterValueException("Invalid scope specified");
}
}
@ -330,9 +356,8 @@ public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiServ
}
Webhook.State state = Webhook.State.Enabled;
if (StringUtils.isNotEmpty(stateStr)) {
try {
state = Webhook.State.valueOf(stateStr);
} catch (IllegalArgumentException iae) {
state = EnumUtils.getEnumIgnoreCase(Webhook.State.class, stateStr);
if (state == null) {
throw new InvalidParameterValueException("Invalid state specified");
}
}
@ -353,6 +378,7 @@ public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiServ
WebhookVO webhook = new WebhookVO(name, description, state, domainId, owner.getId(), payloadUrl, secretKey,
sslVerification, scope);
webhook = webhookDao.persist(webhook);
webhookService.invalidateWebhooksCache();
return createWebhookResponse(webhook.getId());
}
@ -365,7 +391,11 @@ public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiServ
throw new InvalidParameterValueException("Unable to find the webhook with the specified ID");
}
accountManager.checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhook);
return webhookDao.remove(id);
boolean removed = webhookDao.remove(id);
if (removed) {
webhookService.invalidateWebhooksCache();
}
return removed;
}
@Override
@ -394,29 +424,27 @@ public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiServ
updateNeeded = true;
}
if (StringUtils.isNotEmpty(stateStr)) {
try {
Webhook.State state = Webhook.State.valueOf(stateStr);
webhook.setState(state);
updateNeeded = true;
} catch (IllegalArgumentException iae) {
Webhook.State state = EnumUtils.getEnumIgnoreCase(Webhook.State.class, stateStr);
if (state == null) {
throw new InvalidParameterValueException("Invalid state specified");
}
webhook.setState(state);
updateNeeded = true;
}
Account owner = accountManager.getAccount(webhook.getAccountId());
if (StringUtils.isNotEmpty(scopeStr)) {
try {
Webhook.Scope scope = Webhook.Scope.valueOf(scopeStr);
if ((Webhook.Scope.Global.equals(scope) && !Account.Type.ADMIN.equals(owner.getType())) ||
(Webhook.Scope.Domain.equals(scope) &&
!List.of(Account.Type.ADMIN, Account.Type.DOMAIN_ADMIN).contains(owner.getType()))) {
throw new InvalidParameterValueException(
String.format("Scope %s can not be specified for owner %s", scope, owner.getName()));
}
webhook.setScope(scope);
updateNeeded = true;
} catch (IllegalArgumentException iae) {
Webhook.Scope scope = EnumUtils.getEnumIgnoreCase(Webhook.Scope.class, scopeStr);
if (scope == null) {
throw new InvalidParameterValueException("Invalid scope specified");
}
if ((Webhook.Scope.Global.equals(scope) && !Account.Type.ADMIN.equals(owner.getType())) ||
(Webhook.Scope.Domain.equals(scope) &&
!List.of(Account.Type.ADMIN, Account.Type.DOMAIN_ADMIN).contains(owner.getType()))) {
throw new InvalidParameterValueException(
String.format("Scope %s can not be specified for owner %s", scope, owner.getName()));
}
webhook.setScope(scope);
updateNeeded = true;
}
URI uri = URI.create(webhook.getPayloadUrl());
if (StringUtils.isNotEmpty(payloadUrl)) {
@ -427,7 +455,7 @@ public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiServ
updateNeeded = true;
}
if (sslVerification != null) {
if (Boolean.TRUE.equals(sslVerification) && !HttpConstants.HTTPS.equalsIgnoreCase(uri.getScheme())) {
if (sslVerification && !HttpConstants.HTTPS.equalsIgnoreCase(uri.getScheme())) {
throw new InvalidParameterValueException(
String.format("SSL verification can be specified only for HTTPS URLs, %s", payloadUrl));
}
@ -444,6 +472,7 @@ public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiServ
if (updateNeeded && !webhookDao.update(id, webhook)) {
return null;
}
webhookService.invalidateWebhooksCache();
return createWebhookResponse(webhook.getId());
}
@ -455,8 +484,7 @@ public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiServ
@Override
public ListResponse<WebhookDeliveryResponse> listWebhookDeliveries(ListWebhookDeliveriesCmd cmd) {
final CallContext ctx = CallContext.current();
final Account caller = ctx.getCallingAccount();
final Account caller = CallContext.current().getCallingAccount();
final Long id = cmd.getId();
final Long webhookId = cmd.getWebhookId();
final Long managementServerId = cmd.getManagementServerId();
@ -507,20 +535,23 @@ public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiServ
@Override
public WebhookDeliveryResponse executeWebhookDelivery(ExecuteWebhookDeliveryCmd cmd) throws CloudRuntimeException {
final CallContext ctx = CallContext.current();
final Account caller = ctx.getCallingAccount();
final Account caller = CallContext.current().getCallingAccount();
final Long deliveryId = cmd.getId();
final Long webhookId = cmd.getWebhookId();
final String payloadUrl = getNormalizedPayloadUrl(cmd.getPayloadUrl());
final String secretKey = cmd.getSecretKey();
final Boolean sslVerification = cmd.isSslVerification();
final String payload = cmd.getPayload();
final Account owner = accountManager.finalizeOwner(caller, null, null, null);
if (ObjectUtils.allNull(deliveryId, webhookId) && StringUtils.isBlank(payloadUrl)) {
throw new InvalidParameterValueException(String.format("One of the %s, %s or %s must be specified",
ApiConstants.ID, ApiConstants.WEBHOOK_ID, ApiConstants.PAYLOAD_URL));
}
if (deliveryId != null && (webhookId != null || StringUtils.isNotBlank(payloadUrl))) {
throw new InvalidParameterValueException(
String.format("%s cannot be specified with %s or %s", ApiConstants.ID, ApiConstants.WEBHOOK_ID,
ApiConstants.PAYLOAD_URL));
}
WebhookDeliveryVO existingDelivery = null;
WebhookVO webhook = null;
if (deliveryId != null) {
@ -545,11 +576,14 @@ public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiServ
webhook.setSecretKey(secretKey);
}
if (sslVerification != null) {
webhook.setSslVerification(Boolean.TRUE.equals(sslVerification));
webhook.setSslVerification(sslVerification);
}
}
if (webhook != null) {
accountManager.checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhook);
}
if (ObjectUtils.allNull(deliveryId, webhookId)) {
webhook = new WebhookVO(owner.getDomainId(), owner.getId(), payloadUrl, secretKey,
webhook = new WebhookVO(caller.getDomainId(), caller.getId(), payloadUrl, secretKey,
Boolean.TRUE.equals(sslVerification));
}
WebhookDelivery webhookDelivery = webhookService.executeWebhookDelivery(existingDelivery, webhook, payload);
@ -559,6 +593,87 @@ public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiServ
return createTestWebhookDeliveryResponse(webhookDelivery, webhook);
}
@Override
public ListResponse<WebhookFilterResponse> listWebhookFilters(ListWebhookFiltersCmd cmd) throws CloudRuntimeException {
Pair<List<WebhookFilterVO>, Integer> filtersAndCount = webhookFilterDao.searchBy(cmd.getId(), cmd.getWebhookId(),
cmd.getStartIndex(), cmd.getPageSizeVal());
List<WebhookFilterResponse> responsesList = new ArrayList<>();
WebhookVO webhookVO = null;
if (filtersAndCount.second() > 0) {
webhookVO = webhookDao.findById(filtersAndCount.first().get(0).getWebhookId());
}
for (WebhookFilterVO filter : filtersAndCount.first()) {
WebhookFilterResponse response = createWebhookFilterResponse(filter, webhookVO);
responsesList.add(response);
}
ListResponse<WebhookFilterResponse> response = new ListResponse<>();
response.setResponses(responsesList, responsesList.size());
return response;
}
@Override
public WebhookFilterResponse addWebhookFilter(AddWebhookFilterCmd cmd) throws CloudRuntimeException {
final Account caller = CallContext.current().getCallingAccount();
final long id = cmd.getId();
final String typeStr = cmd.getType();
final String modeStr = cmd.getMode();
final String matchTypeStr = cmd.getMatchType();
final String value = cmd.getValue();
WebhookVO webhook = webhookDao.findById(id);
if (webhook == null) {
throw new InvalidParameterValueException("Unable to find the webhook with the specified ID");
}
accountManager.checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhook);
WebhookFilter.Type type = EnumUtils.getEnumIgnoreCase(WebhookFilter.Type.class, typeStr, WebhookFilter.Type.EventType);
WebhookFilter.Mode mode = WebhookFilter.Mode.Include;
if (StringUtils.isNotBlank(modeStr)) {
mode = EnumUtils.getEnumIgnoreCase(WebhookFilter.Mode.class, modeStr);
if (mode == null) {
throw new InvalidParameterValueException("Invalid mode specified");
}
}
WebhookFilter.MatchType matchType = WebhookFilter.MatchType.Exact;
if (StringUtils.isNotBlank(matchTypeStr)) {
matchType = EnumUtils.getEnumIgnoreCase(WebhookFilter.MatchType.class, matchTypeStr);
if (matchType == null) {
throw new InvalidParameterValueException("Invalid match type specified");
}
}
WebhookFilterVO webhookFilter = new WebhookFilterVO(webhook.getId(), type, mode, matchType, value);
List<? extends WebhookFilter> existingFilters = webhookFilterDao.listByWebhook(webhook.getId());
if (CollectionUtils.isNotEmpty(existingFilters)) {
WebhookFilter conflicting = webhookFilter.getConflicting(existingFilters);
if (conflicting != null) {
logger.error("Conflict detected when adding WebhookFilter having type: {}, mode: {}, " +
"matchtype: {}, value: {} with existing {} for {}", type, mode, matchType, value, conflicting,
webhook);
throw new InvalidParameterValueException(String.format("Conflicting Webhook filter exists ID: %s",
conflicting.getId()));
}
}
webhookFilter = webhookFilterDao.persist(webhookFilter);
webhookService.invalidateWebhookFiltersCache(webhook.getId());
return createWebhookFilterResponse(webhookFilter, webhook);
}
@Override
public int deleteWebhookFilter(DeleteWebhookFilterCmd cmd) throws CloudRuntimeException {
final Account caller = CallContext.current().getCallingAccount();
final Pair<List<WebhookFilterVO>, Integer> filtersAndCount =
webhookFilterDao.searchBy(cmd.getId(), cmd.getWebhookId(), 0L, 1L);
if (filtersAndCount.second() == 0) {
return 0;
}
final long webhookId = filtersAndCount.first().get(0).getWebhookId();
Webhook webhook = webhookDao.findById(webhookId);
accountManager.checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhook);
int result = webhookFilterDao.delete(cmd.getId(), webhookId);
if (result > 0) {
webhookService.invalidateWebhookFiltersCache(webhookId);
}
return result;
}
@Override
public List<Class<?>> getCommands() {
List<Class<?>> cmdList = new ArrayList<>();
@ -569,6 +684,9 @@ public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiServ
cmdList.add(ListWebhookDeliveriesCmd.class);
cmdList.add(DeleteWebhookDeliveryCmd.class);
cmdList.add(ExecuteWebhookDeliveryCmd.class);
cmdList.add(ListWebhookFiltersCmd.class);
cmdList.add(AddWebhookFilterCmd.class);
cmdList.add(DeleteWebhookFilterCmd.class);
return cmdList;
}
}

View File

@ -0,0 +1,114 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.mom.webhook;
import java.util.Date;
import java.util.List;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
public interface WebhookFilter extends Identity, InternalIdentity {
enum Type {
EventType
}
enum Mode {
Include, Exclude
}
enum MatchType {
Exact, Prefix, Suffix, Contains
}
long getId();
long getWebhookId();
Type getType();
Mode getMode();
MatchType getMatchType();
String getValue();
Date getCreated();
static boolean overlaps(WebhookFilter.MatchType oldMatchType, String oldValue, WebhookFilter.MatchType newMatchType, String newValue) {
switch (oldMatchType) {
case Exact:
switch (newMatchType) {
case Exact:
return oldValue.equals(newValue);
}
break;
case Prefix:
switch (newMatchType) {
case Exact:
case Prefix:
return newValue.startsWith(oldValue);
}
break;
case Suffix:
switch (newMatchType) {
case Exact:
case Suffix:
return newValue.endsWith(oldValue);
}
break;
case Contains:
switch (newMatchType) {
case Exact:
case Prefix:
case Suffix:
case Contains:
return newValue.contains(oldValue);
}
break;
default:
break;
}
return false;
}
default WebhookFilter getConflicting(List<? extends WebhookFilter> existing) {
for (WebhookFilter f : existing) {
if (f.getType() != this.getType()) {
continue;
}
// 1. Duplicate entry (same mode, match type, and value)
if (f.getMode() == this.getMode()
&& f.getMatchType() == this.getMatchType()
&& f.getValue().equalsIgnoreCase(this.getValue())) {
return f;
}
// 2. Opposite mode (INCLUDE vs EXCLUDE) check for overlap
if (f.getMode() != this.getMode()) {
String oldVal = f.getValue().toUpperCase();
String newVal = this.getValue().toUpperCase();
if (overlaps(f.getMatchType(), oldVal, this.getMatchType(), newVal)) {
return f;
}
}
}
return null;
}
}

View File

@ -60,4 +60,6 @@ public interface WebhookService extends PluggableService, Configurable {
void handleEvent(Event event) throws EventBusException;
WebhookDelivery executeWebhookDelivery(WebhookDelivery delivery, Webhook webhook, String payload)
throws CloudRuntimeException;
void invalidateWebhooksCache();
void invalidateWebhookFiltersCache(long webhookId);
}

View File

@ -22,6 +22,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -32,7 +33,6 @@ import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.framework.async.AsyncCallFuture;
import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.framework.async.AsyncRpcContext;
@ -42,10 +42,14 @@ import org.apache.cloudstack.framework.events.EventBusException;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.mom.webhook.dao.WebhookDao;
import org.apache.cloudstack.mom.webhook.dao.WebhookDeliveryDao;
import org.apache.cloudstack.mom.webhook.dao.WebhookFilterDao;
import org.apache.cloudstack.mom.webhook.vo.WebhookDeliveryVO;
import org.apache.cloudstack.mom.webhook.vo.WebhookFilterVO;
import org.apache.cloudstack.mom.webhook.vo.WebhookVO;
import org.apache.cloudstack.utils.cache.LazyCache;
import org.apache.cloudstack.utils.identity.ManagementServerNode;
import org.apache.cloudstack.webhook.WebhookHelper;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import com.cloud.api.query.vo.EventJoinVO;
@ -75,7 +79,9 @@ public class WebhookServiceImpl extends ManagerBase implements WebhookService, W
@Inject
WebhookDao webhookDao;
@Inject
protected WebhookDeliveryDao webhookDeliveryDao;
WebhookDeliveryDao webhookDeliveryDao;
@Inject
WebhookFilterDao webhookFilterDao;
@Inject
ManagementServerHostDao managementServerHostDao;
@Inject
@ -83,6 +89,9 @@ public class WebhookServiceImpl extends ManagerBase implements WebhookService, W
@Inject
AccountManager accountManager;
protected LazyCache<org.apache.commons.lang3.tuple.Pair<Long, List<Long>>, List<WebhookVO>> webhooksCache;
protected LazyCache<Long, List<WebhookFilterVO>> webhookFiltersCache;
protected WebhookDeliveryThread getDeliveryJob(Event event, Webhook webhook, Pair<Integer, Integer> configs) {
WebhookDeliveryThread.WebhookDeliveryContext<WebhookDeliveryThread.WebhookDeliveryResult> context =
new WebhookDeliveryThread.WebhookDeliveryContext<>(null, event.getEventId(), webhook.getId());
@ -97,13 +106,74 @@ public class WebhookServiceImpl extends ManagerBase implements WebhookService, W
return job;
}
protected String getEventValueByFilterType(Event event, WebhookFilter.Type filterType) {
if (WebhookFilter.Type.EventType.equals(filterType)) {
return event.getEventType();
}
return null;
}
protected boolean isValueMatchingFilter(String eventValue, WebhookFilter.MatchType matchType, String filterValue) {
switch (matchType) {
case Exact:
return eventValue.equals(filterValue);
case Prefix:
return eventValue.startsWith(filterValue);
case Suffix:
return eventValue.endsWith(filterValue);
case Contains:
return eventValue.contains(filterValue);
default:
return false;
}
}
protected boolean isEventMatchingFilters(Event event, List<? extends WebhookFilter> filters) {
if (CollectionUtils.isEmpty(filters)) {
return true;
}
boolean hasAnyInclude = false;
boolean anyIncludeMatched = false;
// First pass: short-circuit on any Exclude match; track Include presence/match
for (WebhookFilter f : filters) {
final WebhookFilter.Type type = f.getType();
String eventValue = getEventValueByFilterType(event, type);
if (f.getMode() == WebhookFilter.Mode.Exclude) {
if (eventValue != null && isValueMatchingFilter(eventValue, f.getMatchType(), f.getValue())) {
logger.trace("{} matched Exclude {}, webhook delivery will be skipped", event, f);
return false;
}
continue;
}
if (f.getMode() == WebhookFilter.Mode.Include) {
hasAnyInclude = true;
if (!anyIncludeMatched && eventValue != null &&
isValueMatchingFilter(eventValue, f.getMatchType(), f.getValue())) {
logger.trace("{} matched Include {}", event, f);
anyIncludeMatched = true;
}
}
}
// If there were includes, we must have matched at least one; otherwise allow by default
if (hasAnyInclude && !anyIncludeMatched) {
return false;
}
return true;
}
protected List<Runnable> getDeliveryJobs(Event event) throws EventBusException {
List<Runnable> jobs = new ArrayList<>();
if (!EventCategory.ACTION_EVENT.getName().equals(event.getEventCategory())) {
return jobs;
}
if (event.getResourceAccountId() == null) {
logger.warn("Skipping delivering event {} to any webhook as account ID is missing", event);
logger.warn("Skipping delivering {} to any webhook as account ID is missing", event);
throw new EventBusException(String.format("Account missing for the event ID: %s", event.getEventUuid()));
}
List<Long> domainIds = new ArrayList<>();
@ -112,9 +182,14 @@ public class WebhookServiceImpl extends ManagerBase implements WebhookService, W
domainIds.addAll(domainDao.getDomainParentIds(event.getResourceDomainId()));
}
List<WebhookVO> webhooks =
webhookDao.listByEnabledForDelivery(event.getResourceAccountId(), domainIds);
webhooksCache.get(org.apache.commons.lang3.tuple.Pair.of(event.getResourceAccountId(), domainIds));
Map<Long, Pair<Integer, Integer>> domainConfigs = new HashMap<>();
for (WebhookVO webhook : webhooks) {
List<? extends WebhookFilter> filters = webhookFiltersCache.get(webhook.getId());
if (!isEventMatchingFilters(event, filters)) {
logger.debug("Skipping delivering {} to {} as it doesn't match filters", event, webhook);
continue;
}
if (!domainConfigs.containsKey(webhook.getDomainId())) {
domainConfigs.put(webhook.getDomainId(),
new Pair<>(WebhookDeliveryTries.valueIn(webhook.getDomainId()),
@ -128,7 +203,7 @@ public class WebhookServiceImpl extends ManagerBase implements WebhookService, W
}
protected Runnable getManualDeliveryJob(WebhookDelivery existingDelivery, Webhook webhook, String payload,
AsyncCallFuture<WebhookDeliveryThread.WebhookDeliveryResult> future) {
CompletableFuture<WebhookDeliveryThread.WebhookDeliveryResult> future) {
if (StringUtils.isBlank(payload)) {
payload = "{ \"CloudStack\": \"works!\" }";
}
@ -155,7 +230,7 @@ public class WebhookServiceImpl extends ManagerBase implements WebhookService, W
event.setDescription(description);
event.setResourceAccountUuid(resourceAccountUuid);
ManualDeliveryContext<WebhookDeliveryThread.WebhookDeliveryResult> context =
new ManualDeliveryContext<>(null, webhook, future);
new ManualDeliveryContext<>(null, future);
AsyncCallbackDispatcher<WebhookServiceImpl, WebhookDeliveryThread.WebhookDeliveryResult> caller =
AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().manualDeliveryCompleteCallback(null, null))
@ -181,7 +256,7 @@ public class WebhookServiceImpl extends ManagerBase implements WebhookService, W
AsyncCallbackDispatcher<WebhookServiceImpl, WebhookDeliveryThread.WebhookDeliveryResult> callback,
ManualDeliveryContext<WebhookDeliveryThread.WebhookDeliveryResult> context) {
WebhookDeliveryThread.WebhookDeliveryResult result = callback.getResult();
context.future.complete(result);
context.getFuture().complete(result);
return null;
}
@ -205,8 +280,20 @@ public class WebhookServiceImpl extends ManagerBase implements WebhookService, W
return processed;
}
protected void initCaches() {
webhooksCache = new LazyCache<>(
16, 60,
(key) -> webhookDao.listByEnabledForDelivery(key.getLeft(), key.getRight())
);
webhookFiltersCache = new LazyCache<>(
16, 60,
(webhookId) -> webhookFilterDao.listByWebhook(webhookId)
);
}
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
initCaches();
try {
webhookJobExecutor = Executors.newFixedThreadPool(WebhookDeliveryThreadPoolSize.value(),
new NamedThreadFactory(WEBHOOK_JOB_POOL_THREAD_PREFIX));
@ -273,7 +360,7 @@ public class WebhookServiceImpl extends ManagerBase implements WebhookService, W
@Override
public WebhookDelivery executeWebhookDelivery(WebhookDelivery delivery, Webhook webhook, String payload)
throws CloudRuntimeException {
AsyncCallFuture<WebhookDeliveryThread.WebhookDeliveryResult> future = new AsyncCallFuture<>();
CompletableFuture<WebhookDeliveryThread.WebhookDeliveryResult> future = new CompletableFuture<>();
Runnable job = getManualDeliveryJob(delivery, webhook, payload, future);
webhookJobExecutor.submit(job);
WebhookDeliveryThread.WebhookDeliveryResult result = null;
@ -297,22 +384,33 @@ public class WebhookServiceImpl extends ManagerBase implements WebhookService, W
return webhookDeliveryVO;
}
@Override
public void invalidateWebhooksCache() {
webhooksCache.clear();
}
@Override
public void invalidateWebhookFiltersCache(long webhookId) {
webhookFiltersCache.invalidate(webhookId);
}
@Override
public List<Class<?>> getCommands() {
return new ArrayList<>();
}
static public class ManualDeliveryContext<T> extends AsyncRpcContext<T> {
final Webhook webhook;
final AsyncCallFuture<WebhookDeliveryThread.WebhookDeliveryResult> future;
protected static class ManualDeliveryContext<T> extends AsyncRpcContext<T> {
private final CompletableFuture<WebhookDeliveryThread.WebhookDeliveryResult> future;
public ManualDeliveryContext(AsyncCompletionCallback<T> callback, Webhook webhook,
AsyncCallFuture<WebhookDeliveryThread.WebhookDeliveryResult> future) {
super(callback);
this.webhook = webhook;
this.future = future;
public CompletableFuture<WebhookDeliveryThread.WebhookDeliveryResult> getFuture() {
return future;
}
public ManualDeliveryContext(AsyncCompletionCallback<T> callback,
CompletableFuture<WebhookDeliveryThread.WebhookDeliveryResult> future) {
super(callback);
this.future = future;
}
}
public class WebhookDeliveryCleanupWorker extends ManagedContextRunnable {

View File

@ -0,0 +1,118 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.mom.webhook.api.command.user;
import javax.inject.Inject;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.mom.webhook.WebhookApiService;
import org.apache.cloudstack.mom.webhook.WebhookFilter;
import org.apache.cloudstack.mom.webhook.api.response.WebhookFilterResponse;
import org.apache.cloudstack.mom.webhook.api.response.WebhookResponse;
import com.cloud.utils.exception.CloudRuntimeException;
@APICommand(name = "addWebhookFilter",
description = "Adds a Webhook filter",
responseObject = WebhookResponse.class,
entityType = {WebhookFilter.class},
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
since = "4.23.0")
public class AddWebhookFilterCmd extends BaseCmd {
@Inject
WebhookApiService webhookApiService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.WEBHOOK_ID, type = CommandType.UUID, required = true,
entityType = WebhookResponse.class, description = "ID for the Webhook")
private Long id;
@Parameter(name = ApiConstants.MODE, type = BaseCmd.CommandType.STRING,
description = "Mode for the Webhook filter - Include or Exclude")
private String mode;
@Parameter(name = ApiConstants.MATCH_TYPE, type = BaseCmd.CommandType.STRING,
description = "Match type for the Webhook filter - Exact, Prefix, Suffix or Contains")
private String matchType;
@Parameter(name = ApiConstants.VALUE, type = BaseCmd.CommandType.STRING, required = true,
description = "Value for the Webhook which that will be matched")
private String value;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccountId();
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public String getType() {
return WebhookFilter.Type.EventType.name();
}
public String getMode() {
return mode;
}
public String getMatchType() {
return matchType;
}
public String getValue() {
return value;
}
@Override
public void execute() throws ServerApiException {
try {
WebhookFilterResponse response = webhookApiService.addWebhookFilter(this);
if (response == null) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add webhook filter");
}
response.setResponseName(getCommandName());
setResponseObject(response);
} catch (CloudRuntimeException ex) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage());
}
}
}

Some files were not shown because too many files have changed in this diff Show More