package com.vmware.vhadoop.vhm.vc;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.vmware.vhadoop.util.ExternalizedParameters;
import com.vmware.vim.vmomi.client.Client;
/**
* This class is a thread-safe way of accessing and validating pre-created VCClients
*/
public class VcClientFactory {
boolean _initialized = false;
private static final Logger _log = Logger.getLogger(VcClientFactory.class.getName());
private final long VC_CONTROL_CONNECTION_TIMEOUT_MILLIS = ExternalizedParameters.get().getLong("VC_CONTROL_CONNECTION_TIMEOUT_MILLIS");
private final long VC_WAIT_FOR_UPDATES_CONNECTION_TIMEOUT_MILLIS = ExternalizedParameters.get().getLong("VC_WAIT_FOR_UPDATES_CONNECTION_TIMEOUT_MILLIS"); /* WaitForUpdates will block for at most this period */
private final long VC_STATS_POLL_CONNECTION_TIMEOUT_MILLIS = ExternalizedParameters.get().getLong("VC_STATS_POLL_CONNECTION_TIMEOUT_MILLIS"); /* Stats collection timeout should be short */
private final VcVlsi _vcVlsi;
private final VcCredentials _vcCreds;
private Client _controlClient; // used for VC control operations and is the parent client for the others
private Client _waitForUpdateClient; // used for the main waitForPropertyChange loop
private Client _statsPollClient; // used for VC stats collection
private String _waitForUpdatesVersion = "";
private AtomicReference<Thread> _initiatingConnectionThread = new AtomicReference<Thread>();
protected enum VcClientKey{CONTROL_CLIENT, WAIT_FOR_UPDATE_CLIENT, STATS_POLL_CLIENT};
protected VcClientFactory(VcVlsi vcVlsi, VcCredentials vcCreds) {
_vcVlsi = vcVlsi;
_vcCreds = vcCreds;
}
/* Getting and setting of the clients is synchronized */
private synchronized Client getClientForKey(VcClientKey clientKey) {
if (clientKey.equals(VcClientKey.CONTROL_CLIENT)) {
return _controlClient;
} else
if (clientKey.equals(VcClientKey.WAIT_FOR_UPDATE_CLIENT)) {
return _waitForUpdateClient;
} else
if (clientKey.equals(VcClientKey.STATS_POLL_CLIENT)) {
return _statsPollClient;
}
return null;
}
/* Getting and setting of the clients is synchronized */
private synchronized void setClientForKey(VcClientKey clientKey, Client client) {
if (clientKey.equals(VcClientKey.CONTROL_CLIENT)) {
_controlClient = client;
} else
if (clientKey.equals(VcClientKey.WAIT_FOR_UPDATE_CLIENT)) {
_waitForUpdateClient = client;
} else
if (clientKey.equals(VcClientKey.STATS_POLL_CLIENT)) {
_statsPollClient = client;
}
}
// returns true if it successfully connected to VC
private boolean initClients(boolean useCert, Long customTimeout) {
try {
setClientForKey(VcClientKey.CONTROL_CLIENT, _vcVlsi.connect(_vcCreds, useCert, null,
(customTimeout != null ? customTimeout : VC_CONTROL_CONNECTION_TIMEOUT_MILLIS)));
setClientForKey(VcClientKey.WAIT_FOR_UPDATE_CLIENT, _vcVlsi.connect(_vcCreds, useCert, _controlClient,
(customTimeout != null ? customTimeout : VC_WAIT_FOR_UPDATES_CONNECTION_TIMEOUT_MILLIS)));
setClientForKey(VcClientKey.STATS_POLL_CLIENT, _vcVlsi.connect(_vcCreds, useCert, _controlClient,
(customTimeout != null ? customTimeout : VC_STATS_POLL_CONNECTION_TIMEOUT_MILLIS)));
if ((_controlClient == null) || (_waitForUpdateClient == null) || (_statsPollClient == null)) {
_log.log(Level.WARNING, "Unable to get VC client");
return false;
}
return true;
} catch (Exception e) {
_log.warning("VHM: connection to vCenter failed ("+e.getClass()+"): "+e.getMessage());
return false;
}
}
private boolean connect(Long customTimeout) {
boolean useCert = false;
if ((_vcCreds.keyStoreFile != null) && (_vcCreds.keyStorePwd != null) && (_vcCreds.vcExtKey != null)) {
useCert = true;
}
boolean success = initClients(useCert, customTimeout);
if (useCert && !success && (_vcCreds.user != null) && (_vcCreds.password != null)) {
_log.warning("VHM: certificate based login failed, trying with username and password");
success = initClients(false, customTimeout);
}
if (!success) {
_log.warning("VHM: could not obtain vCenter connection through any protocol");
return false;
}
return _initialized = true;
}
/**
* First tests the validity of a given connection and if it fails, attempts to reset all connections
*
* @param client
* @param customTimeout
* @return
*/
private boolean validateConnection(Client client, Long customTimeout) {
boolean success = false;
if (client != null) {
success = _vcVlsi.testConnection(client);
}
if (!success) {
/* Only one thread should win this race. That thread will initiate the connection and others will not be blocked waiting */
Thread currentThread = Thread.currentThread();
if (_initiatingConnectionThread.compareAndSet(null, currentThread)) {
try {
if (_initialized) {
_log.warning("VHM: connection to vCenter dropped, attempting reconnection");
}
success = connect(customTimeout);
} finally {
_initiatingConnectionThread.compareAndSet(currentThread, null);
}
} else {
_log.info("VHM: VC undergoing initialization by other thread, so returning false");
return false;
}
}
return success;
}
private Client getAndValidate(VcClientKey clientKey, Long customTimeout) {
if (validateConnection(getClientForKey(clientKey), customTimeout)) {
_log.finer("Returning successfully validated client for "+clientKey);
return getClientForKey(clientKey);
}
_log.finer("Returning null client for "+clientKey);
return null;
}
/**
* Returns a client for a particular task after validating that the connection is good
* If the connection is not good, an attempt will be made to re-connect with VC
* If another thread is already in the process of reconnecting, this method will not block and will return null
*
* @param clientKey The VC Client
* @return A valid client or null if a valid connection is not possible
*/
protected Client getAndValidateClient(VcClientKey clientKey) {
return getAndValidate(clientKey, null);
}
/**
* Same as getAndValidateClient except that it has the side effect of resetting waitForUpdates state
* and can take a custom timeout to wait less time for the retry
* If another thread is already in the process of reconnecting, this method will not block and will return null
*
* @param clientKey The VC Client
* @param customTimeout If not null, the custom timeout will be applied to the connection
* @return A valid client or null if a valid connection is not possible
*/
protected Client resetClient(VcClientKey clientKey, Long customTimeout) {
if (clientKey.equals(VcClientKey.WAIT_FOR_UPDATE_CLIENT)) {
_waitForUpdatesVersion = "";
}
return getAndValidate(clientKey, customTimeout);
}
/**
* Returns the current waitForUpdates version used in the waitForUpdates client
* The version is used as a stateful diff mechanism. Eg Block until there are any changes since version X
*
* @return current waitForUpdates
*/
protected String getWaitForUpdatesVersion() {
return _waitForUpdatesVersion;
}
/**
* Sets a new waitForUpdatesVersion following a successful waitForUpdates call
*/
protected void updateWaitForUpdatesVersion(String waitForUpUpdatesVersion) {
_waitForUpdatesVersion = waitForUpUpdatesVersion;
}
}