/* * RHQ Management Platform * Copyright (C) 2005-2014 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * 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, * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package org.rhq.enterprise.server.configuration; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.persistence.EntityManager; import javax.persistence.EntityNotFoundException; import javax.persistence.NoResultException; import javax.persistence.PersistenceContext; import javax.persistence.Query; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.Hibernate; import org.jetbrains.annotations.Nullable; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.rhq.core.clientapi.agent.PluginContainerException; import org.rhq.core.clientapi.agent.configuration.ConfigurationAgentService; import org.rhq.core.clientapi.agent.configuration.ConfigurationUpdateRequest; import org.rhq.core.clientapi.server.configuration.ConfigurationUpdateResponse; import org.rhq.core.db.DatabaseTypeFactory; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.authz.Permission; import org.rhq.core.domain.configuration.AbstractResourceConfigurationUpdate; import org.rhq.core.domain.configuration.Configuration; import org.rhq.core.domain.configuration.ConfigurationUpdateStatus; import org.rhq.core.domain.configuration.ConfigurationUtility; import org.rhq.core.domain.configuration.PluginConfigurationUpdate; import org.rhq.core.domain.configuration.Property; import org.rhq.core.domain.configuration.PropertyList; import org.rhq.core.domain.configuration.PropertyMap; import org.rhq.core.domain.configuration.PropertySimple; import org.rhq.core.domain.configuration.RawConfiguration; import org.rhq.core.domain.configuration.ResourceConfigurationUpdate; import org.rhq.core.domain.configuration.composite.ConfigurationUpdateComposite; import org.rhq.core.domain.configuration.definition.ConfigurationDefinition; import org.rhq.core.domain.configuration.definition.ConfigurationFormat; import org.rhq.core.domain.configuration.definition.PropertyDefinition; import org.rhq.core.domain.configuration.definition.PropertyDefinitionEnumeration; import org.rhq.core.domain.configuration.definition.PropertyDefinitionList; import org.rhq.core.domain.configuration.definition.PropertyDefinitionMap; import org.rhq.core.domain.configuration.definition.PropertyDefinitionSimple; import org.rhq.core.domain.configuration.definition.PropertyOptionsSource; import org.rhq.core.domain.configuration.group.AbstractGroupConfigurationUpdate; import org.rhq.core.domain.configuration.group.GroupPluginConfigurationUpdate; import org.rhq.core.domain.configuration.group.GroupResourceConfigurationUpdate; import org.rhq.core.domain.content.PackageType; import org.rhq.core.domain.criteria.AbstractConfigurationUpdateCriteria; import org.rhq.core.domain.criteria.GroupPluginConfigurationUpdateCriteria; import org.rhq.core.domain.criteria.GroupResourceConfigurationUpdateCriteria; import org.rhq.core.domain.criteria.PluginConfigurationUpdateCriteria; import org.rhq.core.domain.criteria.ResourceConfigurationUpdateCriteria; import org.rhq.core.domain.criteria.ResourceCriteria; import org.rhq.core.domain.resource.Agent; import org.rhq.core.domain.resource.Resource; import org.rhq.core.domain.resource.ResourceError; import org.rhq.core.domain.resource.ResourceErrorType; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.domain.resource.group.GroupCategory; import org.rhq.core.domain.resource.group.ResourceGroup; import org.rhq.core.domain.resource.group.composite.ResourceGroupComposite; import org.rhq.core.domain.server.PersistenceUtility; import org.rhq.core.domain.util.OrderingField; import org.rhq.core.domain.util.PageControl; import org.rhq.core.domain.util.PageList; import org.rhq.core.domain.util.PageOrdering; import org.rhq.core.domain.util.ResourceUtility; import org.rhq.core.util.MessageDigestGenerator; import org.rhq.core.util.collection.ArrayUtils; import org.rhq.core.util.exception.ThrowableUtil; import org.rhq.enterprise.server.RHQConstants; import org.rhq.enterprise.server.agentclient.AgentClient; import org.rhq.enterprise.server.alert.engine.AlertConditionCacheManagerLocal; import org.rhq.enterprise.server.alert.engine.AlertConditionCacheStats; import org.rhq.enterprise.server.alert.engine.internal.Tuple; import org.rhq.enterprise.server.auth.SubjectManagerLocal; import org.rhq.enterprise.server.auth.prefs.SubjectPreferences; import org.rhq.enterprise.server.authz.AuthorizationManagerLocal; import org.rhq.enterprise.server.authz.PermissionException; import org.rhq.enterprise.server.configuration.job.AbstractGroupConfigurationUpdateJob; import org.rhq.enterprise.server.configuration.job.GroupPluginConfigurationUpdateJob; import org.rhq.enterprise.server.configuration.job.GroupResourceConfigurationUpdateJob; import org.rhq.enterprise.server.configuration.util.ConfigurationMaskingUtility; import org.rhq.enterprise.server.core.AgentManagerLocal; import org.rhq.enterprise.server.resource.ResourceManagerLocal; import org.rhq.enterprise.server.resource.ResourceNotFoundException; import org.rhq.enterprise.server.resource.group.ResourceGroupManagerLocal; import org.rhq.enterprise.server.resource.group.ResourceGroupNotFoundException; import org.rhq.enterprise.server.resource.group.ResourceGroupUpdateException; import org.rhq.enterprise.server.rest.BadArgumentException; import org.rhq.enterprise.server.scheduler.SchedulerLocal; import org.rhq.enterprise.server.util.CriteriaQuery; import org.rhq.enterprise.server.util.CriteriaQueryExecutor; import org.rhq.enterprise.server.util.CriteriaQueryGenerator; import org.rhq.enterprise.server.util.CriteriaQueryRunner; import org.rhq.enterprise.server.util.QuartzUtil; /** * The manager responsible for working with Resource and plugin configurations. * * @author John Mazzitelli * @author Ian Springer */ @Stateless public class ConfigurationManagerBean implements ConfigurationManagerLocal, ConfigurationManagerRemote { private static final Log LOG = LogFactory.getLog(ConfigurationManagerBean.class); @PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME) private EntityManager entityManager; @EJB private AgentManagerLocal agentManager; @EJB private AlertConditionCacheManagerLocal alertConditionCacheManager; @EJB private AuthorizationManagerLocal authorizationManager; @EJB private ResourceGroupManagerLocal resourceGroupManager; @EJB private ResourceManagerLocal resourceManager; @EJB private ConfigurationManagerLocal configurationManager; // yes, this is ourself @EJB private SchedulerLocal scheduler; @EJB private SubjectManagerLocal subjectManager; @Override @Nullable public Configuration getPluginConfiguration(Subject subject, int resourceId) { if (LOG.isDebugEnabled()) { LOG.debug("Getting current plugin configuration for resource [" + resourceId + "]"); } Resource resource = entityManager.find(Resource.class, resourceId); if (resource == null) { throw new IllegalStateException("Cannot retrieve plugin config for unknown resource [" + resourceId + "]"); } if (!authorizationManager.canViewResource(subject, resourceId)) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view plugin configuration for [" + resource + "]"); } Configuration pluginConfiguration = configurationManager.getPluginConfiguration(resourceId); return pluginConfiguration; } // Use new transaction because this only works if the resource in question has not // yet been loaded by Hibernate. We want the query to return a non-proxied configuration, // this is critical for remote API use. @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public Configuration getPluginConfiguration(int resourceId) { // Ensure that we return a non-proxied Configuration object that can survive after the // Hibernate session goes away. Query query = entityManager.createNamedQuery(Configuration.QUERY_GET_PLUGIN_CONFIG_BY_RESOURCE_ID); query.setParameter("resourceId", resourceId); Configuration pluginConfiguration = (Configuration) query.getSingleResult(); // Mask the configuration before returning it. Resource resource = resourceManager.getResourceById(subjectManager.getOverlord(), resourceId); ConfigurationDefinition pluginConfigurationDefinition = getPluginConfigurationDefinitionForResourceType( subjectManager.getOverlord(), resource.getResourceType().getId()); // We do not want the masked configurations persisted, so detach all entities before masking the configurations. pluginConfiguration.getMap().size(); entityManager.clear(); ConfigurationMaskingUtility.maskConfiguration(pluginConfiguration, pluginConfigurationDefinition); return pluginConfiguration; } @Override public void completePluginConfigurationUpdate(Integer updateId) { PluginConfigurationUpdate update = entityManager.find(PluginConfigurationUpdate.class, updateId); configurationManager.completePluginConfigurationUpdate(update); } /* * this method will not fire off the update asynchronously (like the completeResourceConfigurationUpdate method * does); instead, it will block until an update response is retrieved from the agent-side resource */ @Override public void completePluginConfigurationUpdate(PluginConfigurationUpdate update) { // use EJB3 reference to ourself so that transaction semantics are correct ConfigurationUpdateResponse response = configurationManager.executePluginConfigurationUpdate(update); Resource resource = update.getResource(); // link to the newer, persisted configuration object -- regardless of errors resource.setAgentSynchronizationNeeded(); resource.setPluginConfiguration(update.getConfiguration()); if (response.getStatus() == ConfigurationUpdateStatus.SUCCESS) { update.setStatus(ConfigurationUpdateStatus.SUCCESS); resource.setConnected(true); removeAnyExistingInvalidPluginConfigurationErrors(resource); // Flush before merging to ensure the update has been persisted and avoid StaleStateExceptions. entityManager.flush(); entityManager.merge(update); } else { handlePluginConfiguratonUpdateRemoteException(resource, response.getStatus().toString(), response.getErrorMessage()); update.setStatus(response.getStatus()); update.setErrorMessage(response.getErrorMessage()); } } // use requires new so that exceptions bubbling up from the agent.updatePluginConfiguration don't force callers to rollback as well @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public ConfigurationUpdateResponse executePluginConfigurationUpdate(PluginConfigurationUpdate update) { Resource resource = update.getResource(); Configuration configuration = update.getConfiguration(); configuration = configuration.deepCopy(false); ConfigurationUpdateResponse response = null; try { // now let's tell the agent to actually update the resource component's plugin configuration AgentClient agentClient = this.agentManager.getAgentClient(resource.getAgent()); agentClient.getDiscoveryAgentService().updatePluginConfiguration(resource.getId(), configuration); try { agentClient.getDiscoveryAgentService().executeServiceScanDeferred(resource.getId()); } catch (Exception e) { LOG.warn("Failed to execute service scan - cannot detect children of the newly connected resource [" + resource + "]", e); } response = new ConfigurationUpdateResponse(update.getId(), null, ConfigurationUpdateStatus.SUCCESS, null); } catch (Exception e) { response = new ConfigurationUpdateResponse(update.getId(), null, e); } return response; } @Override public PluginConfigurationUpdate updatePluginConfiguration(Subject subject, int resourceId, Configuration newPluginConfiguration) throws ResourceNotFoundException { Subject overlord = subjectManager.getOverlord(); Resource resource = resourceManager.getResourceById(overlord, resourceId); // make sure the user has the proper permissions to do this ensureModifyPermission(subject, resource); // Make sure to unmask the configuration before persisting the update. Configuration existingPluginConfiguration = resource.getPluginConfiguration(); ConfigurationMaskingUtility.unmaskConfiguration(newPluginConfiguration, existingPluginConfiguration); ConfigurationDefinition pluginConfigDef = getPluginConfigurationDefinitionForResourceType(overlord, resource .getResourceType().getId()); List<String> validationErrors = ConfigurationUtility.validateConfiguration(newPluginConfiguration, existingPluginConfiguration, pluginConfigDef); if (!validationErrors.isEmpty()) { throw new BadArgumentException("Invalid newPluginConfiguration, configuration not updated: " + validationErrors); } // create our new update request and assign it to our resource - its status will initially be "in progress" PluginConfigurationUpdate update = new PluginConfigurationUpdate(resource, newPluginConfiguration, subject.getName()); update.setStatus(ConfigurationUpdateStatus.SUCCESS); entityManager.persist(update); resource.addPluginConfigurationUpdates(update); // agent field is LAZY - force it to load because the caller will need it. Agent agent = resource.getAgent(); if (agent != null) { agent.getName(); } configurationManager.completePluginConfigurationUpdate(update); return update; } @Override public PluginConfigurationUpdate upgradePluginConfiguration(Subject subject, int resourceId, Configuration newPluginConfiguration) throws ResourceNotFoundException { Subject overlord = subjectManager.getOverlord(); Resource resource = resourceManager.getResourceById(overlord, resourceId); // make sure the user has the proper permissions to do this ensureModifyPermission(subject, resource); // Make sure to unmask the configuration before persisting the update. Configuration existingPluginConfiguration = resource.getPluginConfiguration(); ConfigurationMaskingUtility.unmaskConfiguration(newPluginConfiguration, existingPluginConfiguration); // create our new update request and assign it to our resource - its status will initially be "in progress" PluginConfigurationUpdate update = new PluginConfigurationUpdate(resource, newPluginConfiguration, subject.getName()); update.setStatus(ConfigurationUpdateStatus.SUCCESS); entityManager.persist(update); resource.addPluginConfigurationUpdates(update); resource.setPluginConfiguration(update.getConfiguration()); entityManager.merge(update); return update; } @Override public Configuration getResourceConfiguration(Subject subject, int resourceId) { Resource resource = entityManager.find(Resource.class, resourceId); if (resource == null) { throw new NoResultException("Cannot get live configuration for unknown resource [" + resourceId + "]"); } if (!authorizationManager.hasResourcePermission(subject, Permission.CONFIGURE_READ, resource.getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view resource configuration for [" + resource + "]"); } Configuration result = configurationManager.getResourceConfiguration(resourceId); return result; } // local only @Override public void setResourceConfiguration(int resourceId, Configuration configuration) { Resource resource = entityManager.find(Resource.class, resourceId); if (resource == null) { throw new ResourceNotFoundException("Resource [" + resourceId + "] does not exist."); } resource.setResourceConfiguration(configuration); entityManager.merge(resource); } // Use new transaction because this only works if the resource in question has not // yet been loaded by Hibernate. We want the query to return a non-proxied configuration, // this is critical for remote API use. @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public Configuration getResourceConfiguration(int resourceId) { // Ensure that we return a non-proxied Configuration object that can survive after the // Hibernate session goes away. Query query = entityManager.createNamedQuery(Configuration.QUERY_GET_RESOURCE_CONFIG_BY_RESOURCE_ID); query.setParameter("resourceId", resourceId); Configuration resourceConfiguration = (Configuration) query.getSingleResult(); // Mask the configuration before returning it. Resource resource = resourceManager.getResourceById(subjectManager.getOverlord(), resourceId); ConfigurationDefinition resourceConfigurationDefinition = getResourceConfigurationDefinitionForResourceType( subjectManager.getOverlord(), resource.getResourceType().getId()); // We do not want the masked configurations persisted, so detach all entities before masking the configurations. resourceConfiguration.getMap().size(); entityManager.clear(); ConfigurationMaskingUtility.maskConfiguration(resourceConfiguration, resourceConfigurationDefinition); return resourceConfiguration; } @Override public ResourceConfigurationUpdate getLatestResourceConfigurationUpdate(Subject subject, int resourceId, boolean fromStructured) { if (LOG.isDebugEnabled()) { LOG.debug("Getting current Resource configuration for Resource [" + resourceId + "]..."); } Resource resource = entityManager.find(Resource.class, resourceId); if (resource == null) { throw new NoResultException("Cannot get latest resource configuration for unknown Resource [" + resourceId + "]."); } if (!authorizationManager.hasResourcePermission(subject, Permission.CONFIGURE_READ, resource.getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view Resource configuration for [" + resource + "]."); } ResourceConfigurationUpdate current; // Get the latest configuration as known to the server (i.e. persisted in the DB). try { Query query = entityManager .createNamedQuery(ResourceConfigurationUpdate.QUERY_FIND_CURRENTLY_ACTIVE_CONFIG); query.setParameter("resourceId", resourceId); current = (ResourceConfigurationUpdate) query.getSingleResult(); resource = current.getResource(); } catch (NoResultException nre) { // The Resource hasn't been successfully configured yet - return null. current = null; } // Check whether or not a resource configuration update is currently in progress. ResourceConfigurationUpdate latest; try { Query query = entityManager.createNamedQuery(ResourceConfigurationUpdate.QUERY_FIND_LATEST_BY_RESOURCE_ID); query.setParameter("resourceId", resourceId); latest = (ResourceConfigurationUpdate) query.getSingleResult(); if (latest.getStatus() == ConfigurationUpdateStatus.INPROGRESS) { // The agent is in the process of a config update, so we do not want to ask it for the live config. // Instead, simply return the most recent persisted config w/ a SUCCESS status (possibly null). if (current != null) { // Fetch and mask the configuration before returning the update. Configuration configuration = current.getConfiguration(); configuration.getMap().size(); ConfigurationDefinition configurationDefinition = getResourceConfigurationDefinitionForResourceType( subjectManager.getOverlord(), resource.getResourceType().getId()); // We do not want the masked configuration persisted, so detach all entities before masking the configuration. entityManager.clear(); ConfigurationMaskingUtility.maskConfiguration(configuration, configurationDefinition); } return current; } } catch (NoResultException nre) { // The resource hasn't been successfully configured yet - not a problem, we'll ask the agent for the live // config... } // ask the agent to get us the live, most up-to-date configuration for the resource, // then compare it to make sure what we think is the latest configuration is really the latest Configuration liveConfig = getLiveResourceConfiguration(resource, true, fromStructured); if (liveConfig != null) { Configuration currentConfig = (current != null) ? current.getConfiguration() : null; // Compare the live values and, if there is a difference with the current, store the live config as a new // update. Note that, if there is no current configuration stored, the live config is stored as the first // update. boolean theSame = (currentConfig != null && currentConfig.equals(liveConfig)); // Someone dorked with the configuration on the agent side - save the live config as a new update. if (!theSame) { try { current = persistNewAgentReportedResourceConfiguration(resource, liveConfig); } catch (ConfigurationUpdateStillInProgressException e) { // This means a config update is INPROGRESS. // Return the current in this case since it is our latest committed active config. // Note that even though this application exception specifies "rollback=true", it will // not effect our current transaction since the persist call was made with REQUIRES_NEW // and thus only that new tx was rolled back if (LOG.isDebugEnabled()) { LOG.debug("Resource is currently in progress of changing its resource configuration - " + "since it hasn't finished yet, will use the last successful resource configuration: " + e); } } } } else { LOG.warn("Could not get live resource configuration for resource [" + resource + "]; will assume latest resource configuration update is the current resource configuration."); } if (current != null) { // Mask the configuration before returning the update. Configuration configuration = current.getConfiguration(); configuration.getMap().size(); ConfigurationDefinition configurationDefinition = getResourceConfigurationDefinitionForResourceType( subjectManager.getOverlord(), resource.getResourceType().getId()); // We do not want the masked configuration persisted, so detach all entities before masking the configuration. // But before we detach the entities, let's flush the entity manager to persist any pending changes. // This will ensure that: // 1) All changes in the entity manager are persisted // 2) Any changes to the entities made after the clear() call are NOT persisted. entityManager.flush(); entityManager.clear(); ConfigurationMaskingUtility.maskConfiguration(configuration, configurationDefinition); } return current; } @Override @Nullable public ResourceConfigurationUpdate getLatestResourceConfigurationUpdate(Subject subject, int resourceId) { return getLatestResourceConfigurationUpdate(subject, resourceId, true); } /** * Will return the persisted Resource Configuration update, or null if the specified Configuration * is identical to the currently persisted Configuration. */ private ResourceConfigurationUpdate persistNewAgentReportedResourceConfiguration(Resource resource, Configuration liveConfig) throws ConfigurationUpdateStillInProgressException { if (liveConfig.getRawConfigurations() != null) { for (RawConfiguration raw : liveConfig.getRawConfigurations()) { MessageDigestGenerator sha256Generator = new MessageDigestGenerator(MessageDigestGenerator.SHA_256); sha256Generator.add(raw.getContents().getBytes()); raw.setSha256(sha256Generator.getDigestString()); } } /* * NOTE: We pass the overlord, since this is a system side-effect. here, the system * and *not* the user, is choosing to persist the most recent configuration because it was different * from the last known value. again, the user isn't attempting to change the value; instead, *JON* * is triggering save based on the semantics that we want to provide for configuration updates. * For the same reason, we pass null as the subject. */ ResourceConfigurationUpdate update = this.configurationManager .persistResourceConfigurationUpdateInNewTransaction(this.subjectManager.getOverlord(), resource.getId(), liveConfig, ConfigurationUpdateStatus.SUCCESS, null, false); // resource.setResourceConfiguration(liveConfig.deepCopy(false)); resource.setResourceConfiguration(liveConfig.deepCopyWithoutProxies()); return update; } @Override public PluginConfigurationUpdate getLatestPluginConfigurationUpdate(Subject subject, int resourceId) { if (LOG.isDebugEnabled()) { LOG.debug("Getting current plugin configuration for resource [" + resourceId + "]..."); } Resource resource = entityManager.find(Resource.class, resourceId); if (resource == null) { throw new NoResultException("Cannot get latest plugin configuration for unknown Resource [" + resourceId + "]."); } if (!authorizationManager.canViewResource(subject, resource.getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view plugin configuration for [" + resource + "]."); } PluginConfigurationUpdate current; // Get the latest configuration as known to the server (i.e. persisted in the DB). try { Query query = entityManager.createNamedQuery(PluginConfigurationUpdate.QUERY_FIND_CURRENTLY_ACTIVE_CONFIG); query.setParameter("resourceId", resourceId); current = (PluginConfigurationUpdate) query.getSingleResult(); resource = current.getResource(); // Mask the configuration before returning the update. Configuration configuration = current.getConfiguration(); configuration.getMap().size(); ConfigurationDefinition configurationDefinition = getPluginConfigurationDefinitionForResourceType( subjectManager.getOverlord(), resource.getResourceType().getId()); // We do not want the masked configuration persisted, so detach all entities before masking the configuration. entityManager.clear(); ConfigurationMaskingUtility.maskConfiguration(configuration, configurationDefinition); } catch (NoResultException nre) { // The resource hasn't been successfully configured yet - return null. current = null; } return current; } @Override public boolean isResourceConfigurationUpdateInProgress(Subject subject, int resourceId) { boolean updateInProgress; try { Query query = entityManager.createNamedQuery(ResourceConfigurationUpdate.QUERY_FIND_LATEST_BY_RESOURCE_ID); query.setParameter("resourceId", resourceId); ResourceConfigurationUpdate latestConfigUpdate = (ResourceConfigurationUpdate) query.getSingleResult(); if (!authorizationManager.hasResourcePermission(subject, Permission.CONFIGURE_READ, latestConfigUpdate .getResource().getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view Resource configuration for [" + latestConfigUpdate.getResource() + "]"); } updateInProgress = (latestConfigUpdate.getStatus() == ConfigurationUpdateStatus.INPROGRESS); } catch (NoResultException nre) { // The resource config history is empty, so there's obviously no update in progress. updateInProgress = false; } return updateInProgress; } @Override public boolean isPluginConfigurationUpdateInProgress(Subject subject, int resourceId) { boolean updateInProgress; try { Query query = entityManager.createNamedQuery(PluginConfigurationUpdate.QUERY_FIND_LATEST_BY_RESOURCE_ID); query.setParameter("resourceId", resourceId); PluginConfigurationUpdate latestConfigUpdate = (PluginConfigurationUpdate) query.getSingleResult(); if (!authorizationManager.canViewResource(subject, latestConfigUpdate.getResource().getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view plugin configuration for [" + latestConfigUpdate.getResource() + "]"); } updateInProgress = (latestConfigUpdate.getStatus() == ConfigurationUpdateStatus.INPROGRESS); } catch (NoResultException nre) { // The resource config history is empty, so there's obviously no update in progress. updateInProgress = false; } return updateInProgress; } @Override public boolean isGroupResourceConfigurationUpdateInProgress(Subject subject, int groupId) { boolean updateInProgress; try { Query query = entityManager .createNamedQuery(GroupResourceConfigurationUpdate.QUERY_FIND_LATEST_BY_GROUP_ID); query.setParameter("groupId", groupId); GroupResourceConfigurationUpdate latestConfigGroupUpdate = (GroupResourceConfigurationUpdate) query .getSingleResult(); if (!authorizationManager.canViewGroup(subject, latestConfigGroupUpdate.getGroup().getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view group Resource configuration for [" + latestConfigGroupUpdate.getGroup() + "]"); } updateInProgress = (latestConfigGroupUpdate.getStatus() == ConfigurationUpdateStatus.INPROGRESS); } catch (NoResultException nre) { // The group resource config history is empty, so there's obviously no update in progress. updateInProgress = false; } return updateInProgress; } @Override public boolean isGroupPluginConfigurationUpdateInProgress(Subject subject, int groupId) { boolean updateInProgress; try { Query query = entityManager.createNamedQuery(GroupPluginConfigurationUpdate.QUERY_FIND_LATEST_BY_GROUP_ID); query.setParameter("groupId", groupId); GroupPluginConfigurationUpdate latestConfigGroupUpdate = (GroupPluginConfigurationUpdate) query .getSingleResult(); if (!authorizationManager.canViewGroup(subject, latestConfigGroupUpdate.getGroup().getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view group plugin configuration for [" + latestConfigGroupUpdate.getGroup() + "]"); } updateInProgress = (latestConfigGroupUpdate.getStatus() == ConfigurationUpdateStatus.INPROGRESS); } catch (NoResultException nre) { // The group resource config history is empty, so there's obviously no update in progress. updateInProgress = false; } return updateInProgress; } @Override public Map<Integer, Configuration> getResourceConfigurationsForCompatibleGroup(Subject subject, int groupId) throws ConfigurationUpdateStillInProgressException, Exception { if (authorizationManager.hasGroupPermission(subject, Permission.CONFIGURE_READ, groupId) == false) { throw new PermissionException("User[name=" + subject.getName() + "] does not have permission to view configuration for group[id=" + groupId + "]"); } // The below call will also handle the check to see if the subject has perms to view the group. ResourceGroupComposite groupComposite = this.resourceGroupManager.getResourceGroupComposite(subject, groupId); // if the group is empty (has no members) the availability will be null if (0 == groupComposite.getExplicitCount()) { return new HashMap<Integer, Configuration>(); } if (groupComposite.getExplicitDown() > 0) throw new Exception("Current group Resource configuration for " + groupId + " cannot be calculated, because one or more of this group's member Resources are DOWN."); // If we got this far, all member Resources are UP. Now check to make sure no config updates, group-level or // resource-level, are in progress. ResourceGroup group = groupComposite.getResourceGroup(); ensureNoResourceConfigurationUpdatesInProgress(group); // If we got this far, no updates are in progress. Now try to obtain the live configs from the Agents. // If any of the requests for live configs fail (e.g. because an Agent is down) or if all of the live // configs can't be obtained within the specified timeout, this call will throw an exception. int userPreferencesTimeout = new SubjectPreferences(subject).getGroupConfigurationTimeoutPeriod(); Set<Resource> groupMembers = group.getExplicitResources(); Map<Integer, Configuration> liveConfigs = LiveConfigurationLoader.getInstance().loadLiveResourceConfigurations( groupMembers, userPreferencesTimeout); // If we got this far, we were able to retrieve all of the live configs from the Agents. Now load the current // persisted configs from the DB and compare them to the corresponding live configs. For any that are not equal, // persist the live config to the DB as the new current config. Map<Integer, Configuration> currentPersistedConfigs = getPersistedResourceConfigurationsForCompatibleGroup(group); for (Resource memberResource : groupMembers) { Configuration liveConfig = liveConfigs.get(memberResource.getId()); // NOTE: The persisted config may be null if no config has been persisted yet. Configuration currentPersistedConfig = currentPersistedConfigs.get(memberResource.getId()); if (!liveConfig.equals(currentPersistedConfig)) { // If the live config is different than the persisted config, persist it as the new current config. ResourceConfigurationUpdate update = persistNewAgentReportedResourceConfiguration(memberResource, liveConfig); if (update != null) { currentPersistedConfigs.put(memberResource.getId(), update.getConfiguration()); LOG.info("Live configuration for [" + memberResource + "] did not match latest associated ResourceConfigurationUpdate with SUCCESS status."); } else { // this means the live config is identical to the persisted config currentPersistedConfigs.put(memberResource.getId(), liveConfig); } } } // Mask the configurations before returning them. for (Configuration resourceConfiguration : currentPersistedConfigs.values()) { resourceConfiguration.getMap().size(); } ConfigurationDefinition resourceConfigurationDefinition = getResourceConfigurationDefinitionForResourceType( subjectManager.getOverlord(), group.getResourceType().getId()); // We do not want the masked configurations persisted, so detach all entities before masking the configurations. entityManager.clear(); for (Configuration resourceConfiguration : currentPersistedConfigs.values()) { ConfigurationMaskingUtility.maskConfiguration(resourceConfiguration, resourceConfigurationDefinition); } return currentPersistedConfigs; } @Override public Map<Integer, Configuration> getPluginConfigurationsForCompatibleGroup(Subject subject, int groupId) throws ConfigurationUpdateStillInProgressException, Exception { // The below call will also handle the check to see if the subject has perms to view the group. ResourceGroup group = this.resourceGroupManager .getResourceGroupById(subject, groupId, GroupCategory.COMPATIBLE); // Check to make sure no config updates, group-level or resource-level, are in progress. ensureNoPluginConfigurationUpdatesInProgress(group); // If we got this far, no updates are in progress, so go ahead and load the plugin configs from the DB. Map<Integer, Configuration> currentPersistedConfigs = getPersistedPluginConfigurationsForCompatibleGroup(group); // Mask the configurations before returning them. for (Configuration pluginConfiguration : currentPersistedConfigs.values()) { pluginConfiguration.getMap().size(); } ConfigurationDefinition pluginConfigurationDefinition = getPluginConfigurationDefinitionForResourceType( subjectManager.getOverlord(), group.getResourceType().getId()); // We do not want the masked configurations persisted, so detach all entities before masking the configurations. entityManager.clear(); for (Configuration pluginConfiguration : currentPersistedConfigs.values()) { ConfigurationMaskingUtility.maskConfiguration(pluginConfiguration, pluginConfigurationDefinition); } return currentPersistedConfigs; } @SuppressWarnings("unchecked") private void ensureNoResourceConfigurationUpdatesInProgress(ResourceGroup compatibleGroup) throws ConfigurationUpdateStillInProgressException { if (isGroupResourceConfigurationUpdateInProgress(this.subjectManager.getOverlord(), compatibleGroup.getId())) { throw new ConfigurationUpdateStillInProgressException("Current group Resource configuration for " + compatibleGroup + " cannot be calculated, because a group Resource configuration update is currently in progress " + " (please wait for this update to complete or delete it from the history)."); } Query countQuery = PersistenceUtility.createCountQuery(entityManager, ResourceConfigurationUpdate.QUERY_FIND_BY_GROUP_ID_AND_STATUS); countQuery.setParameter("groupId", compatibleGroup.getId()); countQuery.setParameter("status", ConfigurationUpdateStatus.INPROGRESS); long count = (Long) countQuery.getSingleResult(); if (count != 0) { Query query = entityManager.createNamedQuery(ResourceConfigurationUpdate.QUERY_FIND_BY_GROUP_ID_AND_STATUS); query.setParameter("groupId", compatibleGroup.getId()); query.setParameter("status", ConfigurationUpdateStatus.INPROGRESS); List<Resource> resources = query.getResultList(); List<String> names = new ArrayList<String>(); for (Resource resource : resources) { names.add(resource.getName()); } throw new ConfigurationUpdateStillInProgressException("Current group Resource configuration for " + compatibleGroup + " cannot be calculated, because Resource configuration updates are currently in progress for the" + " following Resources (please wait for these updates to complete or delete them from the history): " + names); } } @SuppressWarnings("unchecked") private void ensureNoPluginConfigurationUpdatesInProgress(ResourceGroup compatibleGroup) throws ConfigurationUpdateStillInProgressException { if (isGroupPluginConfigurationUpdateInProgress(this.subjectManager.getOverlord(), compatibleGroup.getId())) { throw new ConfigurationUpdateStillInProgressException("Current group plugin configuration for " + compatibleGroup + " cannot be calculated, because a group plugin configuration update is currently in progress."); } Query countQuery = PersistenceUtility.createCountQuery(entityManager, PluginConfigurationUpdate.QUERY_FIND_BY_GROUP_ID_AND_STATUS); countQuery.setParameter("groupId", compatibleGroup.getId()); countQuery.setParameter("status", ConfigurationUpdateStatus.INPROGRESS); long count = (Long) countQuery.getSingleResult(); if (count > 0) { Query query = entityManager.createNamedQuery(PluginConfigurationUpdate.QUERY_FIND_BY_GROUP_ID_AND_STATUS); query.setParameter("groupId", compatibleGroup.getId()); query.setParameter("status", ConfigurationUpdateStatus.INPROGRESS); List<PluginConfigurationUpdate> pluginConfigUpdates = query.getResultList(); if (!pluginConfigUpdates.isEmpty()) { List<Integer> resourceIds = new ArrayList<Integer>(pluginConfigUpdates.size()); for (PluginConfigurationUpdate pluginConfigUpdate : pluginConfigUpdates) { resourceIds.add(pluginConfigUpdate.getResource().getId()); } throw new ConfigurationUpdateStillInProgressException("Current group plugin configuration for " + compatibleGroup + " cannot be calculated, because plugin configuration updates are currently in progress for the" + " member Resources with the following ID's (please wait for these updates to complete): " + resourceIds); } } } @SuppressWarnings("unchecked") private Map<Integer, Configuration> getPersistedResourceConfigurationsForCompatibleGroup( ResourceGroup compatibleGroup) { Query countQuery = PersistenceUtility.createCountQuery(entityManager, Configuration.QUERY_GET_RESOURCE_CONFIG_MAP_BY_GROUP_ID); countQuery.setParameter("resourceGroupId", compatibleGroup.getId()); long count = (Long) countQuery.getSingleResult(); int groupSize = resourceGroupManager.getExplicitGroupMemberCount(compatibleGroup.getId()); if (count != groupSize) { throw new IllegalStateException("Size of group changed from " + groupSize + " to " + count + " - please retry the operation."); } // Configurations are very expensive to load, so load 'em in chunks to ease the strain on the DB. PageControl pageControl = new PageControl(0, 20); Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, Configuration.QUERY_GET_RESOURCE_CONFIG_MAP_BY_GROUP_ID, new OrderingField("r.id", PageOrdering.ASC)); query.setParameter("resourceGroupId", compatibleGroup.getId()); Map<Integer, Configuration> results = new HashMap<Integer, Configuration>((int) count); int rowsProcessed = 0; while (true) { PersistenceUtility.setDataPage(query, pageControl); // retrieve one page at a time List<Object[]> pagedResults = query.getResultList(); if (pagedResults.size() <= 0) { break; } for (Object[] result : pagedResults) { results.put((Integer) result[0], (Configuration) result[1]); } rowsProcessed += pagedResults.size(); if (rowsProcessed >= count) { break; } pageControl.setPageNumber(pageControl.getPageNumber() + 1); // advance the page } return results; } @SuppressWarnings("unchecked") private Map<Integer, Configuration> getPersistedPluginConfigurationsForCompatibleGroup(ResourceGroup compatibleGroup) { Query countQuery = PersistenceUtility.createCountQuery(entityManager, Configuration.QUERY_GET_PLUGIN_CONFIG_MAP_BY_GROUP_ID); countQuery.setParameter("resourceGroupId", compatibleGroup.getId()); long count = (Long) countQuery.getSingleResult(); int groupSize = resourceGroupManager.getExplicitGroupMemberCount(compatibleGroup.getId()); if (count != groupSize) { throw new IllegalStateException("Size of group changed from " + groupSize + " to " + count + " - please retry the operation."); } // Configurations are very expensive to load, so load 'em in chunks to ease the strain on the DB. PageControl pageControl = new PageControl(0, 20); Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, Configuration.QUERY_GET_PLUGIN_CONFIG_MAP_BY_GROUP_ID, new OrderingField("r.id", PageOrdering.ASC)); query.setParameter("resourceGroupId", compatibleGroup.getId()); Map<Integer, Configuration> results = new HashMap<Integer, Configuration>((int) count); int rowsProcessed = 0; while (true) { PersistenceUtility.setDataPage(query, pageControl); // retrieve one page at a time List<Object[]> pagedResults = query.getResultList(); if (pagedResults.size() <= 0) { break; } for (Object[] result : pagedResults) { results.put((Integer) result[0], (Configuration) result[1]); } rowsProcessed += pagedResults.size(); if (rowsProcessed >= count) { break; } pageControl.setPageNumber(pageControl.getPageNumber() + 1); // advance the page } return results; } @Override public Configuration getLiveResourceConfiguration(Subject subject, int resourceId, boolean pingAgentFirst) throws Exception { return getLiveResourceConfiguration(subject, resourceId, pingAgentFirst, true); } @Override public Configuration getLiveResourceConfiguration(Subject subject, int resourceId, boolean pingAgentFirst, boolean fromStructured) throws Exception { Resource resource = entityManager.find(Resource.class, resourceId); if (resource == null) { throw new NoResultException("Cannot get live configuration for unknown resource [" + resourceId + "]"); } if (!authorizationManager.canViewResource(subject, resource.getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view resource configuration for [" + resource + "]"); } // ask the agent to get us the live, most up-to-date configuration for the resource Configuration liveConfig = getLiveResourceConfiguration(resource, pingAgentFirst, fromStructured); return liveConfig; } @Override public void checkForTimedOutConfigurationUpdateRequests() { LOG.debug("Begin scanning for timed out configuration update requests"); checkForTimedOutResourceConfigurationUpdateRequests(); checkForTimedOutGroupResourceConfigurationUpdateRequests(); LOG.debug("Finished scanning for timed out configuration update requests"); } @SuppressWarnings("unchecked") private void checkForTimedOutResourceConfigurationUpdateRequests() { try { // TODO (ips): Optimize this so the query actually does the timeout check too, // i.e. "WHERE cu.createdtime > :maxCreatedTime". Query query = entityManager.createNamedQuery(ResourceConfigurationUpdate.QUERY_FIND_ALL_IN_STATUS); query.setParameter("status", ConfigurationUpdateStatus.INPROGRESS); List<ResourceConfigurationUpdate> requests = query.getResultList(); for (ResourceConfigurationUpdate request : requests) { // TODO [mazz]: should we make this configurable? long timeout = 1000L * 60 * 10; // 10 minutes - should be more than enough time long duration = request.getDuration(); if (duration > timeout) { LOG.info("Resource configuration update request seems to have been orphaned - timing it out: " + request); request.setErrorMessage("Timed out - did not complete after " + duration + " ms" + " (the timeout period was " + timeout + " ms)"); request.setStatus(ConfigurationUpdateStatus.FAILURE); // If it's part of a group update, check if all member updates of the group update have completed, // and, if so, update the group update's status. checkForCompletedGroupResourceConfigurationUpdate(request.getId()); } } } catch (Throwable t) { LOG.error("Failed to check for timed out Resource configuration update requests. Cause: " + t); } } @SuppressWarnings("unchecked") private void checkForTimedOutGroupResourceConfigurationUpdateRequests() { try { // TODO (ips): Optimize this so the query actually does the timeout check too, // i.e. "WHERE cu.createdtime > :maxCreatedTime". Query query = entityManager.createNamedQuery(GroupResourceConfigurationUpdate.QUERY_FIND_ALL_IN_STATUS); query.setParameter("status", ConfigurationUpdateStatus.INPROGRESS); List<GroupResourceConfigurationUpdate> requests = query.getResultList(); for (GroupResourceConfigurationUpdate request : requests) { // Note: Make this a little longer than the timeout used for individual Resource config updates // (see checkForTimedOutResourceConfigurationUpdateRequests()), to ensure a group update never // gets timed out before one or more of its member updates. long timeout = 1000L * 60 * 11; // 11 minutes long duration = request.getDuration(); if (duration > timeout) { LOG.info("Group Resource config update request seems to be orphaned - timing it out: " + request); request.setErrorMessage("Timed out - did not complete after " + duration + " ms" + " (the timeout period was " + timeout + " ms)"); request.setStatus(ConfigurationUpdateStatus.FAILURE); } } } catch (Throwable t) { LOG.error("Failed to check for timed out group Resource configuration update requests. Cause: " + t); } } /** * @deprecated use {@link #findPluginConfigurationUpdatesByCriteria(org.rhq.core.domain.auth.Subject, org.rhq.core.domain.criteria.PluginConfigurationUpdateCriteria)} */ @Deprecated @Override @SuppressWarnings("unchecked") public PageList<PluginConfigurationUpdate> findPluginConfigurationUpdates(Subject subject, int resourceId, Long beginDate, Long endDate, PageControl pc) { Resource resource = entityManager.find(Resource.class, resourceId); if (resource.getResourceType().getPluginConfigurationDefinition() == null || resource.getResourceType().getPluginConfigurationDefinition().getPropertyDefinitions().isEmpty()) { return new PageList<PluginConfigurationUpdate>(pc); } pc.initDefaultOrderingField("cu.id", PageOrdering.DESC); String queryName = PluginConfigurationUpdate.QUERY_FIND_ALL_BY_RESOURCE_ID; Query queryCount = PersistenceUtility.createCountQuery(entityManager, queryName); Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, queryName, pc); queryCount.setParameter("resourceId", resourceId); query.setParameter("resourceId", resourceId); queryCount.setParameter("startTime", beginDate); query.setParameter("startTime", beginDate); queryCount.setParameter("endTime", endDate); query.setParameter("endTime", endDate); long totalCount = (Long) queryCount.getSingleResult(); List<PluginConfigurationUpdate> updates = query.getResultList(); if ((updates == null) || (updates.size() == 0)) { // there is no configuration yet - get the latest from the agent, if possible updates = new ArrayList<PluginConfigurationUpdate>(); PluginConfigurationUpdate latest = getLatestPluginConfigurationUpdate(subject, resourceId); if (latest != null) { updates.add(latest); } } else if (updates.size() > 0) { resource = updates.get(0).getResource(); if (!authorizationManager.canViewResource(subject, resource.getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view resource [" + resource + "]"); } } /*// Mask the configurations before returning the updates. // We do not want the masked configurations persisted, so detach all entities before masking the configurations. entityManager.clear(); for (PluginConfigurationUpdate update : updates) { Configuration configuration = update.getConfiguration(); ConfigurationDefinition configurationDefinition = getPluginConfigurationDefinitionForResourceType( subjectManager.getOverlord(), update.getResource().getResourceType().getId()); ConfigurationMaskingUtility.maskConfiguration(configuration, configurationDefinition); }*/ return new PageList<PluginConfigurationUpdate>(updates, (int) totalCount, pc); } /** * @deprecated use {@link #findResourceConfigurationUpdatesByCriteria(org.rhq.core.domain.auth.Subject, org.rhq.core.domain.criteria.ResourceConfigurationUpdateCriteria)} */ @Deprecated @Override @SuppressWarnings("unchecked") public PageList<ResourceConfigurationUpdate> findResourceConfigurationUpdates(Subject subject, Integer resourceId, Long beginDate, Long endDate, boolean suppressOldest, PageControl pc) { if (resourceId == null && !authorizationManager.isInventoryManager(subject)) { throw new PermissionException("User[" + subject.getName() + "] Must be an inventory manager to query " + "without a resource id."); } else if (!authorizationManager.hasResourcePermission(subject, Permission.CONFIGURE_READ, resourceId)) { throw new PermissionException("User[" + subject.getName() + "] does not have permission to view configuration history for resource[id=" + resourceId + "]"); } // TODO (ips, 04/01/10): Our id's are not guaranteed to be sequential, because our sequences are configured to // pre-create and cache blocks of 10 sequence id's, so it may be better to order by // "cu.createdTime", rather than "cu.id". pc.initDefaultOrderingField("cu.id", PageOrdering.DESC); String queryName = ResourceConfigurationUpdate.QUERY_FIND_ALL_BY_RESOURCE_ID; Query queryCount = PersistenceUtility.createCountQuery(entityManager, queryName); Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, queryName, pc); queryCount.setParameter("resourceId", resourceId); query.setParameter("resourceId", resourceId); queryCount.setParameter("startTime", beginDate); query.setParameter("startTime", beginDate); queryCount.setParameter("endTime", endDate); query.setParameter("endTime", endDate); int includeAll = suppressOldest ? 0 : 1; queryCount.setParameter("includeAll", includeAll); query.setParameter("includeAll", includeAll); long totalCount = (Long) queryCount.getSingleResult(); List<ResourceConfigurationUpdate> updates = query.getResultList(); if (suppressOldest == false && updates.size() == 0) { // there is no configuration yet - get the latest from the agent, if possible updates = new ArrayList<ResourceConfigurationUpdate>(); ResourceConfigurationUpdate latest = getLatestResourceConfigurationUpdate(subject, resourceId); if (latest != null) { updates.add(latest); } } /*// Mask the configurations before returning the updates. // We do not want the masked configurations persisted, so detach all entities before masking the configurations. entityManager.clear(); for (ResourceConfigurationUpdate update : updates) { Configuration configuration = update.getConfiguration(); ConfigurationDefinition configurationDefinition = getResourceConfigurationDefinitionForResourceType( subjectManager.getOverlord(), update.getResource().getResourceType().getId()); ConfigurationMaskingUtility.maskConfiguration(configuration, configurationDefinition); }*/ return new PageList<ResourceConfigurationUpdate>(updates, (int) totalCount, pc); } /** * @deprecated use criteria-based API */ @Deprecated @Override public PluginConfigurationUpdate getPluginConfigurationUpdate(Subject subject, int configurationUpdateId) { PluginConfigurationUpdate update = entityManager.find(PluginConfigurationUpdate.class, configurationUpdateId); if (!authorizationManager.canViewResource(subject, update.getResource().getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view plugin configuration update for [" + update.getResource() + "]"); } update.getConfiguration(); // this is EAGER loaded, so this really doesn't do anything return update; } /** * @deprecated use criteria-based API */ @Deprecated @Override public ResourceConfigurationUpdate getResourceConfigurationUpdate(Subject subject, int configurationUpdateId) { ResourceConfigurationUpdate update = entityManager.find(ResourceConfigurationUpdate.class, configurationUpdateId); if (!authorizationManager.canViewResource(subject, update.getResource().getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view resource configuration update for [" + update.getResource() + "]"); } update.getConfiguration(); // this is EAGER loaded, so this really doesn't do anything return update; } @Override public void purgePluginConfigurationUpdate(Subject subject, int configurationUpdateId, boolean purgeInProgress) { PluginConfigurationUpdate doomedRequest = entityManager.find(PluginConfigurationUpdate.class, configurationUpdateId); if (doomedRequest == null) { if (LOG.isDebugEnabled()) { LOG.debug("Asked to purge a non-existing config update request [" + configurationUpdateId + "]"); } return; } if ((doomedRequest.getStatus() == ConfigurationUpdateStatus.INPROGRESS) && !purgeInProgress) { throw new IllegalStateException( "The update request is still in the in-progress state. Please wait for it to complete: " + doomedRequest); } // make sure the user has the proper permissions to do this Resource resource = doomedRequest.getResource(); if (!authorizationManager.hasResourcePermission(subject, Permission.MODIFY_RESOURCE, resource.getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to purge a plugin configuration update audit trail for resource [" + resource + "]"); } resource.getPluginConfigurationUpdates().remove(doomedRequest); entityManager.remove(doomedRequest); entityManager.flush(); return; } @Override public void purgePluginConfigurationUpdates(Subject subject, int[] configurationUpdateIds, boolean purgeInProgress) { if ((configurationUpdateIds == null) || (configurationUpdateIds.length == 0)) { return; } // TODO [mazz]: ugly - let's make this more efficient, just getting this to work first for (int configurationUpdateId : configurationUpdateIds) { purgePluginConfigurationUpdate(subject, configurationUpdateId, purgeInProgress); } return; } @Override public void purgeResourceConfigurationUpdate(Subject subject, int configurationUpdateId, boolean purgeInProgress) { ResourceConfigurationUpdate doomedRequest = entityManager.find(ResourceConfigurationUpdate.class, configurationUpdateId); if (doomedRequest == null) { if (LOG.isDebugEnabled()) { LOG.debug("Asked to purge a non-existing config update request [" + configurationUpdateId + "]"); } return; } if ((doomedRequest.getStatus() == ConfigurationUpdateStatus.INPROGRESS) && !purgeInProgress) { throw new IllegalStateException( "The update request is still in the in-progress state. Please wait for it to complete: " + doomedRequest); } // make sure the user has the proper permissions to do this Resource resource = doomedRequest.getResource(); if (!authorizationManager.hasResourcePermission(subject, Permission.CONFIGURE_WRITE, resource.getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to purge a configuration update audit trail for resource [" + resource + "]"); } resource.getResourceConfigurationUpdates().remove(doomedRequest); entityManager.remove(doomedRequest); entityManager.flush(); return; } @Override public void purgeResourceConfigurationUpdates(Subject subject, int[] configurationUpdateIds, boolean purgeInProgress) { if ((configurationUpdateIds == null) || (configurationUpdateIds.length == 0)) { return; } // TODO [mazz]: ugly - let's make this more efficient, just getting this to work first for (int configurationUpdateId : configurationUpdateIds) { purgeResourceConfigurationUpdate(subject, configurationUpdateId, purgeInProgress); } return; } @Override public ResourceConfigurationUpdate updateStructuredOrRawConfiguration(Subject subject, int resourceId, Configuration newConfiguration, boolean fromStructured) throws ResourceNotFoundException, ConfigurationUpdateStillInProgressException { if (authorizationManager.hasResourcePermission(subject, Permission.CONFIGURE_WRITE, resourceId) == false) { throw new PermissionException("User[name=" + subject.getName() + "] does not have the permission to update configuration for resource[id=" + resourceId + "]"); } Configuration configToUpdate = newConfiguration; if (isStructuredAndRawSupported(resourceId)) { configToUpdate = translateResourceConfiguration(subject, resourceId, newConfiguration, fromStructured); } try { Configuration invalidConfig = validateResourceConfiguration(subject, resourceId, newConfiguration, fromStructured); if (null != invalidConfig) { Resource resource = resourceManager.getResourceById(subject, resourceId); ResourceConfigurationUpdate resourceConfigurationUpdate = new ResourceConfigurationUpdate(resource, invalidConfig, subject.getName()); resourceConfigurationUpdate.setErrorMessage("resource.validation.failed"); resourceConfigurationUpdate.setStatus(ConfigurationUpdateStatus.FAILURE); return resourceConfigurationUpdate; } } catch (PluginContainerException e) { Resource resource = resourceManager.getResourceById(subject, resourceId); ResourceConfigurationUpdate resourceConfigurationUpdate = new ResourceConfigurationUpdate(resource, newConfiguration, subject.getName()); resourceConfigurationUpdate.setErrorMessage(e.getMessage()); resourceConfigurationUpdate.setStatus(ConfigurationUpdateStatus.FAILURE); return resourceConfigurationUpdate; } ResourceConfigurationUpdate newUpdate = configurationManager .persistResourceConfigurationUpdateInNewTransaction(subject, resourceId, configToUpdate, ConfigurationUpdateStatus.INPROGRESS, subject.getName(), false); executeResourceConfigurationUpdate(newUpdate); return newUpdate; } private Configuration validateResourceConfiguration(Subject subject, int resourceId, Configuration configuration, boolean isStructured) throws PluginContainerException { Resource resource = entityManager.find(Resource.class, resourceId); if (resource == null) { throw new NoResultException("Cannot get live configuration for unknown resource [" + resourceId + "]"); } if (!authorizationManager.canViewResource(subject, resource.getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view resource configuration for [" + resource + "]"); } Agent agent = resource.getAgent(); AgentClient agentClient = this.agentManager.getAgentClient(agent); ConfigurationAgentService configService = agentClient.getConfigurationAgentService(); return configService.validate(configuration, resourceId, isStructured); } private boolean isStructuredAndRawSupported(int resourceId) { Resource resource = entityManager.find(Resource.class, resourceId); ConfigurationDefinition configDef = resource.getResourceType().getResourceConfigurationDefinition(); if (configDef == null) { return false; } return ConfigurationFormat.STRUCTURED_AND_RAW == configDef.getConfigurationFormat(); } @Override @Nullable public ResourceConfigurationUpdate updateResourceConfiguration(Subject subject, int resourceId, Configuration newResourceConfiguration) throws ResourceNotFoundException { if (isStructuredAndRawSupported(resourceId)) { throw new ConfigurationUpdateNotSupportedException("Cannot update a resource configuration that " + "supports both structured and raw configuration using this method because there is insufficient " + "information. You should instead call updateStructuredOrRawConfiguration() which requires you " + "whether the structured or raw was updated."); } if (authorizationManager.hasResourcePermission(subject, Permission.CONFIGURE_WRITE, resourceId) == false) { throw new PermissionException("User[name=" + subject.getName() + "] does not have the permission to update configuration for resource[id=" + resourceId + "]"); } // Make sure to unmask the configuration before persisting the update. Subject overlord = subjectManager.getOverlord(); Resource resource = resourceManager.getResource(overlord, resourceId); Configuration existingResourceConfiguration = resource.getResourceConfiguration(); ConfigurationMaskingUtility.unmaskConfiguration(newResourceConfiguration, existingResourceConfiguration); ConfigurationDefinition resourceConfigDef = getResourceConfigurationDefinitionForResourceType(overlord, resource.getResourceType().getId()); List<String> validationErrors = ConfigurationUtility.validateConfiguration(newResourceConfiguration, existingResourceConfiguration, resourceConfigDef); if (!validationErrors.isEmpty()) { throw new BadArgumentException("Invalid newResourceConfiguration, configuration not updated: " + validationErrors); } // Calling the persist method via the EJB interface to pick up the method's REQUIRES_NEW semantics and persist // the update in a separate transaction; this way, the update is committed prior to sending the agent request // Note, the persist method will return null if newConfiguration is no different than the current Resource // configuration. // TODO: Consider synchronizing to avoid the condition where someone calls this method twice quickly in two // different tx's, which would put two updates in INPROGRESS and cause havoc. ResourceConfigurationUpdate newUpdate = configurationManager .persistResourceConfigurationUpdateInNewTransaction(subject, resourceId, newResourceConfiguration, ConfigurationUpdateStatus.INPROGRESS, subject.getName(), false); executeResourceConfigurationUpdate(newUpdate); return newUpdate; } @Override public void executeResourceConfigurationUpdate(int updateId) { ResourceConfigurationUpdate update = getResourceConfigurationUpdate(subjectManager.getOverlord(), updateId); Configuration originalConfig = update.getConfiguration(); update.setConfiguration(originalConfig.deepCopy(false)); executeResourceConfigurationUpdate(update); } /** * Tells the Agent to asynchronously update a managed resource's configuration as per the specified * <code>ResourceConfigurationUpdate</code>. */ private void executeResourceConfigurationUpdate(ResourceConfigurationUpdate update) { try { AgentClient agentClient = agentManager.getAgentClient(update.getResource().getAgent()); ConfigurationUpdateRequest request = new ConfigurationUpdateRequest(update.getId(), update.getConfiguration(), update.getResource().getId()); agentClient.getConfigurationAgentService().updateResourceConfiguration(request); } catch (RuntimeException e) { // Any exception means the remote call itself failed - make sure to change the status on the update to FAILURE // and set its error message field. if (null != update) { update.setStatus(ConfigurationUpdateStatus.FAILURE); update.setErrorMessage(ThrowableUtil.getStackAsString(e)); // Here we call ourselves, but we do so via the EJB interface so we pick up the REQUIRES_NEW semantics. this.configurationManager.mergeConfigurationUpdate(update); checkForCompletedGroupResourceConfigurationUpdate(update.getId()); } } } @Override public void rollbackResourceConfiguration(Subject subject, int resourceId, int configHistoryId) throws ConfigurationUpdateException { ResourceConfigurationUpdate configurationUpdateHistory = entityManager.find(ResourceConfigurationUpdate.class, configHistoryId); Configuration configuration = configurationUpdateHistory.getConfiguration(); if (configuration == null) { throw new ConfigurationUpdateException("No configuration history element exists with id = '" + configHistoryId + "'"); } if (isStructuredAndRawSupported(resourceId)) { updateStructuredOrRawConfiguration(subject, resourceId, configuration, false); } else { updateResourceConfiguration(subject, resourceId, configuration); } } @Override public void rollbackPluginConfiguration(Subject subject, int resourceId, int configHistoryId) throws ConfigurationUpdateException { PluginConfigurationUpdate configurationUpdateHistory = entityManager.find(PluginConfigurationUpdate.class, configHistoryId); Configuration configuration = configurationUpdateHistory.getConfiguration(); if (configuration == null) { throw new ConfigurationUpdateException("No plugin configuration history element exists with id = '" + configHistoryId + "'"); } updatePluginConfiguration(subject, resourceId, configuration); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public ResourceConfigurationUpdate persistResourceConfigurationUpdateInNewTransaction(Subject subject, int resourceId, Configuration newConfiguration, ConfigurationUpdateStatus newStatus, String newSubject, boolean isPartofGroupUpdate) throws ResourceNotFoundException, ConfigurationUpdateStillInProgressException { ResourceConfigurationUpdate current = null; String errorMessage = null; // for efficiency, in one query fetch IN_PROGRESS and/or the current update Query query = entityManager .createNamedQuery(ResourceConfigurationUpdate.QUERY_FIND_CURRENT_AND_IN_PROGRESS_CONFIGS); query.setParameter("resourceId", resourceId); List<?> updates = query.getResultList(); for (Object result : updates) { current = (ResourceConfigurationUpdate) result; if (ConfigurationUpdateStatus.INPROGRESS == current.getStatus()) { // A previous update is still in progress for this Resource. If this update is part of a group // update, persist it with FAILURE status, so it is still listed as part of the group history. // Otherwise, throw an exception that can bubble up to the GUI. if (isPartofGroupUpdate) { newStatus = ConfigurationUpdateStatus.FAILURE; errorMessage = "Resource configuration Update was aborted because an update request for the Resource was already in progress."; } else { // NOTE: If you change this to another exception, make sure you change getLatestResourceConfigurationUpdate(). throw new ConfigurationUpdateStillInProgressException( "Resource [" + resourceId + "] has a resource configuration update request already in progress - please wait for it to finish: " + current); } } } Resource resource = null; if (null != current) { // Always persist a group update because each member must have an update. Otherwise, only persist a // single resource update if it has changed. if (!isPartofGroupUpdate) { Configuration currentConfiguration = current.getConfiguration(); Hibernate.initialize(currentConfiguration.getMap()); if (currentConfiguration.equals(newConfiguration)) { return null; } } resource = current.getResource(); } else { // make sure the resource exists resource = resourceManager.getResourceById(subject, resourceId); } Configuration zeroedConfiguration = newConfiguration.deepCopyWithoutProxies(); // create our new update request and assign it to our resource - its status will initially be INPROGRESS ResourceConfigurationUpdate newUpdateRequest = new ResourceConfigurationUpdate(resource, zeroedConfiguration, newSubject); newUpdateRequest.setStatus(newStatus); if (ConfigurationUpdateStatus.FAILURE == newStatus) { newUpdateRequest.setErrorMessage(errorMessage); } entityManager.persist(newUpdateRequest); // No need to alert on the first configuration update, only on subsequent change if (null != current) { if (ConfigurationUpdateStatus.SUCCESS == newStatus) { notifyAlertConditionCacheManager("persistNewResourceConfigurationUpdateHistory", newUpdateRequest); } } resource.addResourceConfigurationUpdates(newUpdateRequest); // provide the agent while we have the entity, it's typically needed by the caller Hibernate.initialize(resource.getAgent()); return newUpdateRequest; } private void notifyAlertConditionCacheManager(String callingMethod, ResourceConfigurationUpdate update) { AlertConditionCacheStats stats = alertConditionCacheManager.checkConditions(update); if (LOG.isDebugEnabled()) { LOG.debug(callingMethod + ": " + stats); } } @Override public void completeResourceConfigurationUpdate(ConfigurationUpdateResponse response) { if (LOG.isDebugEnabled()) { LOG.debug("Received a configuration-update-completed message: " + response); } // find the current update request that is persisted - this is the one that is being reported as being complete ResourceConfigurationUpdate update = entityManager.find(ResourceConfigurationUpdate.class, response.getConfigurationUpdateId()); if (update == null) { throw new IllegalStateException( "The completed request passed in does not match any request for any resource in inventory: " + response); } if (response.getStatus() == ConfigurationUpdateStatus.FAILURE) { // TODO [mazz]: what happens if the plugin dorked with the configuration ID? need to assert it hasn't changed if (response.getConfiguration() != null) { Configuration failedConfiguration = response.getConfiguration(); failedConfiguration = entityManager.merge(failedConfiguration); // merge in any property error messages update.setConfiguration(failedConfiguration); } } else if (response.getStatus() == ConfigurationUpdateStatus.SUCCESS) { // link to the newer, persisted configuration object Resource resource = update.getResource(); resource.setResourceConfiguration(update.getConfiguration().deepCopyWithoutProxies()); notifyAlertConditionCacheManager("completeResourceConfigurationUpdate", update); } // Make sure we update the persisted request with the new status and any error message. update.setStatus(response.getStatus()); update.setErrorMessage(response.getErrorMessage()); /* * instead of checking for completed group resource configuration updates here, let our caller (the * ConfigurationServerService) do it so that this transaction completes before the check begins */ return; } @Override @SuppressWarnings("unchecked") public void checkForCompletedGroupResourceConfigurationUpdate(int resourceConfigUpdateId) { ResourceConfigurationUpdate resourceConfigUpdate = entityManager.find(ResourceConfigurationUpdate.class, resourceConfigUpdateId); if (resourceConfigUpdate.getStatus() == ConfigurationUpdateStatus.INPROGRESS) { // If this update isn't done, then, by definition, the group update isn't done either. return; } GroupResourceConfigurationUpdate groupConfigUpdate = resourceConfigUpdate.getGroupConfigurationUpdate(); if (groupConfigUpdate == null) { // The update isn't part of a group update - nothing we need to do. return; } Query inProgressResourcesCountQuery = PersistenceUtility.createCountQuery(this.entityManager, ResourceConfigurationUpdate.QUERY_FIND_BY_PARENT_UPDATE_ID_AND_STATUS); inProgressResourcesCountQuery.setParameter("parentUpdateId", groupConfigUpdate.getId()); inProgressResourcesCountQuery.setParameter("status", ConfigurationUpdateStatus.INPROGRESS); long inProgressResourcesCount = (Long) inProgressResourcesCountQuery.getSingleResult(); if (inProgressResourcesCount == 0) { // No more member updates in progress - the group update is complete. Query failedResourcesQuery = this.entityManager .createNamedQuery(ResourceConfigurationUpdate.QUERY_FIND_BY_PARENT_UPDATE_ID_AND_STATUS); failedResourcesQuery.setParameter("parentUpdateId", groupConfigUpdate.getId()); failedResourcesQuery.setParameter("status", ConfigurationUpdateStatus.FAILURE); List<Resource> failedResources = failedResourcesQuery.getResultList(); ConfigurationUpdateStatus groupStatus; if (failedResources.isEmpty()) { groupStatus = ConfigurationUpdateStatus.SUCCESS; } else { groupStatus = ConfigurationUpdateStatus.FAILURE; groupConfigUpdate.setErrorMessage("The following Resources failed to update their Configurations: " + failedResources); } groupConfigUpdate.setStatus(groupStatus); LOG.info("Group Resource configuration update [" + groupConfigUpdate.getId() + "] for " + groupConfigUpdate.getGroup() + " has completed with status [" + groupStatus + "]."); // TODO: Add support for alerting on completion of group resource config updates. //notifyAlertConditionCacheManager("checkForCompletedGroupResourceConfigurationUpdate", groupUpdate); } else { if (LOG.isDebugEnabled()) { LOG.debug("Group Resource configuration update [" + groupConfigUpdate.getId() + "] for " + groupConfigUpdate.getGroup() + " has " + inProgressResourcesCount + " member updates still in progress."); } } return; } @Override public ConfigurationDefinition getPackageTypeConfigurationDefinition(Subject subject, int packageTypeId) { Query query = entityManager.createNamedQuery(ConfigurationDefinition.QUERY_FIND_DEPLOYMENT_BY_PACKAGE_TYPE_ID); query.setParameter("packageTypeId", packageTypeId); ConfigurationDefinition configurationDefinition = null; try { configurationDefinition = (ConfigurationDefinition) query.getSingleResult(); } catch (NoResultException e) { PackageType packageType = entityManager.find(PackageType.class, packageTypeId); if (packageType == null) { throw new EntityNotFoundException("A package type with id " + packageTypeId + " does not exist."); } } return configurationDefinition; } @Override public ConfigurationDefinition getResourceConfigurationDefinitionForResourceType(Subject subject, int resourceTypeId) { Query query = entityManager.createNamedQuery(ConfigurationDefinition.QUERY_FIND_RESOURCE_BY_RESOURCE_TYPE_ID); query.setParameter("resourceTypeId", resourceTypeId); ConfigurationDefinition configurationDefinition = null; try { configurationDefinition = (ConfigurationDefinition) query.getSingleResult(); } catch (NoResultException e) { ResourceType resourceType = entityManager.find(ResourceType.class, resourceTypeId); if (resourceType == null) { throw new EntityNotFoundException("A resource type with id " + resourceTypeId + " does not exist."); } } return configurationDefinition; } @Override @Nullable public ConfigurationDefinition getResourceConfigurationDefinitionWithTemplatesForResourceType(Subject subject, int resourceTypeId) { Query query = entityManager.createNamedQuery(ConfigurationDefinition.QUERY_FIND_RESOURCE_BY_RESOURCE_TYPE_ID); query.setParameter("resourceTypeId", resourceTypeId); ConfigurationDefinition configurationDefinition = null; try { configurationDefinition = (ConfigurationDefinition) query.getSingleResult(); } catch (NoResultException e) { ResourceType resourceType = entityManager.find(ResourceType.class, resourceTypeId); if (resourceType == null) { throw new EntityNotFoundException("A resource type with id " + resourceTypeId + " does not exist."); } } // Eager Load the templates if ((configurationDefinition != null) && (configurationDefinition.getTemplates() != null)) { configurationDefinition.getTemplates().size(); } return configurationDefinition; } @Override public ConfigurationDefinition getPluginConfigurationDefinitionForResourceType(Subject subject, int resourceTypeId) { Query query = entityManager.createNamedQuery(ConfigurationDefinition.QUERY_FIND_PLUGIN_BY_RESOURCE_TYPE_ID); query.setParameter("resourceTypeId", resourceTypeId); ConfigurationDefinition configurationDefinition = null; try { configurationDefinition = (ConfigurationDefinition) query.getSingleResult(); } catch (NoResultException e) { ResourceType resourceType = entityManager.find(ResourceType.class, resourceTypeId); if (resourceType == null) { throw new EntityNotFoundException("A resource type with id " + resourceTypeId + " does not exist."); } } // Eager Load the templates if ((configurationDefinition != null) && (configurationDefinition.getTemplates() != null)) { configurationDefinition.getTemplates().size(); } return configurationDefinition; } /** * Given an actual resource, this asks the agent to return that resource's live configuration. Note that this does * not perform any authorization checks - it is assumed the caller has permissions to view the configuration. This * also assumes <code>resource</code> is a non-<code>null</code> and existing resource. * * <p>If failed to contact the agent or any other communications problem occurred, <code>null</code> will be * returned.</p> * * @param resource an existing resource whose live configuration is to be retrieved * @param pingAgentFirst true if the underlying Agent should be pinged successfully before attempting to retrieve * the configuration, or false otherwise * * @return the resource's live configuration or <code>null</code> if it could not be retrieved from the agent */ private Configuration getLiveResourceConfiguration(Resource resource, boolean pingAgentFirst, boolean fromStructured) { Configuration liveConfig = null; try { Agent agent = resource.getAgent(); AgentClient agentClient = this.agentManager.getAgentClient(agent); boolean agentPingedSuccessfully = false; // Getting live configuration is mostly for the UI's benefit - as such, do not hang // for a long time in the event the agent is down or can't be reached. Let's make the UI // responsive even in the case of an agent down by pinging it quickly to verify the agent is up. if (pingAgentFirst) { agentPingedSuccessfully = agentClient.pingService(5000L); } if (!pingAgentFirst || agentPingedSuccessfully) { liveConfig = agentClient.getConfigurationAgentService().loadResourceConfiguration(resource.getId()); if (liveConfig == null) { // This should really never occur - the PC should never return a null, always at least an empty config. LOG.debug("ConfigurationAgentService.loadResourceConfiguration() returned a null Configuration."); liveConfig = new Configuration(); } } else { LOG.warn("Agent is unreachable [" + agent + "] - cannot get live configuration for resource [" + resource + "]"); } } catch (Exception e) { LOG.warn("Could not get live configuration for resource [" + resource + "]" + ThrowableUtil.getAllMessages(e, true)); } return liveConfig; } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public AbstractResourceConfigurationUpdate mergeConfigurationUpdate( AbstractResourceConfigurationUpdate configurationUpdate) { return this.entityManager.merge(configurationUpdate); } @Override public Configuration getConfigurationById(int id) { return entityManager.find(Configuration.class, id); } @Override public Configuration getConfiguration(Subject subject, int configurationId) { Configuration configuration = getConfigurationById(configurationId); return configuration; } @Override public Configuration getConfigurationFromDefaultTemplate(ConfigurationDefinition definition) { ConfigurationDefinition managedDefinition = entityManager.find(ConfigurationDefinition.class, definition.getId()); Configuration configuration = managedDefinition.getDefaultTemplate().getConfiguration(); ConfigurationMaskingUtility.maskConfiguration(configuration, managedDefinition); return configuration; } private void handlePluginConfiguratonUpdateRemoteException(Resource resource, String summary, String detail) { resource.setConnected(false); ResourceError invalidPluginConfigError = new ResourceError(resource, ResourceErrorType.INVALID_PLUGIN_CONFIGURATION, summary, detail, Calendar.getInstance().getTimeInMillis()); this.resourceManager.addResourceError(invalidPluginConfigError); } private void removeAnyExistingInvalidPluginConfigurationErrors(Resource resource) { this.resourceManager.clearResourceConfigError(resource.getId()); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public int createGroupConfigurationUpdate(AbstractGroupConfigurationUpdate update) throws SchedulerException { entityManager.persist(update); return update.getId(); } @Override public int scheduleGroupPluginConfigurationUpdate(Subject subject, int compatibleGroupId, Map<Integer, Configuration> memberPluginConfigurations) { if (memberPluginConfigurations == null) { throw new IllegalArgumentException( "GroupPluginConfigurationUpdate must have non-null member configurations."); } ResourceGroup group = getCompatibleGroupIfAuthorized(subject, compatibleGroupId); ensureModifyResourcePermission(subject, group); /* * we need to create and persist the group in a new/separate transaction before the rest of the * processing of this method; if we try to create and attach the PluginConfigurationUpdate children * to the parent group before the group update is actually persisted, we'll get StaleStateExceptions * from Hibernate because of our use of flush/clear (we're trying to update it before it actually * actually exists) */ GroupPluginConfigurationUpdate groupUpdate = new GroupPluginConfigurationUpdate(group, subject.getName()); int updateId = -1; try { updateId = configurationManager.createGroupConfigurationUpdate(groupUpdate); } catch (SchedulerException sche) { String message = "Error scheduling plugin configuration update for group[id=" + group.getId() + "]"; LOG.error(message, sche); throw new ResourceGroupUpdateException(message + ": " + sche); } // Create and persist updates for each of the members. for (Integer resourceId : memberPluginConfigurations.keySet()) { Configuration memberPluginConfiguration = memberPluginConfigurations.get(resourceId); // Make sure to unmask the configuration before persisting the update. Resource resource = resourceManager.getResource(subjectManager.getOverlord(), resourceId); Configuration existingPluginConfiguration = resource.getPluginConfiguration(); ConfigurationMaskingUtility.unmaskConfiguration(memberPluginConfiguration, existingPluginConfiguration); Resource flyWeight = new Resource(resourceId); PluginConfigurationUpdate memberUpdate = new PluginConfigurationUpdate(flyWeight, memberPluginConfiguration, subject.getName()); memberUpdate.setGroupConfigurationUpdate(groupUpdate); entityManager.persist(memberUpdate); } JobDataMap jobDataMap = new JobDataMap(); jobDataMap.putAsString(AbstractGroupConfigurationUpdateJob.DATAMAP_INT_CONFIG_GROUP_UPDATE_ID, updateId); jobDataMap.putAsString(AbstractGroupConfigurationUpdateJob.DATAMAP_INT_SUBJECT_ID, subject.getId()); /* * acquire quartz objects and schedule the group update, but deferred the execution for 10 seconds * because we need this transaction to complete so that the data is available when the quartz job triggers */ JobDetail jobDetail = GroupPluginConfigurationUpdateJob.getJobDetail(group, subject, jobDataMap); Trigger trigger = QuartzUtil.getFireOnceOffsetTrigger(jobDetail, 10000); try { scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException e) { String message = "Error scheduling job named '" + jobDetail.getName() + "':"; LOG.error(message, e); throw new ResourceGroupUpdateException(message + e.getMessage()); } if (LOG.isDebugEnabled()) { LOG.debug("Scheduled plugin configuration update against compatibleGroup[id=" + compatibleGroupId + "]"); } return updateId; } @Override public int scheduleGroupResourceConfigurationUpdate(Subject subject, int compatibleGroupId, Map<Integer, Configuration> newResourceConfigurationMap) { if (newResourceConfigurationMap == null) { throw new IllegalArgumentException( "GroupResourceConfigurationUpdate must have non-null member configurations."); } ResourceGroup group = getCompatibleGroupIfAuthorized(subject, compatibleGroupId); if (!authorizationManager.hasGroupPermission(subject, Permission.CONFIGURE_WRITE, group.getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission " + "to modify Resource configurations for members of group [" + group + "]."); } ensureNoResourceConfigurationUpdatesInProgress(group); /* * we need to create and persist the group update in a new/separate transaction before the rest of the * processing of this method; if we try to create and attach the PluginConfigurationUpdate children * to the parent group before the group update is actually persisted, we'll get StaleStateExceptions * from Hibernate because of our use of flush/clear (we're trying to update it before it actually exists) */ GroupResourceConfigurationUpdate groupUpdate = new GroupResourceConfigurationUpdate(group, subject.getName()); int updateId = -1; try { updateId = configurationManager.createGroupConfigurationUpdate(groupUpdate); } catch (SchedulerException sche) { String message = "Error scheduling resource configuration update for group[id=" + group.getId() + "]"; LOG.error(message, sche); throw new ResourceGroupUpdateException(message + ": " + sche); } // Create and persist updates for each of the members. for (Integer resourceId : newResourceConfigurationMap.keySet()) { Configuration memberResourceConfiguration = newResourceConfigurationMap.get(resourceId); // Make sure to unmask the configuration before persisting the update. Resource resource = resourceManager.getResource(subjectManager.getOverlord(), resourceId); Configuration existingResourceConfiguration = resource.getResourceConfiguration(); ConfigurationMaskingUtility.unmaskConfiguration(memberResourceConfiguration, existingResourceConfiguration); Resource flyWeight = new Resource(resourceId); ResourceConfigurationUpdate memberUpdate = new ResourceConfigurationUpdate(flyWeight, memberResourceConfiguration, subject.getName()); memberUpdate.setGroupConfigurationUpdate(groupUpdate); entityManager.persist(memberUpdate); } JobDataMap jobDataMap = new JobDataMap(); jobDataMap.putAsString(AbstractGroupConfigurationUpdateJob.DATAMAP_INT_CONFIG_GROUP_UPDATE_ID, updateId); jobDataMap.putAsString(AbstractGroupConfigurationUpdateJob.DATAMAP_INT_SUBJECT_ID, subject.getId()); /* * Acquire Quartz objects and schedule the group update, but defer the execution for 10 seconds, * because we need this transaction to complete so that the data is available when the Quartz job triggers. */ JobDetail jobDetail = GroupResourceConfigurationUpdateJob.getJobDetail(group, subject, jobDataMap); Trigger trigger = QuartzUtil.getFireOnceOffsetTrigger(jobDetail, 10000); try { scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException e) { String message = "Error scheduling job named '" + jobDetail.getName() + "':"; LOG.error(message, e); throw new ResourceGroupUpdateException(message + e.getMessage()); } if (LOG.isDebugEnabled()) { LOG.debug("Scheduled Resource configuration update against compatible ResourceGroup[id=" + compatibleGroupId + "]."); } return updateId; } private ResourceGroup getCompatibleGroupIfAuthorized(Subject subject, int compatibleGroupId) { ResourceGroup group; try { // resourceGroupManager will test for necessary permissions too group = resourceGroupManager.getResourceGroupById(subject, compatibleGroupId, GroupCategory.COMPATIBLE); } catch (ResourceGroupNotFoundException e) { throw new RuntimeException("Cannot get support operations for unknown group [" + compatibleGroupId + "]: " + e, e); } return group; } private void ensureModifyPermission(Subject subject, Resource resource) throws PermissionException { if (!authorizationManager.hasResourcePermission(subject, Permission.MODIFY_RESOURCE, resource.getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission " + "to modify plugin configuration for resource [" + resource + "]"); } } private void ensureModifyResourcePermission(Subject subject, ResourceGroup group) throws PermissionException { if (!authorizationManager.hasGroupPermission(subject, Permission.MODIFY_RESOURCE, group.getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission " + "to modify plugin configuration for members of group [" + group + "]"); } } /** * @deprecated use {@link #findGroupPluginConfigurationUpdatesByCriteria(org.rhq.core.domain.auth.Subject, org.rhq.core.domain.criteria.GroupPluginConfigurationUpdateCriteria)} */ @Deprecated @Override public GroupPluginConfigurationUpdate getGroupPluginConfigurationById(int configurationUpdateId) { GroupPluginConfigurationUpdate update = entityManager.find(GroupPluginConfigurationUpdate.class, configurationUpdateId); return update; } /** * @deprecated use {@link #findGroupResourceConfigurationUpdatesByCriteria(org.rhq.core.domain.auth.Subject, org.rhq.core.domain.criteria.GroupResourceConfigurationUpdateCriteria)} */ @Deprecated @Override public GroupResourceConfigurationUpdate getGroupResourceConfigurationById(int configurationUpdateId) { GroupResourceConfigurationUpdate update = entityManager.find(GroupResourceConfigurationUpdate.class, configurationUpdateId); return update; } @Override @SuppressWarnings("unchecked") public PageList<ConfigurationUpdateComposite> findPluginConfigurationUpdateCompositesByParentId( int configurationUpdateId, PageControl pageControl) { pageControl.initDefaultOrderingField("cu.modifiedTime"); Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, PluginConfigurationUpdate.QUERY_FIND_COMPOSITE_BY_PARENT_UPDATE_ID, pageControl); query.setParameter("groupConfigurationUpdateId", configurationUpdateId); long count = getPluginConfigurationUpdateCountByParentId(configurationUpdateId); List<ConfigurationUpdateComposite> results = query.getResultList(); return new PageList<ConfigurationUpdateComposite>(results, (int) count, pageControl); } @Override @SuppressWarnings("unchecked") public PageList<ConfigurationUpdateComposite> findResourceConfigurationUpdateCompositesByParentId(Subject subject, int configurationUpdateId, PageControl pageControl) { // will perform CONFIGURE_READ security check for us, no need to save the getGroupResourceConfigurationUpdate(subject, configurationUpdateId); pageControl.initDefaultOrderingField("cu.modifiedTime"); Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, ResourceConfigurationUpdate.QUERY_FIND_COMPOSITE_BY_PARENT_UPDATE_ID, pageControl); query.setParameter("groupConfigurationUpdateId", configurationUpdateId); long count = getResourceConfigurationUpdateCountByParentId(configurationUpdateId); List<ConfigurationUpdateComposite> results = query.getResultList(); return new PageList<ConfigurationUpdateComposite>(results, (int) count, pageControl); } /** * @deprecated use {@link #findPluginConfigurationUpdatesByCriteria(org.rhq.core.domain.auth.Subject, org.rhq.core.domain.criteria.PluginConfigurationUpdateCriteria)} */ @Deprecated @Override @SuppressWarnings("unchecked") public PageList<Integer> findPluginConfigurationUpdatesByParentId(int configurationUpdateId, PageControl pageControl) { pageControl.initDefaultOrderingField("cu.modifiedTime"); Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, PluginConfigurationUpdate.QUERY_FIND_BY_PARENT_UPDATE_ID, pageControl); query.setParameter("groupConfigurationUpdateId", configurationUpdateId); long count = getPluginConfigurationUpdateCountByParentId(configurationUpdateId); List<Integer> results = query.getResultList(); return new PageList<Integer>(results, (int) count, pageControl); } @Override public long getPluginConfigurationUpdateCountByParentId(int configurationUpdateId) { Query countQuery = PersistenceUtility.createCountQuery(entityManager, PluginConfigurationUpdate.QUERY_FIND_BY_PARENT_UPDATE_ID); countQuery.setParameter("groupConfigurationUpdateId", configurationUpdateId); return (Long) countQuery.getSingleResult(); } /** * @deprecated use {@link #findResourceConfigurationUpdatesByCriteria(org.rhq.core.domain.auth.Subject, org.rhq.core.domain.criteria.ResourceConfigurationUpdateCriteria)} */ @Deprecated @Override @SuppressWarnings("unchecked") public PageList<Integer> findResourceConfigurationUpdatesByParentId(int groupConfigurationUpdateId, PageControl pageControl) { pageControl.initDefaultOrderingField("cu.modifiedTime"); Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, ResourceConfigurationUpdate.QUERY_FIND_BY_PARENT_UPDATE_ID, pageControl); query.setParameter("groupConfigurationUpdateId", groupConfigurationUpdateId); long count = getResourceConfigurationUpdateCountByParentId(groupConfigurationUpdateId); List<Integer> results = query.getResultList(); return new PageList<Integer>(results, (int) count, pageControl); } @Override public long getResourceConfigurationUpdateCountByParentId(int groupConfigurationUpdateId) { Query countQuery = PersistenceUtility.createCountQuery(entityManager, ResourceConfigurationUpdate.QUERY_FIND_BY_PARENT_UPDATE_ID); countQuery.setParameter("groupConfigurationUpdateId", groupConfigurationUpdateId); return (Long) countQuery.getSingleResult(); } @Override @SuppressWarnings("unchecked") public Map<Integer, Configuration> getResourceConfigurationMapForGroupUpdate(Subject subject, Integer groupResourceConfigurationUpdateId) { // this method will perform the CONFIGURE_READ security check for us, no need to keep reference to result GroupResourceConfigurationUpdate groupResourceConfigurationUpdate = getGroupResourceConfigurationUpdate( subject, groupResourceConfigurationUpdateId); Tuple<String, Object> groupIdParameter = new Tuple<String, Object>("groupConfigurationUpdateId", groupResourceConfigurationUpdateId); Map<Integer, Configuration> results = executeGetConfigurationMapQuery( Configuration.QUERY_GET_RESOURCE_CONFIG_MAP_BY_GROUP_UPDATE_ID, 100, groupIdParameter); // Mask the configurations before returning them. for (Configuration configuration : results.values()) { configuration.getMap().size(); } ConfigurationDefinition configurationDefinition = getResourceConfigurationDefinitionForResourceType( subjectManager.getOverlord(), groupResourceConfigurationUpdate.getGroup().getResourceType().getId()); // We do not want the masked configurations persisted, so detach all entities before masking the configurations. entityManager.clear(); for (Configuration configuration : results.values()) { ConfigurationMaskingUtility.maskConfiguration(configuration, configurationDefinition); } return results; } @Override @SuppressWarnings("unchecked") public Map<Integer, Configuration> getPluginConfigurationMapForGroupUpdate(Subject subject, Integer groupPluginConfigurationUpdateId) { // this method will perform the CONFIGURE_READ security check for us, no need to keep reference to result GroupPluginConfigurationUpdate groupPluginConfigurationUpdate = getGroupPluginConfigurationUpdate(subject, groupPluginConfigurationUpdateId); Tuple<String, Object> groupIdParameter = new Tuple<String, Object>("groupConfigurationUpdateId", groupPluginConfigurationUpdateId); Map<Integer, Configuration> results = executeGetConfigurationMapQuery( Configuration.QUERY_GET_PLUGIN_CONFIG_MAP_BY_GROUP_UPDATE_ID, 100, groupIdParameter); // Mask the configurations before returning them. for (Configuration configuration : results.values()) { configuration.getMap().size(); } ConfigurationDefinition configurationDefinition = getPluginConfigurationDefinitionForResourceType( subjectManager.getOverlord(), groupPluginConfigurationUpdate.getGroup().getResourceType().getId()); // We do not want the masked configurations persisted, so detach all entities before masking the configurations. entityManager.clear(); for (Configuration configuration : results.values()) { ConfigurationMaskingUtility.maskConfiguration(configuration, configurationDefinition); } return results; } @SuppressWarnings("unchecked") private Map<Integer, Configuration> executeGetConfigurationMapQuery(String memberQueryName, int maxSize, Tuple<String, Object>... parameters) { Query countQuery = PersistenceUtility.createCountQuery(entityManager, memberQueryName); Query query = entityManager.createNamedQuery(memberQueryName); for (Tuple<String, Object> param : parameters) { countQuery.setParameter(param.lefty, param.righty); query.setParameter(param.lefty, param.righty); } PersistenceUtility.setDataPage(query, new PageControl(0, maxSize)); // limit the results long count = (Long) countQuery.getSingleResult(); int resultsSize; if (count > maxSize) { LOG.error("Configuration set contains more than " + maxSize + " members - " + "returning only " + maxSize + " Configurations (the maximum allowed)."); resultsSize = maxSize; } else { resultsSize = (int) count; } // initialize the map to be 150% more than the results, so that the fill factor only reaches 66% Map<Integer, Configuration> results = new HashMap<Integer, Configuration>((int) (resultsSize * 1.5)); List<Object[]> pagedResults = query.getResultList(); for (Object[] result : pagedResults) { Integer resourceId = (Integer) result[0]; Configuration configuration = (Configuration) result[1]; results.put(resourceId, configuration); } return results; } /** * @deprecated use {@link #findGroupPluginConfigurationUpdatesByCriteria(org.rhq.core.domain.auth.Subject, org.rhq.core.domain.criteria.GroupPluginConfigurationUpdateCriteria)} */ @Deprecated @Override @SuppressWarnings("unchecked") public PageList<GroupPluginConfigurationUpdate> findGroupPluginConfigurationUpdates(int groupId, PageControl pc) { pc.initDefaultOrderingField("modifiedTime", PageOrdering.DESC); Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, GroupPluginConfigurationUpdate.QUERY_FIND_BY_GROUP_ID, pc); Query countQuery = PersistenceUtility.createCountQuery(entityManager, GroupPluginConfigurationUpdate.QUERY_FIND_BY_GROUP_ID); query.setParameter("groupId", groupId); countQuery.setParameter("groupId", groupId); long count = (Long) countQuery.getSingleResult(); List<GroupPluginConfigurationUpdate> results = query.getResultList(); return new PageList<GroupPluginConfigurationUpdate>(results, (int) count, pc); } /** * @deprecated use {@link #findGroupResourceConfigurationUpdatesByCriteria(org.rhq.core.domain.auth.Subject, org.rhq.core.domain.criteria.GroupResourceConfigurationUpdateCriteria)} */ @Deprecated @Override @SuppressWarnings("unchecked") public PageList<GroupResourceConfigurationUpdate> findGroupResourceConfigurationUpdates(Subject subject, int groupId, PageControl pc) { if (authorizationManager.hasGroupPermission(subject, Permission.CONFIGURE_READ, groupId) == false) { throw new PermissionException("User[name=" + subject.getName() + "] does not have permission to view configuration for group[id=" + groupId + "]"); } pc.initDefaultOrderingField("modifiedTime", PageOrdering.DESC); Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, GroupResourceConfigurationUpdate.QUERY_FIND_BY_GROUP_ID, pc); Query countQuery = PersistenceUtility.createCountQuery(entityManager, GroupResourceConfigurationUpdate.QUERY_FIND_BY_GROUP_ID); query.setParameter("groupId", groupId); countQuery.setParameter("groupId", groupId); long count = (Long) countQuery.getSingleResult(); List<GroupResourceConfigurationUpdate> results = query.getResultList(); return new PageList<GroupResourceConfigurationUpdate>(results, (int) count, pc); } @Override @SuppressWarnings("unchecked") public ConfigurationUpdateStatus updateGroupResourceConfigurationUpdateStatus( int groupResourceConfigurationUpdateId, String errorMessages) { GroupResourceConfigurationUpdate groupResourceConfigUpdate = configurationManager .getGroupResourceConfigurationById(groupResourceConfigurationUpdateId); // NOTE: None of the individual updates should still be INPROGRESS at the time this method is called! Query query = entityManager.createNamedQuery(ResourceConfigurationUpdate.QUERY_FIND_STATUS_BY_PARENT_UPDATE_ID); query.setParameter("groupConfigurationUpdateId", groupResourceConfigUpdate.getId()); List<ConfigurationUpdateStatus> updateStatusTuples = query.getResultList(); return completeGroupConfigurationUpdate(groupResourceConfigUpdate, updateStatusTuples, errorMessages); } @Override @SuppressWarnings("unchecked") public ConfigurationUpdateStatus updateGroupPluginConfigurationUpdateStatus(int groupPluginConfigurationUpdateId, String errorMessages) { GroupPluginConfigurationUpdate groupPluginConfigUpdate = configurationManager .getGroupPluginConfigurationById(groupPluginConfigurationUpdateId); // NOTE: None of the individual updates should still be INPROGRESS at the time this method is called! Query query = entityManager.createNamedQuery(PluginConfigurationUpdate.QUERY_FIND_STATUS_BY_PARENT_UPDATE_ID); query.setParameter("groupConfigurationUpdateId", groupPluginConfigUpdate.getId()); List<ConfigurationUpdateStatus> updateStatusTuples = query.getResultList(); return completeGroupConfigurationUpdate(groupPluginConfigUpdate, updateStatusTuples, errorMessages); } private ConfigurationUpdateStatus completeGroupConfigurationUpdate( AbstractGroupConfigurationUpdate groupConfigUpdate, List<ConfigurationUpdateStatus> memberConfigUpdateStatusTuples, String errorMessages) { ConfigurationUpdateStatus groupConfigUpdateStatus; if (memberConfigUpdateStatusTuples.contains(ConfigurationUpdateStatus.FAILURE) || errorMessages != null) { groupConfigUpdateStatus = ConfigurationUpdateStatus.FAILURE; } else { groupConfigUpdateStatus = ConfigurationUpdateStatus.SUCCESS; } groupConfigUpdate.setStatus(groupConfigUpdateStatus); groupConfigUpdate.setErrorMessage(errorMessages); configurationManager.updateGroupConfigurationUpdate(groupConfigUpdate); return groupConfigUpdateStatus; // if the caller wants to know what the new status was } @Override public int deleteGroupPluginConfigurationUpdates(Subject subject, Integer resourceGroupId, Integer[] groupPluginConfigurationUpdateIds) { // TODO: use subject and resourceGroupId to perform security check if (authorizationManager.hasGroupPermission(subject, Permission.MODIFY_RESOURCE, resourceGroupId) == false) { LOG.error(subject + " attempted to delete " + groupPluginConfigurationUpdateIds.length + " group resource configuration updates for ResourceGroup[id" + resourceGroupId + "], but did not have the " + Permission.MODIFY_RESOURCE.name() + " permission for this group"); return 0; } int removed = 0; for (Integer apcuId : groupPluginConfigurationUpdateIds) { /* * use this strategy instead of GroupPluginConfigurationUpdate.QUERY_DELETE_BY_ID because removing via * the entityManager will respect cascading rules, using a JPQL DELETE statement will not */ try { // break the plugin configuration update links in order to preserve individual change history Query q = entityManager.createNamedQuery(PluginConfigurationUpdate.QUERY_DELETE_GROUP_UPDATE); q.setParameter("apcuId", apcuId); q.executeUpdate(); GroupPluginConfigurationUpdate update = getGroupPluginConfigurationById(apcuId); entityManager.remove(update); removed++; } catch (Exception e) { LOG.error("Problem removing group plugin configuration update", e); } } return removed; } @Override public int deleteGroupResourceConfigurationUpdates(Subject subject, Integer resourceGroupId, Integer[] groupResourceConfigurationUpdateIds) { if (authorizationManager.hasGroupPermission(subject, Permission.MODIFY_RESOURCE, resourceGroupId) == false) { LOG.error(subject + " attempted to delete " + groupResourceConfigurationUpdateIds.length + " group resource configuration updates for ResourceGroup[id" + resourceGroupId + "], but did not have the " + Permission.MODIFY_RESOURCE.name() + " permission for this group"); return 0; } int removed = 0; for (Integer arcuId : groupResourceConfigurationUpdateIds) { /* * use this strategy instead of GroupResourceConfigurationUpdate.QUERY_DELETE_BY_ID because removing via * the entityManager will respect cascading rules, using a JPQL DELETE statement will not */ try { // break the resource configuration update links in order to preserve individual change history Query q = entityManager.createNamedQuery(ResourceConfigurationUpdate.QUERY_DELETE_GROUP_UPDATE); q.setParameter("arcuId", arcuId); q.executeUpdate(); GroupResourceConfigurationUpdate update = getGroupResourceConfigurationById(arcuId); entityManager.remove(update); removed++; } catch (Exception e) { LOG.error("Problem removing group resource configuration update", e); } } return removed; } @Override public void updateGroupConfigurationUpdate(AbstractGroupConfigurationUpdate groupUpdate) { // TODO jmarques: if (errorMessages != null) set any remaining INPROGRESS children to FAILURE entityManager.merge(groupUpdate); } @Override public void deleteConfigurations(List<Integer> configurationIds) { if (configurationIds == null || configurationIds.size() == 0) { return; } boolean supportsCascade = DatabaseTypeFactory.getDefaultDatabaseType().supportsSelfReferringCascade(); if (supportsCascade == false) { Query breakPropertyRecursionQuery = entityManager .createNamedQuery(Configuration.QUERY_BREAK_PROPERTY_RECURSION_BY_CONFIGURATION_IDS); breakPropertyRecursionQuery.setParameter("configurationIds", configurationIds); breakPropertyRecursionQuery.executeUpdate(); } Query rawConfigurationsQuery = entityManager .createNamedQuery(Configuration.QUERY_DELETE_RAW_CONFIGURATIONS_CONFIGURATION_IDS); Query configurationsQuery = entityManager .createNamedQuery(Configuration.QUERY_DELETE_CONFIGURATIONS_BY_CONFIGURATION_IDs); rawConfigurationsQuery.setParameter("configurationIds", configurationIds); configurationsQuery.setParameter("configurationIds", configurationIds); rawConfigurationsQuery.executeUpdate(); configurationsQuery.executeUpdate(); // uses DB-level cascades to delete properties } @Override public void deleteProperties(int[] propertyIds) { if (propertyIds == null || propertyIds.length == 0) { return; } Query propertiesQuery = entityManager.createNamedQuery(Property.QUERY_DELETE_BY_PROPERTY_IDS); propertiesQuery.setParameter("propertyIds", ArrayUtils.wrapInList(propertyIds)); propertiesQuery.executeUpdate(); } @Override public GroupPluginConfigurationUpdate getGroupPluginConfigurationUpdate(Subject subject, int configurationUpdateId) { GroupPluginConfigurationUpdate update = getGroupPluginConfigurationById(configurationUpdateId); int groupId = update.getGroup().getId(); if (!authorizationManager.canViewGroup(subject, groupId)) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view group Resource configuration for [" + update.getGroup() + "]"); } return update; } @Override public GroupResourceConfigurationUpdate getGroupResourceConfigurationUpdate(Subject subject, int configurationUpdateId) { GroupResourceConfigurationUpdate update = getGroupResourceConfigurationById(configurationUpdateId); int groupId = update.getGroup().getId(); if (authorizationManager.hasGroupPermission(subject, Permission.CONFIGURE_READ, groupId) == false) { throw new PermissionException("User[" + subject.getName() + "] does not have permission to view group resourceConfiguration[id=" + configurationUpdateId + "]"); } return update; } @Override public Configuration translateResourceConfiguration(Subject subject, int resourceId, Configuration configuration, boolean fromStructured) { if (!isStructuredAndRawSupported(resourceId)) { throw new TranslationNotSupportedException("The translation operation is only supported for " + "configurations that support both structured and raw."); } Resource resource = entityManager.find(Resource.class, resourceId); if (resource == null) { throw new NoResultException("Cannot get live configuration for unknown resource [" + resourceId + "]"); } if (!authorizationManager.hasResourcePermission(subject, Permission.CONFIGURE_READ, resource.getId())) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view resource configuration for [" + resource + "]"); } try { Agent agent = resource.getAgent(); AgentClient agentClient = this.agentManager.getAgentClient(agent); ConfigurationAgentService configService = agentClient.getConfigurationAgentService(); return configService.merge(configuration, resourceId, fromStructured); } catch (PluginContainerException e) { LOG.error("An error occurred while trying to translate the configuration.", e); return null; } } @Override public Configuration mergeConfiguration(Configuration config) { Configuration out = entityManager.merge(config); return out; } @Override public PageList<ResourceConfigurationUpdate> findResourceConfigurationUpdatesByCriteria(Subject subject, ResourceConfigurationUpdateCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); if (!authorizationManager.isInventoryManager(subject)) { generator.setAuthorizationResourceFragment(CriteriaQueryGenerator.AuthorizationTokenType.RESOURCE, "resource", subject.getId()); } CriteriaQueryRunner<ResourceConfigurationUpdate> queryRunner = new CriteriaQueryRunner<ResourceConfigurationUpdate>( criteria, generator, entityManager); PageList<ResourceConfigurationUpdate> updates = queryRunner.execute(); // If configurations were fetched, mask them before returning the updates. // We do not want the masked configurations persisted, so detach all entities before masking the configurations. Set<String> fetchFields = new HashSet<String>(generator.getFetchFields(criteria)); if (fetchFields.contains(AbstractConfigurationUpdateCriteria.FETCH_FIELD_CONFIGURATION)) { for (ResourceConfigurationUpdate update : updates) { Configuration configuration = update.getConfiguration(); configuration.getMap().size(); } entityManager.clear(); for (ResourceConfigurationUpdate update : updates) { Configuration configuration = update.getConfiguration(); ConfigurationDefinition configurationDefinition = getResourceConfigurationDefinitionForResourceType( subjectManager.getOverlord(), update.getResource().getResourceType().getId()); ConfigurationMaskingUtility.maskConfiguration(configuration, configurationDefinition); } } return updates; } @Override public PageList<PluginConfigurationUpdate> findPluginConfigurationUpdatesByCriteria(Subject subject, PluginConfigurationUpdateCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); if (!authorizationManager.isInventoryManager(subject)) { generator.setAuthorizationResourceFragment(CriteriaQueryGenerator.AuthorizationTokenType.RESOURCE, "resource", subject.getId()); } CriteriaQueryRunner<PluginConfigurationUpdate> queryRunner = new CriteriaQueryRunner<PluginConfigurationUpdate>( criteria, generator, entityManager); PageList<PluginConfigurationUpdate> updates = queryRunner.execute(); // If configurations were fetched, mask them before returning the updates. // We do not want the masked configurations persisted, so detach all entities before masking the configurations. Set<String> fetchFields = new HashSet<String>(generator.getFetchFields(criteria)); if (fetchFields.contains(AbstractConfigurationUpdateCriteria.FETCH_FIELD_CONFIGURATION)) { for (PluginConfigurationUpdate update : updates) { Configuration configuration = update.getConfiguration(); configuration.getMap().size(); } entityManager.clear(); for (PluginConfigurationUpdate update : updates) { Configuration configuration = update.getConfiguration(); ConfigurationDefinition configurationDefinition = getPluginConfigurationDefinitionForResourceType( subjectManager.getOverlord(), update.getResource().getResourceType().getId()); ConfigurationMaskingUtility.maskConfiguration(configuration, configurationDefinition); } } return updates; } @Override public PageList<GroupResourceConfigurationUpdate> findGroupResourceConfigurationUpdatesByCriteria(Subject subject, GroupResourceConfigurationUpdateCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); if (!authorizationManager.isInventoryManager(subject)) { generator.setAuthorizationResourceFragment(CriteriaQueryGenerator.AuthorizationTokenType.GROUP, "group", subject.getId()); } CriteriaQueryRunner<GroupResourceConfigurationUpdate> queryRunner = new CriteriaQueryRunner<GroupResourceConfigurationUpdate>( criteria, generator, entityManager); PageList<GroupResourceConfigurationUpdate> updates = queryRunner.execute(); return updates; } @Override public PageList<GroupPluginConfigurationUpdate> findGroupPluginConfigurationUpdatesByCriteria(Subject subject, GroupPluginConfigurationUpdateCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); if (!authorizationManager.isInventoryManager(subject)) { generator.setAuthorizationResourceFragment(CriteriaQueryGenerator.AuthorizationTokenType.GROUP, "group", subject.getId()); } CriteriaQueryRunner<GroupPluginConfigurationUpdate> queryRunner = new CriteriaQueryRunner<GroupPluginConfigurationUpdate>( criteria, generator, entityManager); PageList<GroupPluginConfigurationUpdate> updates = queryRunner.execute(); return updates; } @Override public ConfigurationDefinition getOptionsForConfigurationDefinition(Subject subject, int resourceId, int parentResourceId, ConfigurationDefinition def) { Resource resource = null, baseResource = null, parentResource = null; if (resourceId >= 0) { resource = resourceManager.getResource(subject, resourceId); } if (parentResourceId >= 0) { parentResource = resourceManager.getResource(subject, parentResourceId); baseResource = ResourceUtility.getBaseServerOrService(parentResource); } else if (resource != null) { baseResource = ResourceUtility.getBaseServerOrService(resource); } for (Map.Entry<String, PropertyDefinition> entry : def.getPropertyDefinitions().entrySet()) { PropertyDefinition pd = entry.getValue(); if (pd instanceof PropertyDefinitionSimple) { PropertyDefinitionSimple pds = (PropertyDefinitionSimple) pd; handlePDS(subject, resource, parentResource, baseResource, pds); } else if (pd instanceof PropertyDefinitionList) { PropertyDefinitionList pdl = (PropertyDefinitionList) pd; PropertyDefinition memberDef = pdl.getMemberDefinition(); if (memberDef instanceof PropertyDefinitionSimple) { PropertyDefinitionSimple pds = (PropertyDefinitionSimple) memberDef; handlePDS(subject, resource, parentResource, baseResource, pds); } else if (memberDef instanceof PropertyDefinitionMap) { PropertyDefinitionMap pdm = (PropertyDefinitionMap) memberDef; for (PropertyDefinition inner : pdm.getOrderedPropertyDefinitions()) { if (inner instanceof PropertyDefinitionSimple) { handlePDS(subject, resource, parentResource, baseResource, (PropertyDefinitionSimple) inner); } if (LOG.isDebugEnabled()) { LOG.debug("3 ____[ " + inner.toString() + " in " + pdl.toString() + " ]____ not yet supported"); } } } else { if (LOG.isDebugEnabled()) { LOG.debug("2 ____[ " + memberDef.toString() + " in " + pdl.toString() + " ]____ not yet supported"); } } } else if (pd instanceof PropertyDefinitionMap) { PropertyDefinitionMap pdm = (PropertyDefinitionMap) pd; for (PropertyDefinition inner : pdm.getOrderedPropertyDefinitions()) { if (inner instanceof PropertyDefinitionSimple) { handlePDS(subject, resource, parentResource, baseResource, (PropertyDefinitionSimple) inner); } else { if (LOG.isDebugEnabled()) { LOG.debug("4 ____[ " + inner.toString() + " in " + pdm.toString() + " ]____ not yet supported"); } } } } else { if (LOG.isDebugEnabled()) { LOG.debug("1 ____[ " + pd.toString() + " ]____ not yet supported"); } } } return def; // TODO clone the incoming definition? } /** * Determine the dynamic enumeration values for one PropertyDefinitionSimple * @param subject Subject of the caller - may limit search results * @param pds the PropertyDefinitionSimple to work on */ private void handlePDS(final Subject subject, Resource resource, Resource parentResource, Resource baseResource, PropertyDefinitionSimple pds) { if (pds.getOptionsSource() != null) { // evaluate the source parameters PropertyOptionsSource pos = pds.getOptionsSource(); PropertyOptionsSource.TargetType tt = pos.getTargetType(); String expression = pos.getExpression(); PropertyOptionsSource.ExpressionScope expressionScope = pos.getExpressionScope(); String filter = pos.getFilter(); Pattern filterPattern = null; if (filter != null) filterPattern = Pattern.compile(filter); Predicate<Resource> baseResourcePredicate = null; if (expressionScope == PropertyOptionsSource.ExpressionScope.BASE_RESOURCE) { String[] expressionTokens = expression.split("\\s+"); String ancestorType = null; final Pattern ancestorTypePattern = Pattern.compile("(.*)BaseResource\\.plugin"); for (String token : expressionTokens) { Matcher ancestorTypeMatcher = ancestorTypePattern.matcher(token); if (ancestorTypeMatcher.lookingAt()) { ancestorType = ancestorTypeMatcher.group(1); break; } } if (ancestorType != null) { if (resource == null && parentResource == null) { LOG.warn("Different base resource type requested but resource id and parent_resource_id are not valid." + "Option source expression:" + expression); return; } Resource nonNullResource = (resource == null? parentResource : resource); Resource foundBaseResource = ResourceUtility.getAncestorResourceOfType( nonNullResource, ancestorType); if (foundBaseResource != null) { baseResourcePredicate = new isAncestorResourcePredicate(foundBaseResource); } else { LOG.warn("Couldn't find base resource of type [" + ancestorType + "] for resource [" + nonNullResource.getName() + "]"); return; } } if (baseResourcePredicate == null && baseResource != null) { baseResourcePredicate = new IsInBaseResourcePredicate(baseResource); } } if (tt == PropertyOptionsSource.TargetType.RESOURCE || tt == PropertyOptionsSource.TargetType.CONFIGURATION) { ResourceCriteria criteria = new ResourceCriteria(); //Use CriteriaQuery to automatically chunk/page through criteria query results CriteriaQueryExecutor<Resource, ResourceCriteria> queryExecutor = new CriteriaQueryExecutor<Resource, ResourceCriteria>() { @Override public PageList<Resource> execute(ResourceCriteria criteria) { return resourceManager.findResourcesByCriteria(subject, criteria); } }; Iterable<Resource> foundResources = null; if (tt == PropertyOptionsSource.TargetType.CONFIGURATION) { // split out expression part for target=configuration // return if no property specifier is given String expr = expression; if (expr.contains(":")) { expr = expr.substring(expr.indexOf(':') + 1); if (!"self".equals(expr)) { foundResources = processSearchExpression( expression, criteria, queryExecutor, expressionScope, baseResourcePredicate); } else if (resource != null) { ArrayList<Resource> resourceList = new ArrayList<Resource>(); resourceList.add(resource); foundResources = resourceList; } else { LOG.warn("Self reference requested but resource id is not valid." + "Option source expression:" + expression); return; } } else { LOG.warn("Option source expression for property " + pds.getName() + " and target configuration contains no ':'"); return; } } else { foundResources = processSearchExpression( expression, criteria, queryExecutor, expressionScope, baseResourcePredicate); } for (Resource foundResource : foundResources) { processPropertyOptionsSource(resource, baseResource, pds, tt, expression, filterPattern, foundResource); } } else if (tt == PropertyOptionsSource.TargetType.GROUP) { // spinder 2-15-13: commenting out this code below as we don't appear to be using any of it. Half done. // // for groups we need to talk to the group manager // ResourceGroupCriteria criteria = new ResourceGroupCriteria(); // criteria.setSearchExpression(expression); // // resourceGroupManager.findResourceGroupCompositesByCriteria(subject, criteria); } // TODO plugin and resourceType } } private Iterable<Resource> processSearchExpression(String expression, ResourceCriteria criteria, CriteriaQueryExecutor<Resource, ResourceCriteria> queryExecutor, PropertyOptionsSource.ExpressionScope expressionScope, Predicate<Resource> baseResourcePredicate) { criteria.setSearchExpression(expression); Iterable<Resource> foundResources = new CriteriaQuery<Resource, ResourceCriteria>(criteria, queryExecutor); if (expressionScope == PropertyOptionsSource.ExpressionScope.BASE_RESOURCE && baseResourcePredicate != null) { foundResources = Iterables.filter(foundResources, baseResourcePredicate); } return foundResources; } private void processPropertyOptionsSource(Resource resource, Resource baseResource, PropertyDefinitionSimple pds, PropertyOptionsSource.TargetType tt, String expression, Pattern filterPattern, Resource foundResource) { if (tt == PropertyOptionsSource.TargetType.RESOURCE) { String name = foundResource.getName(); // filter if the user provided a filter if (filterPattern != null) { Matcher m = filterPattern.matcher(name); if (m.matches()) { PropertyDefinitionEnumeration pde = new PropertyDefinitionEnumeration(name, "" + name); pds.getEnumeratedValues().add(pde); } } else { // Filter is null -> none provided -> do not filter PropertyDefinitionEnumeration pde = new PropertyDefinitionEnumeration(name, "" + name); pds.getEnumeratedValues().add(pde); } } else if (tt == PropertyOptionsSource.TargetType.CONFIGURATION) { // for configuration we need to drill down into the resource configuration if (!handleConfigurationTarget(resource, baseResource, pds, expression, foundResource)) return; } } /** * Drill down in the case the user set up a target of "configuration". We need to check * that the target property actually exists and that it has a format we understand * * @param resource the resource we are looking options for * @param baseResource the base resource of <code>resource</code> * @param pds Property definition to examine * @param expression The whole expression starting with identifier: for the configuration * identifier. This looks like <i>listname</i> for list of * property simple or <i>mapname=mapkey</i> for a map with simple properties * @param foundResource the resource to look at * @return false if the property can not be resolved, true otherwise */ private boolean handleConfigurationTarget(Resource resource, Resource baseResource, PropertyDefinitionSimple pds, String expression, Resource foundResource) { Configuration configuration = foundResource.getResourceConfiguration(); if (expression.indexOf(":") != -1) { expression = expression.substring(0, expression.indexOf(":")); } boolean isMapOrList = expression.contains("="); Property p; if (isMapOrList) { String mapPropLocation = expression.substring(0, expression.indexOf("=")); if (mapPropLocation.contains("/")) { mapPropLocation = mapPropLocation.substring(0, mapPropLocation.indexOf('/')); } p = configuration.get(mapPropLocation); } else { p = configuration.get(expression); } if (p == null) { if (LOG.isDebugEnabled()) { LOG.debug(resource + " in " + baseResource + ": option source expression for property " + pds.getName() + " and target configuration of " + foundResource + " not found"); } return false; } if (!(p instanceof PropertyList)) { LOG.warn(resource + " in " + baseResource + ": option source expression for property " + pds.getName() + " and target configuration does not point to a list"); return false; } PropertyList pl = (PropertyList) p; List<Property> propertyList = pl.getList(); if (propertyList.size() == 0) return false; // Now List of simple or list of maps (of simple) ? if (propertyList.get(0) instanceof PropertySimple) { if (isMapOrList) { LOG.warn(resource + " in " + baseResource + ": expected a List of Maps, but got a list of simple"); return false; } for (Property tmp : propertyList) { PropertySimple ps = (PropertySimple) tmp; String name = ps.getStringValue(); if (name != null) { PropertyDefinitionEnumeration pde = new PropertyDefinitionEnumeration(name, name); pds.getEnumeratedValues().add(pde); } } } else if (propertyList.get(0) instanceof PropertyMap) { if (!isMapOrList) { LOG.warn(resource + " in " + baseResource + ": expected a List of simple, but got a list of Maps"); return false; } String subPropName; subPropName = expression.substring(expression.indexOf("=") + 1); for (Property tmp : propertyList) { PropertyMap pm = (PropertyMap) tmp; Property ps = pm.get(subPropName); if (ps == null) { LOG.warn(resource + " in " + baseResource + ": option source expression for property " + pds.getName() + " and target configuration does not have a map element " + subPropName); return false; } if (!(ps instanceof PropertySimple)) { LOG.warn(resource + " in " + baseResource + ": ListOfMapOf!Simple are not supported"); return false; } PropertySimple propertySimple = (PropertySimple) ps; String name = propertySimple.getStringValue(); if (name != null) { PropertyDefinitionEnumeration pde = new PropertyDefinitionEnumeration(name, name); pds.getEnumeratedValues().add(pde); } } } return true; } private static final class IsInBaseResourcePredicate implements Predicate<Resource> { private Resource baseResource; private IsInBaseResourcePredicate(Resource baseResource) { this.baseResource = baseResource; } @Override public boolean apply(Resource resource) { Resource baseServerOrService = ResourceUtility.getBaseServerOrService(resource); return baseResource.equals(baseServerOrService); } } private static final class isAncestorResourcePredicate implements Predicate<Resource> { private Resource ancestor; private isAncestorResourcePredicate(Resource ancestor) { this.ancestor = ancestor; } @Override public boolean apply(Resource resource) { return ResourceUtility.isAncestor(resource, this.ancestor); } } }