This is an automated email from the ASF dual-hosted git repository.
nvazquez pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/main by this push:
new 988bbaa1f18 Disk ERROR policy for KVM hypervisors (#14499)
988bbaa1f18 is described below
commit 988bbaa1f189795e2cef0408d7b97f6d3bd4fb61
Author: fermosan <[email protected]>
AuthorDate: Mon Nov 9 22:17:04 2026 +0200
Disk ERROR policy for KVM hypervisors (#14499)
* DISK ERROR POLICY FOR KVM HYPERVISORS
KVM-hosted VM disks had no way to configure libvirt/QEMU's error_policy
(rerror/werror). Every disk defaulted to QEMU's "report" policy, so any
I/O error from backing storage -- even a brief, otherwise-recoverable
one -- was passed straight through to the guest as a real disk error.
During a planned, successful NFS HA failover (VIP moved in ~6s, well
within normal client timeouts), this caused a guest's ext4 filesystem
to abort its journal and remount read-only within 3-4 seconds, long
before storage-side recovery had a chance to run.
Adds error_policy as a first-class disk/compute offering attribute,
following the existing cacheMode plumbing end to end:
- Schema: new disk_offering.error_policy column (default 'report'),
added to disk_offering_view/service_offering_view/volume_view.
- Domain model: DiskOffering.DiskErrorPolicy enum (REPORT, STOP,
IGNORE, ENOSPACE), getter/setter on DiskOffering/DiskOfferingVO.
- API: new errorpolicy param on createDiskOffering/updateDiskOffering
and the compute-offering equivalents, surfaced on
DiskOfferingResponse/ServiceOfferingResponse.
- Orchestration: value carried DiskOfferingVO -> VolumeObject ->
VolumeObjectTO -> hypervisor command.
- KVM agent: LibvirtVMDef.DiskDef renders error_policy='<value>' on
the <driver> element, set from the volume's offering in
KVMStorageProcessor next to the existing cache mode handling.
- UI: errorpolicy exposed on the Add/Clone Disk Offering and
Add/Clone Compute Offering forms, defaulting to 'report'.
Default is 'report' (fully backward compatible); supported on both
disk and compute offerings, matching cacheMode's precedent. Alerting
on a long-paused domain is left to operators/monitoring.
Verified end-to-end with error_policy=stop against a real production
NFS HA failover and a disk-full/ENOSPC test, on both Linux and
Windows guests across local, self-hosted NFS, and production-NFS
storage: VMs pause on the real I/O error and resume cleanly with zero
data loss. Also identified and documented a related limitation: a
hard-mounted NFS backend that goes silently unreachable (vs. a real
I/O error) bypasses this feature entirely, since the guest's I/O just
hangs rather than erroring -- confirmed protocol-independent across
NFSv3 and NFSv4.
Fixes Issue: #14366
* Add missing error_policy wiring: VolumeInfo interface, VolumeObject, join
DAOs, LibvirtCreateCommandWrapper, LibvirtComputingResource
* Add missing error_policy test coverage and UI translation labels
* Fix error_policy mock matchers in clone integration test and brace
nesting in LibvirtVMDef disk rendering
* Address review feedback: drop underscore-prefixed fields, dedup
DiskCacheMode/DiskErrorPolicy via shared interface, use IDEMPOTENT_ADD_COLUMN,
fix spacing
* Deduplicate DiskErrorPolicy enum: use DiskOffering.DiskErrorPolicy
directly in KVM plugin instead of a separate copy
---
.../main/java/com/cloud/agent/api/to/VolumeTO.java | 9 ++
.../main/java/com/cloud/offering/DiskOffering.java | 19 ++++
api/src/main/java/com/cloud/vm/DiskProfile.java | 9 ++
.../org/apache/cloudstack/api/ApiConstants.java | 1 +
.../admin/offering/CreateDiskOfferingCmd.java | 10 ++
.../admin/offering/CreateServiceOfferingCmd.java | 10 ++
.../admin/offering/UpdateDiskOfferingCmd.java | 7 ++
.../api/response/DiskOfferingResponse.java | 12 +++
.../api/response/ServiceOfferingResponse.java | 8 ++
.../cloudstack/storage/to/VolumeObjectTO.java | 11 +++
.../engine/subsystem/api/storage/VolumeInfo.java | 3 +
.../java/com/cloud/storage/DiskOfferingVO.java | 14 +++
.../resources/META-INF/db/schema-42300to2400.sql | 2 +
.../META-INF/db/views/cloud.disk_offering_view.sql | 1 +
.../db/views/cloud.service_offering_view.sql | 1 +
.../META-INF/db/views/cloud.volume_view.sql | 1 +
.../cloudstack/storage/volume/VolumeObject.java | 7 ++
.../kvm/resource/LibvirtComputingResource.java | 5 +
.../kvm/resource/LibvirtDomainXMLParser.java | 9 ++
.../hypervisor/kvm/resource/LibvirtVMDef.java | 14 +++
.../wrapper/LibvirtCreateCommandWrapper.java | 1 +
.../kvm/storage/KVMStorageProcessor.java | 16 +++-
.../hypervisor/kvm/resource/LibvirtVMDefTest.java | 46 +++++++++
.../api/query/dao/DiskOfferingJoinDaoImpl.java | 1 +
.../api/query/dao/ServiceOfferingJoinDaoImpl.java | 1 +
.../com/cloud/api/query/vo/DiskOfferingJoinVO.java | 11 +++
.../cloud/api/query/vo/ServiceOfferingJoinVO.java | 7 ++
.../java/com/cloud/api/query/vo/VolumeJoinVO.java | 7 ++
.../configuration/ConfigurationManagerImpl.java | 76 ++++++++++++---
.../ConfigurationManagerCloneIntegrationTest.java | 20 ++--
.../configuration/ConfigurationManagerTest.java | 32 +++++--
test/integration/smoke/test_disk_offerings.py | 72 ++++++++++++++
test/integration/smoke/test_service_offerings.py | 103 +++++++++++++++++++++
ui/public/locales/en.json | 5 +
ui/src/components/offering/ComputeOfferingForm.vue | 14 +++
ui/src/components/offering/DiskOfferingForm.vue | 26 ++++++
ui/src/views/offering/AddComputeOffering.vue | 3 +
ui/src/views/offering/AddDiskOffering.vue | 1 +
ui/src/views/offering/CloneComputeOffering.vue | 7 ++
ui/src/views/offering/CloneDiskOffering.vue | 6 ++
40 files changed, 574 insertions(+), 34 deletions(-)
diff --git a/api/src/main/java/com/cloud/agent/api/to/VolumeTO.java
b/api/src/main/java/com/cloud/agent/api/to/VolumeTO.java
index 2ad5e0b26f7..6432f52a158 100644
--- a/api/src/main/java/com/cloud/agent/api/to/VolumeTO.java
+++ b/api/src/main/java/com/cloud/agent/api/to/VolumeTO.java
@@ -42,6 +42,7 @@ public class VolumeTO implements InternalIdentity {
private Long iopsReadRate;
private Long iopsWriteRate;
private String cacheMode;
+ private String errorPolicy;
private Long chainSize;
public VolumeTO(long id, Volume.Type type, StoragePoolType poolType,
String poolUuid, String name, String mountPoint, String path, long size, String
chainInfo) {
@@ -187,6 +188,14 @@ public class VolumeTO implements InternalIdentity {
return cacheMode;
}
+ public void setErrorPolicy(String errorPolicy) {
+ this.errorPolicy = errorPolicy;
+ }
+
+ public String getErrorPolicy() {
+ return errorPolicy;
+ }
+
public Long getChainSize() {
return chainSize;
}
diff --git a/api/src/main/java/com/cloud/offering/DiskOffering.java
b/api/src/main/java/com/cloud/offering/DiskOffering.java
index d74f5703cc9..2066c9858b6 100644
--- a/api/src/main/java/com/cloud/offering/DiskOffering.java
+++ b/api/src/main/java/com/cloud/offering/DiskOffering.java
@@ -51,6 +51,21 @@ public interface DiskOffering extends InfrastructureEntity,
Identity, InternalId
}
};
+ enum DiskErrorPolicy {
+ REPORT("report"), STOP("stop"), IGNORE("ignore"), ENOSPACE("enospace");
+
+ private final String diskErrorPolicy;
+
+ DiskErrorPolicy(String errorPolicy) {
+ diskErrorPolicy = errorPolicy;
+ }
+
+ @Override
+ public String toString() {
+ return diskErrorPolicy;
+ }
+ };
+
String getUniqueName();
boolean isUseLocalStorage();
@@ -145,6 +160,10 @@ public interface DiskOffering extends
InfrastructureEntity, Identity, InternalId
void setCacheMode(DiskCacheMode cacheMode);
+ DiskErrorPolicy getErrorPolicy();
+
+ void setErrorPolicy(DiskErrorPolicy errorPolicy);
+
boolean isComputeOnly();
boolean getDiskSizeStrictness();
diff --git a/api/src/main/java/com/cloud/vm/DiskProfile.java
b/api/src/main/java/com/cloud/vm/DiskProfile.java
index 766573d4d40..e76aece9363 100644
--- a/api/src/main/java/com/cloud/vm/DiskProfile.java
+++ b/api/src/main/java/com/cloud/vm/DiskProfile.java
@@ -42,6 +42,7 @@ public class DiskProfile {
private Long iopsReadRate;
private Long iopsWriteRate;
private String cacheMode;
+ private String errorPolicy;
private Long minIops;
private Long maxIops;
private boolean requiresEncryption;
@@ -238,6 +239,14 @@ public class DiskProfile {
return cacheMode;
}
+ public void setErrorPolicy(String errorPolicy) {
+ this.errorPolicy = errorPolicy;
+ }
+
+ public String getErrorPolicy() {
+ return errorPolicy;
+ }
+
public Long getMinIops() {
return minIops;
}
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index 7485029f22c..7b25db61247 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -1293,6 +1293,7 @@ public class ApiConstants {
public static final String LAST_UPDATED = "lastupdated";
public static final String PERFORM_FRESH_CHECKS = "performfreshchecks";
public static final String CACHE_MODE = "cachemode";
+ public static final String ERROR_POLICY = "errorpolicy";
public static final String CONSOLE_END_POINT = "consoleendpoint";
public static final String EXTERNAL_LOAD_BALANCER_IP_ADDRESS =
"externalloadbalanceripaddress";
diff --git
a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java
b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java
index e1ff90e4bd8..c6b21dcf180 100644
---
a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java
+++
b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java
@@ -154,6 +154,12 @@ public class CreateDiskOfferingCmd extends BaseCmd {
since = "4.14")
private String cacheMode;
+ @Parameter(name = ApiConstants.ERROR_POLICY,
+ type = CommandType.STRING,
+ description = "The disk error policy to use for this disk
offering. report, stop, ignore or enospace. Only applicable to KVM",
+ since = "24.0.0")
+ private String errorPolicy;
+
@Parameter(name = ApiConstants.STORAGE_POLICY, type = CommandType.UUID,
entityType = VsphereStoragePoliciesResponse.class,required = false, description
= "Name of the storage policy defined at vCenter, this is applicable only for
VMware", since = "4.15")
private Long storagePolicy;
@@ -297,6 +303,10 @@ public class CreateDiskOfferingCmd extends BaseCmd {
return cacheMode;
}
+ public String getErrorPolicy() {
+ return errorPolicy;
+ }
+
public Map<String, String> getDetails() {
Map<String, String> detailsMap = new HashMap<>();
if (MapUtils.isNotEmpty(details)) {
diff --git
a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java
b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java
index 4363d6861ba..5ea8e0e10a7 100644
---
a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java
+++
b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java
@@ -193,6 +193,12 @@ public class CreateServiceOfferingCmd extends BaseCmd {
since = "4.14")
private String cacheMode;
+ @Parameter(name = ApiConstants.ERROR_POLICY,
+ type = CommandType.STRING,
+ description = "The disk error policy to use for the root disk of
this compute offering. report, stop, ignore or enospace. Only applicable to
KVM",
+ since = "24.0.0")
+ private String errorPolicy;
+
// Introduce 4 new optional parameters to work custom compute offerings
@Parameter(name = ApiConstants.CUSTOMIZED,
type = CommandType.BOOLEAN,
@@ -475,6 +481,10 @@ public class CreateServiceOfferingCmd extends BaseCmd {
return cacheMode;
}
+ public String getErrorPolicy() {
+ return errorPolicy;
+ }
+
/**
* If customized parameter is true, then cpuNumber, memory and cpuSpeed
must be null
* Check if the optional params min/max CPU/Memory have been specified
diff --git
a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java
b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java
index 4d48327eeb7..f9f004592de 100644
---
a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java
+++
b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java
@@ -122,6 +122,9 @@ public class UpdateDiskOfferingCmd extends BaseCmd
implements DomainAndZoneIdRes
@Parameter(name = ApiConstants.CACHE_MODE, type = CommandType.STRING,
description = "The cache mode to use for this disk offering", since = "4.15")
private String cacheMode;
+ @Parameter(name = ApiConstants.ERROR_POLICY, type = CommandType.STRING,
description = "The disk error policy to use for this disk offering. Only
applicable to KVM", since = "24.0.0")
+ private String errorPolicy;
+
@Parameter(name = ApiConstants.STATE, type = CommandType.STRING,
description = "state of the disk offering")
private String diskOfferingState;
@@ -165,6 +168,10 @@ public class UpdateDiskOfferingCmd extends BaseCmd
implements DomainAndZoneIdRes
return cacheMode;
}
+ public String getErrorPolicy() {
+ return errorPolicy;
+ }
+
public Long getBytesReadRate() {
return bytesReadRate;
}
diff --git
a/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java
b/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java
index 528890ea5fa..68b7ac3b5b3 100644
---
a/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java
+++
b/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java
@@ -152,6 +152,10 @@ public class DiskOfferingResponse extends
BaseResponseWithAnnotations {
@Param(description = "The cache mode to use for this disk offering. none,
writeback or writethrough", since = "4.4")
private String cacheMode;
+ @SerializedName("errorpolicy")
+ @Param(description = "The disk error policy to use for this disk offering.
report, stop, ignore or enospace", since = "24.0.0")
+ private String errorPolicy;
+
@SerializedName("displayoffering")
@Param(description = "Whether to display the offering to the end user or
not.")
private Boolean displayOffering;
@@ -322,6 +326,14 @@ public class DiskOfferingResponse extends
BaseResponseWithAnnotations {
this.cacheMode = cacheMode;
}
+ public String getErrorPolicy() {
+ return errorPolicy;
+ }
+
+ public void setErrorPolicy(String errorPolicy) {
+ this.errorPolicy = errorPolicy;
+ }
+
public String getStorageType() {
return storageType;
}
diff --git
a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java
b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java
index 99c50829f04..c92d6757efb 100644
---
a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java
+++
b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java
@@ -200,6 +200,10 @@ public class ServiceOfferingResponse extends
BaseResponseWithAnnotations {
@Param(description = "The cache mode to use for this disk offering. none,
writeback, writethrough or hypervisor default", since = "4.14")
private String cacheMode;
+ @SerializedName("errorpolicy")
+ @Param(description = "The disk error policy to use for the root disk of
this compute offering. report, stop, ignore or enospace", since = "24.0.0")
+ private String errorPolicy;
+
@SerializedName("vspherestoragepolicy")
@Param(description = "The vsphere storage policy tagged to the service
offering in case of VMware", since = "4.15")
private String vsphereStoragePolicy;
@@ -553,6 +557,10 @@ public class ServiceOfferingResponse extends
BaseResponseWithAnnotations {
this.cacheMode = cacheMode;
}
+ public void setErrorPolicy(String errorPolicy) {
+ this.errorPolicy = errorPolicy;
+ }
+
public Integer getLeaseDuration() {
return leaseDuration;
}
diff --git
a/core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java
b/core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java
index df98149faab..7f742d2678d 100644
--- a/core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java
+++ b/core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java
@@ -27,6 +27,7 @@ import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.DataTO;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.offering.DiskOffering.DiskCacheMode;
+import com.cloud.offering.DiskOffering.DiskErrorPolicy;
import com.cloud.storage.MigrationOptions;
import com.cloud.storage.Storage;
import com.cloud.storage.Volume;
@@ -68,6 +69,7 @@ public class VolumeObjectTO extends DownloadableObjectTO
implements DataTO, Seri
private Long iopsWriteRateMax;
private Long iopsWriteRateMaxLength;
private DiskCacheMode cacheMode;
+ private DiskErrorPolicy errorPolicy;
private Hypervisor.HypervisorType hypervisorType;
private MigrationOptions migrationOptions;
private boolean directDownload;
@@ -119,6 +121,7 @@ public class VolumeObjectTO extends DownloadableObjectTO
implements DataTO, Seri
iopsWriteRateMaxLength = volume.getIopsWriteRateMaxLength();
hypervisorType = volume.getHypervisorType();
setCacheMode(volume.getCacheMode());
+ setErrorPolicy(volume.getErrorPolicy());
setDeviceId(volume.getDeviceId());
this.migrationOptions = volume.getMigrationOptions();
this.directDownload = volume.isDirectDownload();
@@ -355,6 +358,14 @@ public class VolumeObjectTO extends DownloadableObjectTO
implements DataTO, Seri
return cacheMode;
}
+ public void setErrorPolicy(DiskErrorPolicy errorPolicy) {
+ this.errorPolicy = errorPolicy;
+ }
+
+ public DiskErrorPolicy getErrorPolicy() {
+ return errorPolicy;
+ }
+
public MigrationOptions getMigrationOptions() {
return migrationOptions;
}
diff --git
a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeInfo.java
b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeInfo.java
index 4937edd33d1..f4c49caad03 100644
---
a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeInfo.java
+++
b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeInfo.java
@@ -21,6 +21,7 @@ package org.apache.cloudstack.engine.subsystem.api.storage;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.offering.DiskOffering.DiskCacheMode;
+import com.cloud.offering.DiskOffering.DiskErrorPolicy;
import com.cloud.storage.MigrationOptions;
import com.cloud.storage.Storage;
import com.cloud.storage.Volume;
@@ -79,6 +80,8 @@ public interface VolumeInfo extends DownloadableDataInfo,
Volume {
DiskCacheMode getCacheMode();
+ DiskErrorPolicy getErrorPolicy();
+
/**
* Currently available for KVM volumes
*/
diff --git a/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java
b/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java
index 7f6b6d8adf0..c27aea4bce3 100644
--- a/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java
+++ b/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java
@@ -137,6 +137,10 @@ public class DiskOfferingVO implements DiskOffering {
@Enumerated(value = EnumType.STRING)
private DiskCacheMode cacheMode;
+ @Column(name = "error_policy", updatable = true, nullable = false)
+ @Enumerated(value = EnumType.STRING)
+ private DiskErrorPolicy errorPolicy;
+
@Column(name = "provisioning_type")
Storage.ProvisioningType provisioningType;
@@ -288,6 +292,16 @@ public class DiskOfferingVO implements DiskOffering {
this.cacheMode = cacheMode;
}
+ @Override
+ public DiskErrorPolicy getErrorPolicy() {
+ return errorPolicy;
+ }
+
+ @Override
+ public void setErrorPolicy(DiskErrorPolicy errorPolicy) {
+ this.errorPolicy = errorPolicy;
+ }
+
@Override
public String getUniqueName() {
return uniqueName;
diff --git
a/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql
b/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql
index 3365f1f855b..9aaf0905786 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql
@@ -239,3 +239,5 @@ CREATE TABLE IF NOT EXISTS
`cloud`.`instance_boot_group_details` (
PRIMARY KEY (`id`),
CONSTRAINT `fk_instance_boot_group_details__group_id` FOREIGN KEY
(`boot_group_id`) REFERENCES `instance_boot_group` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.disk_offering', 'error_policy',
'VARCHAR(16) DEFAULT ''report'' COMMENT ''disk error policy
(report/stop/ignore/enospace) used by KVM hypervisor''');
diff --git
a/engine/schema/src/main/resources/META-INF/db/views/cloud.disk_offering_view.sql
b/engine/schema/src/main/resources/META-INF/db/views/cloud.disk_offering_view.sql
index dffaec575ce..65def2628ba 100644
---
a/engine/schema/src/main/resources/META-INF/db/views/cloud.disk_offering_view.sql
+++
b/engine/schema/src/main/resources/META-INF/db/views/cloud.disk_offering_view.sql
@@ -49,6 +49,7 @@ SELECT
`disk_offering`.`iops_write_rate_max` AS `iops_write_rate_max`,
`disk_offering`.`iops_write_rate_max_length` AS
`iops_write_rate_max_length`,
`disk_offering`.`cache_mode` AS `cache_mode`,
+ `disk_offering`.`error_policy` AS `error_policy`,
`disk_offering`.`sort_key` AS `sort_key`,
`disk_offering`.`compute_only` AS `compute_only`,
`disk_offering`.`display_offering` AS `display_offering`,
diff --git
a/engine/schema/src/main/resources/META-INF/db/views/cloud.service_offering_view.sql
b/engine/schema/src/main/resources/META-INF/db/views/cloud.service_offering_view.sql
index eb987af3ffb..768f324475e 100644
---
a/engine/schema/src/main/resources/META-INF/db/views/cloud.service_offering_view.sql
+++
b/engine/schema/src/main/resources/META-INF/db/views/cloud.service_offering_view.sql
@@ -53,6 +53,7 @@ SELECT
`disk_offering`.`iops_write_rate_max` AS `iops_write_rate_max`,
`disk_offering`.`iops_write_rate_max_length` AS
`iops_write_rate_max_length`,
`disk_offering`.`cache_mode` AS `cache_mode`,
+ `disk_offering`.`error_policy` AS `error_policy`,
`disk_offering`.`disk_size` AS `root_disk_size`,
`disk_offering`.`encrypt` AS `encrypt_root`,
`service_offering`.`cpu` AS `cpu`,
diff --git
a/engine/schema/src/main/resources/META-INF/db/views/cloud.volume_view.sql
b/engine/schema/src/main/resources/META-INF/db/views/cloud.volume_view.sql
index 8ba7e5c6df7..e08b5f83761 100644
--- a/engine/schema/src/main/resources/META-INF/db/views/cloud.volume_view.sql
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.volume_view.sql
@@ -82,6 +82,7 @@ SELECT
`disk_offering`.`iops_read_rate` AS `iops_read_rate`,
`disk_offering`.`iops_write_rate` AS `iops_write_rate`,
`disk_offering`.`cache_mode` AS `cache_mode`,
+ `disk_offering`.`error_policy` AS `error_policy`,
`storage_pool`.`id` AS `pool_id`,
`storage_pool`.`uuid` AS `pool_uuid`,
`storage_pool`.`name` AS `pool_name`,
diff --git
a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java
b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java
index de85067aae5..bc7399b34c2 100644
---
a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java
+++
b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java
@@ -67,6 +67,7 @@ import com.cloud.agent.api.to.DataTO;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.offering.DiskOffering.DiskCacheMode;
+import com.cloud.offering.DiskOffering.DiskErrorPolicy;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.Storage;
@@ -350,6 +351,12 @@ public class VolumeObject implements VolumeInfo {
return diskOfferingVO == null ? null : diskOfferingVO.getCacheMode();
}
+ @Override
+ public DiskErrorPolicy getErrorPolicy() {
+ DiskOfferingVO diskOfferingVO = getDiskOfferingVO();
+ return diskOfferingVO == null ? null : diskOfferingVO.getErrorPolicy();
+ }
+
@Override
public MigrationOptions getMigrationOptions() {
return migrationOptions;
diff --git
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
index acffa395494..dac79eabb75 100644
---
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
+++
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
@@ -16,6 +16,7 @@
// under the License.
package com.cloud.hypervisor.kvm.resource;
+import com.cloud.offering.DiskOffering;
import static com.cloud.host.Host.HOST_CDROM_MAX_COUNT;
import static com.cloud.host.Host.HOST_CPU_MAX_SPEED_MHZ;
import static com.cloud.host.Host.HOST_INSTANCE_CONVERSION;
@@ -3929,6 +3930,10 @@ public class LibvirtComputingResource extends
ServerResourceBase implements Serv
disk.setCacheMode(DiskDef.DiskCacheMode.valueOf(volumeObjectTO.getCacheMode().toString().toUpperCase()));
}
+ if (volumeObjectTO.getErrorPolicy() != null) {
+
disk.setErrorPolicy(DiskOffering.DiskErrorPolicy.valueOf(volumeObjectTO.getErrorPolicy().toString().toUpperCase()));
+ }
+
if (volumeObjectTO.requiresEncryption() &&
pool.getType().encryptionSupportMode() ==
Storage.EncryptionSupport.Hypervisor ) {
String secretUuid = createLibvirtVolumeSecret(conn,
volumeObjectTO.getPath(), volumeObjectTO.getPassphrase());
diff --git
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java
index 1a4d23af1c9..578793e3cab 100644
---
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java
+++
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java
@@ -16,6 +16,7 @@
// under the License.
package com.cloud.hypervisor.kvm.resource;
+import com.cloud.offering.DiskOffering;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
@@ -90,6 +91,7 @@ public class LibvirtDomainXMLParser {
if (type.equalsIgnoreCase("network")) {
String diskFmtType = getAttrValue("driver", "type", disk);
String diskCacheMode = getAttrValue("driver", "cache",
disk);
+ String diskErrorPolicy = getAttrValue("driver",
"error_policy", disk);
String diskPath = getAttrValue("source", "name", disk);
String protocol = getAttrValue("source", "protocol", disk);
String authUserName = getAttrValue("auth", "username",
disk);
@@ -114,9 +116,13 @@ public class LibvirtDomainXMLParser {
if (StringUtils.isNotBlank(diskCacheMode)) {
def.setCacheMode(DiskDef.DiskCacheMode.valueOf(diskCacheMode.toUpperCase()));
}
+ if (StringUtils.isNotBlank(diskErrorPolicy)) {
+
def.setErrorPolicy(DiskOffering.DiskErrorPolicy.valueOf(diskErrorPolicy.toUpperCase()));
+ }
} else {
String diskFmtType = getAttrValue("driver", "type", disk);
String diskCacheMode = getAttrValue("driver", "cache",
disk);
+ String diskErrorPolicy = getAttrValue("driver",
"error_policy", disk);
String diskFile = getAttrValue("source", "file", disk);
String diskDev = getAttrValue("source", "dev", disk);
@@ -141,6 +147,9 @@ public class LibvirtDomainXMLParser {
if (StringUtils.isNotBlank(diskCacheMode)) {
def.setCacheMode(DiskDef.DiskCacheMode.valueOf(diskCacheMode.toUpperCase()));
}
+ if (StringUtils.isNotBlank(diskErrorPolicy)) {
+
def.setErrorPolicy(DiskOffering.DiskErrorPolicy.valueOf(diskErrorPolicy.toUpperCase()));
+ }
}
NodeList iotune = disk.getElementsByTagName("iotune");
diff --git
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java
index 4a1aa0ee924..dcee9a97412 100644
---
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java
+++
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java
@@ -28,6 +28,7 @@ import java.util.List;
import java.util.Map;
import com.cloud.cpu.CPU;
+import com.cloud.offering.DiskOffering;
import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy;
import org.apache.cloudstack.utils.qemu.QemuObject;
import org.apache.commons.lang.StringEscapeUtils;
@@ -985,6 +986,7 @@ public class LibvirtVMDef {
private Long _iopsWriteRateMax;
private Long _iopsWriteRateMaxLength;
private DiskCacheMode _diskCacheMode;
+ private DiskOffering.DiskErrorPolicy _errorPolicy;
private String _serial;
private boolean qemuDriver = true;
private DiscardType _discard = DiscardType.IGNORE;
@@ -1340,6 +1342,14 @@ public class LibvirtVMDef {
return _diskCacheMode;
}
+ public void setErrorPolicy(DiskOffering.DiskErrorPolicy errorPolicy) {
+ _errorPolicy = errorPolicy;
+ }
+
+ public DiskOffering.DiskErrorPolicy getErrorPolicy() {
+ return _errorPolicy;
+ }
+
public void setQemuDriver(boolean qemuDriver){
this.qemuDriver = qemuDriver;
}
@@ -1403,6 +1413,10 @@ public class LibvirtVMDef {
diskBuilder.append("discard='" + _discard.toString() + "'
");
}
+ if (_errorPolicy != null) {
+ diskBuilder.append("error_policy='" + _errorPolicy + "' ");
+ }
+
if(ioDriver != null) {
diskBuilder.append(String.format("io='%s' ", ioDriver));
}
diff --git
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateCommandWrapper.java
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateCommandWrapper.java
index 5ec00889df9..d3276491ab2 100644
---
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateCommandWrapper.java
+++
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateCommandWrapper.java
@@ -76,6 +76,7 @@ public final class LibvirtCreateCommandWrapper extends
CommandWrapper<CreateComm
volume.setIopsReadRate(dskch.getIopsReadRate());
volume.setIopsWriteRate(dskch.getIopsWriteRate());
volume.setCacheMode(dskch.getCacheMode());
+ volume.setErrorPolicy(dskch.getErrorPolicy());
return new CreateAnswer(command, volume);
} catch (final CloudRuntimeException e) {
logger.debug("Failed to create volume: " + e.toString());
diff --git
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
index 29b23e6c645..235949502c7 100644
---
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
+++
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
@@ -18,6 +18,7 @@
*/
package com.cloud.hypervisor.kvm.storage;
+import com.cloud.offering.DiskOffering;
import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
import static com.cloud.utils.storage.S3.S3Utils.putFile;
@@ -1595,11 +1596,11 @@ public class KVMStorageProcessor implements
StorageProcessor {
final String serial, final
Long bytesReadRate, final Long bytesReadRateMax, final Long
bytesReadRateMaxLength,
final Long bytesWriteRate,
final Long bytesWriteRateMax, final Long bytesWriteRateMaxLength, final Long
iopsReadRate,
final Long iopsReadRateMax,
final Long iopsReadRateMaxLength, final Long iopsWriteRate, final Long
iopsWriteRateMax,
- final Long
iopsWriteRateMaxLength, final String cacheMode, final
DiskDef.LibvirtDiskEncryptDetails encryptDetails, Map<String, String> details,
Map<String, String> controllerInfo)
+ final Long
iopsWriteRateMaxLength, final String cacheMode, final String errorPolicy, final
DiskDef.LibvirtDiskEncryptDetails encryptDetails, Map<String, String> details,
Map<String, String> controllerInfo)
throws LibvirtException, InternalErrorException {
attachOrDetachDisk(conn, attach, vmName, attachingDisk, devId, serial,
bytesReadRate, bytesReadRateMax, bytesReadRateMaxLength,
bytesWriteRate, bytesWriteRateMax, bytesWriteRateMaxLength,
iopsReadRate, iopsReadRateMax, iopsReadRateMaxLength, iopsWriteRate,
- iopsWriteRateMax, iopsWriteRateMaxLength, cacheMode,
encryptDetails, 0l, details, controllerInfo);
+ iopsWriteRateMax, iopsWriteRateMaxLength, cacheMode,
errorPolicy, encryptDetails, 0l, details, controllerInfo);
}
/**
@@ -1634,7 +1635,7 @@ public class KVMStorageProcessor implements
StorageProcessor {
final String serial, final
Long bytesReadRate, final Long bytesReadRateMax, final Long
bytesReadRateMaxLength,
final Long bytesWriteRate,
final Long bytesWriteRateMax, final Long bytesWriteRateMaxLength, final Long
iopsReadRate,
final Long iopsReadRateMax,
final Long iopsReadRateMaxLength, final Long iopsWriteRate, final Long
iopsWriteRateMax,
- final Long
iopsWriteRateMaxLength, final String cacheMode, final
DiskDef.LibvirtDiskEncryptDetails encryptDetails,
+ final Long
iopsWriteRateMaxLength, final String cacheMode, final String errorPolicy, final
DiskDef.LibvirtDiskEncryptDetails encryptDetails,
long waitDetachDevice,
Map<String, String> details, Map<String, String> controllerInfo)
throws LibvirtException, InternalErrorException {
@@ -1761,6 +1762,9 @@ public class KVMStorageProcessor implements
StorageProcessor {
if (cacheMode != null) {
diskdef.setCacheMode(DiskDef.DiskCacheMode.valueOf(cacheMode.toUpperCase()));
}
+ if (errorPolicy != null) {
+
diskdef.setErrorPolicy(DiskOffering.DiskErrorPolicy.valueOf(errorPolicy.toUpperCase()));
+ }
diskdef.isIothreadsEnabled(details != null &&
details.containsKey(VmDetailConstants.IOTHREADS));
@@ -1843,13 +1847,14 @@ public class KVMStorageProcessor implements
StorageProcessor {
final KVMPhysicalDisk phyDisk =
storagePoolMgr.getPhysicalDisk(primaryStore.getPoolType(),
primaryStore.getUuid(), vol.getPath());
final String volCacheMode = vol.getCacheMode() == null ? null :
vol.getCacheMode().toString();
+ final String volErrorPolicy = vol.getErrorPolicy() == null ? null
: vol.getErrorPolicy().toString();
logger.debug(String.format("Attaching physical disk %s with format
%s", phyDisk.getPath(), phyDisk.getFormat()));
attachOrDetachDisk(conn, true, vmName, phyDisk,
disk.getDiskSeq().intValue(), serial,
vol.getBytesReadRate(), vol.getBytesReadRateMax(),
vol.getBytesReadRateMaxLength(),
vol.getBytesWriteRate(), vol.getBytesWriteRateMax(),
vol.getBytesWriteRateMaxLength(),
vol.getIopsReadRate(), vol.getIopsReadRateMax(),
vol.getIopsReadRateMaxLength(),
- vol.getIopsWriteRate(), vol.getIopsWriteRateMax(),
vol.getIopsWriteRateMaxLength(), volCacheMode,
+ vol.getIopsWriteRate(), vol.getIopsWriteRateMax(),
vol.getIopsWriteRateMaxLength(), volCacheMode, volErrorPolicy,
encryptDetails, disk.getDetails(),
cmd.getControllerInfo());
resource.recreateCheckpointsOnVm(List.of((VolumeObjectTO)
disk.getData()), vmName, conn);
@@ -1884,12 +1889,13 @@ public class KVMStorageProcessor implements
StorageProcessor {
final KVMPhysicalDisk phyDisk =
storagePoolMgr.getPhysicalDisk(primaryStore.getPoolType(),
primaryStore.getUuid(), vol.getPath());
final String volCacheMode = vol.getCacheMode() == null ? null :
vol.getCacheMode().toString();
+ final String volErrorPolicy = vol.getErrorPolicy() == null ? null
: vol.getErrorPolicy().toString();
attachOrDetachDisk(conn, false, vmName, phyDisk,
disk.getDiskSeq().intValue(), serial,
vol.getBytesReadRate(), vol.getBytesReadRateMax(),
vol.getBytesReadRateMaxLength(),
vol.getBytesWriteRate(), vol.getBytesWriteRateMax(),
vol.getBytesWriteRateMaxLength(),
vol.getIopsReadRate(), vol.getIopsReadRateMax(),
vol.getIopsReadRateMaxLength(),
- vol.getIopsWriteRate(), vol.getIopsWriteRateMax(),
vol.getIopsWriteRateMaxLength(), volCacheMode, null, waitDetachDevice, null,
null);
+ vol.getIopsWriteRate(), vol.getIopsWriteRateMax(),
vol.getIopsWriteRateMaxLength(), volCacheMode, volErrorPolicy, null,
waitDetachDevice, null, null);
storagePoolMgr.disconnectPhysicalDisk(primaryStore.getPoolType(),
primaryStore.getUuid(), vol.getPath());
diff --git
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java
index a51719a3326..9a1acf84745 100644
---
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java
+++
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java
@@ -19,6 +19,7 @@
package com.cloud.hypervisor.kvm.resource;
+import com.cloud.offering.DiskOffering;
import java.io.File;
import java.util.Arrays;
import java.util.List;
@@ -263,6 +264,51 @@ public class LibvirtVMDefTest extends TestCase {
assertEquals(expectedXml, xmlDef);
}
+ @Test
+ public void testDiskDefWithErrorPolicy() {
+ String filePath = "/var/lib/libvirt/images/disk.qcow2";
+ String diskLabel = "vda";
+
+ DiskDef disk = new DiskDef();
+ DiskDef.DiskBus bus = DiskDef.DiskBus.VIRTIO;
+ DiskDef.DiskFmtType type = DiskDef.DiskFmtType.QCOW2;
+ DiskDef.DiskCacheMode cacheMode = DiskDef.DiskCacheMode.WRITEBACK;
+ DiskOffering.DiskErrorPolicy errorPolicy =
DiskOffering.DiskErrorPolicy.STOP;
+
+ disk.defFileBasedDisk(filePath, diskLabel, bus, type);
+ disk.setCacheMode(cacheMode);
+ disk.setErrorPolicy(errorPolicy);
+
+ assertEquals(filePath, disk.getDiskPath());
+ assertEquals(diskLabel, disk.getDiskLabel());
+ assertEquals(bus, disk.getBusType());
+ assertEquals(DiskDef.DeviceType.DISK, disk.getDeviceType());
+ assertEquals(errorPolicy, disk.getErrorPolicy());
+
+ String xmlDef = disk.toString();
+ String expectedXml = "<disk device='disk' type='file'>\n<driver
name='qemu' type='" + type.toString() + "' cache='" + cacheMode.toString() + "'
error_policy='" + errorPolicy.toString() + "' />\n" +
+ "<source file='" + filePath + "'/>\n<target
dev='" + diskLabel + "' bus='" + bus.toString() + "'/>\n</disk>\n";
+
+ assertEquals(expectedXml, xmlDef);
+ }
+
+ @Test
+ public void testDiskDefWithoutErrorPolicy() {
+ String filePath = "/var/lib/libvirt/images/disk.qcow2";
+ String diskLabel = "vda";
+
+ DiskDef disk = new DiskDef();
+ DiskDef.DiskBus bus = DiskDef.DiskBus.VIRTIO;
+ DiskDef.DiskFmtType type = DiskDef.DiskFmtType.QCOW2;
+
+ disk.defFileBasedDisk(filePath, diskLabel, bus, type);
+
+ assertNull(disk.getErrorPolicy());
+
+ String xmlDef = disk.toString();
+ assertFalse(xmlDef.contains("error_policy"));
+ }
+
@Test
public void testDiskDefWithEncryption() {
String passphraseUuid = UUID.randomUUID().toString();
diff --git
a/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java
b/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java
index e53f94fa0c3..c79937db994 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java
@@ -137,6 +137,7 @@ public class DiskOfferingJoinDaoImpl extends
GenericDaoBase<DiskOfferingJoinVO,
diskOfferingResponse.setIopsWriteRateMax(offering.getIopsWriteRateMax());
diskOfferingResponse.setIopsWriteRateMaxLength(offering.getIopsWriteRateMaxLength());
diskOfferingResponse.setCacheMode(offering.getCacheMode());
+ diskOfferingResponse.setErrorPolicy(offering.getErrorPolicy());
diskOfferingResponse.setObjectName("diskoffering");
Map<String, String> offeringDetails =
ApiDBUtils.getResourceDetails(offering.getId(),
ResourceTag.ResourceObjectType.DiskOffering);
diskOfferingResponse.setDetails(offeringDetails);
diff --git
a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java
b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java
index 579425a68c1..a2c2029d57f 100644
---
a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java
+++
b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java
@@ -171,6 +171,7 @@ public class ServiceOfferingJoinDaoImpl extends
GenericDaoBase<ServiceOfferingJo
offeringResponse.setObjectName("serviceoffering");
offeringResponse.setIscutomized(offering.isDynamic());
offeringResponse.setCacheMode(offering.getCacheMode());
+ offeringResponse.setErrorPolicy(offering.getErrorPolicy());
offeringResponse.setDynamicScalingEnabled(offering.isDynamicScalingEnabled());
offeringResponse.setEncryptRoot(offering.getEncryptRoot());
diff --git
a/server/src/main/java/com/cloud/api/query/vo/DiskOfferingJoinVO.java
b/server/src/main/java/com/cloud/api/query/vo/DiskOfferingJoinVO.java
index 206c59f3a41..f490de01379 100644
--- a/server/src/main/java/com/cloud/api/query/vo/DiskOfferingJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/DiskOfferingJoinVO.java
@@ -118,6 +118,9 @@ public class DiskOfferingJoinVO extends BaseViewVO
implements InternalIdentity,
@Column(name = "cache_mode")
String cacheMode;
+ @Column(name = "error_policy")
+ String errorPolicy;
+
@Column(name = "compute_only")
boolean computeOnly;
@@ -229,6 +232,14 @@ public class DiskOfferingJoinVO extends BaseViewVO
implements InternalIdentity,
this.cacheMode = cacheMode;
}
+ public String getErrorPolicy() {
+ return errorPolicy;
+ }
+
+ public void setErrorPolicy(String errorPolicy) {
+ this.errorPolicy = errorPolicy;
+ }
+
public boolean isDisplayOffering() {
return displayOffering;
}
diff --git
a/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java
b/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java
index 1d86d14cf63..ab3dc44cad1 100644
--- a/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java
@@ -181,6 +181,9 @@ public class ServiceOfferingJoinVO extends BaseViewVO
implements InternalIdentit
@Column(name = "cache_mode")
String cacheMode;
+ @Column(name = "error_policy")
+ String errorPolicy;
+
@Column(name = "min_cpu")
Integer minCpu;
@@ -448,6 +451,10 @@ public class ServiceOfferingJoinVO extends BaseViewVO
implements InternalIdentit
return cacheMode;
}
+ public String getErrorPolicy() {
+ return errorPolicy;
+ }
+
public Integer getMinCpu() {
return minCpu;
}
diff --git a/server/src/main/java/com/cloud/api/query/vo/VolumeJoinVO.java
b/server/src/main/java/com/cloud/api/query/vo/VolumeJoinVO.java
index e18d49c9c31..3b65b3e5f0a 100644
--- a/server/src/main/java/com/cloud/api/query/vo/VolumeJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/VolumeJoinVO.java
@@ -222,6 +222,9 @@ public class VolumeJoinVO extends
BaseViewWithTagInformationVO implements Contro
@Column(name = "cache_mode")
String cacheMode;
+ @Column(name = "error_policy")
+ String errorPolicy;
+
@Column(name = "pool_id")
private long poolId;
@@ -502,6 +505,10 @@ public class VolumeJoinVO extends
BaseViewWithTagInformationVO implements Contro
return cacheMode;
}
+ public String getErrorPolicy() {
+ return errorPolicy;
+ }
+
public long getPoolId() {
return poolId;
}
diff --git
a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
index 17dbced5423..70ec6b3d8b0 100644
--- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
+++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
@@ -3450,6 +3450,9 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
// check if cache_mode parameter is valid
validateCacheMode(cmd.getCacheMode());
+ // check if error_policy parameter is valid
+ validateErrorPolicy(cmd.getErrorPolicy());
+
final Boolean offerHA = cmd.isOfferHa();
boolean localStorageRequired = false;
@@ -3552,7 +3555,7 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
cmd.getBytesWriteRate(), cmd.getBytesWriteRateMax(),
cmd.getBytesWriteRateMaxLength(),
cmd.getIopsReadRate(), cmd.getIopsReadRateMax(),
cmd.getIopsReadRateMaxLength(),
cmd.getIopsWriteRate(), cmd.getIopsWriteRateMax(),
cmd.getIopsWriteRateMaxLength(),
- cmd.getHypervisorSnapshotReserve(), cmd.getCacheMode(),
storagePolicyId, cmd.getDynamicScalingEnabled(), diskOfferingId,
+ cmd.getHypervisorSnapshotReserve(), cmd.getCacheMode(),
cmd.getErrorPolicy(), storagePolicyId, cmd.getDynamicScalingEnabled(),
diskOfferingId,
cmd.getDiskOfferingStrictness(), cmd.isCustomized(),
cmd.getEncryptRoot(), vgpuProfileId, gpuCount, cmd.getGpuDisplay(),
cmd.isPurgeResources(), leaseDuration, leaseExpiryAction);
}
@@ -3581,7 +3584,7 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
Long bytesWriteRate,
Long bytesWriteRateMax, Long bytesWriteRateMaxLength,
Long iopsReadRate, Long
iopsReadRateMax, Long iopsReadRateMaxLength,
Long iopsWriteRate, Long
iopsWriteRateMax, Long iopsWriteRateMaxLength,
- final Integer
hypervisorSnapshotReserve, String cacheMode, final Long storagePolicyID,
+ final Integer
hypervisorSnapshotReserve, String cacheMode, String errorPolicy, final Long
storagePolicyID,
final boolean
dynamicScalingEnabled, final Long diskOfferingId, final boolean
diskOfferingStrictness,
final boolean
isCustomized, final boolean encryptRoot, Long vgpuProfileId, Integer gpuCount,
Boolean gpuDisplay, final boolean purgeResources, Integer leaseDuration,
VMLeaseManager.ExpiryAction leaseExpiryAction) {
@@ -3678,7 +3681,7 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
bytesWriteRate, bytesWriteRateMax, bytesWriteRateMaxLength,
iopsReadRate, iopsReadRateMax, iopsReadRateMaxLength,
iopsWriteRate, iopsWriteRateMax, iopsWriteRateMaxLength,
- hypervisorSnapshotReserve, cacheMode, storagePolicyID,
encryptRoot);
+ hypervisorSnapshotReserve, cacheMode, errorPolicy,
storagePolicyID, encryptRoot);
} else {
diskOffering = _diskOfferingDao.findById(diskOfferingId);
String diskStoragePolicyId =
diskOfferingDetailsDao.getDetail(diskOfferingId, ApiConstants.STORAGE_POLICY);
@@ -3761,7 +3764,7 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
Long bytesWriteRate,
Long bytesWriteRateMax, Long bytesWriteRateMaxLength,
Long iopsReadRate, Long
iopsReadRateMax, Long iopsReadRateMaxLength,
Long iopsWriteRate, Long
iopsWriteRateMax, Long iopsWriteRateMaxLength,
- final Integer
hypervisorSnapshotReserve, String cacheMode, final Long storagePolicyID,
boolean encrypt) {
+ final Integer
hypervisorSnapshotReserve, String cacheMode, String errorPolicy, final Long
storagePolicyID, boolean encrypt) {
DiskOfferingVO diskOffering = new DiskOfferingVO(name, displayText,
typedProvisioningType, false, tags, false, localStorageRequired, false);
@@ -3810,6 +3813,10 @@ public class ConfigurationManagerImpl extends
ManagerBase implements Configurati
diskOffering.setCacheMode(DiskOffering.DiskCacheMode.valueOf(cacheMode.toUpperCase()));
}
+ if (errorPolicy != null) {
+
diskOffering.setErrorPolicy(DiskOffering.DiskErrorPolicy.valueOf(errorPolicy.toUpperCase()));
+ }
+
if (hypervisorSnapshotReserve != null && hypervisorSnapshotReserve <
0) {
throw new InvalidParameterValueException("If provided, Hypervisor
Snapshot Reserve must be greater than or equal to 0.");
}
@@ -3956,6 +3963,9 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
if (cmd.getCacheMode() != null) {
validateCacheMode(cmd.getCacheMode());
}
+ if (cmd.getErrorPolicy() != null) {
+ validateErrorPolicy(cmd.getErrorPolicy());
+ }
final Integer finalGpuCount =
validateVgpuProfileAndGetGpuCount(vgpuProfileId, gpuCount);
final Map<String, String> mergedDetails = mergeOfferingDetails(cmd,
sourceOffering, customParams);
@@ -3976,7 +3986,7 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
diskParams.bytesWriteRate, diskParams.bytesWriteRateMax,
diskParams.bytesWriteRateMaxLength,
diskParams.iopsReadRate, diskParams.iopsReadRateMax,
diskParams.iopsReadRateMaxLength,
diskParams.iopsWriteRate, diskParams.iopsWriteRateMax,
diskParams.iopsWriteRateMaxLength,
- diskParams.hypervisorSnapshotReserve, diskParams.cacheMode,
customParams.storagePolicy, dynamicScalingEnabled,
+ diskParams.hypervisorSnapshotReserve, diskParams.cacheMode,
diskParams.errorPolicy, customParams.storagePolicy, dynamicScalingEnabled,
diskOfferingId, diskOfferingStrictness, isCustomized,
encryptRoot,
vgpuProfileId, finalGpuCount, gpuDisplay, purgeResources,
leaseParams.leaseDuration, leaseParams.leaseExpiryAction);
}
@@ -4066,6 +4076,12 @@ public class ConfigurationManagerImpl extends
ManagerBase implements Configurati
params.cacheMode = sourceDiskOffering.getCacheMode().toString();
}
+ if (cmd.getErrorPolicy() != null) {
+ params.errorPolicy = cmd.getErrorPolicy();
+ } else if (sourceDiskOffering != null &&
sourceDiskOffering.getErrorPolicy() != null) {
+ params.errorPolicy =
sourceDiskOffering.getErrorPolicy().toString();
+ }
+
return params;
}
@@ -4178,6 +4194,7 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
Long maxIops;
Integer hypervisorSnapshotReserve;
String cacheMode;
+ String errorPolicy;
}
private static class CustomOfferingParams {
@@ -4223,6 +4240,7 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
final Integer hypervisorSnapshotReserve =
getOrDefault(cmd.getHypervisorSnapshotReserve(),
sourceOffering.getHypervisorSnapshotReserve());
final String cacheMode = resolveCacheMode(cmd, sourceOffering);
+ final String errorPolicy = resolveErrorPolicy(cmd, sourceOffering);
final Long storagePolicy = resolveStoragePolicyForDiskOffering(cmd,
sourceOffering);
final Map<String, String> mergedDetails =
mergeDiskOfferingDetails(cmd, sourceOffering);
@@ -4230,6 +4248,9 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
if (cmd.getCacheMode() != null) {
validateCacheMode(cmd.getCacheMode());
}
+ if (cmd.getErrorPolicy() != null) {
+ validateErrorPolicy(cmd.getErrorPolicy());
+ }
validateMaxRateEqualsOrGreater(iopsParams.iopsReadRate,
iopsParams.iopsReadRateMax, IOPS_READ_RATE);
validateMaxRateEqualsOrGreater(iopsParams.iopsWriteRate,
iopsParams.iopsWriteRateMax, IOPS_WRITE_RATE);
@@ -4244,7 +4265,7 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
rateParams.bytesWriteRate, rateParams.bytesWriteRateMax,
rateParams.bytesWriteRateMaxLength,
iopsParams.iopsReadRate, iopsParams.iopsReadRateMax,
iopsParams.iopsReadRateMaxLength,
iopsParams.iopsWriteRate, iopsParams.iopsWriteRateMax,
iopsParams.iopsWriteRateMaxLength,
- hypervisorSnapshotReserve, cacheMode, mergedDetails,
storagePolicy, diskSizeStrictness, encrypt);
+ hypervisorSnapshotReserve, cacheMode, errorPolicy,
mergedDetails, storagePolicy, diskSizeStrictness, encrypt);
}
private DiskOfferingVO getAndValidateSourceDiskOffering(Long
sourceOfferingId) {
@@ -4288,6 +4309,16 @@ public class ConfigurationManagerImpl extends
ManagerBase implements Configurati
return null;
}
+ private String resolveErrorPolicy(CloneDiskOfferingCmd cmd, DiskOfferingVO
sourceOffering) {
+ if (cmd.getErrorPolicy() != null) {
+ return cmd.getErrorPolicy();
+ }
+ if (sourceOffering.getErrorPolicy() != null) {
+ return sourceOffering.getErrorPolicy().toString();
+ }
+ return null;
+ }
+
private Long resolveStoragePolicyForDiskOffering(CloneDiskOfferingCmd cmd,
DiskOfferingVO sourceOffering) {
Long storagePolicy = cmd.getStoragePolicy();
if (storagePolicy == null) {
@@ -4596,7 +4627,7 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
Long bytesWriteRate, Long
bytesWriteRateMax, Long bytesWriteRateMaxLength,
Long iopsReadRate, Long
iopsReadRateMax, Long iopsReadRateMaxLength,
Long iopsWriteRate, Long
iopsWriteRateMax, Long iopsWriteRateMaxLength,
- final Integer
hypervisorSnapshotReserve, String cacheMode, final Map<String, String> details,
final Long storagePolicyID,
+ final Integer
hypervisorSnapshotReserve, String cacheMode, String errorPolicy, final
Map<String, String> details, final Long storagePolicyID,
final boolean
diskSizeStrictness, final boolean encrypt) {
long diskSize = 0;// special case for custom disk offerings
long maxVolumeSizeInGb =
VolumeOrchestrationService.MaxVolumeSize.value();
@@ -4675,6 +4706,10 @@ public class ConfigurationManagerImpl extends
ManagerBase implements Configurati
newDiskOffering.setCacheMode(DiskOffering.DiskCacheMode.valueOf(cacheMode.toUpperCase()));
}
+ if (errorPolicy != null) {
+
newDiskOffering.setErrorPolicy(DiskOffering.DiskErrorPolicy.valueOf(errorPolicy.toUpperCase()));
+ }
+
if (hypervisorSnapshotReserve != null && hypervisorSnapshotReserve <
0) {
throw new InvalidParameterValueException("If provided, Hypervisor
Snapshot Reserve must be greater than or equal to 0.");
}
@@ -4759,6 +4794,9 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
// check if cache_mode parameter is valid
validateCacheMode(cmd.getCacheMode());
+ // check if error_policy parameter is valid
+ validateErrorPolicy(cmd.getErrorPolicy());
+
boolean localStorageRequired = false;
final String storageType = cmd.getStorageType();
if (storageType != null) {
@@ -4792,6 +4830,7 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
final Long iopsWriteRateMaxLength = cmd.getIopsWriteRateMaxLength();
final Integer hypervisorSnapshotReserve =
cmd.getHypervisorSnapshotReserve();
final String cacheMode = cmd.getCacheMode();
+ final String errorPolicy = cmd.getErrorPolicy();
final boolean encrypt = cmd.getEncrypt();
validateMaxRateEqualsOrGreater(iopsReadRate, iopsReadRateMax,
IOPS_READ_RATE);
@@ -4806,7 +4845,7 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
localStorageRequired, isDisplayOfferingEnabled,
isCustomizedIops, minIops,
maxIops, bytesReadRate, bytesReadRateMax,
bytesReadRateMaxLength, bytesWriteRate, bytesWriteRateMax,
bytesWriteRateMaxLength,
iopsReadRate, iopsReadRateMax, iopsReadRateMaxLength,
iopsWriteRate, iopsWriteRateMax, iopsWriteRateMaxLength,
- hypervisorSnapshotReserve, cacheMode, details,
storagePolicyId, diskSizeStrictness, encrypt);
+ hypervisorSnapshotReserve, cacheMode, errorPolicy, details,
storagePolicyId, diskSizeStrictness, encrypt);
}
/**
@@ -4885,6 +4924,7 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
Long iopsWriteRateMax = cmd.getIopsWriteRateMax();
Long iopsWriteRateMaxLength = cmd.getIopsWriteRateMaxLength();
String cacheMode = cmd.getCacheMode();
+ String errorPolicy = cmd.getErrorPolicy();
DiskOffering.State state = cmd.getState();
// Check if diskOffering exists
@@ -4939,7 +4979,7 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
throw new InvalidParameterValueException(String.format("Unable to
update disk offering: %s by id user: %s because it is not root-admin or
domain-admin", diskOfferingHandle, user));
}
- boolean updateNeeded = shouldUpdateDiskOffering(name, displayText,
sortKey, displayDiskOffering, tags, cacheMode, state) ||
+ boolean updateNeeded = shouldUpdateDiskOffering(name, displayText,
sortKey, displayDiskOffering, tags, cacheMode, errorPolicy, state) ||
shouldUpdateIopsRateParameters(iopsReadRate, iopsReadRateMax,
iopsReadRateMaxLength, iopsWriteRate, iopsWriteRateMax, iopsWriteRateMaxLength)
||
shouldUpdateBytesRateParameters(bytesReadRate,
bytesReadRateMax, bytesReadRateMaxLength, bytesWriteRate, bytesWriteRateMax,
bytesWriteRateMaxLength);
@@ -4967,6 +5007,11 @@ public class ConfigurationManagerImpl extends
ManagerBase implements Configurati
diskOffering.setCacheMode(DiskOffering.DiskCacheMode.valueOf(cacheMode.toUpperCase()));
}
+ if (errorPolicy != null) {
+ validateErrorPolicy(errorPolicy);
+
diskOffering.setErrorPolicy(DiskOffering.DiskErrorPolicy.valueOf(errorPolicy.toUpperCase()));
+ }
+
if (state != null) {
diskOffering.setState(state);
}
@@ -5193,8 +5238,8 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
* Check if it needs to update any parameter when updateDiskoffering is
called
* Verify if name or displayText are not blank, tags is not null, sortkey
and displayDiskOffering is not null
*/
- protected boolean shouldUpdateDiskOffering(String name, String
displayText, Integer sortKey, Boolean displayDiskOffering, String tags, String
cacheMode, DiskOffering.State state) {
- return !StringUtils.isAllBlank(name, displayText, cacheMode) || tags
!= null || sortKey != null || displayDiskOffering != null || state != null;
+ protected boolean shouldUpdateDiskOffering(String name, String
displayText, Integer sortKey, Boolean displayDiskOffering, String tags, String
cacheMode, String errorPolicy, DiskOffering.State state) {
+ return !StringUtils.isAllBlank(name, displayText, cacheMode,
errorPolicy) || tags != null || sortKey != null || displayDiskOffering != null
|| state != null;
}
protected boolean shouldUpdateBytesRateParameters(Long bytesReadRate, Long
bytesReadRateMax, Long bytesReadRateMaxLength, Long bytesWriteRate, Long
bytesWriteRateMax, Long bytesWriteRateMaxLength) {
@@ -9421,6 +9466,15 @@ public class ConfigurationManagerImpl extends
ManagerBase implements Configurati
}
}
+ protected void validateErrorPolicy(String errorPolicy){
+ if (errorPolicy != null &&
+ !Enums.getIfPresent(DiskOffering.DiskErrorPolicy.class,
+ errorPolicy.toUpperCase()).isPresent()) {
+ throw new InvalidParameterValueException(String.format("Invalid
error policy (%s). Please specify one of the following " +
+ "valid error policy parameters: report, stop, ignore or
enospace.", errorPolicy));
+ }
+ }
+
public List<SecurityChecker> getSecChecker() {
return _secChecker;
}
diff --git
a/server/src/test/java/com/cloud/configuration/ConfigurationManagerCloneIntegrationTest.java
b/server/src/test/java/com/cloud/configuration/ConfigurationManagerCloneIntegrationTest.java
index 67d43bf6933..33093a60d0c 100644
---
a/server/src/test/java/com/cloud/configuration/ConfigurationManagerCloneIntegrationTest.java
+++
b/server/src/test/java/com/cloud/configuration/ConfigurationManagerCloneIntegrationTest.java
@@ -164,7 +164,7 @@ public class ConfigurationManagerCloneIntegrationTest {
anyLong(), anyString(), anyBoolean(), anyBoolean(), anyBoolean(),
any(),
anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(),
anyLong(),
anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(),
anyLong(),
- anyInt(), anyString(), any(), anyLong(), anyBoolean(),
anyBoolean());
+ anyInt(), anyString(), anyString(), any(), anyLong(),
anyBoolean(), anyBoolean());
// User/Account DAO stubs used by createDiskOffering
@@ -250,7 +250,7 @@ public class ConfigurationManagerCloneIntegrationTest {
anyBoolean(), anyBoolean(), anyBoolean(), anyString(), anyList(),
anyList(), anyString(), any(Integer.class),
anyString(), anyMap(), anyLong(), any(Boolean.class),
anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(),
anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(),
anyLong(),
- any(Integer.class), anyString(), anyLong(), anyBoolean(),
anyLong(), anyBoolean(), anyBoolean(), anyBoolean(),
+ any(Integer.class), anyString(), anyString(), anyLong(),
anyBoolean(), anyLong(), anyBoolean(), anyBoolean(), anyBoolean(),
anyLong(), any(Integer.class), any(Boolean.class), anyBoolean(),
any(Integer.class), any(VMLeaseManager.ExpiryAction.class)
);
@@ -326,7 +326,7 @@ public class ConfigurationManagerCloneIntegrationTest {
anyString(), anyList(), anyList(), anyString(), anyInt(),
anyString(), any(),
anyLong(), anyBoolean(), anyLong(), anyLong(), anyLong(),
anyLong(), anyLong(),
anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(),
anyLong(),
- anyLong(), anyLong(), anyInt(), anyString(), anyLong(),
anyBoolean(), anyLong(),
+ anyLong(), anyLong(), anyInt(), anyString(), anyString(),
anyLong(), anyBoolean(), anyLong(),
anyBoolean(), anyBoolean(), anyBoolean(), anyLong(), anyInt(),
anyBoolean(),
anyBoolean(), anyInt(), any());
@@ -395,7 +395,7 @@ public class ConfigurationManagerCloneIntegrationTest {
anyLong(), anyString(), anyBoolean(), anyBoolean(), anyBoolean(),
any(),
anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(),
anyLong(),
anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(),
anyLong(),
- anyInt(), anyString(), any(), anyLong(), anyBoolean(),
anyBoolean());
+ anyInt(), anyString(), anyString(), any(), anyLong(),
anyBoolean(), anyBoolean());
DiskOffering result = configurationManager.cloneDiskOffering(cmd);
@@ -451,7 +451,7 @@ public class ConfigurationManagerCloneIntegrationTest {
anyLong(), eq("tag1,tag2"), anyBoolean(), anyBoolean(),
anyBoolean(), any(),
eq(100L), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(),
anyLong(),
anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(),
anyLong(),
- anyInt(), anyString(), any(), anyLong(), anyBoolean(),
anyBoolean());
+ anyInt(), anyString(), anyString(), any(), anyLong(),
anyBoolean(), anyBoolean());
DiskOffering result = configurationManager.cloneDiskOffering(cmd);
@@ -504,7 +504,7 @@ public class ConfigurationManagerCloneIntegrationTest {
anyLong(), anyString(), anyBoolean(), anyBoolean(), anyBoolean(),
any(),
anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(),
anyLong(),
anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(),
anyLong(),
- anyInt(), anyString(), any(), anyLong(), anyBoolean(),
anyBoolean());
+ anyInt(), anyString(), anyString(), any(), anyLong(),
anyBoolean(), anyBoolean());
DiskOffering result = configurationManager.cloneDiskOffering(cmd);
@@ -551,7 +551,7 @@ public class ConfigurationManagerCloneIntegrationTest {
anyString(), anyList(), anyList(), anyString(), anyInt(),
anyString(), any(),
anyLong(), anyBoolean(), anyLong(), anyLong(), anyLong(),
anyLong(), anyLong(),
anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(),
anyLong(),
- anyLong(), anyLong(), anyInt(), anyString(), anyLong(),
anyBoolean(), anyLong(),
+ anyLong(), anyLong(), anyInt(), anyString(), anyString(),
anyLong(), anyBoolean(), anyLong(),
anyBoolean(), anyBoolean(), anyBoolean(), anyLong(), anyInt(),
anyBoolean(),
anyBoolean(), anyInt(), any());
@@ -597,7 +597,7 @@ public class ConfigurationManagerCloneIntegrationTest {
anyString(), eq(50L), eq("production,ssd"), anyBoolean(),
anyBoolean(),
anyBoolean(), any(), eq(1000L), eq(5000L), anyLong(), anyLong(),
anyLong(),
anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(),
anyLong(),
- anyLong(), anyLong(), eq(20), anyString(), any(), anyLong(),
anyBoolean(),
+ anyLong(), anyLong(), eq(20), anyString(), anyString(), any(),
anyLong(), anyBoolean(),
eq(true));
DiskOffering result = configurationManager.cloneDiskOffering(cmd);
@@ -651,7 +651,7 @@ public class ConfigurationManagerCloneIntegrationTest {
eq("production,high-performance"), anyBoolean(), anyBoolean(),
anyBoolean(),
any(), eq(2000L), eq(10000L), anyLong(), anyLong(), anyLong(),
anyLong(),
anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(),
anyLong(),
- anyLong(), anyInt(), anyString(), any(), anyLong(), anyBoolean(),
eq(true));
+ anyLong(), anyInt(), anyString(), anyString(), any(), anyLong(),
anyBoolean(), eq(true));
DiskOffering result = configurationManager.cloneDiskOffering(cmd);
@@ -706,7 +706,7 @@ public class ConfigurationManagerCloneIntegrationTest {
anyLong(), anyString(), anyBoolean(), anyBoolean(), anyBoolean(),
any(),
anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(),
anyLong(),
anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(),
anyLong(),
- anyInt(), anyString(), any(), anyLong(), anyBoolean(),
anyBoolean());
+ anyInt(), anyString(), anyString(), any(), anyLong(),
anyBoolean(), anyBoolean());
DiskOffering result = configurationManager.cloneDiskOffering(cmd);
diff --git
a/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java
b/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java
index 24616435645..41f2d564661 100644
--- a/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java
+++ b/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java
@@ -1088,18 +1088,34 @@ public class ConfigurationManagerTest {
@Test
public void shouldUpdateDiskOfferingTests(){
-
assertTrue(configurationMgr.shouldUpdateDiskOffering(Mockito.anyString(),
Mockito.anyString(), Mockito.anyInt(), Mockito.anyBoolean(),
Mockito.anyString(), Mockito.anyString(),
Mockito.any(DiskOffering.State.class)));
-
assertTrue(configurationMgr.shouldUpdateDiskOffering(Mockito.anyString(),
nullable(String.class), nullable(Integer.class), nullable(Boolean.class),
nullable(String.class), nullable(String.class),
nullable(DiskOffering.State.class)));
-
assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class),
nullable(String.class), nullable(Integer.class), nullable(Boolean.class),
nullable(String.class), nullable(String.class),
Mockito.any(DiskOffering.State.class)));
-
assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class),
Mockito.anyString(), nullable(Integer.class), nullable(Boolean.class),
nullable(String.class), nullable(String.class),
nullable(DiskOffering.State.class)));
-
assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class),
nullable(String.class), Mockito.anyInt(), nullable(Boolean.class),
nullable(String.class), nullable(String.class),
nullable(DiskOffering.State.class)));
-
assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class),
nullable(String.class), nullable(int.class), Mockito.anyBoolean(),
nullable(String.class), nullable(String.class),
nullable(DiskOffering.State.class)));
-
assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class),
nullable(String.class), nullable(int.class), nullable(Boolean.class),
Mockito.anyString(), Mockito.anyString(), nullable(DiskOffering.State.class)));
+
assertTrue(configurationMgr.shouldUpdateDiskOffering(Mockito.anyString(),
Mockito.anyString(), Mockito.anyInt(), Mockito.anyBoolean(),
Mockito.anyString(), Mockito.anyString(), nullable(String.class),
Mockito.any(DiskOffering.State.class)));
+
assertTrue(configurationMgr.shouldUpdateDiskOffering(Mockito.anyString(),
nullable(String.class), nullable(Integer.class), nullable(Boolean.class),
nullable(String.class), nullable(String.class), nullable(String.class),
nullable(DiskOffering.State.class)));
+
assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class),
nullable(String.class), nullable(Integer.class), nullable(Boolean.class),
nullable(String.class), nullable(String.class), nullable(String.class),
Mockito.any(DiskOffering.State.class)));
+
assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class),
Mockito.anyString(), nullable(Integer.class), nullable(Boolean.class),
nullable(String.class), nullable(String.class), nullable(String.class),
nullable(DiskOffering.State.class)));
+
assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class),
nullable(String.class), Mockito.anyInt(), nullable(Boolean.class),
nullable(String.class), nullable(String.class), nullable(String.class),
nullable(DiskOffering.State.class)));
+
assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class),
nullable(String.class), nullable(int.class), Mockito.anyBoolean(),
nullable(String.class), nullable(String.class), nullable(String.class),
nullable(DiskOffering.State.class)));
+
assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class),
nullable(String.class), nullable(int.class), nullable(Boolean.class),
Mockito.anyString(), Mockito.anyString(), nullable(String.class),
nullable(DiskOffering.State.class)));
+
assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class),
nullable(String.class), nullable(int.class), nullable(Boolean.class),
nullable(String.class), nullable(String.class), Mockito.anyString(),
nullable(DiskOffering.State.class)));
}
@Test
public void shouldUpdateDiskOfferingTestFalse(){
- assertFalse(configurationMgr.shouldUpdateDiskOffering(null, null,
null, null, null, null, null));
+ assertFalse(configurationMgr.shouldUpdateDiskOffering(null, null,
null, null, null, null, null, null));
+ }
+
+ @Test
+ public void testValidateErrorPolicy() {
+ configurationMgr.validateErrorPolicy(null);
+ configurationMgr.validateErrorPolicy("report");
+ configurationMgr.validateErrorPolicy("stop");
+ configurationMgr.validateErrorPolicy("ignore");
+ configurationMgr.validateErrorPolicy("enospace");
+ configurationMgr.validateErrorPolicy("REPORT");
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void testValidateErrorPolicyInvalid() {
+ configurationMgr.validateErrorPolicy("invalid_error_policy_type");
}
@Test
diff --git a/test/integration/smoke/test_disk_offerings.py
b/test/integration/smoke/test_disk_offerings.py
index dc23a52a026..20b20ab8147 100644
--- a/test/integration/smoke/test_disk_offerings.py
+++ b/test/integration/smoke/test_disk_offerings.py
@@ -342,6 +342,78 @@ class TestCreateDiskOffering(cloudstackTestCase):
)
return
+ @attr(tags=["advanced", "basic", "eip", "sg", "advancedns", "smoke"],
required_hardware="false")
+ def test_09_create_disk_offering_with_error_policy_type(self):
+ """Test to create disk offering with each one of the valid error
policy types : report, stop, ignore and enospace
+
+ # Validate the following:
+ # 1. createDiskOfferings should return valid info for new offering
+ # 2. The Cloud Database contains the valid information
+ """
+ error_policy_types=["report", "stop", "ignore", "enospace"]
+ for i in range(4):
+ disk_offering = DiskOffering.create(
+ self.apiclient,
+ self.services["disk_offering"],
+ errorPolicy=error_policy_types[i]
+ )
+ self.cleanup.append(disk_offering)
+
+ self.debug("Created Disk offering with valid errorPolicy param
with ID: %s" % disk_offering.id)
+
+ list_disk_response = list_disk_offering(
+ self.apiclient,
+ id=disk_offering.id
+ )
+ self.assertEqual(
+ isinstance(list_disk_response, list),
+ True,
+ "Check list response returns a valid list"
+ )
+ self.assertNotEqual(
+ len(list_disk_response),
+ 0,
+ "Check Disk offering is created"
+ )
+ disk_response = list_disk_response[0]
+
+ self.assertEqual(
+ disk_response.displaytext,
+ self.services["disk_offering"]["displaytext"],
+ "Check server id in createServiceOffering"
+ )
+ self.assertEqual(
+ disk_response.name,
+ self.services["disk_offering"]["name"],
+ "Check name in createServiceOffering"
+ )
+ self.assertEqual(
+ disk_response.errorpolicy,
+ error_policy_types[i],
+ "Check errorPolicy in createServiceOffering"
+ )
+
+ return
+
+ @attr(tags=["advanced", "basic", "eip", "sg", "advancedns", "smoke"],
required_hardware="false")
+ def test_10_create_disk_offering_with_invalid_error_policy_type(self):
+ """Test to create disk offering with invalid errorPolicy type
+
+ # Validate the following:
+ # 1. createDiskOfferings should return valid info for new offering
+ # 2. The Cloud Database contains the valid information
+ """
+
+ with self.assertRaises(Exception):
+ disk_offering = DiskOffering.create(
+ self.apiclient,
+ self.services["disk_offering"],
+ errorPolicy="invalid_error_policy_type"
+ )
+
+
+ return
+
class TestDiskOfferings(cloudstackTestCase):
def setUp(self):
diff --git a/test/integration/smoke/test_service_offerings.py
b/test/integration/smoke/test_service_offerings.py
index a523cd0e698..69746e714ea 100644
--- a/test/integration/smoke/test_service_offerings.py
+++ b/test/integration/smoke/test_service_offerings.py
@@ -514,6 +514,109 @@ class TestCreateServiceOffering(cloudstackTestCase):
value=value):
self.fail(f'instance.lease.enabled should be: {value}')
+ @attr(
+ tags=[
+ "advanced",
+ "advancedns",
+ "smoke",
+ "basic",
+ "eip",
+ "sg"],
+ required_hardware="false")
+ def test_09_create_service_offering_with_error_policy_type(self):
+ """Test to create service offering with each one of the valid error
policy types : report, stop, ignore and enospace"""
+
+ # Validate the following:
+ # 1. createServiceOfferings should return a valid information
+ # for newly created offering
+ # 2. The Cloud Database contains the valid information
+
+ error_policy_types=["report", "stop", "ignore", "enospace"]
+ for i in range(4):
+ service_offering = ServiceOffering.create(
+ self.apiclient,
+ self.services["service_offerings"]["tiny"],
+ errorPolicy=error_policy_types[i]
+ )
+ self.cleanup.append(service_offering)
+
+ self.debug(
+ "Created service offering with ID: %s" %
+ service_offering.id)
+
+ list_service_response = list_service_offering(
+ self.apiclient,
+ id=service_offering.id
+ )
+ self.assertEqual(
+ isinstance(list_service_response, list),
+ True,
+ "Check list response returns a valid list"
+ )
+
+ self.assertNotEqual(
+ len(list_service_response),
+ 0,
+ "Check Service offering is created"
+ )
+
+ self.assertEqual(
+ list_service_response[0].cpunumber,
+ self.services["service_offerings"]["tiny"]["cpunumber"],
+ "Check server id in createServiceOffering"
+ )
+ self.assertEqual(
+ list_service_response[0].cpuspeed,
+ self.services["service_offerings"]["tiny"]["cpuspeed"],
+ "Check cpuspeed in createServiceOffering"
+ )
+ self.assertEqual(
+ list_service_response[0].displaytext,
+ self.services["service_offerings"]["tiny"]["displaytext"],
+ "Check server displaytext in createServiceOfferings"
+ )
+ self.assertEqual(
+ list_service_response[0].memory,
+ self.services["service_offerings"]["tiny"]["memory"],
+ "Check memory in createServiceOffering"
+ )
+ self.assertEqual(
+ list_service_response[0].name,
+ self.services["service_offerings"]["tiny"]["name"],
+ "Check name in createServiceOffering"
+ )
+ self.assertEqual(
+ list_service_response[0].errorpolicy,
+ error_policy_types[i],
+ "Check errorPolicy in createServiceOffering"
+ )
+ return
+
+ @attr(
+ tags=[
+ "advanced",
+ "advancedns",
+ "smoke",
+ "basic",
+ "eip",
+ "sg"],
+ required_hardware="false")
+ def test_10_create_service_offering_with_invalid_error_policy_type(self):
+ """Test to create service offering with invalid error policy type"""
+
+ # Validate the following:
+ # 1. createServiceOfferings should return a valid information
+ # for newly created offering
+ # 2. The Cloud Database contains the valid information
+
+ with self.assertRaises(Exception):
+ service_offering = ServiceOffering.create(
+ self.apiclient,
+ self.services["service_offerings"]["tiny"],
+ errorPolicy="invalid_error_policy_type"
+ )
+ return
+
class TestServiceOfferings(cloudstackTestCase):
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 0acafc3b801..c791e5096ab 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -1121,6 +1121,11 @@
"label.entitytype": "Entity Type",
"label.error": "Error",
"label.errormessage": "Error message",
+"label.errorpolicy": "Disk error policy",
+"label.errorpolicy.report": "Report",
+"label.errorpolicy.stop": "Stop",
+"label.errorpolicy.ignore": "Ignore",
+"label.errorpolicy.enospace": "No space",
"label.error.caught": "Error caught",
"label.error.code": "Error code",
"label.error.file.read": "Cannot read file.",
diff --git a/ui/src/components/offering/ComputeOfferingForm.vue
b/ui/src/components/offering/ComputeOfferingForm.vue
index 0e293895f3f..1226541cde2 100644
--- a/ui/src/components/offering/ComputeOfferingForm.vue
+++ b/ui/src/components/offering/ComputeOfferingForm.vue
@@ -401,6 +401,17 @@
<a-radio-button value="hypervisor_default">{{
$t('label.hypervisor.default') }}</a-radio-button>
</a-radio-group>
</a-form-item>
+ <a-form-item name="errorpolicy" ref="errorpolicy">
+ <template #label>
+ <tooltip-label :title="$t('label.errorpolicy')"
:tooltip="apiParams.errorpolicy.description"/>
+ </template>
+ <a-radio-group v-model:value="form.errorpolicy" buttonStyle="solid"
@change="selected => { handleErrorPolicyChange(selected.target.value) }">
+ <a-radio-button value="report">{{ $t('label.errorpolicy.report')
}}</a-radio-button>
+ <a-radio-button value="stop">{{ $t('label.errorpolicy.stop')
}}</a-radio-button>
+ <a-radio-button value="ignore">{{ $t('label.errorpolicy.ignore')
}}</a-radio-button>
+ <a-radio-button value="enospace">{{
$t('label.errorpolicy.enospace') }}</a-radio-button>
+ </a-radio-group>
+ </a-form-item>
<a-form-item :label="$t('label.qostype')" name="qostype" ref="qostype">
<a-radio-group v-model:value="form.qostype" buttonStyle="solid"
@change="selected => { handleQosTypeChange(selected.target.value) }">
<a-radio-button value="">{{ $t('label.none') }}</a-radio-button>
@@ -616,6 +627,7 @@ export default {
storagetype: 'shared',
provisioningtype: 'thin',
cachemode: 'none',
+ errorpolicy: 'report',
qostype: '',
iscustomizeddiskiops: false,
diskofferingid: null,
@@ -629,6 +641,7 @@ export default {
storageType: 'shared',
provisioningType: 'thin',
cacheMode: 'none',
+ errorPolicy: 'report',
offeringType: 'fixed',
isCustomizedDiskIops: false,
isPublic: true,
@@ -785,6 +798,7 @@ export default {
handleStorageTypeChange (val) { this.storageType = val },
handleProvisioningTypeChange (val) { this.provisioningType = val },
handleCacheModeChange (val) { this.cacheMode = val },
+ handleErrorPolicyChange (val) { this.errorPolicy = val },
handleComputeOfferingTypeChange (val) { this.offeringType = val },
handleQosTypeChange (val) { this.qosType = val },
handleDeploymentPlannerChange (planner) { this.selectedDeploymentPlanner =
planner; this.plannerModeVisible = false; if (this.selectedDeploymentPlanner
=== 'ImplicitDedicationPlanner') this.plannerModeVisible = isAdmin() },
diff --git a/ui/src/components/offering/DiskOfferingForm.vue
b/ui/src/components/offering/DiskOfferingForm.vue
index f3f39647fef..d8847b2e89c 100644
--- a/ui/src/components/offering/DiskOfferingForm.vue
+++ b/ui/src/components/offering/DiskOfferingForm.vue
@@ -213,6 +213,28 @@
</a-radio-button>
</a-radio-group>
</a-form-item>
+ <a-form-item name="errorpolicy" ref="errorpolicy">
+ <template #label>
+ <tooltip-label :title="$t('label.errorpolicy')"
:tooltip="apiParams.errorpolicy.description"/>
+ </template>
+ <a-radio-group
+ v-model:value="form.errorpolicy"
+ buttonStyle="solid"
+ @change="selected => { handleErrorPolicyChange(selected.target.value)
}">
+ <a-radio-button value="report">
+ {{ $t('label.errorpolicy.report') }}
+ </a-radio-button>
+ <a-radio-button value="stop">
+ {{ $t('label.errorpolicy.stop') }}
+ </a-radio-button>
+ <a-radio-button value="ignore">
+ {{ $t('label.errorpolicy.ignore') }}
+ </a-radio-button>
+ <a-radio-button value="enospace">
+ {{ $t('label.errorpolicy.enospace') }}
+ </a-radio-button>
+ </a-radio-group>
+ </a-form-item>
<a-form-item v-if="isAdmin() || isDomainAdminAllowedToInformTags"
name="tags" ref="tags">
<template #label>
<tooltip-label :title="$t('label.storagetags')"
:tooltip="apiParams.tags.description"/>
@@ -350,6 +372,7 @@ export default {
provisioningtype: 'thin',
customdisksize: true,
writecachetype: 'none',
+ errorpolicy: 'report',
qostype: '',
ispublic: true,
disksizestrictness: false,
@@ -422,6 +445,9 @@ export default {
handleWriteCacheTypeChange (val) {
this.form.writecachetype = val
},
+ handleErrorPolicyChange (val) {
+ this.form.errorpolicy = val
+ },
isDomainAdmin () {
return ['DomainAdmin'].includes(this.$store.getters.userInfo.roletype)
},
diff --git a/ui/src/views/offering/AddComputeOffering.vue
b/ui/src/views/offering/AddComputeOffering.vue
index 72a63ef43f9..a3649d3ec04 100644
--- a/ui/src/views/offering/AddComputeOffering.vue
+++ b/ui/src/views/offering/AddComputeOffering.vue
@@ -79,6 +79,7 @@ export default {
storageType: 'shared',
provisioningType: 'thin',
cacheMode: 'none',
+ errorPolicy: 'report',
offeringType: 'fixed',
isCustomizedDiskIops: false,
isPublic: true,
@@ -155,6 +156,7 @@ export default {
storagetype: this.storageType,
provisioningtype: this.provisioningType,
cachemode: this.cacheMode,
+ errorpolicy: this.errorPolicy,
qostype: this.qosType,
iscustomizeddiskiops: this.isCustomizedDiskIops,
diskofferingid: this.selectedDiskOfferingId,
@@ -344,6 +346,7 @@ export default {
storagetype: values.storagetype,
provisioningtype: values.provisioningtype,
cachemode: values.cachemode,
+ errorpolicy: values.errorpolicy,
customized: values.offeringtype !== 'fixed',
offerha: values.offerha === true,
limitcpuuse: values.limitcpuuse === true,
diff --git a/ui/src/views/offering/AddDiskOffering.vue
b/ui/src/views/offering/AddDiskOffering.vue
index 0013425d106..ac825bc9f18 100644
--- a/ui/src/views/offering/AddDiskOffering.vue
+++ b/ui/src/views/offering/AddDiskOffering.vue
@@ -74,6 +74,7 @@ export default {
displaytext: values.displaytext,
storageType: values.storagetype,
cacheMode: values.writecachetype,
+ errorpolicy: values.errorpolicy,
provisioningType: values.provisioningtype,
customized: values.customdisksize,
disksizestrictness: values.disksizestrictness,
diff --git a/ui/src/views/offering/CloneComputeOffering.vue
b/ui/src/views/offering/CloneComputeOffering.vue
index 07ce8852add..d17085fa85a 100644
--- a/ui/src/views/offering/CloneComputeOffering.vue
+++ b/ui/src/views/offering/CloneComputeOffering.vue
@@ -102,6 +102,7 @@ export default {
storageType: 'shared',
provisioningType: 'thin',
cacheMode: 'none',
+ errorPolicy: 'report',
offeringType: 'fixed',
isCustomizedDiskIops: false,
isPublic: true,
@@ -178,6 +179,7 @@ export default {
storagetype: this.storageType,
provisioningtype: this.provisioningType,
cachemode: this.cacheMode,
+ errorpolicy: this.errorPolicy,
qostype: this.qosType,
iscustomizeddiskiops: this.isCustomizedDiskIops,
diskofferingid: this.selectedDiskOfferingId,
@@ -316,6 +318,10 @@ export default {
this.cacheMode = r.cachemode
this.form.cachemode = r.cachemode
}
+ if (r.errorpolicy) {
+ this.errorPolicy = r.errorpolicy
+ this.form.errorpolicy = r.errorpolicy
+ }
if (r.diskofferingstrictness !== undefined) {
this.form.diskofferingstrictness = r.diskofferingstrictness
@@ -487,6 +493,7 @@ export default {
storagetype: values.storagetype,
provisioningtype: values.provisioningtype,
cachemode: values.cachemode,
+ errorpolicy: values.errorpolicy,
customized: values.offeringtype !== 'fixed',
offerha: values.offerha === true,
limitcpuuse: values.limitcpuuse === true,
diff --git a/ui/src/views/offering/CloneDiskOffering.vue
b/ui/src/views/offering/CloneDiskOffering.vue
index 3e5ddd6ce84..e6e2406255c 100644
--- a/ui/src/views/offering/CloneDiskOffering.vue
+++ b/ui/src/views/offering/CloneDiskOffering.vue
@@ -102,6 +102,9 @@ export default {
if (r.cachemode) {
this.form.writecachetype = r.cachemode
}
+ if (r.errorpolicy) {
+ this.form.errorpolicy = r.errorpolicy
+ }
if (r.disksizestrictness !== undefined) {
this.form.disksizestrictness = r.disksizestrictness
@@ -158,6 +161,9 @@ export default {
if (values.writecachetype) {
params.cachemode = values.writecachetype
}
+ if (values.errorpolicy) {
+ params.errorpolicy = values.errorpolicy
+ }
if (values.provisioningtype) {
params.provisioningtype = values.provisioningtype
}