/*
* Copyright (c) 2012-2015 iWave Software LLC
* All Rights Reserved
*/
package com.emc.sa.service.vipr;
import static com.emc.sa.service.ServiceParams.ARTIFICIAL_FAILURE;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.emc.sa.engine.ExecutionUtils;
import com.emc.sa.engine.bind.Param;
import com.emc.sa.engine.service.AbstractExecutionService;
import com.emc.sa.model.dao.ModelClient;
import com.emc.sa.service.vipr.block.BlockStorageUtils;
import com.emc.sa.service.vipr.tasks.AcquireClusterLock;
import com.emc.sa.service.vipr.tasks.AcquireHostLock;
import com.emc.sa.service.vipr.tasks.ReleaseHostLock;
import com.emc.storageos.db.client.constraint.NamedElementQueryResultList.NamedElement;
import com.emc.storageos.db.client.model.Cluster;
import com.emc.storageos.db.client.model.EncryptionProvider;
import com.emc.storageos.db.client.model.Host;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.uimodels.RetainedReplica;
import com.emc.storageos.db.client.model.uimodels.ScheduledEvent;
import com.emc.storageos.model.DataObjectRestRep;
import com.emc.storageos.model.block.BlockObjectRestRep;
import com.emc.vipr.client.ClientConfig;
import com.emc.vipr.client.Task;
import com.emc.vipr.client.Tasks;
import com.emc.vipr.client.ViPRCoreClient;
import com.emc.vipr.client.core.util.ResourceUtils;
import com.emc.vipr.model.catalog.OrderCreateParam;
import com.google.common.collect.Lists;
public abstract class ViPRService extends AbstractExecutionService {
private static Charset UTF_8 = Charset.forName("UTF-8");
@Autowired
private ViPRProxyUser proxyUser;
@Autowired
private ModelClient modelClient;
@Autowired
private ClientConfig clientConfig;
@Autowired
private EncryptionProvider encryptionProvider;
@Param(value=ARTIFICIAL_FAILURE, required=false)
protected static String artificialFailure;
private ViPRCoreClient client;
private List<String> locks = Lists.newArrayList();
public EncryptionProvider getEncryptionProvider() {
return encryptionProvider;
}
public void setEncryptionProvider(EncryptionProvider encryptionProvider) {
this.encryptionProvider = encryptionProvider;
}
public ViPRProxyUser getProxyUser() {
return proxyUser;
}
public void setProxyUser(ViPRProxyUser proxyUser) {
this.proxyUser = proxyUser;
}
public ModelClient getModelClient() {
return modelClient;
}
public void setModelClient(ModelClient modelClient) {
this.modelClient = modelClient;
}
public ClientConfig getClientConfig() {
return clientConfig;
}
public void setClientConfig(ClientConfig clientConfig) {
this.clientConfig = clientConfig;
}
public ViPRCoreClient getClient() {
if (client == null) {
String proxyToken = ExecutionUtils.currentContext().getExecutionState().getProxyToken();
client = new ViPRCoreClient(clientConfig);
proxyUser.login(client.auth());
client.setProxyToken(proxyToken);
}
return client;
}
protected void addAffectedResource(URI resourceId) {
if (resourceId != null) {
addAffectedResource(resourceId.toString());
}
}
protected void addAffectedResource(DataObjectRestRep value) {
if (value != null) {
addAffectedResource(value.getId());
}
}
protected void addAffectedResource(Task<? extends DataObjectRestRep> task) {
if (task.getResourceId() != null) {
addAffectedResource(task.getResourceId());
if (task.getAssociatedResources() != null
&& !task.getAssociatedResources().isEmpty()) {
for (URI id : ResourceUtils.refIds(task.getAssociatedResources())) {
addAffectedResource(id);
}
}
} else {
warn("null resource for task, not adding to affected resources: %s", task);
}
}
protected void addAffectedResources(Tasks<? extends DataObjectRestRep> tasks) {
if (tasks != null) {
for (Task<? extends DataObjectRestRep> task : tasks.getTasks()) {
addAffectedResource(task);
}
}
}
@Override
public void init() throws Exception {
addInjectedValue(ViPRCoreClient.class, getClient());
addInjectedValue(ModelClient.class, modelClient);
addInjectedValue(EncryptionProvider.class, encryptionProvider);
}
@Override
public void destroy() {
super.destroy();
releaseAllLocks();
if (client != null && client.auth().isLoggedIn()) {
client.auth().logout();
}
}
protected <T> void addInjectedValue(Class<? extends T> clazz, T value) {
ExecutionUtils.currentContext().addInjectedValue(clazz, value);
}
public static URI uri(String uri) {
return ResourceUtils.uri(uri);
}
public static List<URI> uris(List<String> ids) {
return ResourceUtils.uris(ids);
}
protected void acquireHostLock(Host host) {
execute(new AcquireHostLock(host));
locks.add(host.getId().toString());
}
protected void acquireHostLock(Host host, Cluster cluster) {
execute(new AcquireHostLock(host, cluster));
locks.add(host.getId().toString());
logDebug("Locks that already exist:", locks);
if (cluster != null) {
locks.add(cluster.getId().toString());
}
}
protected void releaseHostLock(Host host, Cluster cluster) {
execute(new ReleaseHostLock(host, cluster));
locks.remove(host.getId().toString());
logDebug("Locks that already exist:", locks);
if (cluster != null) {
locks.remove(cluster.getId().toString());
}
}
protected void acquireClusterLock(Cluster cluster) {
if (cluster != null) {
execute(new AcquireClusterLock(cluster));
locks.add(cluster.getId().toString());
}
}
private void releaseAllLocks() {
for (String lock : locks) {
logInfo("vipr.service.release.lock", lock);
ExecutionUtils.releaseLock(lock);
}
}
/**
* Check if it is a recurrent order and retention policy is defined
*
* @return true if retention policy defined
*/
protected boolean isRetentionRequired() {
ScheduledEvent event = ExecutionUtils.currentContext().getScheduledEvent();
if (event == null) {
return false;
}
try {
OrderCreateParam param = OrderCreateParam.deserialize(org.apache.commons.codec.binary.Base64.decodeBase64(event.getOrderCreationParam().getBytes(UTF_8)));
String additionalScheduleInfo = param.getAdditionalScheduleInfo();
if (additionalScheduleInfo == null) {
return false;
}
Integer.parseInt(additionalScheduleInfo);
} catch (Exception ex) {
error("Unexpected exception when checking scheduler retention", ex);
return false;
}
return true;
}
/**
* Add newly created replica for given volume/CG to db and keep records for applying retention policy
*
* @param sourceId
* @param tasks
*/
protected <T extends DataObjectRestRep> void addRetainedReplicas(URI sourceId, List< Task<T> > tasks) {
if (!isRetentionRequired()) {
return;
}
if (tasks == null) {
return;
}
ScheduledEvent event = ExecutionUtils.currentContext().getScheduledEvent();
RetainedReplica retention = new RetainedReplica();
retention.setScheduledEventId(event.getId());
retention.setResourceId(sourceId);
StringSet retainedResource = new StringSet();
retention.setAssociatedReplicaIds(retainedResource);
for (Task<? extends DataObjectRestRep> task : tasks) {
URI resourceId = task.getResourceId();
if (resourceId != null && !sourceId.equals(resourceId)) {
info("Add %s to retained replica", resourceId.toString());
retainedResource.add(resourceId.toString());
}
if (task.getAssociatedResources() != null
&& !task.getAssociatedResources().isEmpty()) {
for (URI id : ResourceUtils.refIds(task.getAssociatedResources())) {
if (sourceId.equals(id)) {
continue;
}
info("Add %s to retained replica", id.toString());
retainedResource.add(id.toString());
}
}
}
modelClient.save(retention);
}
protected <T extends DataObjectRestRep> void addRetainedReplicas(URI sourceId, String replicaName) {
if (!isRetentionRequired()) {
return;
}
ScheduledEvent event = ExecutionUtils.currentContext().getScheduledEvent();
RetainedReplica retention = new RetainedReplica();
retention.setScheduledEventId(event.getId());
retention.setResourceId(sourceId);
StringSet retainedResource = new StringSet();
retention.setAssociatedReplicaIds(retainedResource);
retainedResource.add(replicaName);
modelClient.save(retention);
}
/**
*
* Find obsolete replicas for given resource according to defined retention policy of this order
*
* @param resourceId
* @return the replica to be removed. Otherwise null in case of no removal required
*/
protected List<RetainedReplica> findObsoleteReplica(String resourceId) {
List<RetainedReplica> obsoleteReplicas = new ArrayList<RetainedReplica>();
ScheduledEvent event = ExecutionUtils.currentContext().getScheduledEvent();
Integer maxNumOfCopies = Integer.MAX_VALUE;
try {
OrderCreateParam param = OrderCreateParam.deserialize(org.apache.commons.codec.binary.Base64.decodeBase64(event.getOrderCreationParam().getBytes(UTF_8)));
String additionalScheduleInfo = param.getAdditionalScheduleInfo();
maxNumOfCopies = Integer.parseInt(additionalScheduleInfo);
} catch (Exception ex) {
error("Unexpected exception when checking scheduler retention", ex);
return obsoleteReplicas;
}
List<NamedElement> replicaIdList = modelClient.findBy(RetainedReplica.class, "scheduledEventId", event.getId());
List<RetainedReplica> replicas = new ArrayList<RetainedReplica>();
for (NamedElement uri : replicaIdList) {
RetainedReplica retention = modelClient.findById(RetainedReplica.class, uri.getId());
if (retention.getResourceId().toString().equals(resourceId)) {
replicas.add(retention);
}
}
if (replicas.size() >= maxNumOfCopies) {
Collections.sort(replicas, new Comparator<RetainedReplica>() {
public int compare(RetainedReplica o1, RetainedReplica o2){
return o1.getCreationTime().compareTo(o2.getCreationTime());
}
});
// get top oldest records
int endIndex = replicas.size() - maxNumOfCopies + 1;
obsoleteReplicas.addAll(replicas.subList(0, endIndex));
}
return obsoleteReplicas;
}
/**
* Check for a boot volume. Throw failure message if found.
* @param volumeId volume ID
*/
public static void checkForBootVolume(URI volumeId) {
checkForBootVolumes(Arrays.asList(volumeId.toASCIIString()));
}
/**
* Check for boot volumes. Throw failure message if found.
* @param volumeIds volume IDs
*/
public static void checkForBootVolumes(Collection<String> volumeIds) {
for (URI volumeId : ResourceUtils.uris(volumeIds)) {
BlockObjectRestRep volume = BlockStorageUtils.getBlockResource(volumeId);
if (BlockStorageUtils.isVolumeBootVolume(volume)) {
ExecutionUtils.fail("failTask.verifyBootVolume", volume.getName(), volume.getName());
}
}
}
/**
* Invoke a failure if the artificialFailure variable is passed by the service.
* This is an internal-only setting that allows testers and automated suites to inject a failure
* into a catalog service step at key locations to test rollback.
*
* @param failure
* key from above
*/
public static void artificialFailure(String failure) {
if (artificialFailure != null && artificialFailure.equals(failure)) {
log("Injecting catalog failure: " + failure);
ExecutionUtils.fail("failTask.ArtificialFailure", artificialFailure, artificialFailure);
}
}
/**
* Local logging, needed for debug on failure detection. Copied from InvokeTestFailure#log(String)
*
* @see com.emc.storageos.util.InvokeTestFailure#log(String)
* @param msg error message
*/
private static void log(String msg) {
FileOutputStream fop = null;
try {
String logFileName = "/opt/storageos/logs/invoke-test-failure.log";
File logFile = new File(logFileName);
if (!logFile.exists()) {
logFile.createNewFile();
}
fop = new FileOutputStream(logFile, true);
fop.flush();
StringBuffer sb = new StringBuffer(msg + "\n");
// Last chance, if file is deleted, write manually.
fop.write(sb.toString().getBytes());
} catch (IOException e) {
// It's OK if we can't log this.
} finally {
IOUtils.closeQuietly(fop);
}
}
}