/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.computesystemcontroller.impl.adapter;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.emc.storageos.db.client.util.CommonTransformerFunctions;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.iwave.ext.windows.WindowsClusterUtils;
import com.iwave.ext.windows.model.wmi.*;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import com.emc.storageos.computesystemcontroller.exceptions.ComputeSystemControllerException;
import com.emc.storageos.computesystemcontroller.impl.ComputeSystemHelper;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.model.Cluster;
import com.emc.storageos.db.client.model.DiscoveredDataObject.CompatibilityStatus;
import com.emc.storageos.db.client.model.Host;
import com.emc.storageos.db.client.model.Host.HostType;
import com.emc.storageos.db.client.model.HostInterface.Protocol;
import com.emc.storageos.db.client.model.AuthnProvider;
import com.emc.storageos.db.client.model.Initiator;
import com.emc.storageos.db.client.model.IpInterface;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.util.KerberosUtil;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.iwave.ext.windows.WindowsSystemWinRM;
import com.iwave.ext.windows.winrm.WinRMException;
import com.iwave.ext.windows.winrm.WinRMSoapException;
import com.iwave.ext.windows.winrm.WinRMTarget;
@Component
public class WindowsHostDiscoveryAdapter extends AbstractHostDiscoveryAdapter {
@Override
protected String getSupportedType() {
return HostType.Windows.name();
}
protected void init() {
List<AuthnProvider> authProviders = new ArrayList<AuthnProvider>();
Iterables.addAll(authProviders, getModelClient().of(AuthnProvider.class).findAll(true));
KerberosUtil.initializeKerberos(authProviders);
}
@Override
protected void discoverHost(Host host, HostStateChange changes) {
init();
WindowsVersion version = getVersion(host);
host.setOsVersion(version.toString());
if (getVersionValidator().isValidWindowsVersion(version)) {
URI cluster = findCluster(host);
changes.setOldCluster(host.getCluster());
changes.setNewCluster(cluster);
host.setCompatibilityStatus(CompatibilityStatus.COMPATIBLE.name());
save(host);
super.discoverHost(host, changes);
}
else {
host.setCompatibilityStatus(CompatibilityStatus.INCOMPATIBLE.name());
save(host);
throw ComputeSystemControllerException.exceptions.incompatibleHostVersion(
getSupportedType(), version.toString(), getVersionValidator().getWindowsMinimumVersion(false).toString());
}
}
protected URI findCluster(Host host) {
WindowsSystemWinRM system = createWindowsSystem(host);
try {
if (system.isClustered()) {
Map<String, List<MSClusterNetworkInterface>> clusterToNetworkInterfaces = system.getClusterToNetworkInterfaces();
String clusterName = WindowsClusterUtils.findWindowsClusterHostIsIn(host.getHostName(), clusterToNetworkInterfaces);
if (clusterName == null) {
if (clusterToNetworkInterfaces.size() == 1) {
clusterName = clusterToNetworkInterfaces.keySet().iterator().next();
} else if (clusterToNetworkInterfaces.isEmpty()) {
warn("Host '%s' appears to be clustered, but cannot find any cluster interfaces",
host.getHostName());
return NullColumnValueGetter.getNullURI();
} else {
warn("Host '%s' is configured in multiple clusters %s, cannot determine primary cluster by network interface",
host.getHostName(), clusterToNetworkInterfaces.keySet());
return NullColumnValueGetter.getNullURI();
}
}
List<String> clusterIpAddresses = WindowsClusterUtils
.getClusterIpAddresses(clusterToNetworkInterfaces.get(clusterName));
// Find the cluster by address
URI cluster = findClusterByAddresses(host, host.getTenant(), HostType.Windows, clusterIpAddresses);
if (cluster != null) {
updateClusterName(cluster, clusterName);
return cluster;
}
// Find the cluster by name
cluster = findClusterByName(host.getTenant(), clusterName);
if (cluster != null) {
// Ensure the cluster is empty before using it or if host already belongs to this cluster
if (Iterables.isEmpty(getModelClient().hosts().findByCluster(cluster, true))
|| (!NullColumnValueGetter.isNullURI(host.getCluster())
&& host.getCluster().toString().equals(cluster.toString()))) {
updateClusterName(cluster, clusterName);
return cluster;
}
// Log a warning
warn("Host '%s' is in a cluster named '%s' which could not be matched by address. "
+ "An existing non-empty ViPR cluster exists with the same name; "
+ "manual cluster assignment required", host.getHostName(), clusterName);
return NullColumnValueGetter.getNullURI();
}
// No cluster matched by address or name, create a new one
return createNewCluster(host.getTenant(), clusterName);
}
// Host is not currently in a Windows Cluster
return NullColumnValueGetter.getNullURI();
} catch (WinRMException e) {
throw new RuntimeException(e);
}
}
private void updateClusterName(URI clusterId, String name) {
Cluster cluster = dbClient.queryObject(Cluster.class, clusterId);
if (cluster != null) {
cluster.setLabel(name);
dbClient.updateObject(cluster);
ComputeSystemHelper.updateInitiatorClusterName(dbClient, clusterId);
}
}
protected WindowsVersion getVersion(Host host) {
WindowsSystemWinRM system = createWindowsSystem(host);
try {
WindowsVersion version = system.getVersion();
if (version == null) {
version = new WindowsVersion("-");
}
return version;
} catch (WinRMException e) {
throw new RuntimeException(e);
}
}
@Override
protected void discoverInitiators(Host host, List<Initiator> oldInitiators, HostStateChange changes) {
WindowsSystemWinRM windows = createWindowsSystem(host);
List<Initiator> addedInitiators = new ArrayList<Initiator>();
try {
for (FibreChannelHBA hba : windows.listFibreChannelHBAs()) {
Initiator initiator;
if (findInitiatorByPort(oldInitiators, hba.getPortWWN()) == null) {
initiator = getOrCreateInitiator(host.getId(), oldInitiators, hba.getPortWWN());
addedInitiators.add(initiator);
} else {
initiator = getOrCreateInitiator(host.getId(), oldInitiators, hba.getPortWWN());
}
discoverFCInitiator(host, initiator, hba);
}
} catch (WinRMSoapException e) {
info("Could not retrieve fibre channel HBAs: %s", e.getMessage());
clearInitiators(oldInitiators, Protocol.FC.name());
} catch (WinRMException e) {
warn(e, "Error while retrieving fibre channel HBAs: %s", e.getMessage());
clearInitiators(oldInitiators, Protocol.FC.name());
}
try {
for (String iqn : windows.listIScsiInitiators()) {
Initiator initiator;
if (findInitiatorByPort(oldInitiators, iqn) == null) {
initiator = getOrCreateInitiator(host.getId(), oldInitiators, iqn);
addedInitiators.add(initiator);
} else {
initiator = getOrCreateInitiator(host.getId(), oldInitiators, iqn);
}
discoverISCSIInitiator(host, initiator, iqn);
}
} catch (WinRMSoapException e) {
info("Could not retrieve iSCSI interfaces: %s", e.getMessage());
clearInitiators(oldInitiators, Protocol.iSCSI.name());
} catch (WinRMException e) {
warn(e, "Error while retrieving iSCSI interfaces: %s", e.getMessage());
clearInitiators(oldInitiators, Protocol.iSCSI.name());
}
// update export groups with new initiators if host is in use.
if (!addedInitiators.isEmpty()) {
Collection<URI> addedInitiatorIds = Lists.newArrayList(Collections2.transform(addedInitiators,
CommonTransformerFunctions.fctnDataObjectToID()));
changes.setNewInitiators(addedInitiatorIds);
}
}
private void discoverFCInitiator(Host host, Initiator initiator, FibreChannelHBA hba) {
setInitiator(initiator, host);
initiator.setProtocol(Protocol.FC.name());
initiator.setInitiatorNode(hba.getNodeWWN());
initiator.setInitiatorPort(hba.getPortWWN());
setHostInterfaceRegistrationStatus(initiator, host);
save(initiator);
}
private void discoverISCSIInitiator(Host host, Initiator initiator, String iqn) {
setInitiator(initiator, host);
initiator.setInitiatorNode("");
initiator.setInitiatorPort(iqn);
initiator.setProtocol(Protocol.iSCSI.name());
setHostInterfaceRegistrationStatus(initiator, host);
save(initiator);
}
@Override
protected void discoverIpInterfaces(Host host, List<IpInterface> oldIpInterfaces) {
WindowsSystemWinRM windows = createWindowsSystem(host);
try {
for (NetworkAdapter adapter : windows.listNetworkAdapters()) {
if (StringUtils.isNotBlank(adapter.getIpAddress())) {
IpInterface ipInterface = getOrCreateIpInterface(oldIpInterfaces, adapter.getIpAddress());
discoverIp4Interface(host, ipInterface, adapter);
}
if (StringUtils.isNotBlank(adapter.getIp6Address())) {
IpInterface ipInterface = getOrCreateIpInterface(oldIpInterfaces, adapter.getIp6Address());
discoverIp6Interface(host, ipInterface, adapter);
}
}
} catch (WinRMException e) {
warn(e, "Error while retrieving IP interfaces: %s", e.getMessage());
oldIpInterfaces.clear();
}
}
private void discoverIp4Interface(Host host, IpInterface ipInterface, NetworkAdapter adapter) {
ipInterface.setHost(host.getId());
ipInterface.setProtocol(Protocol.IPV4.name());
ipInterface.setIpAddress(adapter.getIpAddress());
ipInterface.setNetmask(adapter.getSubnetMask());
ipInterface.setIsManualCreation(false);
setHostInterfaceRegistrationStatus(ipInterface, host);
save(ipInterface);
}
private void discoverIp6Interface(Host host, IpInterface ipInterface, NetworkAdapter adapter) {
ipInterface.setHost(host.getId());
ipInterface.setProtocol(Protocol.IPV6.name());
// TODO parse ip6 address?
ipInterface.setIpAddress(adapter.getIp6Address());
ipInterface.setIsManualCreation(false);
setHostInterfaceRegistrationStatus(ipInterface, host);
save(ipInterface);
}
public static WindowsSystemWinRM createWindowsSystem(Host host) {
boolean ssl = (host.getUseSSL() != null) ? host.getUseSSL() : false;
int port = (host.getPortNumber() != null) ? host.getPortNumber() : (ssl ? WinRMTarget.DEFAULT_HTTPS_PORT
: WinRMTarget.DEFAULT_HTTP_PORT);
WinRMTarget target = new WinRMTarget(host.getHostName(), port, ssl, host.getUsername(), host.getPassword());
return new WindowsSystemWinRM(target);
}
@Override
public String getErrorMessage(Throwable t) {
Throwable rootCause = getRootCause(t);
if (rootCause instanceof WinRMException) {
if (StringUtils.equals("Authentication Failed", rootCause.getMessage())) {
return "Login failed, invalid username or password";
}
}
return super.getErrorMessage(t);
}
public void setDbCLient(DbClient dbClient) {
super.setDbClient(dbClient);
}
/**
* Gets the primary network adapter by returning adapter with lowest index (name)
*
* @param adapters
* @return network adapter
*/
private NetworkAdapter getPrimaryNetworkAdapter(List<NetworkAdapter> adapters) {
if (adapters.isEmpty()) {
return null;
}
Collections.sort(adapters, new Comparator<NetworkAdapter>() {
@Override
public int compare(NetworkAdapter na1, NetworkAdapter na2) {
return na1.getName().compareTo(na2.getName());
}
});
return adapters.get(0);
}
/**
* Removes initiators from list that have the given protocol
*
* @param initiators list of initiators
* @param protocol protocol to compare and remove
*/
private void clearInitiators(List<Initiator> initiators, String protocol) {
Iterator<Initiator> iterator = initiators.iterator();
while (iterator.hasNext()) {
Initiator initiator = iterator.next();
if (StringUtils.equals(initiator.getProtocol(), protocol)) {
iterator.remove();
}
}
}
@Override
protected void setNativeGuid(Host host) {
WindowsSystemWinRM windows = createWindowsSystem(host);
try {
NetworkAdapter adapter = getPrimaryNetworkAdapter(windows.listNetworkAdapters());
if (adapter != null && !host.getNativeGuid().equalsIgnoreCase(adapter.getMacAddress())) {
checkDuplicateHost(host, adapter.getMacAddress());
info("Setting nativeGuid for " + host.getId() + " as " + adapter.getMacAddress());
host.setNativeGuid(adapter.getMacAddress());
save(host);
}
} catch (WinRMException e) {
throw new RuntimeException(e);
}
}
}