/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.computesystemcontroller.impl; import java.net.URI; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.computesystemcontroller.exceptions.ComputeSystemControllerException; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.model.ComputeElement; import com.emc.storageos.db.client.model.DataObject; import com.emc.storageos.db.client.model.DiscoveredDataObject.RegistrationStatus; import com.emc.storageos.db.client.model.DiscoveredSystemObject; import com.emc.storageos.db.client.model.Host; import com.emc.storageos.db.client.model.UCSServiceProfile; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.volumecontroller.impl.ControllerUtils; public final class HostToComputeElementMatcher { private final static Logger _log = LoggerFactory.getLogger(HostToComputeElementMatcher.class); private static StringBuffer failureMessages; private static DbClient dbClient; private static Map<URI,Host> hostMap = new HashMap<>(); private static Map<URI,ComputeElement> computeElementMap = new HashMap<>(); private static Map<URI,UCSServiceProfile> serviceProfileMap = new HashMap<>(); private static boolean allHostsLoaded = false; private final static String UUID_REGEX = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"; private final static Pattern UUID_PATTERN = Pattern.compile(UUID_REGEX,Pattern.CASE_INSENSITIVE); private final static String FIELD_UUID = "uuid"; private final static String FIELD_COMPUTE_ELEMENT = "computeElement"; private final static String FIELD_REG_STATUS = "registrationStatus"; private final static String FIELD_HOST_NAME = "hostName"; private final static String FIELD_SERVICE_PROFILE = "serviceProfile"; private final static String FIELD_LABEL = "label"; private final static String FIELD_DN = "dn"; private final static String FIELD_AVAIL = "available"; private final static String FIELD_COMPUTE_SYSTEM = "computeSystem"; private HostToComputeElementMatcher(){} public static synchronized void matchHostToComputeElements(DbClient _dbClient, URI hostId) { Collection<URI> hostIds = Arrays.asList(hostId); // single host matchHosts(_dbClient, hostIds, null); } public static synchronized void matchHostsToComputeElements(DbClient _dbClient, Collection<URI> hostIds) { matchHosts(_dbClient, hostIds, null); } public static synchronized void matchAllHostsToComputeElements(DbClient _dbClient, URI computeSystem) { matchHosts(_dbClient, null, computeSystem); } private static void matchHosts(DbClient _dbClient,Collection<URI> hostIds, URI computeSystem) { dbClient = _dbClient; failureMessages = new StringBuffer(); allHostsLoaded = false; if(hostIds == null) { hostIds = dbClient.queryByType(Host.class, true); // all active hosts allHostsLoaded = true; } Collection<URI> computeElementIds = dbClient.queryByType(ComputeElement.class, true); // all active Collection<URI> serviceProfileIds = dbClient.queryByType(UCSServiceProfile.class, true); // all active load(hostIds,computeElementIds,serviceProfileIds); // load hosts, computeElements &SPs removeDuplicateUuids(computeSystem); // detect and remove CEs & SPs with duplicate UUIDs matchHostsToBladesAndSPs(); // find hosts & blades whose UUIDs match catchDuplicateMatches(); // validate matches (check for duplicates) updateDb(); // persist changed Hosts & ServiceProfiles // after correcting all associations possible, cause discovery failure with message if (failureMessages.length() > 0) { throw ComputeSystemControllerException.exceptions.hostMatcherError(failureMessages.toString()); } } private static void load(Collection<URI> hostIds, Collection<URI> computeElementIds, Collection<URI> serviceProfileIds) { Collection<Host> allHosts = dbClient.queryObjectFields(Host.class, Arrays.asList(FIELD_UUID, FIELD_COMPUTE_ELEMENT, FIELD_REG_STATUS, FIELD_HOST_NAME, FIELD_SERVICE_PROFILE, FIELD_LABEL), ControllerUtils.getFullyImplementedCollection(hostIds)); Collection<ComputeElement> allComputeElements = dbClient.queryObjectFields(ComputeElement.class, Arrays.asList(FIELD_UUID, FIELD_REG_STATUS, FIELD_DN, FIELD_AVAIL, FIELD_LABEL, FIELD_COMPUTE_SYSTEM), ControllerUtils.getFullyImplementedCollection(computeElementIds)); Collection<UCSServiceProfile> allUCSServiceProfiles = dbClient.queryObjectFields(UCSServiceProfile.class, Arrays.asList(FIELD_UUID, FIELD_REG_STATUS, FIELD_DN, FIELD_LABEL, FIELD_COMPUTE_SYSTEM), ControllerUtils.getFullyImplementedCollection(serviceProfileIds)); hostMap = makeUriMap(allHosts); computeElementMap = makeUriMap(allComputeElements); serviceProfileMap = makeUriMap(allUCSServiceProfiles); } private static void removeDuplicateUuids(URI computeSystem) { Map<String,URI> ceDuplicateMap = new HashMap<>(); List<URI> ceDuplicateIds = new ArrayList<>(); for (ComputeElement ce : computeElementMap.values()) { if (NullColumnValueGetter.isNotNullValue(ce.getUuid()) && isValidUuid(ce.getUuid())) { if (!ceDuplicateMap.containsKey(ce.getUuid())) { ceDuplicateMap.put(ce.getUuid(),ce.getId()); } else { ComputeElement duplicateCe = computeElementMap.get(ceDuplicateMap.get(ce.getUuid())); String errMsg = "ComputeElements found having the same UUID " + info(ce) + " and " + info(duplicateCe); if( NullColumnValueGetter.isNullURI(computeSystem) || ce.getComputeSystem().equals(computeSystem) || duplicateCe.getComputeSystem().equals(computeSystem)) { failureMessages.append(errMsg); // fail discovery if no UCS or for affected UCS only } else { _log.warn(errMsg); // if neither in UCS, just log warning } ceDuplicateIds.add(ce.getId()); ceDuplicateIds.add(ceDuplicateMap.get(ce.getUuid())); } } } computeElementMap.keySet().removeAll(ceDuplicateIds); Map<String,URI> spDuplicateMap = new HashMap<>(); List<URI> spDuplicateIds = new ArrayList<>(); for (UCSServiceProfile sp : serviceProfileMap.values()) { if (NullColumnValueGetter.isNotNullValue(sp.getUuid()) && isValidUuid(sp.getUuid())) { if (!spDuplicateMap.containsKey(sp.getUuid())) { spDuplicateMap.put(sp.getUuid(),sp.getId()); } else { UCSServiceProfile duplicateSp = serviceProfileMap.get(spDuplicateMap.get(sp.getUuid())); String errMsg = "UCS Service Profiles found having the same UUID " + info(sp) + " and " + info(duplicateSp); if( NullColumnValueGetter.isNullURI(computeSystem) || sp.getComputeSystem().equals(computeSystem) || duplicateSp.getComputeSystem().equals(computeSystem)) { failureMessages.append(errMsg); // fail discovery if no UCS or for affected UCS only } else { _log.warn(errMsg); // if neither in UCS, just log warning } spDuplicateIds.add(sp.getId()); spDuplicateIds.add(spDuplicateMap.get(sp.getUuid())); } } } serviceProfileMap.keySet().removeAll(spDuplicateIds); } private static void matchHostsToBladesAndSPs() { // lookup map (to find CEs by their UUID) Map<String,ComputeElement> ceMap = new HashMap<>(); for(ComputeElement ce : computeElementMap.values()) { if (isValidUuid(ce.getUuid())) { ceMap.put(ce.getUuid(),ce); } } // lookup map (to find SPs by their UUID) Map<String,UCSServiceProfile> spMap = new HashMap<>(); for(UCSServiceProfile sp : serviceProfileMap.values()) { if (isValidUuid(sp.getUuid())) { spMap.put(sp.getUuid(),sp); } } for (Host host: hostMap.values()) { _log.info("matching host " + info(host)); // clear blade & SP associations for hosts that are unregistered or have bad UUIDs if(isUnregistered(host) || !hasValidUuid(host)) { _log.info("skipping host (unregistered or bad UUID); " + info(host)); clearHostAssociations(host); continue; // next host } // find matching blade & SP ComputeElement ce = getMatchingComputeElement(host,ceMap); UCSServiceProfile sp = getMatchingServiceProfile(host,spMap); // update Host & ServiceProfile if (sp == null) { _log.info("no SP match for host " + info(host)); clearHostAssociations(host); // clear associations if no SP match } else { _log.info("matched host to SP & CE " + info(host) + ", " + info(sp) + ", " + info(ce)); setHostAssociations(host,ce,sp); } } } private static void catchDuplicateMatches() { // safety checks to prevent DL/DU if(!allHostsLoaded) { return; // only complete if we loaded all hosts } Map<URI,URI> ceToHostMap = new HashMap<>(); Map<URI,URI> spToHostMap = new HashMap<>(); for(Host host : hostMap.values() ){ if(!NullColumnValueGetter.isNullURI(host.getComputeElement())) { if(!ceToHostMap.containsKey(host.getComputeElement())) { ceToHostMap.put(host.getComputeElement(),host.getId()); } else { String msg = "The hosts " + info(host) + " and " + info(hostMap.get(ceToHostMap.get(host.getComputeElement()))) + " will not be associated to a ComputeElement since they both match the same one: " + info(computeElementMap.get(host.getComputeElement())); failureMessages.append(msg); _log.warn(msg); clearHostAssociations(hostMap.get(ceToHostMap.get(host.getComputeElement()))); clearHostAssociations(host); } } if(!NullColumnValueGetter.isNullURI(host.getServiceProfile())) { if(!spToHostMap.containsKey(host.getServiceProfile())) { spToHostMap.put(host.getServiceProfile(),host.getId()); } else { String msg = "The hosts " + info(host) + " and " + info(hostMap.get(spToHostMap.get(host.getServiceProfile()))) + " will not be associated to a UCS ServiceProfile since they both match the same one: " + info(serviceProfileMap.get(host.getServiceProfile())); failureMessages.append(msg); _log.warn(msg); clearHostAssociations(hostMap.get(spToHostMap.get(host.getServiceProfile()))); clearHostAssociations(host); } } } } private static void updateDb() { List<Host> hostsToUpdate = new ArrayList<>(); for(Host host : hostMap.values()) { if(host.isChanged("computeElement") || host.isChanged("serviceProfile")) { hostsToUpdate.add(host); } } dbClient.updateObject(hostsToUpdate); List<UCSServiceProfile> spsToUpdate = new ArrayList<>(); for(UCSServiceProfile sp : serviceProfileMap.values()) { if(sp.isChanged("host")) { spsToUpdate.add(sp); } } dbClient.updateObject(spsToUpdate); } private static ComputeElement getMatchingComputeElement(Host host, Map<String, ComputeElement> ceMap) { if(!isValidUuid(host.getUuid())) { return null; } // check for matching UUID String uuid = host.getUuid(); ComputeElement ceWithSameUuid = null; if (ceMap.containsKey(uuid) && hostNameMatches(ceMap.get(uuid).getDn(),host) && !isUnregistered(ceMap.get(uuid))) { ceWithSameUuid = ceMap.get(uuid); } // check for matching UUID in mixed-endian format String uuidReversed = reverseUuidBytes(host.getUuid()); ComputeElement ceWithReversedUuid = null; if (ceMap.containsKey(uuidReversed) && hostNameMatches(ceMap.get(uuidReversed).getDn(),host) && !isUnregistered(ceMap.get(uuidReversed))) { ceWithReversedUuid = ceMap.get(uuidReversed); } if( (ceWithSameUuid != null) && // found blade with UUID (ceWithReversedUuid != null) && // found blade with reversed UUID !uuid.equalsIgnoreCase(uuidReversed)) { // UUID is not same when reversed String errMsg = "Host match failed for ComputeElement because host " + info(host) + " matches multiple blades " + info(ceWithSameUuid) + " and " + info(ceWithReversedUuid); _log.error(errMsg); failureMessages.append(errMsg); return null; } return ceWithSameUuid != null ? ceWithSameUuid : ceWithReversedUuid; } private static UCSServiceProfile getMatchingServiceProfile(Host host, Map<String, UCSServiceProfile> spMap) { if(!isValidUuid(host.getUuid())) { return null; } // check for matching UUID String uuid = host.getUuid(); UCSServiceProfile spWithSameUuid = null; if (spMap.containsKey(uuid) && hostNameMatches(spMap.get(uuid).getDn(),host) && !isUnregistered(spMap.get(uuid))) { spWithSameUuid = spMap.get(uuid); } // check for matching UUID in mixed-endian format String uuidReversed = reverseUuidBytes(host.getUuid()); UCSServiceProfile spWithReversedUuid = null; if (spMap.containsKey(uuidReversed) && hostNameMatches(spMap.get(uuidReversed).getDn(),host) && !isUnregistered(spMap.get(uuidReversed))) { spWithReversedUuid = spMap.get(uuidReversed); } if( ((spWithSameUuid != null) && // found SP with UUID (spWithReversedUuid != null)) && // found SP with reversed UUID !uuid.equalsIgnoreCase(uuidReversed)) { // UUID is not same when reversed String errMsg = "Host match failed for UCS Service Profile because host " + info(host) + " matches multiple Service Profiles " + info(spWithSameUuid) + " and " + info(spWithReversedUuid); _log.error(errMsg); failureMessages.append(errMsg); return null; } return spWithSameUuid != null ? spWithSameUuid : spWithReversedUuid; } private static void setHostAssociations(Host hostIn, ComputeElement ceIn, UCSServiceProfile spIn) { Host host = hostMap.get(hostIn.getId()); if (NullColumnValueGetter.isNullURI(host.getServiceProfile())) { host.setServiceProfile(spIn.getId()); // set new SP for host } else if(!host.getServiceProfile().equals(spIn.getId())) { // Unexpected SP association changes should result in discovery failure failureMessages.append("Host's UCS Service Profile unexpectedly tried to change from " + host.getServiceProfile() + " to " + spIn.getId() + " for host " + info(host)); clearHostAssociations(host); return; } if (ceIn == null) { // happens when blade is not associated if (!NullColumnValueGetter.isNullURI(host.getComputeElement()) ) { host.setComputeElement(NullColumnValueGetter.getNullURI()); } } else if( (host.getComputeElement() == null) || (!host.getComputeElement().equals(ceIn.getId()))) { host.setComputeElement(ceIn.getId()); // set new CE for host } if(host.isChanged("computeElement") || host.isChanged("serviceProfile")) { hostMap.put(host.getId(), host); } UCSServiceProfile sp = serviceProfileMap.get(spIn.getId()); if( (sp.getHost() == null) || (!sp.getHost().equals(host.getId()))) { sp.setHost(host.getId()); // set new host in SP serviceProfileMap.put(sp.getId(), sp); } } private static void clearHostAssociations(Host host) { Host h = hostMap.get(host.getId()); if(!NullColumnValueGetter.isNullURI(h.getComputeElement())) { h.setComputeElement(NullColumnValueGetter.getNullURI()); } if(!NullColumnValueGetter.isNullURI(h.getServiceProfile())) { h.setServiceProfile(NullColumnValueGetter.getNullURI()); } if(host.isChanged("computeElement") || host.isChanged("serviceProfile")) { hostMap.put(host.getId(), host); } // clear reference from SPs back to this Host for(UCSServiceProfile sp : serviceProfileMap.values()) { if( (sp.getHost() != null) && sp.getHost().equals(host.getId())) { sp.setHost(NullColumnValueGetter.getNullURI()); serviceProfileMap.put(sp.getId(),sp); } } } private static boolean hasValidUuid(Host h) { return isValidUuid(h.getUuid()); } private static boolean isValidUuid(String uuid) { if ( uuid == null ) { return false; } return UUID_PATTERN.matcher(uuid).matches(); } private static String reverseUuidBytes(String uuid) { /** * Older hosts report UUID in Big-Endian or "network-byte-order" (Most Significant Byte first) * e.g.: {00112233-4455-6677-8899-AABBCCDDEEFF} * Newer Hosts' BIOSs supporting SMBIOS 2.6 or later report UUID in Little-Endian or "wire-format", where * first 3 parts are in byte revered order (aka: 'mixed-endian') * e.g.: {33221100-5544-7766-8899-AABBCCDDEEFF} **/ // reverse bytes UUID uuidObj = UUID.fromString(uuid); ByteBuffer bb = ByteBuffer.wrap(new byte[16]); bb.putLong(uuidObj.getMostSignificantBits()); bb.putLong(uuidObj.getLeastSignificantBits()); byte[] reorderedUuid = new byte[16]; reorderedUuid[0] = bb.get(3); // reverse bytes in 1st part reorderedUuid[1] = bb.get(2); reorderedUuid[2] = bb.get(1); reorderedUuid[3] = bb.get(0); reorderedUuid[4] = bb.get(5); // reverse bytes in 2nd part reorderedUuid[5] = bb.get(4); reorderedUuid[6] = bb.get(7); // reverse bytes in 3rd part reorderedUuid[7] = bb.get(6); for(int byteIndex = 8; byteIndex < 16; byteIndex++ ) { reorderedUuid[byteIndex] = bb.get(byteIndex); // copy 4th & 5th parts unchanged } bb = ByteBuffer.wrap(reorderedUuid); UUID uuidNew = new UUID(bb.getLong(), bb.getLong()); return uuidNew.toString(); } private static <T extends DataObject> Map<URI,T> makeUriMap(Collection<T> c) { Map<URI,T> map = new HashMap<>(); for(T dataObj : c) { map.put(dataObj.getId(), dataObj); } return map; } private static String info(Host h) { return h == null ? "" : (h.getLabel() != null ? "'" + h.getLabel() + "' " : "") + "(" + h.getId() + ")" + (h.getUuid() != null ? " [" + h.getUuid() + "]" : ""); } private static String info(ComputeElement ce) { return ce == null ? "" : (ce.getLabel() != null ? "'" + ce.getLabel() + "' " : "") + "(" + ce.getId() + ")" + (ce.getUuid() != null ? " [" + ce.getUuid() + "]" : ""); } private static String info(UCSServiceProfile sp) { return sp == null ? "" : (sp.getLabel() != null ? "'" + sp.getLabel() + "' " : "") + "(" + sp.getId() + ")" + (sp.getUuid() != null ? " [" + sp.getUuid() + "]" : ""); } private static boolean isUnregistered(DiscoveredSystemObject o) { return RegistrationStatus.UNREGISTERED.name().equals(o.getRegistrationStatus()); } private static boolean hostNameMatches(String dn, Host h) { // dn for CE & SP should end with Host's hostName return (dn != null) && (h.getHostName() != null) && !h.getHostName().isEmpty() && dn.contains(h.getHostName()); } }