Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1007,16 +1007,19 @@ private TemplateInfo createManagedTemplateVolume(TemplateInfo srcTemplateInfo, P
} else {
errMesg = callback.result.getResult();
}
templateOnPrimary.processEvent(Event.OperationFailed);
Comment thread
rajiv-jain-netapp marked this conversation as resolved.
throw new CloudRuntimeException(String.format("Unable to create template %s on primary storage %s: %s", templateOnPrimary.getImage(), destPrimaryDataStore, errMesg));
}

templateOnPrimary.processEvent(Event.OperationSucceeded);

} catch (Throwable e) {
logger.debug("Failed to create template volume on storage", e);
templateOnPrimary.processEvent(Event.OperationFailed);
throw new CloudRuntimeException(e.getMessage());
try {
templateOnPrimary.processEvent(Event.OperationFailed);
} catch (Exception stateEx) {
logger.warn("Unable to mark template {} as failed on primary storage {}: {}", templateOnPrimary.getImage(), destPrimaryDataStore, stateEx.getMessage());
}
throw new CloudRuntimeException(e.getMessage(), e);
} finally {
_tmpltPoolDao.releaseFromLockTable(templatePoolRefId);
}
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ public class FileCloneRequest {
@JsonProperty("overwrite_destination")
private Boolean overwriteDestination;

/**
* Optional FlexVolume snapshot to clone from. When set, ONTAP clones {@code source_path}
* as it existed in that snapshot rather than from the live file/LUN.
*
* <p>Used by create-volume-from-snapshot (same FlexVol). Omitted for live template-cache clones.</p>
*/
@JsonProperty("snapshot")
private SnapshotRef snapshot;

public FileCloneRequest() {
}

Expand All @@ -55,6 +64,14 @@ public FileCloneRequest(String flexVolUuid, String flexVolName, String sourcePat
this.destinationPath = destinationPath;
}

public FileCloneRequest(String flexVolUuid, String flexVolName, String sourcePath, String destinationPath,
String snapshotName) {
this(flexVolUuid, flexVolName, sourcePath, destinationPath);
if (snapshotName != null && !snapshotName.isEmpty()) {
this.snapshot = new SnapshotRef(snapshotName);
}
}

public VolumeRef getVolume() {
return volume;
}
Expand Down Expand Up @@ -87,6 +104,14 @@ public void setOverwriteDestination(Boolean overwriteDestination) {
this.overwriteDestination = overwriteDestination;
}

public SnapshotRef getSnapshot() {
return snapshot;
}

public void setSnapshot(SnapshotRef snapshot) {
this.snapshot = snapshot;
}

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class VolumeRef {
Expand Down Expand Up @@ -122,10 +147,37 @@ public void setName(String name) {
}
}

/**
* Snapshot identity for {@code POST /api/storage/file/clone} when cloning from a FlexVol snapshot.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class SnapshotRef {

@JsonProperty("name")
private String name;

public SnapshotRef() {
}

public SnapshotRef(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

@Override
public String toString() {
return "FileCloneRequest{volume=" + (volume != null ? volume.getUuid() : null)
+ ", sourcePath=" + sourcePath
+ ", destinationPath=" + destinationPath + "}";
+ ", destinationPath=" + destinationPath
+ ", snapshot=" + (snapshot != null ? snapshot.getName() : null) + "}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
import feign.FeignException;

import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;

/**
* Storage Strategy represents the communication path for all the ONTAP storage options
*
Expand Down Expand Up @@ -823,6 +822,14 @@ abstract public CloudStackVolume createTemplateCache(StoragePoolVO storagePool,
* <p>ONTAP requires the source and the destination to live in the same FlexVolume, which
* holds because a CloudStack primary storage pool maps one-to-one onto a FlexVolume.</p>
*
* <p>The source may be an active object or one inside a FlexVolume snapshot:</p>
* <ul>
* <li><b>SAN</b> — {@code lun.clone.source.name} set to
* {@code /vol/&lt;fv&gt;/.snapshot/&lt;snap&gt;/&lt;lun&gt;}</li>
* <li><b>NAS</b> — {@link CloudStackVolume#getSnapshotName()} set; it is sent as
* {@code snapshot.name} on {@code POST /api/storage/file/clone}</li>
* </ul>
*
* @param cloudstackVolume describes the clone to create; the source is carried in the
* protocol-specific clone reference (for SAN, {@code lun.clone.source})
* @return the created CloudStackVolume, populated with the backend identity of the clone
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ public void deleteCloudStackVolume(CloudStackVolume cloudstackVolume) {
* Clones a file inside the FlexVolume using ONTAP's file clone API.
*
* <p>The source is taken from {@code file.path} and the destination from
* {@code destinationPath}, both relative to the root of the FlexVolume backing the pool.</p>
* {@code destinationPath}, both relative to the root of the FlexVolume backing the pool.
* When {@code snapshotName} is set, the source is read from that FlexVolume snapshot.</p>
*/
@Override
public CloudStackVolume cloneCloudStackVolume(CloudStackVolume cloudstackVolume) {
Expand All @@ -157,14 +158,16 @@ public CloudStackVolume cloneCloudStackVolume(CloudStackVolume cloudstackVolume)
String flexVolUuid = details.get(OntapStorageConstants.VOLUME_UUID);
String flexVolName = details.get(OntapStorageConstants.VOLUME_NAME);
if (flexVolUuid == null || flexVolUuid.isEmpty()) {
throw new CloudRuntimeException("Failed to clone file, FlexVolume uuid is missing from pool details");
throw new CloudRuntimeException("Failed to clone file, FlexVolume uuid is missing from pool poolDetails");
}
String sourcePath = cloudstackVolume.getFile().getPath();
String destinationPath = cloudstackVolume.getDestinationPath();
String snapshotName = cloudstackVolume.getSnapshotName();

logger.info("cloneCloudStackVolume: Cloning file [{}] to [{}] in FlexVol [{}]", sourcePath, destinationPath, flexVolName);
logger.info("cloneCloudStackVolume: Cloning file [{}] to [{}] in FlexVol [{}] from snapshot [{}]",
sourcePath, destinationPath, flexVolName, snapshotName);
try {
FileCloneRequest request = new FileCloneRequest(flexVolUuid, flexVolName, sourcePath, destinationPath);
FileCloneRequest request = new FileCloneRequest(flexVolUuid, flexVolName, sourcePath, destinationPath, snapshotName);
JobResponse jobResponse = nasFeignClient.cloneFile(getAuthHeader(), request);
pollJobIfPresent(jobResponse, "clone file [" + sourcePath + "] to [" + destinationPath + "]");

Expand All @@ -177,6 +180,7 @@ public CloudStackVolume cloneCloudStackVolume(CloudStackVolume cloudstackVolume)
clonedCloudStackVolume.setFile(clonedFile);
clonedCloudStackVolume.setDatastoreId(cloudstackVolume.getDatastoreId());
clonedCloudStackVolume.setVolumeInfo(cloudstackVolume.getVolumeInfo());
clonedCloudStackVolume.setSnapshotName(snapshotName);
return clonedCloudStackVolume;
} catch (FeignException e) {
logger.error("FeignException occurred while cloning file [{}], Status: {}, Exception: {}",
Expand Down Expand Up @@ -266,7 +270,7 @@ public AccessGroup createAccessGroup(AccessGroup accessGroup) {
logger.info("createAccessGroup: ExportPolicy created: {}, now attaching this policy to storage pool volume", createdPolicy.getName());
// attach export policy to volume of storage pool
assignExportPolicyToVolume(volumeUUID,createdPolicy.getName());
// save the export policy details in storage pool details
// save the export policy poolDetails in storage pool poolDetails
storagePoolDetailsDao.addDetail(accessGroup.getStoragePoolId(), OntapStorageConstants.EXPORT_POLICY_ID, String.valueOf(createdPolicy.getId()), true);
storagePoolDetailsDao.addDetail(accessGroup.getStoragePoolId(), OntapStorageConstants.EXPORT_POLICY_NAME, createdPolicy.getName(), true);
logger.info("Successfully assigned exportPolicy {} to volume {}", policyRequest.getName(), volumeName);
Expand Down Expand Up @@ -327,7 +331,7 @@ public AccessGroup updateAccessGroup(AccessGroup accessGroup) {

Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(accessGroup.getStoragePoolId());
if (details == null || details.isEmpty()) {
throw new CloudRuntimeException("No storage pool details found for storagePoolId: " + accessGroup.getStoragePoolId());
throw new CloudRuntimeException("No storage pool poolDetails found for storagePoolId: " + accessGroup.getStoragePoolId());
}
String exportPolicyId = details.get(OntapStorageConstants.EXPORT_POLICY_ID);
if (exportPolicyId == null || exportPolicyId.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ public CloudStackVolume createTemplateCache(StoragePoolVO storagePool, TemplateI
Map<String, String> details, long sizeInBytes) {
if (sizeInBytes <= 0) {
throw new CloudRuntimeException("Unknown virtual size for template [" + templateInfo.getId()
+ "]; cannot size the template LUN on pool [" + storagePool.getId() + "]");
+ "]; cannot size the template LUN on pool [" + storagePool.getId() + "]. The template size in vm_template"
+ " is unset; verify the template was registered/seeded with its virtual size (virtualsize in"
+ " template.properties on secondary storage).");
}

CloudStackVolume request = buildTemplateLunRequest(storagePool, details, templateInfo.getId(), sizeInBytes);
Expand Down Expand Up @@ -260,7 +262,6 @@ public CloudStackVolume cloneCloudStackVolume(CloudStackVolume cloudstackVolume)
}
}


/**
* Ensures ONTAP returned a usable LUN identity from create/clone. Callers in the datastore
* driver rely on non-null name and uuid, so reject incomplete records at the Feign boundary.
Expand Down Expand Up @@ -340,10 +341,10 @@ public CloudStackVolume getCloudStackVolume(Map<String, String> values) {
return null;
}
logger.error("FeignException occurred while fetching Lun, Status: {}, Exception: {}", e.status(), e.getMessage());
throw new CloudRuntimeException("Failed to fetch Lun details: " + e.getMessage());
throw new CloudRuntimeException("Failed to fetch Lun poolDetails: " + e.getMessage());
} catch (Exception e) {
logger.error("Exception occurred while fetching Lun, Exception: {}", e.getMessage());
throw new CloudRuntimeException("Failed to fetch Lun details: " + e.getMessage());
throw new CloudRuntimeException("Failed to fetch Lun poolDetails: " + e.getMessage());
}
}

Expand All @@ -354,9 +355,9 @@ public AccessGroup createAccessGroup(AccessGroup accessGroup) {
logger.error("createAccessGroup: Igroup creation failed. Invalid request: {}", accessGroup);
throw new CloudRuntimeException("Failed to create Igroup, invalid request");
}
// Get StoragePool details
// Get StoragePool poolDetails
if (accessGroup.getStoragePoolId() == null) {
throw new CloudRuntimeException("Failed to create Igroup, invalid datastore details in the request");
throw new CloudRuntimeException("Failed to create Igroup, invalid datastore poolDetails in the request");
}
if (accessGroup.getHostsToConnect() == null || accessGroup.getHostsToConnect().isEmpty()) {
throw new CloudRuntimeException("Failed to create Igroup, no hosts to connect provided in the request");
Expand All @@ -365,7 +366,7 @@ public AccessGroup createAccessGroup(AccessGroup accessGroup) {
String igroupName = null;
try {
Map<String, String> dataStoreDetails = storagePoolDetailsDao.listDetailsKeyPairs(accessGroup.getStoragePoolId());
logger.trace("createAccessGroup: Successfully fetched datastore details.");
logger.trace("createAccessGroup: Successfully fetched datastore poolDetails.");

// Generate Igroup request
Igroup igroupRequest = new Igroup();
Expand Down Expand Up @@ -441,9 +442,9 @@ public void deleteAccessGroup(AccessGroup accessGroup) {
logger.error("deleteAccessGroup: Igroup deletion failed. Invalid request: {}", accessGroup);
throw new CloudRuntimeException("Failed to delete Igroup, invalid request");
}
// Get StoragePool details
// Get StoragePool poolDetails
if (accessGroup.getStoragePoolId() == null) {
throw new CloudRuntimeException("Failed to delete Igroup, invalid datastore details in the request");
throw new CloudRuntimeException("Failed to delete Igroup, invalid datastore poolDetails in the request");
}
try {
String authHeader = OntapStorageUtils.generateAuthHeader(storage.getUsername(), storage.getPassword());
Expand Down Expand Up @@ -550,10 +551,10 @@ public AccessGroup getAccessGroup(Map<String, String> values) {
return null;
}
logger.error("FeignException occurred while fetching Igroup, Status: {}, Exception: {}", e.status(), e.getMessage());
throw new CloudRuntimeException("Failed to fetch Igroup details: " + e.getMessage());
throw new CloudRuntimeException("Failed to fetch Igroup poolDetails: " + e.getMessage());
} catch (Exception e) {
logger.error("Exception occurred while fetching Igroup, Exception: {}", e.getMessage());
throw new CloudRuntimeException("Failed to fetch Igroup details: " + e.getMessage());
throw new CloudRuntimeException("Failed to fetch Igroup poolDetails: " + e.getMessage());
}
}

Expand Down Expand Up @@ -599,7 +600,7 @@ public String enableLogicalAccess(Map<String, String> values) {
throw feignEx;
}
}
// Get the LunMap details
// Get the LunMap poolDetails
OntapResponse<LunMap> lunMapResponse = null;
try {
lunMapResponse = sanFeignClient.getLunMapResponse(authHeader,
Expand All @@ -614,12 +615,12 @@ public String enableLogicalAccess(Map<String, String> values) {
lunNumber = lunMapResponse.getRecords().get(0).getLogicalUnitNumber().toString();

} else {
logger.error("enableLogicalAccess: Failed to fetch LunMap details for Lun: {} and igroup: {}. LunMap response is null or empty.", lunName, igroupName);
throw new CloudRuntimeException("Failed to fetch LunMap details for Lun: " + lunName + " and igroup: " + igroupName);
logger.error("enableLogicalAccess: Failed to fetch LunMap poolDetails for Lun: {} and igroup: {}. LunMap response is null or empty.", lunName, igroupName);
throw new CloudRuntimeException("Failed to fetch LunMap poolDetails for Lun: " + lunName + " and igroup: " + igroupName);
}
} catch (Exception e) {
logger.error("enableLogicalAccess: Failed to fetch LunMap details for Lun: {} and igroup: {}, Exception: {}", lunName, igroupName, e);
throw new CloudRuntimeException("Failed to fetch LunMap details for Lun: " + lunName + " and igroup: " + igroupName);
logger.error("enableLogicalAccess: Failed to fetch LunMap poolDetails for Lun: {} and igroup: {}, Exception: {}", lunName, igroupName, e);
throw new CloudRuntimeException("Failed to fetch LunMap poolDetails for Lun: " + lunName + " and igroup: " + igroupName);
}
logger.trace("enableLogicalAccess: LunMap created successfully, LunMap: {}", lunMapResponse.getRecords().get(0));
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ public class CloudStackVolume {
*/
private String destinationPath;

/**
* ONTAP FlexVolume snapshot name when cloning a new file/LUN from a snapshot
* (create-volume-from-snapshot). Null for live clones (e.g. template cache).
*/
private String snapshotName;

private DataObject volumeInfo; // This is needed as we need DataObject to be passed to agent to create volume

public FileInfo getFile() {
Expand Down Expand Up @@ -101,4 +107,12 @@ public void setDestinationPath(String destinationPath) {
this.destinationPath = destinationPath;
}

public String getSnapshotName() {
return snapshotName;
}

public void setSnapshotName(String snapshotName) {
this.snapshotName = snapshotName;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ public class OntapStorageConstants {

public static final String VOLUME_PATH_PREFIX = "/vol/";

/**
* Path segment inserted after the FlexVol name when identifying a LUN inside a FlexVol snapshot
* for {@code POST /api/storage/luns} clone ({@code /vol/&lt;fv&gt;/.snapshot/&lt;snap&gt;/&lt;lun&gt;}).
*/
public static final String SNAPSHOT_PATH_SEGMENT = "/.snapshot/";

public static final String ONTAP_NAME_REGEX = "^[a-zA-Z][a-zA-Z0-9_]*$";
public static final String KVM = "KVM";

Expand All @@ -115,6 +121,13 @@ public class OntapStorageConstants {
public static final String VOLUME_PATH = "volume_path";
public static final String PRIMARY_POOL_ID = "primary_pool_id";
public static final String ONTAP_SNAP_SIZE = "ontap_snap_size";
/**
* Optional {@code snapshot_details} keys: min/max IOPS from the source volume at take-snapshot
* time. Persisted only when the volume has configured values; applied to volumes created from
* the snapshot in a later change (see TODO on create-from-snapshot).
*/
public static final String MIN_IOPS = "min_iops";
public static final String MAX_IOPS = "max_iops";
public static final String FILE_PATH = "file_path";
public static final int MAX_SNAPSHOT_NAME_LENGTH = 255;
public static final String ONTAP_TEMP_CG_PREFIX = "cs-temp-cg-";
Expand Down Expand Up @@ -148,6 +161,26 @@ public class OntapStorageConstants {
*/
public static final String CLONE_OF_TEMPLATE = "cloneOfTemplate";

/**
* Key of the {@code volume_details} row that {@code StorageSystemDataMotionStrategy} writes
* immediately before {@code createAsync} when a volume is to be created from a CloudStack
* snapshot already present on this pool. The value is the CloudStack snapshot id. The literal
* must stay in sync with the string used by the orchestrator.
*/
public static final String CLONE_OF_SNAPSHOT = "cloneOfSnapshot";

/**
* Driver capability read by {@code StorageSystemDataMotionStrategy} when copying a snapshot to
* secondary storage. The literal must stay in sync with the string used by the orchestrator.
*/
public static final String CAN_DIRECT_ATTACH_SNAPSHOT = "CAN_DIRECT_ATTACH_SNAPSHOT";

/**
* Name prefix of the temporary volume a snapshot is cloned into for createTemplate(snapshotid),
* suffixed with the CloudStack snapshot id and volume id.
*/
public static final String TEMP_SNAPSHOT_COPY_NAME_PREFIX = "cs_tmp_snap_";

// ASUP (AutoSupport) / EMS telemetry
public static final String ADVANCED_CONFIG_KEY_CATEGORY = "Advanced";
public static final String ASUP_CATEGORY = "provisioning";
Expand Down
Loading
Loading