/*
* Copyright (c) 2015 EMC Software LLC
* All Rights Reserved
*/
package com.emc.sa.service.vipr.block;
import static com.emc.sa.service.ServiceParams.LINKED_SNAPSHOT_COPYMODE;
import static com.emc.sa.service.ServiceParams.LINKED_SNAPSHOT_COUNT;
import static com.emc.sa.service.ServiceParams.LINKED_SNAPSHOT_NAME;
import static com.emc.sa.service.ServiceParams.NAME;
import static com.emc.sa.service.ServiceParams.PROJECT;
import static com.emc.sa.service.ServiceParams.READ_ONLY;
import static com.emc.sa.service.ServiceParams.STORAGE_TYPE;
import static com.emc.sa.service.ServiceParams.TYPE;
import static com.emc.sa.service.ServiceParams.VOLUMES;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import com.emc.sa.asset.providers.BlockProvider;
import com.emc.sa.engine.ExecutionContext;
import com.emc.sa.engine.ExecutionUtils;
import com.emc.sa.engine.bind.Param;
import com.emc.sa.engine.service.Service;
import com.emc.sa.service.vipr.ViPRService;
import com.emc.sa.service.vipr.block.tasks.CreateBlockSnapshot;
import com.emc.sa.service.vipr.block.tasks.CreateBlockSnapshotSession;
import com.emc.sa.service.vipr.block.tasks.DeactivateBlockSnapshot;
import com.emc.sa.service.vipr.block.tasks.DeactivateBlockSnapshotSession;
import com.emc.storageos.coordinator.client.model.Constants;
import com.emc.storageos.db.client.model.uimodels.RetainedReplica;
import com.emc.storageos.model.DataObjectRestRep;
import com.emc.storageos.model.block.BlockConsistencyGroupRestRep;
import com.emc.storageos.model.block.BlockObjectRestRep;
import com.emc.storageos.model.block.BlockSnapshotSessionRestRep;
import com.emc.storageos.model.block.VolumeDeleteTypeEnum;
import com.emc.storageos.model.block.VolumeRestRep;
import com.emc.vipr.client.Tasks;
@Service("CreateBlockSnapshot")
public class CreateBlockSnapshotService extends ViPRService {
@Param(PROJECT)
protected URI project;
@Param(value = STORAGE_TYPE, required = false)
protected String storageType;
@Param(VOLUMES)
protected List<String> volumeIds;
@Param(NAME)
protected String nameParam;
@Param(value = TYPE, required = false)
protected String type;
@Param(value = READ_ONLY, required = false)
protected Boolean readOnly;
@Param(value = LINKED_SNAPSHOT_NAME, required = false)
protected String linkedSnapshotName;
@Param(value = LINKED_SNAPSHOT_COUNT, required = false)
protected Integer linkedSnapshotCount;
@Param(value = LINKED_SNAPSHOT_COPYMODE, required = false)
protected String linkedSnapshotCopyMode;
private List<BlockObjectRestRep> volumes;
@Override
public void precheck() {
if (ConsistencyUtils.isVolumeStorageType(storageType)) {
volumes = new ArrayList<>();
volumes = BlockStorageUtils.getBlockResources(uris(volumeIds));
}
if (BlockProvider.SNAPSHOT_SESSION_TYPE_VALUE.equals(type)
|| BlockProvider.CG_SNAPSHOT_SESSION_TYPE_VALUE.equals(type)) {
if (linkedSnapshotName != null && !linkedSnapshotName.isEmpty()) {
// If trying to create a Snapshot Session and the optional linkedSnapshotName
// is populated, make sure that linkedSnapshotCount > 0.
if (linkedSnapshotCount == null || linkedSnapshotCount.intValue() <= 0) {
ExecutionUtils.fail("failTask.CreateBlockSnapshot.linkedSnapshotCount.precheck", new Object[] {}, new Object[] {});
}
// Ensure that copy mode is selected
if (linkedSnapshotCopyMode == null
|| !(BlockProvider.LINKED_SNAPSHOT_COPYMODE_VALUE.equals(linkedSnapshotCopyMode)
|| BlockProvider.LINKED_SNAPSHOT_NOCOPYMODE_VALUE.equals(linkedSnapshotCopyMode))) {
ExecutionUtils.fail("failTask.CreateBlockSnapshot.linkedSnapshotCopyMode.precheck", new Object[] {}, new Object[] {});
}
}
}
// We disable recurring VMAX V3 snapshot in case when "Local Array Snapshot Type" is selected. With "Local Array Snapshot Type" enabled for VMAX V3,
// both snapshot sessions and snapshot are created. It brings some difficulties for snapshot rotation. So far we don't have single API to delete
// both snapshot session and snapshot in single shot. If we implement orchestration of those 2 calls at sasvc, we may introduce unnecessary complexities
// for parameter preparation, error handling in the middle etc. So we would take out this special case and go back to it until a backend API is ready for
// deletion both snapshot session and snapshot in single shot.
if (isRetentionRequired()) {
if (ConsistencyUtils.isVolumeStorageType(storageType)) {
for (String volumeId : volumeIds) {
if(!BlockProvider.SNAPSHOT_SESSION_TYPE_VALUE.equals(type) && isSnapshotSessionSupportedForVolume(uri(volumeId))) {
ExecutionUtils.fail("failTask.CreateBlockSnapshot.localArraySnapshotTypeNotSupportedForScheduler.precheck", new Object[] {}, new Object[] {});
}
}
} else {
for (String consistencyGroupId : volumeIds) {
if(!BlockProvider.CG_SNAPSHOT_SESSION_TYPE_VALUE.equals(type) && isSnapshotSessionSupportedForCG(uri(consistencyGroupId))) {
ExecutionUtils.fail("failTask.CreateBlockSnapshot.CGSnapshotTypeNotSupportedForScheduler.precheck", new Object[] {}, new Object[] {});
}
}
}
}
// Show alert in case of approaching 90% of the limit
ExecutionContext context = ExecutionUtils.currentContext();
long limit = context.getResourceLimit(Constants.RESOURCE_LIMIT_PROJECT_SNAPSHOTS);
int numOfSnapshots = 0;
if(BlockProvider.SNAPSHOT_SESSION_TYPE_VALUE.equals(type) || BlockProvider.CG_SNAPSHOT_SESSION_TYPE_VALUE.equals(type)) {
numOfSnapshots = getClient().blockSnapshotSessions().countByProject(project);
} else {
numOfSnapshots = getClient().blockSnapshots().countByProject(project);
}
if (numOfSnapshots >= limit * Constants.RESOURCE_LIMIT_ALERT_RATE) {
context.logWarn("alert.createSnapshot.exceedingResourceLimit", numOfSnapshots, limit);
}
}
@Override
public void execute() {
Tasks<? extends DataObjectRestRep> tasks = null;
if (ConsistencyUtils.isVolumeStorageType(storageType)) {
for (BlockObjectRestRep volume : volumes) {
checkAndPurgeObsoleteSnapshots(volume.getId().toString());
if (BlockProvider.SNAPSHOT_SESSION_TYPE_VALUE.equals(type)) {
tasks = execute(new CreateBlockSnapshotSession(volume.getId(), nameParam,
linkedSnapshotName, linkedSnapshotCount, linkedSnapshotCopyMode));
} else {
tasks = execute(new CreateBlockSnapshot(volume.getId(), type, nameParam, readOnly));
}
addAffectedResources(tasks);
addRetainedReplicas(volume.getId(), tasks.getTasks());
}
} else {
for (String consistencyGroupId : volumeIds) {
checkAndPurgeObsoleteSnapshots(consistencyGroupId);
if (BlockProvider.CG_SNAPSHOT_SESSION_TYPE_VALUE.equals(type)) {
tasks = ConsistencyUtils.createSnapshotSession(uri(consistencyGroupId), nameParam,
linkedSnapshotName, linkedSnapshotCount, linkedSnapshotCopyMode);
} else {
tasks = ConsistencyUtils.createSnapshot(uri(consistencyGroupId), nameParam, readOnly);
}
addAffectedResources(tasks);
addRetainedReplicas(uri(consistencyGroupId), tasks.getTasks());
}
}
}
/**
* Check retention policy and delete obsolete snapshots if necessary
*
* @param volumeOrCgId - volume id or consistency group id
*/
private void checkAndPurgeObsoleteSnapshots(String volumeOrCgId) {
if (!isRetentionRequired()) {
return;
}
List<RetainedReplica> replicas = findObsoleteReplica(volumeOrCgId);
for (RetainedReplica replica : replicas) {
if(replica.getAssociatedReplicaIds() == null || replica.getAssociatedReplicaIds().isEmpty())
continue;
for (String obsoleteSnapshotId : replica.getAssociatedReplicaIds()) {
info("Deactivating snapshot %s since it exceeds max number of snapshots allowed", obsoleteSnapshotId);
if (ConsistencyUtils.isVolumeStorageType(storageType)) {
if (BlockProvider.SNAPSHOT_SESSION_TYPE_VALUE.equals(type)) {
BlockSnapshotSessionRestRep obsoloteCopy = getClient().blockSnapshotSessions().get(uri(obsoleteSnapshotId));
info("Deactivating snapshot session %s", obsoloteCopy.getName());
execute(new DeactivateBlockSnapshotSession(uri(obsoleteSnapshotId)));
} else {
BlockObjectRestRep obsoleteCopy = BlockStorageUtils.getVolume(uri(obsoleteSnapshotId));
info("Deactivating snapshot %s", obsoleteCopy.getName());
execute(new DeactivateBlockSnapshot(uri(obsoleteSnapshotId), VolumeDeleteTypeEnum.FULL));
}
} else {
if (BlockProvider.CG_SNAPSHOT_SESSION_TYPE_VALUE.equals(type)) {
BlockSnapshotSessionRestRep obsoloteCopy = getClient().blockSnapshotSessions().get(uri(obsoleteSnapshotId));
info("Deactivating snapshot session %s", obsoloteCopy.getName());
ConsistencyUtils.removeSnapshotSession(uri(volumeOrCgId), uri(obsoleteSnapshotId));
} else {
BlockObjectRestRep obsoleteCopy = BlockStorageUtils.getVolume(uri(obsoleteSnapshotId));
info("Deactivating snapshot %s", obsoleteCopy.getName());
ConsistencyUtils.removeSnapshot(uri(volumeOrCgId), uri(obsoleteSnapshotId));
}
}
}
getModelClient().delete(replica);
}
}
private boolean isSnapshotSessionSupportedForVolume(URI volumeId) {
VolumeRestRep volume = getClient().blockVolumes().get(volumeId);
return volume.getSupportsSnapshotSessions();
}
private boolean isSnapshotSessionSupportedForCG(URI cgId) {
BlockConsistencyGroupRestRep cg = getClient().blockConsistencyGroups().get(cgId);
return cg.getSupportsSnapshotSessions();
}
}