/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.computesystemcontroller.impl.adapter; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Component; import com.emc.storageos.computesystemcontroller.exceptions.ComputeSystemControllerException; import com.emc.storageos.computesystemcontroller.impl.DiscoveryStatusUtils; import com.emc.storageos.computesystemcontroller.impl.HostToComputeElementMatcher; import com.emc.storageos.db.client.constraint.PrefixConstraint; import com.emc.storageos.db.client.model.DiscoveredDataObject.CompatibilityStatus; import com.emc.storageos.db.client.model.DiscoveredDataObject.RegistrationStatus; 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.Initiator; import com.emc.storageos.db.client.model.IpInterface; import com.emc.storageos.db.client.util.CommonTransformerFunctions; import com.emc.storageos.db.client.util.CustomQueryUtility; import com.emc.storageos.util.SanUtils; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.iwave.ext.vmware.EsxVersion; import com.iwave.ext.vmware.HostStorageAPI; import com.iwave.ext.vmware.VCenterAPI; import com.vmware.vim25.HostConfigInfo; import com.vmware.vim25.HostFibreChannelHba; import com.vmware.vim25.HostHardwareInfo; import com.vmware.vim25.HostHostBusAdapter; import com.vmware.vim25.HostInternetScsiHba; import com.vmware.vim25.HostIpConfigIpV6Address; import com.vmware.vim25.HostIpConfigIpV6AddressConfiguration; import com.vmware.vim25.HostRuntimeInfo; import com.vmware.vim25.HostSystemConnectionState; import com.vmware.vim25.HostVirtualNic; import com.vmware.vim25.mo.HostSystem; /** * * Discovery Adapter for ESX hosts. * * @author kumara4 * */ @Component public class EsxHostDiscoveryAdapter extends AbstractHostDiscoveryAdapter { // VMWare KB 1006250: The UUID of a host can be non unique on White box hardware. // This is a known non-unique UUID private static String KNOWN_DUPLICATE_UUID = "03000200-0400-0500-0006-000700080009"; /** * Create helper API instance of VCenter to traverse tree structure of mob. * * @param host * - {@link Host} instance * @return {@link VCenterAPI} */ public static VCenterAPI createVCenterAPI(Host host) { int port = host.getPortNumber() != null ? host.getPortNumber() : 443; URL url; try { url = new URL("https", host.getHostName(), port, "/sdk"); } catch (MalformedURLException e) { throw new RuntimeException(e.getMessage()); } String username = host.getUsername(); String password = host.getPassword(); return new VCenterAPI(url, username, password); } /** * Returns host type (supported type) * * @return */ @Override protected String getSupportedType() { return HostType.Esx.name(); } /** * (non-Javadoc) * * @see com.emc.storageos.computesystemcontroller.impl.ComputeSystemDiscoveryAdapter #discoverTarget(java.lang.String) */ @Override public void discoverTarget(String targetId) { Host host = getModelClient().hosts().findById(targetId); HostStateChange changes = new HostStateChange(host, host.getCluster()); if (checkHostCredentials(host)) { discoverEsxHost(host, changes); } else { debug("Skipping Esx host discovery, credentials not found for host - %s", host.getHostName()); } } /** * Check if the given host has credentials * * @param host - {@link Host} * @return */ private boolean checkHostCredentials(Host host) { boolean hasCredentials = false; if (null != host.getUsername() && null != host.getPassword()) { hasCredentials = true; } return hasCredentials; } /** * Discover Esx host * * @param host * {@link Host} instance to be discovered * @param changes * {@link HostStateChange} instance containing host changes * during discovery. */ private void discoverEsxHost(Host host, HostStateChange changes) { EsxVersion esxVersion = getVersion(host); if (null != esxVersion && getVersionValidator().isValidEsxVersion(esxVersion)) { changes.setNewCluster(host.getCluster()); discoverHost(host, changes); processHostChanges(changes); matchHostToComputeElements(host); } else { host.setCompatibilityStatus(CompatibilityStatus.INCOMPATIBLE.name()); save(host); throw ComputeSystemControllerException.exceptions .incompatibleHostVersion("Esx", esxVersion.toString(), getVersionValidator().getEsxMinimumVersion(false) .toString()); } } /** * Match hosts to compute elements */ @Override public void matchHostToComputeElements(Host host) { HostToComputeElementMatcher.matchHostToComputeElements(getDbClient(),host.getId()); } /** * Discover Esx host * * @param host * {@link Host} instance to be discovered * @param changes * {@link HostStateChange} instance containing host changes * during discovery. */ @Override protected void discoverHost(Host host, HostStateChange changes) { VCenterAPI api = createVCenterAPI(host); try { List<HostSystem> hostSystems = api.listAllHostSystems(); if (null != hostSystems && !hostSystems.isEmpty()) { // getting the 0th element only coz we are querying an ESX for // hostsystems and this will always return one or none. HostSystem hostSystem = hostSystems.get(0); Host targetHost = null; HostHardwareInfo hw = hostSystem.getHardware(); String uuid = null; if (hw != null && hw.systemInfo != null && StringUtils.isNotBlank(hw.systemInfo.uuid)) { // try finding host by UUID uuid = hw.systemInfo.uuid; if (KNOWN_DUPLICATE_UUID.equalsIgnoreCase(uuid)) { info("Host " + hostSystem.getName() + " contains a known non-unique UUID"); } // search host by uuid in VIPR if host already discovered targetHost = findHostByUuid(uuid); checkDuplicateHost(host, targetHost); } if (targetHost == null) { // if target host is null, this is a new discovery. targetHost = host; } targetHost.setCompatibilityStatus(CompatibilityStatus.COMPATIBLE.name()); targetHost.setDiscoverable(true); if (targetHost.getId() == null) { targetHost .setRegistrationStatus(RegistrationStatus.REGISTERED .toString()); } targetHost.setOsVersion(hostSystem.getConfig().getProduct() .getVersion()); if (hw != null && hw.biosInfo != null && StringUtils.isNotBlank(hw.biosInfo.biosVersion)) { targetHost.setBios(hw.biosInfo.biosVersion); } if (null != uuid) { targetHost.setUuid(uuid); } save(targetHost); DiscoveryStatusUtils.markAsProcessing(getModelClient(), targetHost); try { discoverHost(hostSystem, targetHost, changes); DiscoveryStatusUtils.markAsSucceeded(getModelClient(), targetHost); } catch (RuntimeException e) { warn(e, "Problem discovering host %s", targetHost.getLabel()); DiscoveryStatusUtils.markAsFailed(getModelClient(), targetHost, e.getMessage(), e); } } } finally { api.logout(); } } /** * Check if the host already exists in VIPR * * @param host - {@link Host} instance being discovered / added. * @param targetHost {@link Host} instance from VIPR DB. */ private void checkDuplicateHost(Host host, Host targetHost) { if (targetHost != null && !(host.getId().equals(targetHost.getId()))) { ComputeSystemControllerException ex = ComputeSystemControllerException.exceptions.duplicateSystem("Host", targetHost.getLabel()); DiscoveryStatusUtils.markAsFailed(modelClient, host, ex.getMessage(), ex); throw ex; } } /** * Discover Exs host and its IpIterfaces and Initiators. * * @param hostSystem * - {@link HostSystem} VI SDK managedObject instance * @param targetHost * - {@link Host} being discovered. * @param changes * {@link HostStateChange} instance containing host changes * during discovery. */ private void discoverHost(HostSystem hostSystem, Host targetHost, HostStateChange changes) { // Only attempt to update ip interfaces or initiators for connected // hosts HostSystemConnectionState connectionState = getConnectionState(hostSystem); if (connectionState == HostSystemConnectionState.connected) { // discover initiators List<Initiator> oldInitiators = new ArrayList<Initiator>(); List<Initiator> addedInitiators = new ArrayList<Initiator>(); discoverConnectedHostInitiators(hostSystem, targetHost, oldInitiators, addedInitiators); if (!oldInitiators.isEmpty() || !addedInitiators.isEmpty()) { Collection<URI> oldInitiatorIds = Lists .newArrayList(Collections2 .transform(oldInitiators, CommonTransformerFunctions .fctnDataObjectToID())); changes.setOldInitiators(oldInitiatorIds); Collection<URI> addedInitiatorIds = Lists .newArrayList(Collections2 .transform(addedInitiators, CommonTransformerFunctions .fctnDataObjectToID())); changes.setNewInitiators(addedInitiatorIds); } } else { if (connectionState == HostSystemConnectionState.disconnected) { throw new IllegalStateException("Host is disconnected"); } else if (connectionState == HostSystemConnectionState.notResponding) { throw new IllegalStateException("Host is not responding"); } else { throw new IllegalStateException( "Could not determine host connection state"); } } } /** * Discovers connected Host's Initiators and Ipinterfaces * * @param hostSystem * - {@link HostSystem} VI SDK managedObject instance * @param targetHost * - {@link Host} being discovered. * @param oldInitiators * - old initiator list * @param addedInitiators * - new/added initiator list */ protected void discoverConnectedHostInitiators(HostSystem hostSystem, Host targetHost, List<Initiator> oldInitiators, List<Initiator> addedInitiators) { // discover ipInterfaces info(String.format("Discovering IP interfaces for %s", targetHost.forDisplay())); List<IpInterface> oldIpInterfaces = new ArrayList<IpInterface>(); Iterables.addAll(oldIpInterfaces, getIpInterfaces(targetHost)); for (HostVirtualNic nic : getNics(hostSystem)) { if (isIp6Interface(nic)) { IpInterface ipInterface = getOrCreateIpInterface( oldIpInterfaces, nic.spec.getIp().ipAddress); discoverIp6Interface(targetHost, ipInterface, nic); } if (isIp4Interface(nic)) { IpInterface ipInterface = getOrCreateIpInterface( oldIpInterfaces, nic.spec.getIp().ipAddress); discoverIp4Interface(targetHost, ipInterface, nic); } } removeDiscoveredInterfaces(oldIpInterfaces); info(String.format("Discovering initiators for %s", targetHost.forDisplay())); Iterables.addAll(oldInitiators, getInitiators(targetHost)); for (HostHostBusAdapter hba : getHostBusAdapters(hostSystem)) { if (hba instanceof HostFibreChannelHba) { String port = SanUtils.normalizeWWN(((HostFibreChannelHba) hba) .getPortWorldWideName()); Initiator initiator; if (findInitiatorByPort(oldInitiators, port) == null) { initiator = getOrCreateInitiator(targetHost.getId(), oldInitiators, port); addedInitiators.add(initiator); } else { initiator = getOrCreateInitiator(targetHost.getId(), oldInitiators, port); } discoverInitiator(targetHost, initiator, (HostFibreChannelHba) hba); } else if (hba instanceof HostInternetScsiHba) { String iqn = ((HostInternetScsiHba) hba).getIScsiName(); Initiator initiator; if (findInitiatorByPort(oldInitiators, iqn) == null) { initiator = getOrCreateInitiator(targetHost.getId(), oldInitiators, iqn); addedInitiators.add(initiator); } else { initiator = getOrCreateInitiator(targetHost.getId(), oldInitiators, iqn); } discoverInitiator(targetHost, initiator, (HostInternetScsiHba) hba); } } if (!oldInitiators.isEmpty()) { clearScaleIOInitiators(oldInitiators); } } /** * Get version of host * * @param host * {@link Host} being discovered * @return */ protected EsxVersion getVersion(Host host) { EsxVersion esxVersion = null; VCenterAPI api = createVCenterAPI(host); try { esxVersion = api.getEsxVersion(); } finally { api.logout(); } return esxVersion; } /** * Lookup for host in the db by uuid * * @param uuid * - uuid of host * @return */ protected Host findHostByUuid(String uuid) { return getModelClient().hosts().findByUuid(uuid); } /** * Find an existing host with matching label or ip address * * @param hostSystem * the host system to use * @return host that has a matching label or ip address, null if can't be * found */ protected Host findExistingHost(HostSystem hostSystem) { List<Host> hosts = CustomQueryUtility.queryActiveResourcesByConstraint( dbClient, Host.class, PrefixConstraint.Factory .getFullMatchConstraint(Host.class, "label", hostSystem.getName())); for (Host host : hosts) { if (isEsxOtherOrNoOsHost(host)) { return host; } } List<Host> results = CustomQueryUtility.queryActiveResourcesByAltId( dbClient, Host.class, "hostName", hostSystem.getName()); for (Host host : results) { if (isEsxOtherOrNoOsHost(host)) { return host; } } List<String> ipAddresses = getHostIpAddresses(hostSystem); for (String ipAddress : ipAddresses) { hosts = CustomQueryUtility.queryActiveResourcesByConstraint( dbClient, Host.class, PrefixConstraint.Factory .getFullMatchConstraint(Host.class, "label", ipAddress)); for (Host host : hosts) { if (isEsxOtherOrNoOsHost(host)) { return host; } } } return null; } /** * Finds a matching value in the DB by label, or creates one if none is * found. If a match is found in the list, it will be removed from the list * before returning. * * @param hosts * - host list * @param name * @return */ protected Host getOrCreateHost(List<Host> hosts, String name) { return getOrCreate(Host.class, hosts, name); } /** * Returns true if the host is of type Esx, Other, or No OS * * @param host * host to check the type * @return true if Esx, Other, or No OS, otherwise false */ private boolean isEsxOtherOrNoOsHost(Host host) { return StringUtils.equalsIgnoreCase(host.getType(), HostType.Esx.toString()) || StringUtils.equalsIgnoreCase(host.getType(), HostType.Other.toString()) || StringUtils.equalsIgnoreCase(host.getType(), HostType.No_OS.toString()); } /** * Get list of IP addresses for the given host * * @param hostSystem * {@link HostSystem} vi sdk MO * @return */ private List<String> getHostIpAddresses(HostSystem hostSystem) { List<String> ipAddresses = Lists.newArrayList(); for (HostVirtualNic vnic : getNics(hostSystem)) { if (vnic.getSpec() != null && vnic.getSpec().getIp() != null) { String ipAddress = vnic.getSpec().getIp().getIpAddress(); if (!StringUtils.isEmpty(ipAddress)) { ipAddresses.add(ipAddress); } } } return ipAddresses; } /** * Fetch Nics for the hostsystem * * @param hostSystem * - {@link HostSystem} vi sdk MO * @return */ protected List<HostVirtualNic> getNics(HostSystem hostSystem) { List<HostVirtualNic> nics = Lists.newArrayList(); HostConfigInfo config = hostSystem.getConfig(); if ((config != null) && (config.getNetwork() != null) && (config.getNetwork().getVnic() != null)) { for (HostVirtualNic nic : config.getNetwork().getVnic()) { nics.add(nic); } } return nics; } /** * Find the connection state of the hostsystem * * @param source * - {@link HostSystem} vi sdk MO * @return */ protected HostSystemConnectionState getConnectionState(HostSystem source) { HostRuntimeInfo runtime = source.getRuntime(); HostSystemConnectionState connectionState = (runtime != null) ? runtime .getConnectionState() : null; return connectionState; } /** * Fetches the {@link IpInterface} for the {@link Host} * * @param host * {@link Host} * @return */ protected Iterable<IpInterface> getIpInterfaces(Host host) { return getModelClient().ipInterfaces().findByHost(host.getId(), true); } /** * Check if {@link HostVirtualNic} is Ip4 interface * * @param nic * - {@link HostVirtualNic} * @return */ protected boolean isIp4Interface(HostVirtualNic nic) { return (nic.getSpec() != null) && (nic.getSpec().getIp() != null); } /** * Check if {@link HostVirtualNic} is Ip6 interface * * @param nic * - {@link HostVirtualNic} * @return */ protected boolean isIp6Interface(HostVirtualNic nic) { if ((nic.getSpec() != null) && (nic.getSpec().getIp() != null)) { return getIp6Address(nic) != null; } return false; } /** * Discovery of Ip interface * * @param host * {@linkk Host} * @param ipInterface * {@link IpInterface} * @param nic * {@link HostVirtualNic} */ protected void discoverIp4Interface(Host host, IpInterface ipInterface, HostVirtualNic nic) { setHostInterfaceRegistrationStatus(ipInterface, host); ipInterface.setHost(host.getId()); ipInterface.setProtocol(Protocol.IPV4.name()); ipInterface.setIpAddress(nic.getSpec().getIp().getIpAddress()); ipInterface.setNetmask(nic.getSpec().getIp().getSubnetMask()); ipInterface.setIsManualCreation(false); save(ipInterface); } /** * Discovery of Ip interface * * @param host * {@linkk Host} * @param ipInterface * {@link IpInterface} * @param nic * {@link HostVirtualNic} */ protected void discoverIp6Interface(Host host, IpInterface ipInterface, HostVirtualNic nic) { HostIpConfigIpV6Address config = getIp6Address(nic); setHostInterfaceRegistrationStatus(ipInterface, host); ipInterface.setHost(host.getId()); ipInterface.setProtocol(Protocol.IPV6.name()); ipInterface.setIpAddress(config.getIpAddress()); ipInterface.setPrefixLength(config.getPrefixLength()); ipInterface.setIsManualCreation(false); save(ipInterface); } /** * Fetch the IPv6 address of {@link HostVirtualNic} * * @param nic * {@link HostVirtualNic} * @return */ protected HostIpConfigIpV6Address getIp6Address(HostVirtualNic nic) { HostIpConfigIpV6AddressConfiguration ip6Config = nic.getSpec().getIp() .getIpV6Config(); if (ip6Config != null) { HostIpConfigIpV6Address[] address = ip6Config.getIpV6Address(); if ((address != null) && (address.length == 1)) { return address[0]; } } return null; } /** * Finds a matching value in the list of IpInterfaces by ipAddress, or * creates one if none is found. If a match is found in the list, it will be * removed from the list before returning. * * @param ipInterfaces * @param ip * @return */ protected IpInterface getOrCreateIpInterface( List<IpInterface> ipInterfaces, String ip) { return getOrCreateInterfaceByIp(ipInterfaces, ip); } /** * Fetch initiators corresponding the {@link Host} * * @param host * {@link Host} * @return */ protected Iterable<Initiator> getInitiators(Host host) { return getModelClient().initiators().findByHost(host.getId(), true); } /** * Fetch {@link HostHostBusAdapter} corresponding the {@link HostSystem} * * @param host * {@link HostSystem} * @return */ protected List<HostHostBusAdapter> getHostBusAdapters(HostSystem host) { List<HostHostBusAdapter> hostBusAdapters = Lists.newArrayList(); HostStorageAPI storageAPI = new HostStorageAPI(host); for (HostHostBusAdapter hba : storageAPI.listHostBusAdapters()) { hostBusAdapters.add(hba); } return hostBusAdapters; } /** * Discover FC Initiator * * @param host * {@link Host} * @param initiator * {@link Initiator} * @param hba * {@link HostFibreChannelHba} */ protected void discoverInitiator(Host host, Initiator initiator, HostFibreChannelHba hba) { setInitiatorHost(initiator, host); initiator.setProtocol(Protocol.FC.name()); initiator .setInitiatorNode(SanUtils.normalizeWWN(hba.nodeWorldWideName)); initiator .setInitiatorPort(SanUtils.normalizeWWN(hba.portWorldWideName)); initiator.setIsManualCreation(false); initiator.setLabel(SanUtils.normalizeWWN(hba.portWorldWideName)); save(initiator); } /** * Discover Scsi Initiator * * @param host * {@link Host} * @param initiator * {@link Initiator} * @param hba * {@link HostInternetScsiHba} */ protected void discoverInitiator(Host host, Initiator initiator, HostInternetScsiHba hba) { setInitiatorHost(initiator, host); initiator.setProtocol(Protocol.iSCSI.name()); initiator.setInitiatorNode(""); initiator.setInitiatorPort(hba.getIScsiName()); initiator.setIsManualCreation(false); initiator.setLabel(hba.getIScsiName()); save(initiator); } /** * Sets properties pertaining to the {@link Initiator} * * @param initiator * {@link Initiator} * @param host * {@link Host} */ protected void setInitiatorHost(Initiator initiator, Host host) { setHostInterfaceRegistrationStatus(initiator, host); initiator.setHost(host.getId()); initiator.setHostName(host.getHostName()); } @Override protected void discoverIpInterfaces(Host host, List<IpInterface> oldIpInterfaces) { // Do nothing, for ESX host ip interfaces are discovered differently } @Override protected void discoverInitiators(Host host, List<Initiator> oldInitiators, HostStateChange changes) { // Do nothing, for ESX host Initiators are discovered differently } @Override protected void setNativeGuid(Host host) { // TODO Auto-generated method stub } }