/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.systemservices.impl.property; import java.net.URI; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.coordinator.client.model.ConfigVersion; import com.emc.storageos.coordinator.client.model.PowerOffState; import com.emc.storageos.coordinator.client.model.PropertyInfoExt; import com.emc.storageos.coordinator.client.service.NodeListener; import com.emc.storageos.model.property.PropertiesMetadata; import com.emc.storageos.model.property.PropertyMetadata; import com.emc.storageos.svcs.errorhandling.resources.APIException; import com.emc.storageos.systemservices.exceptions.CoordinatorClientException; import com.emc.storageos.systemservices.exceptions.InvalidLockOwnerException; import com.emc.storageos.systemservices.impl.client.SysClientFactory; import com.emc.storageos.systemservices.impl.util.AbstractManager; public class PropertyManager extends AbstractManager { private static final Logger log = LoggerFactory.getLogger(PropertyManager.class); private boolean shouldReboot = false; // local and target info properties private PropertyInfoExt targetPropInfo; private PropertyInfoExt localTargetPropInfo; private String localConfigVersion; @Override protected URI getWakeUpUrl() { return SysClientFactory.URI_WAKEUP_PROPERTY_MANAGER; } /** * Register repository info listener to monitor repository version changes */ private void addPropertyInfoListener() { try { coordinator.getCoordinatorClient().addNodeListener(new PropertyInfoListener()); } catch (Exception e) { log.error("Fail to add node listener for property info target znode", e); throw APIException.internalServerErrors.addListenerFailed(); } log.info("Successfully added node listener for property info target znode"); } /** * the listener class to listen the property target node change. */ class PropertyInfoListener implements NodeListener{ public String getPath() { return String.format("/config/%s/%s", PropertyInfoExt.TARGET_PROPERTY, PropertyInfoExt.TARGET_PROPERTY_ID); } /** * called when user update the target version */ @Override public void nodeChanged() { log.info("Property info changed. Waking up the property manager..."); wakeup(); } /** * called when connection state changed. */ @Override public void connectionStateChanged(State state) { log.info("Property info connection state changed to {}", state); if (state.equals(State.CONNECTED)) { log.info("Curator (re)connected. Waking up the property manager..."); wakeup(); } } } @Override protected void innerRun() { // need to distinguish persistent locks acquired from UpgradeManager/VdcManager/PropertyManager // otherwise they might release locks acquired by others when they start final String svcId = String.format("%s,property", coordinator.getMySvcId()); addPropertyInfoListener(); while (doRun) { log.debug("Main loop: Start"); shortSleep = false; if (shouldReboot) { reboot(); } // Step0: check if we have the property lock boolean hasLock; try { hasLock = hasRebootLock(svcId); } catch (Exception e) { log.info("Step1: Failed to verify if the current node has the property lock ", e); retrySleep(); continue; } if (hasLock) { try { releaseRebootLock(svcId); log.info("Step0: Released property lock for node: {}", svcId); wakeupOtherNodes(); } catch (InvalidLockOwnerException e) { log.error("Step0: Failed to release the property lock: Not owner."); } catch (Exception e) { log.info("Step0: Failed to release the property lock and will retry: {}", e.getMessage()); retrySleep(); continue; } } // Step1: publish current state, and set target if empty try { initializeLocalAndTargetInfo(svcId); } catch (Exception e) { log.info("Step1b failed and will be retried: {}", e.getMessage()); retrySleep(); continue; } // Step3: if target property is changed, update log.info("Step3: If target property is changed, update"); if (localTargetPropInfo != null && targetPropInfo != null && !localConfigVersion.equals(targetPropInfo.getProperty(PropertyInfoExt.CONFIG_VERSION))) { log.info("Step3a: Current properties are not same as target properties. Updating."); log.debug("Current local target properties: " + localTargetPropInfo); log.debug("Target properties: " + targetPropInfo); try { updateProperties(svcId); } catch (Exception e) { log.info("Step3a: Update failed and will be retried: {}", e.getMessage()); // Restart the loop immediately so that we release the upgrade lock. continue; } continue; } if (shouldReboot == false) { // Step5: sleep log.info("Step5: sleep"); longSleep(); } } } /** * Get local target property info * * For control node, properties that can be found in metadata are target properties * For extra node, not only exist in metadata, but also ControlNodeOnly==false * * @param localPropInfo * @return */ private PropertyInfoExt getLocalTargetPropInfo(final PropertyInfoExt localPropInfo) { Map<String, PropertyMetadata> metadata = PropertiesMetadata.getGlobalMetadata(); PropertyInfoExt localTargetProps = new PropertyInfoExt(); for (Entry<String, String> entry : localPropInfo.getAllProperties().entrySet()) { final String key = entry.getKey(); final String value = entry.getValue(); if (metadata.containsKey(key)) { if (coordinator.isControlNode()) { localTargetProps.addProperty(key, value); } else { if (!metadata.get(key).getControlNodeOnly()) { localTargetProps.addProperty(key, value); } } } } return localTargetProps; } /** * Combine nodeScopePropInfo with targetPropInfo * * @param targetPropInfo target property info * @param nodeScopePropInfo node scope property info * @return combined property info */ private PropertyInfoExt combineProps(final PropertyInfoExt targetPropInfo, final PropertyInfoExt nodeScopePropInfo) { PropertyInfoExt combinedProps = new PropertyInfoExt(); for (Entry<String, String> entry : targetPropInfo.getAllProperties().entrySet()) { combinedProps.addProperty(entry.getKey(), entry.getValue()); } for (Entry<String, String> entry : nodeScopePropInfo.getAllProperties().entrySet()) { combinedProps.addProperty(entry.getKey(), entry.getValue()); } return combinedProps; } /** * Initialize local and target info * * @throws Exception */ private void initializeLocalAndTargetInfo(String svcId) throws Exception { // publish config_version which is also a target property // used as a flag denoting whether target properties have been changed PropertyInfoExt localPropInfo = localRepository.getOverrideProperties(); localConfigVersion = localPropInfo.getProperty(PropertyInfoExt.CONFIG_VERSION); if (localConfigVersion == null) { localConfigVersion = "0"; localPropInfo.addProperty(PropertyInfoExt.CONFIG_VERSION, localConfigVersion); } coordinator.setNodeSessionScopeInfo(new ConfigVersion(localConfigVersion)); log.info("Step1a: Local config version: {}", localConfigVersion); // get local target property info localTargetPropInfo = getLocalTargetPropInfo(localPropInfo); log.debug("Step1a: Local target properties: {}", localTargetPropInfo); // set target if empty targetPropInfo = coordinator.getTargetProperties(); if (targetPropInfo == null) { // only control node can set target try { // Set the updated propperty info in coordinator coordinator.setTargetProperties(localPropInfo.getAllProperties()); coordinator.setTargetInfo(new PowerOffState(PowerOffState.State.NONE)); targetPropInfo = coordinator.getTargetInfo(PropertyInfoExt.class); log.info("Step1b: Target property set to local state: {}", targetPropInfo); } catch (CoordinatorClientException e) { log.info("Step1b: Wait another control node to set target"); retrySleep(); throw e; } } } /** * Update properties * * @param svcId node service id * @throws Exception */ private void updateProperties(String svcId) throws Exception { PropertyInfoExt diffProperties = new PropertyInfoExt(targetPropInfo.getDiffProperties(localTargetPropInfo)); PropertyInfoExt override_properties = new PropertyInfoExt(localRepository.getOverrideProperties().getAllProperties()); log.info("Step3a: Updating User Changed properties file: {}", override_properties); PropertyInfoExt updatedUserChangedProps = combineProps(override_properties, diffProperties); if (diffProperties.hasRebootProperty()) { if (!getRebootLock(svcId)) { retrySleep(); } else if (!isQuorumMaintained()) { releaseRebootLock(svcId); retrySleep(); } else { log.info("Step3a: Reboot property found."); localRepository.setOverrideProperties(updatedUserChangedProps); log.info("Step3a: Updating properties: {}", updatedUserChangedProps); reboot(); } } else if (diffProperties.hasReconfigProperty() || !diffProperties.getNotifierTags().isEmpty()) { log.info("Step3a: Reconfig property found or notifiers specified."); // CTRL-9860: don't update the local config version until everything is done. String targetVersion = targetPropInfo.getProperty(PropertyInfoExt.CONFIG_VERSION); updatedUserChangedProps.addProperty(PropertyInfoExt.CONFIG_VERSION, localConfigVersion); localRepository.setOverrideProperties(updatedUserChangedProps); log.info("Step3a: Updating properties without updating the config version: {}", updatedUserChangedProps); if (diffProperties.hasReconfigAttributeWithoutNotifiers()) { // this is the old-school "complete" reconfig that takes no notifiers as arguments. // moving forward this will diminish // i.e., all reconfigRequired properties will have notifier specified. localRepository.reconfig(); } else if (diffProperties.hasReconfigProperty()) { reconfigProperties(diffProperties); } // the notifier list can be empty, in which case nothing will be done. notifyPropertyChanges(diffProperties); // update the local config version to target version now log.info("Step3a: Updating the config version to {}", targetVersion); updatedUserChangedProps.addProperty(PropertyInfoExt.CONFIG_VERSION, targetVersion); localRepository.setOverrideProperties(updatedUserChangedProps); } else { log.info("Step3a: No reboot property found."); localRepository.setOverrideProperties(updatedUserChangedProps); log.info("Step3a: Updating properties: {}", updatedUserChangedProps); } } private void reconfigProperties(PropertyInfoExt diffProperties) { // only get the notifiers that requires reconfig as well List<String> notifierTagList = diffProperties.getNotifierTags(true); String notifierTags = StringUtils.join(notifierTagList, " "); log.info("Step3a: Reconfiguring properties related to {}", notifierTags); try { localRepository.reconfigProperties(notifierTags); } catch (Exception e) { log.error("Step3a: Fail to reconfig properties related to {}", notifierTags, e); } } private void notifyPropertyChanges(PropertyInfoExt diffProperties) { List<String> notifierTags = diffProperties.getNotifierTags(); for (String notifierTag : notifierTags) { log.info("Step3a: Calling notifier {}", notifierTag); try { Notifier notifier = Notifier.getInstance(notifierTag); if (notifier != null) { notifier.doNotify(); } } catch (Exception e) { log.error("Step3a: Fail to invoke notifier {}", notifierTag, e); } } } }