/*
* Copyright (c) 2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.cinder;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.cinder.CinderConstants;
import com.emc.storageos.cinder.CinderEndPointInfo;
import com.emc.storageos.cinder.api.CinderApi;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.model.StorageHADomain;
import com.emc.storageos.db.client.model.StoragePool;
import com.emc.storageos.db.client.model.StorageProvider;
import com.emc.storageos.db.client.model.StorageProvider.ConnectionStatus;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringMap;
import com.emc.storageos.volumecontroller.impl.NativeGUIDGenerator;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
public class CinderUtils {
private static final Logger _log = LoggerFactory.getLogger(CinderUtils.class);
private static final Integer timeout = 10000; // in milliseconds
private static final Integer connectTimeout = 10000; // in milliseconds
private static final String STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";
private static final String NO = "no";
public static void updateStoragePoolCapacity(DbClient dbClient,
CinderApi client,
StoragePool storagePool,
String capacityInGB,
boolean isSubstract) {
StorageSystem storageSystem = null;
try {
storageSystem = dbClient.queryObject(StorageSystem.class, storagePool.getStorageDevice());
_log.info(String.format("Old storage pool capacity data for \n"
+ " pool %s/%s --- \n free capacity: %s; "
+ "subscribed capacity: %s",
storageSystem.getId(), storagePool.getId(),
storagePool.calculateFreeCapacityWithoutReservations(),
storagePool.getSubscribedCapacity()));
// Update storage pool capacity and save to data base
long sizeInGB = Long.parseLong(capacityInGB);
long currentSubscribedCapacity = storagePool.getSubscribedCapacity();
long currentFreeCapacity = storagePool.getFreeCapacity();
if (isSubstract) { // For delete case
storagePool.setFreeCapacity(currentFreeCapacity + sizeInGB * 1024 * 1024); // KBytes - KBytes
storagePool.setSubscribedCapacity(currentSubscribedCapacity - sizeInGB * 1024 * 1024); // KBytes + KBytes
} else {// For create case
storagePool.setFreeCapacity(currentFreeCapacity - (sizeInGB * 1024 * 1024)); // KBytes - KBytes
long newSubscribedCapacity = currentSubscribedCapacity + (sizeInGB * 1024 * 1024);// KBytes + KBytes
storagePool.setSubscribedCapacity(newSubscribedCapacity);
// Check if total capacity needs an adjustment
if (isCapacityLimitExceeded(newSubscribedCapacity, storagePool.getTotalCapacity())) {
// If 75% mark is reached for consumed percent, then increment the total capacity by 2 times
Long currentTotalCapacity = storagePool.getTotalCapacity();
long newTotalCapacity = currentTotalCapacity * 2;
storagePool.setTotalCapacity(newTotalCapacity);
storagePool.setFreeCapacity(newTotalCapacity - newSubscribedCapacity);
_log.info(String.format("Consumed capacity perecent was greater than or equal to 75 percent \n"
+ " Hence, increased the total capacity by 2 times"
+ " New total capacity is %s",
newTotalCapacity));
}
}
_log.info(String.format("New storage pool capacity data for pool \n"
+ " %s/%s --- \n free capacity: %s;"
+ " subscribed capacity: %s",
storageSystem.getId(), storagePool.getId(),
storagePool.getFreeCapacity(),
storagePool.getSubscribedCapacity()));
dbClient.persistObject(storagePool);
} catch (Exception ex) {
_log.error(
String.format("Failed to update capacity of storage pool after volume provisioning operation."
+ "%n Storage system: %s, storage pool %s .",
storageSystem.getId(), storagePool.getId()),
ex);
}
}
/**
* This method checks if the used capacity is 75% or more.
*
* @param storagePool
* @return
*/
private static boolean isCapacityLimitExceeded(double usedCapacity, double totalCapacity) {
double consumedPercent = (usedCapacity / totalCapacity) * 100;
if (consumedPercent >= 75) {
return true;
}
return false;
}
/**
* Gets the cinder endpoint info to access the endpoint
*
* @param storageProviderURi
* @return
*/
public static CinderEndPointInfo getCinderEndPoint(URI storageProviderURi, DbClient dbClient) {
StorageProvider provider = dbClient.queryObject(StorageProvider.class, storageProviderURi);
// Get the persisted end point info
StringMap endPointKeys = provider.getKeys();
String hostName = endPointKeys.get(CinderConstants.KEY_CINDER_HOST_NAME);
String password = endPointKeys.get(CinderConstants.KEY_CINDER_REST_PASSWORD);
String userName = endPointKeys.get(CinderConstants.KEY_CINDER_REST_USER);
String tenantName = endPointKeys.get(CinderConstants.KEY_CINDER_TENANT_NAME);
String tenantId = endPointKeys.get(CinderConstants.KEY_CINDER_TENANT_ID);
String baseUri = endPointKeys.get(CinderConstants.KEY_CINDER_REST_URI_BASE);
String token = endPointKeys.get(CinderConstants.KEY_CINDER_REST_TOKEN);
CinderEndPointInfo ep = new CinderEndPointInfo(hostName, userName, password, tenantName);
if (baseUri.startsWith(CinderConstants.HTTP_URL)) {
ep.setCinderBaseUriHttp(baseUri);
} else {
ep.setCinderBaseUriHttps(baseUri);
}
ep.setCinderToken(token);
ep.setCinderTenantId(tenantId);
return ep;
}
/**
* Refresh cinder connections.
*
* @param cinderProviderList the cinder provider list
* @param dbClient the db client
* @return the list
*/
public static List<URI> refreshCinderConnections(final List<StorageProvider> cinderProviderList,
DbClient dbClient) {
List<URI> activeProviders = new ArrayList<URI>();
for (StorageProvider storageProvider : cinderProviderList) {
try {
// Makes sure Cinder Provider is reachable
checkProviderConnection(storageProvider);
storageProvider.setConnectionStatus(ConnectionStatus.CONNECTED.name());
activeProviders.add(storageProvider.getId());
_log.info("Storage Provider {} is reachable", storageProvider.getIPAddress());
} catch (Exception e) {
storageProvider.setConnectionStatus(ConnectionStatus.NOTCONNECTED.name());
_log.error("Storage Provider {} is not reachable", storageProvider.getIPAddress());
} finally {
dbClient.persistObject(storageProvider);
}
}
return activeProviders;
}
private static void checkProviderConnection(StorageProvider storageProvider)
throws JSchException, SftpException, IOException {
ChannelSftp sftp = null;
Session session = null;
try {
JSch jsch = new JSch();
session = jsch.getSession(storageProvider.getUserName(),
storageProvider.getIPAddress(),
storageProvider.getPortNumber());
session.setPassword(storageProvider.getPassword());
Hashtable<String, String> config = new Hashtable<String, String>();
config.put(STRICT_HOST_KEY_CHECKING, NO);
session.setConfig(config);
session.connect(timeout);
_log.debug("Session Connected...");
Channel channel = session.openChannel("sftp");
sftp = (ChannelSftp) channel;
sftp.connect(connectTimeout);
} finally {
if (sftp != null) {
sftp.disconnect();
}
if (session != null) {
session.disconnect();
}
}
}
/**
* Creates StorageHADomain for storage port.
*
* Cinder API does not provide the information about the storage adapter.
* Consider it as a dummy storageHADomain for cinder based systems. If the storageHADomain
* is not populated then the port will not be considered for multipath participatipation. While
* creating a virtual pool with multiple paths, it gets discarded if storageHADomain is not found.
*
* @param storageSystem
* @param dbClient
* @return
*/
public static StorageHADomain getStorageAdapter(StorageSystem storageSystem, DbClient dbClient) {
String cinderHostName = "";
URI providerUri = storageSystem.getActiveProviderURI();
StorageProvider provider = dbClient.queryObject(StorageProvider.class, providerUri);
if (null != provider && null != provider.getKeys()) {
cinderHostName = provider.getKeyValue(CinderConstants.KEY_CINDER_HOST_NAME);
}
String adapterNativeGUID = NativeGUIDGenerator.generateNativeGuid(storageSystem,
cinderHostName, NativeGUIDGenerator.ADAPTER);
StorageHADomain adapter = new StorageHADomain();
adapter.setStorageDeviceURI(storageSystem.getId());
adapter.setId(URIUtil.createId(StorageHADomain.class));
adapter.setAdapterName(cinderHostName);
adapter.setLabel(cinderHostName);
adapter.setNativeGuid(adapterNativeGUID);
adapter.setNumberofPorts("1");
adapter.setAdapterType(StorageHADomain.HADomainType.FRONTEND.name());
adapter.setInactive(false);
dbClient.createObject(adapter);
return adapter;
}
/**
* Converts volume size from bytes to GB.
*
* If the converted size is a fractional number,
* then it would ceil it to the next higher value.
*
* @param volumeSize
* @return
*/
public static Long convertToGB(long volumeSize) {
// change it to double to account fraction after conversion
Double doubleVolSize = (double) volumeSize;
// convert it to GB
Double sizeInGB = (double) (doubleVolSize / (CinderConstants.BYTES_TO_GB));
// ceil to higher value
long cinderCapacity = (long) Math.ceil(sizeInGB);
return cinderCapacity;
}
}