/* * NOTE: This copyright does *not* cover user programs that use Hyperic * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004-2011], VMware, Inc. * This file is part of Hyperic. * * Hyperic is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program is distributed * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.hq.appdef.server.session; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Properties; import javax.annotation.PostConstruct; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.Hibernate; import org.hyperic.hq.appdef.ConfigResponseDB; import org.hyperic.hq.appdef.Ip; import org.hyperic.hq.appdef.shared.AIIpValue; import org.hyperic.hq.appdef.shared.AIPlatformValue; import org.hyperic.hq.appdef.shared.AIQueueConstants; import org.hyperic.hq.appdef.shared.AIServerValue; import org.hyperic.hq.appdef.shared.AppdefEntityID; import org.hyperic.hq.appdef.shared.CPropManager; import org.hyperic.hq.appdef.shared.ConfigManager; import org.hyperic.hq.appdef.shared.PlatformManager; import org.hyperic.hq.authz.server.session.AuthzSubject; import org.hyperic.hq.authz.shared.AuthzConstants; import org.hyperic.hq.authz.shared.PermissionException; import org.hyperic.hq.autoinventory.AICompare; import org.hyperic.hq.common.SystemException; import org.hyperic.hq.common.shared.HQConstants; import org.hyperic.hq.measurement.shared.MeasurementManager; import org.hyperic.hq.vm.VCManager; import org.hyperic.hq.vm.VMID; import org.hyperic.util.StringUtil; import org.hyperic.util.config.ConfigResponse; import org.hyperic.util.config.EncodingException; import org.springframework.beans.factory.annotation.Autowired; /** * A utility class for calculating queue status and diff for an AIPlatform. */ public class AI2AppdefDiff { private static Log _log = LogFactory.getLog(AI2AppdefDiff.class); protected VCManager vmMgr; public AI2AppdefDiff (VCManager vmMgr) { this.vmMgr=vmMgr; } /** * @param aiplatform The AI platform data, including nested IPs and servers. * @return A new AI platform value object, with queuestatus and diff set * correctly (including for nested IPs and servers), and only containing the * set of IPs and servers that should be queued (IPs and servers that are * already identical to those in appdef are removed from the value object). */ public AIPlatformValue diffAgainstAppdef(AuthzSubject subject, PlatformManager pmLH, ConfigManager cmLocal, CPropManager cpropMgr, AIPlatformValue aiplatform) { AIPlatformValue revisedAIplatform; // We know we'll at least need to copy all the platform-level attributes revisedAIplatform = new AIPlatformValue(aiplatform); // Initially, set platform status to PLACEHOLDER revisedAIplatform.setQueueStatus(AIQueueConstants.Q_STATUS_PLACEHOLDER); revisedAIplatform.setDiff(AIQueueConstants.Q_DIFF_NONE); // Get the appdef platform Platform appdefPlatform; try { appdefPlatform = pmLH.getPlatformByAIPlatform(subject, aiplatform); // If there was no appdef platform... if (appdefPlatform == null) { return getAiPlatformToAdd(aiplatform); } } catch (PermissionException e) { _log.error("Error looking up platform", e); throw new SystemException(e); } //when scans are run for a device platform, only the fqdn and ipaddress //are available, so we keep the other platform and ip attributes as //they already are in appdef (same as the user entered by hand) boolean isDevice = revisedAIplatform.isPlatformDevice(); if (isDevice) { _log.info("Applying existing appdef attributes for device " + aiplatform.getPlatformTypeName() + "=" + aiplatform.getFqdn()); if (revisedAIplatform.getCpuCount() == null) { revisedAIplatform.setCpuCount(appdefPlatform.getCpuCount()); } ConfigResponseDB config = cmLocal.getConfigResponse(appdefPlatform.getEntityId()); //if the plugin did not set a config, apply the existing config. if (revisedAIplatform.getProductConfig() == null) { revisedAIplatform.setProductConfig(config.getProductResponse()); } if (revisedAIplatform.getControlConfig() == null) { revisedAIplatform.setControlConfig(config.getControlResponse()); } if (revisedAIplatform.getMeasurementConfig() == null) { revisedAIplatform.setMeasurementConfig(config.getMeasurementResponse()); } //XXX might want to do this for all platforms, just checking devices for now. if (!configsEqual(revisedAIplatform.getProductConfig(), config.getProductResponse()) || !configsEqual(revisedAIplatform.getControlConfig(), config.getControlResponse()) || !configsEqual(revisedAIplatform.getMeasurementConfig(), config.getMeasurementResponse())) { revisedAIplatform.setQueueStatus(AIQueueConstants.Q_STATUS_CHANGED); addDiff(revisedAIplatform, AIQueueConstants.Q_PLATFORM_PROPERTIES_CHANGED); _log.info("ConfigResponse changed for " + aiplatform.getFqdn() + " '" + aiplatform.getPlatformTypeName() + "'"); } } // Otherwise, there was an appdef platform that matched, so we // go through and compare IPs, servers, and finally the platform // attributes. // Compare IPs if (_log.isDebugEnabled()) _log.debug("Before IP diff:=" + StringUtil.arrayToString(revisedAIplatform.getAIIpValues())); doIpDiffs(appdefPlatform, aiplatform, revisedAIplatform, isDevice); if (_log.isDebugEnabled()) _log.debug("After IP diff:=" + StringUtil.arrayToString(revisedAIplatform.getAIIpValues())); // Compare servers doServerDiffs(appdefPlatform, cmLocal, cpropMgr, aiplatform, revisedAIplatform); // Compare platform attributes doPlatformAttrDiff(appdefPlatform, revisedAIplatform); if (aiplatform.customPropertiesHasBeenSet()) { AppdefEntityID aid = AppdefEntityID.newPlatformID(appdefPlatform.getId()); int type = appdefPlatform.getPlatformType().getId().intValue(); // only map the UUID for actual platforms, not for virtual ones discovered by the vc plugin if (AuthzConstants.platformPrototypeVmwareVsphereVm.equals(appdefPlatform.getResource().getPrototype().getName())) { updateCprops(cpropMgr, aid, type, aiplatform.getCustomProperties(),null); } else { Collection<Ip> ips = appdefPlatform.getIps(); List<String> macs = new ArrayList<String>(ips.size()); if (ips!=null) { for(Ip ip:ips) { String mac = ip.getMacAddress(); if (mac!=null && !mac.isEmpty() && !mac.equals("")) { macs.add(mac); } } } updateCprops(cpropMgr, aid, type, aiplatform.getCustomProperties(),macs); } } return revisedAIplatform; } private AIPlatformValue getAiPlatformToAdd(AIPlatformValue aiplatform) { // If the aiplatform has status "removed", then appdef model is // correct and the platform has actually been removed. In this // case we return null, which notifies the caller of this condition. if (aiplatform.getQueueStatus() == AIQueueConstants.Q_STATUS_REMOVED) { return null; } // Otherwise, recursively mark everything as new, copying // IPs and servers. AIPlatformValue revisedAIplatform = aiplatform; revisedAIplatform.setQueueStatus(AIQueueConstants.Q_STATUS_ADDED); // All scanned IPs must be new AIIpValue[] newIps = aiplatform.getAIIpValues(); revisedAIplatform.removeAllAIIpValues(); for (int i=0; i<newIps.length; i++) { newIps[i].setQueueStatus(AIQueueConstants.Q_STATUS_ADDED); revisedAIplatform.addAIIpValue(newIps[i]); } // All scanned servers must be new AIServerValue[] newServers = aiplatform.getAIServerValues(); revisedAIplatform.removeAllAIServerValues(); for (int i=0; i<newServers.length; i++) { newServers[i].setQueueStatus(AIQueueConstants.Q_STATUS_ADDED); revisedAIplatform.addAIServerValue(newServers[i]); } return revisedAIplatform; } private void doIpDiffs(Platform appdefPlatform, AIPlatformValue aiPlatform, AIPlatformValue revisedAIplatform, boolean isDevice) { // Compare IP addresses between appdef and AI data. // We iterate over the IPs in the AI data, removing them from // the appdef list as we find them. In the end, the IPs that are // left in appdef but not in the AI data are the IPs that have been // removed from the platform. List appdefIps = new ArrayList(appdefPlatform.getIps()); List scannedIps = Arrays.asList(aiPlatform.getAIIpValues()); if (_log.isDebugEnabled()) _log.debug("appdefIps=" + StringUtil.listToString(appdefIps) + " scannedIps=" + StringUtil.listToString(scannedIps)); revisedAIplatform.removeAllAIIpValues(); Ip appdefIp = null; AIIpValue scannedIp = null; Iterator i = scannedIps.iterator(); while ( i.hasNext() ) { scannedIp = (AIIpValue) i.next(); appdefIp = findAndRemoveAppdefIp(scannedIp.getAddress(), appdefIps); if (scannedIp.getQueueStatus()==AIQueueConstants.Q_STATUS_REMOVED) { if (appdefIp == null) { // scannedIp not found in appdef, and AI thinks it's been // removed, so we're OK. No need to add it anywhere, just // continue on, and when this while loop is finished it will // get added to the revisedAIplatform as "removed". } else { // scannedIp is found in appdef, and AI thinks it's been // removed, so just add it back to the appdef // list so when this while loop is finished it will get // added to the revisedAIplatform as "removed". appdefIps.add(appdefIp); } continue; } scannedIp.setQueueStatus(AIQueueConstants.Q_STATUS_PLACEHOLDER); if ( appdefIp == null ) { // scannedIp was not found amongst appdefIps, therefore // it it a new IP. scannedIp.setQueueStatus(AIQueueConstants.Q_STATUS_ADDED); // Push changes up to platform revisedAIplatform.addAIIpValue(scannedIp); revisedAIplatform.setQueueStatus(AIQueueConstants.Q_STATUS_CHANGED); addDiff(revisedAIplatform, AIQueueConstants.Q_PLATFORM_IPS_CHANGED); } else { if (isDevice) { if (scannedIp.getNetmask() == null) { scannedIp.setNetmask(appdefIp.getNetmask()); } if (scannedIp.getMACAddress() == null) { scannedIp.setMACAddress(appdefIp.getMacAddress()); } } // Scanned IP does exist in appdef, do comparison if ( !objectsEqual(scannedIp.getNetmask(), appdefIp.getNetmask()) ) { // Netmask has changed scannedIp.setQueueStatus(AIQueueConstants.Q_STATUS_CHANGED); addDiff(scannedIp, AIQueueConstants.Q_IP_NETMASK_CHANGED); // Push changes up to platform revisedAIplatform.setQueueStatus(AIQueueConstants.Q_STATUS_CHANGED); addDiff(revisedAIplatform, AIQueueConstants.Q_PLATFORM_IPS_CHANGED); } if ( !objectsEqual(scannedIp.getMACAddress(), appdefIp.getMacAddress()) ) { // MAC has changed scannedIp.setQueueStatus(AIQueueConstants.Q_STATUS_CHANGED); addDiff(scannedIp, AIQueueConstants.Q_IP_MAC_CHANGED); // Push changes up to platform revisedAIplatform.setQueueStatus(AIQueueConstants.Q_STATUS_CHANGED); addDiff(revisedAIplatform, AIQueueConstants.Q_PLATFORM_IPS_CHANGED); } revisedAIplatform.addAIIpValue(scannedIp); } } // Whatever appdef IPs are left were not found in the scannedIps, // so they must have been removed from the platform. i = appdefIps.iterator(); while ( i.hasNext() ) { appdefIp = (Ip) i.next(); scannedIp = new AIIpValue(); scannedIp.setAddress(appdefIp.getAddress()); scannedIp.setNetmask(appdefIp.getNetmask()); scannedIp.setMACAddress(appdefIp.getMacAddress()); scannedIp.setQueueStatus(AIQueueConstants.Q_STATUS_REMOVED); revisedAIplatform.addAIIpValue(scannedIp); // Push changes up to platform revisedAIplatform.setQueueStatus(AIQueueConstants.Q_STATUS_CHANGED); addDiff(revisedAIplatform, AIQueueConstants.Q_PLATFORM_IPS_CHANGED); } } /** * Find (and remove) a scanned IP within a list of appdef IPs. * @param address The IP address to look for. * @param appdefIps The appdef IPs to search. * @return The appdefIp if it was found, null if it was not. If the * appdefIp was found, it is also removed from the appdefIps list. */ private Ip findAndRemoveAppdefIp(String address, List appdefIps) { // Is the appdef ip in the scan state? int size = appdefIps.size(); for (int i=0; i<size; i++ ) { Ip appdefIp = (Ip) appdefIps.get(i); if ( appdefIp.getAddress().equals(address) ) { // Found a match based on address, remove it from // the scanned ip list and return it. appdefIps.remove(i); return appdefIp; } } return null; } private boolean shouldUpdateConfig(byte[] newConfig, byte[] existingConfig, boolean userManaged) { if (configsEqual(newConfig, existingConfig)) { return false; } if (!userManaged ) { // config are different and not user managed return true; } if (existingConfig == null) { // configs are different and userManaged - return true - only if existing config is null return true; } // config diff, userMnaged==true and exisitingConfig is not null return false; } private void doServerDiffs(Platform appdefPlatform, ConfigManager cmLocal, CPropManager cpropMgr, AIPlatformValue aiPlatform, AIPlatformValue revisedAIplatform) { // Compare servers between appdef and AI data. // We iterate over the servers in the AI data, removing them from // the appdef list as we find them. In the end, the servers that are // left in appdef but not in the AI data are the servers that have been // removed from the platform. // Force initialization to ensure AIQ server diffs are up-to-date Hibernate.initialize(appdefPlatform.getServersBag()); List appdefServers = new ArrayList(); appdefServers.addAll(appdefPlatform.getServers()); List scannedServers = new ArrayList(); scannedServers.addAll(Arrays.asList(aiPlatform.getAIServerValues())); if (_log.isDebugEnabled()) _log.debug(" appdefServers=" + StringUtil.listToString(appdefServers) + " scannedServers=" + StringUtil.listToString(scannedServers)); Server appdefServer; AIServerValue scannedServer; Iterator i = scannedServers.iterator(); while (i.hasNext()) { scannedServer = (AIServerValue) i.next(); appdefServer = findAndRemoveAppdefServer(scannedServer, appdefServers); if (scannedServer.getQueueStatus()==AIQueueConstants.Q_STATUS_REMOVED) { if (appdefServer == null) { // scannedServer not found in appdef, and AI thinks it's been // removed, so we're OK. No need to add it anywhere, just // continue on, and when this while loop is finished it will // get added to the revisedAIplatform as "removed". } else { // scannedServer is found in appdef, and AI thinks it's been // removed, so just add it back to the appdef // list so when this while loop is finished it will get // added to the revisedAIplatform as "removed". appdefServers.add(appdefServer); } continue; } scannedServer.setQueueStatus(AIQueueConstants.Q_STATUS_PLACEHOLDER); if (appdefServer == null) { // scannedServer was not found amongst appdefServers, therefore // it it a new Server. scannedServer.setQueueStatus(AIQueueConstants.Q_STATUS_ADDED); // Only set status changed if the server is not ignored if (!scannedServer.getIgnored()) { revisedAIplatform.setQueueStatus(AIQueueConstants.Q_STATUS_CHANGED); addDiff(revisedAIplatform, AIQueueConstants.Q_PLATFORM_SERVERS_CHANGED); } revisedAIplatform.addAIServerValue(scannedServer); } else { String scannedInstallPath = scannedServer.getInstallPath(); if ((scannedInstallPath == null && appdefServer.getInstallPath() != null) || (scannedInstallPath != null && !scannedInstallPath.equals(appdefServer.getInstallPath()))){ // InstallPath has changed scannedServer.setQueueStatus(AIQueueConstants.Q_STATUS_CHANGED); addDiff(scannedServer, AIQueueConstants.Q_SERVER_INSTALLPATH_CHANGED); // Push changes up to platform revisedAIplatform.setQueueStatus(AIQueueConstants.Q_STATUS_CHANGED); addDiff(revisedAIplatform, AIQueueConstants.Q_PLATFORM_SERVERS_CHANGED); } AppdefEntityID aID = AppdefEntityID.newServerID(appdefServer.getId()); boolean configChanged = false; // Look at configs ConfigResponseDB config = cmLocal.getConfigResponse(aID); boolean userManaged = config.getUserManaged() ; if ( shouldUpdateConfig(scannedServer.getProductConfig(), config.getProductResponse(), userManaged) || shouldUpdateConfig(scannedServer.getControlConfig(), config.getControlResponse(), userManaged)|| shouldUpdateConfig(scannedServer.getMeasurementConfig(), config.getMeasurementResponse(), userManaged)|| shouldUpdateConfig(scannedServer.getResponseTimeConfig(), config.getResponseTimeResponse(), userManaged)) { // config was changed (and is NOT user-managed - or exisiting config is null) configChanged = true; } if (configChanged) { scannedServer.setQueueStatus(AIQueueConstants.Q_STATUS_CHANGED); addDiff(scannedServer, AIQueueConstants.Q_SERVER_CONFIG_CHANGED); // Push changes up to platform revisedAIplatform.setQueueStatus(AIQueueConstants.Q_STATUS_CHANGED); addDiff(revisedAIplatform, AIQueueConstants.Q_PLATFORM_SERVERS_CHANGED); } if (scannedServer.customPropertiesHasBeenSet()) { int type = appdefServer.getServerType().getId().intValue(); updateCprops(cpropMgr, aID, type, scannedServer.getCustomProperties(),null); } revisedAIplatform.addAIServerValue(scannedServer); } } } /** * Find (and remove) a scanned server within a list of appdef servers. * @param scannedServer The server to look for, by autoinventory identifier * @param appdefServers The appdef servers to search. * @return The appdefServer if it was found, null if it was not. If the * appdefServer was found, it is also removed from the appdefServers list. */ private Server findAndRemoveAppdefServer(AIServerValue scannedServer, List appdefServers ) { // Is the appdef server in the scan state? String aiid = scannedServer.getAutoinventoryIdentifier(); int size = appdefServers.size(); Server appdefServer; for (int i=0; i<size; i++) { appdefServer = (Server) appdefServers.get(i); if (appdefServer.getAutoinventoryIdentifier().equals(aiid)) { // Found a match based on aiid, remove it from // the scanned server list and return it. appdefServers.remove(i); return appdefServer; } } // No matches. return null; } private void doPlatformAttrDiff(Platform appdefPlatform, AIPlatformValue aiPlatform) { // Compare AI platform against appdefmeasurementManager data. if (!appdefPlatform.getFqdn().equals(aiPlatform.getFqdn())) { aiPlatform.setQueueStatus(AIQueueConstants.Q_STATUS_CHANGED); addDiff(aiPlatform, AIQueueConstants.Q_PLATFORM_FQDN_CHANGED); } // cpu count can be null in appdef if (!objectsEqual(aiPlatform.getCpuCount(), appdefPlatform.getCpuCount())) { aiPlatform.setQueueStatus(AIQueueConstants.Q_STATUS_CHANGED); addDiff(aiPlatform, AIQueueConstants.Q_PLATFORM_PROPERTIES_CHANGED); _log.info("CpuCount changed for " + aiPlatform.getFqdn() + " from: " + appdefPlatform.getCpuCount() + ", to: " + aiPlatform.getCpuCount()); } // Pickup the appdef name attribute if it's not set if (aiPlatform.getName() == null) { aiPlatform.setName(appdefPlatform.getName()); } String aiDescr = aiPlatform.getDescription(); String appdefDescr = appdefPlatform.getDescription(); if ((appdefDescr == null) || (appdefDescr.trim().length() == 0) || //e.g. may have vmguest info appended ((aiDescr != null) && aiDescr.startsWith(appdefDescr + " "))) { if (aiDescr != null) { aiPlatform.setQueueStatus(AIQueueConstants.Q_STATUS_CHANGED); addDiff(aiPlatform, AIQueueConstants.Q_PLATFORM_PROPERTIES_CHANGED); _log.info("Description changed for " + aiPlatform.getFqdn() + " from: '" + appdefDescr + "', to: '" + aiDescr + "'"); } } else { //don't overwrite existing appdef description aiPlatform.setDescription(appdefDescr); } } private static boolean objectsEqual(Object o1, Object o2) { if (o1 == o2) { return true; } if ((o1 == null) || (o2 == null)) { return false; } return o1.equals(o2); } //TODO this seems odd to update something in a method //that is checking for differences. however, at this point //we have the ai object, the existing appdef object and the cpropMgr. //this is simply the easiest place until server AI code is refactored. private void updateCprops(CPropManager cpropMgr, AppdefEntityID id, int type, byte[] data, List<String> macs) { if (data == null) { return; } ConfigResponse aicprops; Properties existing; try { aicprops = ConfigResponse.decode(data); } catch (EncodingException e) { _log.error("Error decoding cprops for: " + id); return; } if (macs!=null) { VMID vmid = this.vmMgr.getVMID(macs); if (vmid!=null) { aicprops.setValue(HQConstants.MOID, vmid.getMoref()); aicprops.setValue(HQConstants.VCUUID, vmid.getVcUUID()); } } try { existing = cpropMgr.getEntries(id); } catch (Exception e) { _log.error("Error looking up cprops for: " + id, e); return; } boolean isChanged = false; for (Iterator it=aicprops.getKeys().iterator(); it.hasNext();) { String key = (String)it.next(); String value = aicprops.getValue(key); String current = existing.getProperty(key); //modified version of cpropMgr.setConfigResponse //here only setValue() for new or changed values if ((current == null) || !value.equals(current)) { try { cpropMgr.setValue(id, type, key, value); isChanged = true; } catch (Exception e) { _log.error("Error updating custom properties for: " + id, e); } } } String un = isChanged ? "" : "un"; _log.debug("Custom Properties " + un + "changed for: " + id); } private static boolean configsEqual(byte[] c1, byte[] c2) { return AICompare.configsEqual(c1, c2); } private void addDiff ( AIPlatformValue aiPlatform, long diff ) { aiPlatform.setDiff(aiPlatform.getDiff() | diff); } private void addDiff ( AIIpValue aiIp, long diff ) { aiIp.setDiff(aiIp.getDiff() | diff); } private void addDiff ( AIServerValue aiServer, long diff ) { aiServer.setDiff(aiServer.getDiff() | diff); } }