/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.cloud.ucsm.service;
import com.emc.cloud.platform.clientlib.ClientGeneralException;
import com.emc.cloud.platform.clientlib.ClientMessageKeys;
import com.emc.cloud.platform.ucs.in.model.AaaLogin;
import com.emc.cloud.platform.ucs.in.model.AaaRefresh;
import com.emc.cloud.platform.ucs.in.model.ObjectFactory;
import com.emc.cloud.platform.ucs.out.model.ConfigConfMo;
import com.emc.cloud.platform.ucs.out.model.ConfigResolveClass;
import com.emc.cloud.platform.ucs.out.model.ConfigResolveDn;
import com.emc.storageos.coordinator.client.service.DistributedDataManager;
import com.emc.storageos.coordinator.client.service.impl.CoordinatorClientImpl;
import com.emc.storageos.db.client.model.EncryptionProvider;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.NumberFormat;
import java.util.concurrent.TimeUnit;
import javax.xml.bind.JAXBElement;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
public class UCSMSession implements ComputeSession {
@Autowired
ApplicationContext applicationContext;
EncryptionProvider encryptionProvider;
private TransportWrapper ucsmTransportWrapper;
private static final Logger LOGGER = LoggerFactory.getLogger(UCSMSession.class);
private String serviceURI;
private final String username;
private final String password;
private final String oneWayHash;
private final String hostAddress;
CoordinatorClientImpl coordinator;
DistributedDataManager distributedDataManager = null;
InterProcessReadWriteLock lock = null;
private ObjectFactory objectFactory = new ObjectFactory();
public UCSMSession(String serviceURI, String username, String password) throws ClientGeneralException {
this.serviceURI = serviceURI;
this.username = username;
this.password = password;
this.oneWayHash = ComputeSessionUtil.generateHash(serviceURI, username, password);
try {
hostAddress = new URL(serviceURI).getHost();
} catch (MalformedURLException e) {
throw new ClientGeneralException(ClientMessageKeys.MALFORMED_URL);
}
}
public <T> T execute(Object object, Class<T> returnType) throws ClientGeneralException {
initializeRequest();
T returnObject = ucsmTransportWrapper.execute(serviceURI, postPayload((JAXBElement<?>) object), returnType);
checkForResponseStatusErrors(returnObject);
return returnObject;
}
public void login() throws ClientGeneralException {
if (isSessionValid()) {
return;
}
attemptSessionRefresh();
if (isSessionValid()) {
return;
}
relogin();
}
private Boolean isSessionValid() throws ClientGeneralException {
Boolean writeLockHeld = false;
try {
writeLockHeld = getLock().writeLock().isAcquiredInThisProcess();
if (!writeLockHeld) {
readLock();
}
ComputeSessionCache cache = decryptAndRetrieveSessionCache();
if (!isCacheNotNull(cache)) {
LOGGER.debug("No available session information for {}. Need to login", hostAddress);
return false;
}
if (!cache.getHashKey().equals(oneWayHash)) {
LOGGER.info("Change of port/creds detected for {}. Need to re-login or refresh", hostAddress);
return false;
}
/*
* The recommended refresh time is returned from the UCSM, which is cached as session length.
*/
if (Math.abs(System.currentTimeMillis() - cache.getCreateTime()) >= ((cache.getSessionLength() * 1000) * (0.80))) {
LOGGER.info("Session has expired for {}. Need to re-login or refresh", hostAddress);
return false;
}
return true;
} finally {
if (!writeLockHeld) {
readUnlock();
}
}
}
private void attemptSessionRefresh() throws ClientGeneralException {
try {
writeLock();
// write lock defence code
LOGGER.debug("Attempt to refresh session for {}", hostAddress);
if (isSessionValid()) {
LOGGER.debug("Session is valid for {}. No need to refresh", hostAddress);
return;
}
ComputeSessionCache cache = decryptAndRetrieveSessionCache();
if (cache == null || cache.getHashKey() == null || !cache.getHashKey().equals(oneWayHash)) {
encryptAndUpdateSessionCache(new ComputeSessionCache());
return;
}
AaaRefresh aaaRefresh = new AaaRefresh();
aaaRefresh.setInName(username);
aaaRefresh.setInPassword(password);
aaaRefresh.setCookie(cache.getSessionId());
aaaRefresh.setInCookie(cache.getSessionId());
Object response = ucsmTransportWrapper.execute(serviceURI, objectFactory.createAaaRefresh(aaaRefresh), Object.class);
Assert.notNull(response, "Authentication Call resulted in Null Response");
Assert.isTrue(response instanceof com.emc.cloud.platform.ucs.out.model.AaaRefresh, "Invalid Response Type!");
com.emc.cloud.platform.ucs.out.model.AaaRefresh refreshResponse = (com.emc.cloud.platform.ucs.out.model.AaaRefresh) response;
if (refreshResponse != null && refreshResponse.getOutCookie() != null && !refreshResponse.getOutCookie().isEmpty()) {
LOGGER.info("Session has been refreshed");
cache = new ComputeSessionCache(refreshResponse.getOutCookie(), System.currentTimeMillis(), parseNumber(
refreshResponse.getOutRefreshPeriod()).longValue(), oneWayHash);
encryptAndUpdateSessionCache(cache);
} else {
LOGGER.info("Session for {} cannot be refreshed", hostAddress);
encryptAndUpdateSessionCache(new ComputeSessionCache());
}
} finally {
writeUnlock();
}
}
public Object postPayload(JAXBElement<?> jaxbElement) throws ClientGeneralException {
try {
login();
try {
readLock();
ComputeSessionCache cache = decryptAndRetrieveSessionCache();
if (cache != null && cache.getSessionId() != null) {
BeanUtils.setProperty(jaxbElement.getValue(), "cookie", cache.getSessionId());
}
} catch (IllegalAccessException e) {
LOGGER.error("Unable to set the cookie on object type: " + jaxbElement.getValue(), e);
throw new ClientGeneralException(ClientMessageKeys.MODEL_EXCEPTION);
} catch (Exception e) {
LOGGER.error("Unable to set the cookie on object type: " + jaxbElement.getValue(), e);
throw new ClientGeneralException(ClientMessageKeys.INTERNAL_SERVER_ERROR);
} finally {
readUnlock();
}
} catch (ClientGeneralException e) {
LOGGER.debug(e.getLocalizedMessage(), e);
throw e;
}
return jaxbElement;
}
private void relogin() throws ClientGeneralException {
try {
writeLock();
LOGGER.info("Attempt to login {}", hostAddress);
// recheck login status
if (isSessionValid()) {
LOGGER.debug("After rechecking. Re-login session not required for {}", hostAddress);
return;
}
ComputeSessionCache cache = null;
AaaLogin aaaLogin = new AaaLogin();
aaaLogin.setInName(username);
aaaLogin.setInPassword(password);
Object response = ucsmTransportWrapper.execute(serviceURI, objectFactory.createAaaLogin(aaaLogin), Object.class);
Assert.notNull(response, "Authentication Call resulted in Null Response");
Assert.isTrue(response instanceof com.emc.cloud.platform.ucs.out.model.AaaLogin, "Invalid Response Type!");
com.emc.cloud.platform.ucs.out.model.AaaLogin loginResponse = (com.emc.cloud.platform.ucs.out.model.AaaLogin) response;
if (loginResponse != null && loginResponse.getOutCookie() != null && !loginResponse.getOutCookie().isEmpty()) {
cache = new ComputeSessionCache(loginResponse.getOutCookie(), System.currentTimeMillis(), parseNumber(
loginResponse.getOutRefreshPeriod()).longValue(), oneWayHash);
encryptAndUpdateSessionCache(cache);
} else {
throw new ClientGeneralException(ClientMessageKeys.UNAUTHORIZED, new String[] { serviceURI, "",
"Unable to authenticate username/credentials pair" });
}
} finally {
writeUnlock();
}
}
@Override
public void logout() throws ClientGeneralException {
// Is is no-op for the UCS
}
@Override
public void clearSession() throws ClientGeneralException {
try {
writeLock();
LOGGER.debug("Acquired write lock to clear out session for {}", hostAddress);
initializeSession();
distributedDataManager.removeNode(getNodePath());
LOGGER.debug("Session cache for {} cleared successfully", hostAddress);
distributedDataManager.close();
LOGGER.debug("DistributedDataManager for {} closed successfully", hostAddress);
} catch (Exception e) {
LOGGER.warn("Unable to clear session cache for {}", hostAddress);
} finally {
writeUnlock();
}
}
public void setUcsmTransportWrapper(TransportWrapper ucsmTransportWrapper) {
this.ucsmTransportWrapper = ucsmTransportWrapper;
}
public void setCoordinator(CoordinatorClientImpl coordinator) {
this.coordinator = coordinator;
}
public void setEncryptionProvider(EncryptionProvider encryptionProvider) {
this.encryptionProvider = encryptionProvider;
}
public ComputeSessionCache decryptAndRetrieveSessionCache() throws ClientGeneralException {
Object zkdata = null;
ComputeSessionCache cache = null;
try {
LOGGER.debug("Attempt to fetch session info for {}", hostAddress);
try {
zkdata = distributedDataManager.getData(getNodePath(), false);
} catch (Exception e) {
throw new ClientGeneralException(ClientMessageKeys.DISTRIBUTED_DATA_CACHE_ERROR, e);
}
if (zkdata != null) {
cache = (ComputeSessionCache) zkdata;
}
try {
if (cache != null && cache.getSessionId() != null) {
cache.setSessionId(encryptionProvider.decrypt(Base64.decodeBase64(cache.getSessionId())));
}
} catch (Exception e) {
throw new ClientGeneralException(ClientMessageKeys.DATA_ENCRYPTION_ERROR, e);
}
} catch (Exception e) {
LOGGER.warn(e.getMessage());
}
return cache;
}
public void encryptAndUpdateSessionCache(ComputeSessionCache cache) throws ClientGeneralException {
try {
if (cache != null && cache.getSessionId() != null) {
cache.setSessionId(encryptionProvider.getEncryptedString(cache.getSessionId()));
}
} catch (Exception e) {
throw new ClientGeneralException(ClientMessageKeys.DATA_ENCRYPTION_ERROR, e);
}
try {
distributedDataManager.putData(getNodePath(), cache);
} catch (Exception e) {
throw new ClientGeneralException(ClientMessageKeys.DISTRIBUTED_DATA_CACHE_ERROR, e);
}
}
private String getNodePath() {
return ComputeSessionUtil.Constants.COMPUTE_SESSION_BASE_PATH.toString() + "/" + hostAddress;
}
private void readLock() throws ClientGeneralException {
try {
getLock().readLock().acquire(1, TimeUnit.HOURS);
} catch (Exception e) {
throw new ClientGeneralException(ClientMessageKeys.DISTRIBUTED_LOCK_ERROR, e);
}
}
private void readUnlock() throws ClientGeneralException {
try {
getLock().readLock().release();
} catch (Exception e) {
throw new ClientGeneralException(ClientMessageKeys.DISTRIBUTED_LOCK_ERROR, e);
}
}
private void writeLock() throws ClientGeneralException {
try {
getLock().writeLock().acquire(1, TimeUnit.HOURS);
} catch (Exception e) {
throw new ClientGeneralException(ClientMessageKeys.DISTRIBUTED_LOCK_ERROR, e);
}
}
private void writeUnlock() throws ClientGeneralException {
try {
getLock().writeLock().release();
} catch (Exception e) {
throw new ClientGeneralException(ClientMessageKeys.DISTRIBUTED_LOCK_ERROR, e);
}
}
private InterProcessReadWriteLock getLock() throws ClientGeneralException {
try {
if (lock == null) {
String lockName = ComputeSessionUtil.Constants.COMPUTE_SESSION_BASE_PATH.toString() + "-" + hostAddress;
lock = coordinator.getReadWriteLock(lockName);
}
} catch (Exception e) {
throw new ClientGeneralException(ClientMessageKeys.DISTRIBUTED_LOCK_ERROR, e);
}
return lock;
}
private void initializeSession() {
distributedDataManager = coordinator
.createDistributedDataManager(ComputeSessionUtil.Constants.COMPUTE_SESSION_BASE_PATH.toString());
}
private void initializeRequest() {
initializeSession();
try {
distributedDataManager.checkExists(getNodePath());
} catch (Exception e) {
LOGGER.warn(e.getMessage());
}
}
private Boolean isCacheNotNull(ComputeSessionCache cache) {
if (cache == null || cache.getSessionId() == null || cache.getSessionLength() == null ||
cache.getHashKey() == null) {
return false;
}
return true;
}
private void checkForResponseStatusErrors(Object response) throws ClientGeneralException {
String errorCode = null;
String errorDescription = null;
if (response instanceof ConfigResolveDn) {
ConfigResolveDn configResolveDn = ((ConfigResolveDn) response);
errorCode = configResolveDn.getErrorCode();
errorDescription = configResolveDn.getErrorDescr();
}
else if (response instanceof ConfigConfMo) {
ConfigConfMo configResolveDn = ((ConfigConfMo) response);
errorCode = configResolveDn.getErrorCode();
errorDescription = configResolveDn.getErrorDescr();
}
else if (response instanceof ConfigResolveClass) {
ConfigResolveClass configResolveDn = ((ConfigResolveClass) response);
errorCode = configResolveDn.getErrorCode();
errorDescription = configResolveDn.getErrorDescr();
}
if (errorCode != null) {
String[] errors = new String[] { errorDescription };
throw new ClientGeneralException(ClientMessageKeys.byErrorCode(parseNumber(errorCode).intValue()), errors);
}
}
private Number parseNumber(String number) {
try {
return NumberFormat.getInstance().parse(number);
} catch (Exception e) {
LOGGER.error("Encountered an parse error for string {} caused by {}", number, e.getMessage());
}
return new Integer(0);
}
}