/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004-2009], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program is distributed * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.hq.control.server.session; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.Future; import javax.annotation.PostConstruct; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hyperic.hq.agent.AgentConnectionException; import org.hyperic.hq.agent.AgentRemoteException; import org.hyperic.hq.appdef.ConfigResponseDB; import org.hyperic.hq.appdef.shared.AgentNotFoundException; import org.hyperic.hq.appdef.shared.AppdefEntityConstants; import org.hyperic.hq.appdef.shared.AppdefEntityID; import org.hyperic.hq.appdef.shared.AppdefEntityNotFoundException; import org.hyperic.hq.appdef.shared.AppdefEntityTypeID; import org.hyperic.hq.appdef.shared.AppdefUtil; import org.hyperic.hq.appdef.shared.ConfigFetchException; import org.hyperic.hq.appdef.shared.ConfigManager; import org.hyperic.hq.appdef.shared.InvalidAppdefTypeException; import org.hyperic.hq.appdef.shared.PlatformManager; import org.hyperic.hq.authz.server.session.AuthzSubject; import org.hyperic.hq.authz.server.session.Resource; import org.hyperic.hq.authz.server.session.ResourceTypeDAO; import org.hyperic.hq.authz.shared.AuthzConstants; import org.hyperic.hq.authz.shared.AuthzSubjectManager; import org.hyperic.hq.authz.shared.PermissionException; import org.hyperic.hq.authz.shared.PermissionManager; import org.hyperic.hq.authz.shared.PermissionManagerFactory; import org.hyperic.hq.authz.shared.ResourceValue; import org.hyperic.hq.common.ApplicationException; import org.hyperic.hq.common.SystemException; import org.hyperic.hq.common.util.MessagePublisher; import org.hyperic.hq.control.ControlActionResult; import org.hyperic.hq.control.ControlEvent; import org.hyperic.hq.control.GroupControlActionResult; import org.hyperic.hq.control.agent.client.ControlCommandsClient; import org.hyperic.hq.control.agent.client.ControlCommandsClientFactory; import org.hyperic.hq.control.shared.ControlConstants; import org.hyperic.hq.control.shared.ControlManager; import org.hyperic.hq.control.shared.ControlScheduleManager; import org.hyperic.hq.control.shared.ScheduledJobRemoveException; import org.hyperic.hq.events.EventConstants; import org.hyperic.hq.grouping.server.session.GroupUtil; import org.hyperic.hq.grouping.shared.GroupNotCompatibleException; import org.hyperic.hq.product.ControlPluginManager; import org.hyperic.hq.product.PluginException; import org.hyperic.hq.product.PluginNotFoundException; import org.hyperic.hq.product.ProductPlugin; import org.hyperic.hq.product.shared.ProductManager; import org.hyperic.hq.scheduler.ScheduleValue; import org.hyperic.util.config.ConfigResponse; import org.hyperic.util.config.EncodingException; import org.hyperic.util.pager.PageControl; import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; /** * The server-side control system. */ @Service @Transactional public class ControlManagerImpl implements ControlManager { private final Log log = LogFactory.getLog(ControlManagerImpl.class.getName()); private ProductManager productManager; private ControlScheduleManager controlScheduleManager; private ControlHistoryDAO controlHistoryDao; private ResourceTypeDAO resourceTypeDao; private ConfigManager configManager; private PlatformManager platformManager; private AuthzSubjectManager authzSubjectManager; private PermissionManager permissionManager; private ControlPluginManager controlPluginManager; private MessagePublisher messagePublisher; private ControlCommandsClientFactory controlCommandsClientFactory; private ControlActionResultsCollector controlActionResultsCollector; private AsyncTaskExecutor executor; private ControlActionExecutor controlActionExecutor; private GroupControlActionExecutor groupControlActionExecutor; // Default timeout is 10 minutes. If a plugin does not define its // own timeout, this is the value that will be used. static final int DEFAULT_RESOURCE_TIMEOUT = 10 * 60 * 1000; @Autowired public ControlManagerImpl(ProductManager productManager, ControlScheduleManager controlScheduleManager, ControlHistoryDAO controlHistoryDao, ResourceTypeDAO resourceTypeDao, ConfigManager configManager, PlatformManager platformManager, AuthzSubjectManager authzSubjectManager, PermissionManager permissionManager, MessagePublisher messagePublisher, ControlCommandsClientFactory controlCommandsClientFactory, ControlActionResultsCollector controlActionResultsCollector, @Value("#{controlExecutor}")AsyncTaskExecutor executor, ControlActionExecutor controlActionExecutor, GroupControlActionExecutor groupControlActionExecutor) { this.productManager = productManager; this.controlScheduleManager = controlScheduleManager; this.controlHistoryDao = controlHistoryDao; this.resourceTypeDao = resourceTypeDao; this.configManager = configManager; this.platformManager = platformManager; this.authzSubjectManager = authzSubjectManager; this.permissionManager = permissionManager; this.messagePublisher = messagePublisher; this.controlCommandsClientFactory = controlCommandsClientFactory; this.controlActionResultsCollector = controlActionResultsCollector; this.executor = executor; this.controlActionExecutor = controlActionExecutor; this.groupControlActionExecutor = groupControlActionExecutor; } @PostConstruct public void createControlPluginManager() { // Get reference to the control plugin manager try { controlPluginManager = (ControlPluginManager) productManager.getPluginManager(ProductPlugin.TYPE_CONTROL); } catch (Exception e) { this.log.error("Unable to get plugin manager", e); } } /** * Enable an entity for control **/ public void configureControlPlugin(AuthzSubject subject, AppdefEntityID id) throws PermissionException, PluginException, ConfigFetchException, AppdefEntityNotFoundException, AgentNotFoundException { // authz check checkModifyPermission(subject, id); String pluginName, pluginType; ConfigResponse mergedResponse; pluginName = id.toString(); try { pluginType = platformManager.getPlatformPluginName(id); mergedResponse = configManager.getMergedConfigResponse(subject, ProductPlugin.TYPE_CONTROL, id, true); ControlCommandsClient client = controlCommandsClientFactory.getClient(id); client.controlPluginAdd(pluginName, pluginType, mergedResponse); } catch (EncodingException e) { throw new PluginException("Unable to decode config", e); } catch (AgentConnectionException e) { throw new PluginException("Agent error: " + e.getMessage(), e); } catch (AgentRemoteException e) { throw new PluginException("Agent error: " + e.getMessage(), e); } } public void doAction(AuthzSubject subject, AppdefEntityID id, String action, String args) throws PluginException, PermissionException { doSingleAction(subject, id, action, args); } /** * Execute a single control action on a given entity. */ private Integer doSingleAction(AuthzSubject subject, AppdefEntityID id, String action, String args) throws PluginException, PermissionException { // This method doesn't support groups. if (id.isGroup()) { throw new IllegalArgumentException("Cannot perform single " + "action on a group."); } checkControlEnabled(subject, id); checkControlPermission(subject, id); if (log.isDebugEnabled()) { log.debug("Running control action for immediate execution: " + " {resource=" + id + ", action=" + action + "}"); } return controlActionExecutor.executeControlAction(id, subject.getName(), new Date(), Boolean.FALSE, "", action, args); } /** * Execute a single control action on a given entity. */ public void doAction(AuthzSubject subject, AppdefEntityID id, String action) throws PluginException, PermissionException { String args = null; doAction(subject, id, action, args); } public Future<ControlActionResult> doAction(AuthzSubject subject, AppdefEntityID id, String action, int waitTimeout) throws PluginException, PermissionException { return doAction(subject, id, action, null,waitTimeout); } public Future<ControlActionResult> doAction(final AuthzSubject subject, final AppdefEntityID id, String action, String args, final int defaultTimeout) throws PluginException, PermissionException { final Integer jobId = doSingleAction(subject,id,action, args); Future<ControlActionResult> result = executor.submit(new Callable<ControlActionResult>() { public ControlActionResult call() throws Exception { return controlActionResultsCollector.waitForResult(jobId, controlActionResultsCollector.getTimeout(subject, id, defaultTimeout)); } }); return result; } /** * Schedule a new control action. */ public void scheduleAction(AuthzSubject subject, AppdefEntityID id, String action, ScheduleValue schedule) throws PluginException, PermissionException, SchedulerException { // This method doesn't support groups. if (id.isGroup()) { throw new IllegalArgumentException("Cannot perform single " + "action on a group."); } checkControlEnabled(subject, id); checkControlPermission(subject, id); controlScheduleManager.scheduleAction(id, subject, action, schedule, null); } /** * Single control action for a group of given entities. */ public Future<GroupControlActionResult> doGroupAction(final AuthzSubject subject, final AppdefEntityID id, final String action, final String args, final int[] order, final int defaultResourceTimeout) throws PluginException, PermissionException, AppdefEntityNotFoundException, GroupNotCompatibleException { List<AppdefEntityID> groupMembers = GroupUtil.getCompatGroupMembers(subject, id, order, PageControl.PAGE_ALL); // For each entity in the list, sanity check config and permissions for (AppdefEntityID entity : groupMembers) { checkControlEnabled(subject, entity); checkControlPermission(subject, entity); } final String subjectName = subject.getName(); return executor.submit(new Callable<GroupControlActionResult>() { public GroupControlActionResult call() throws Exception { return groupControlActionExecutor.executeGroupControlAction(id, subjectName, new Date(), false, "", action, args, order, defaultResourceTimeout); } }); } public void doGroupAction(AuthzSubject subject, AppdefEntityID id, String action, String args, int[] order) throws PluginException, PermissionException, AppdefEntityNotFoundException, GroupNotCompatibleException { doGroupAction(subject, id, action, args, order, DEFAULT_RESOURCE_TIMEOUT); } /** * Schedule a single control action for a group of given entities. * @throws SchedulerException */ public void scheduleGroupAction(AuthzSubject subject, AppdefEntityID id, String action, int[] order, ScheduleValue schedule) throws PluginException, PermissionException, SchedulerException, GroupNotCompatibleException, AppdefEntityNotFoundException { List<AppdefEntityID> groupMembers = GroupUtil.getCompatGroupMembers(subject, id, order, PageControl.PAGE_ALL); // For each entity in the list, sanity check config and permissions for (AppdefEntityID entity : groupMembers) { checkControlEnabled(subject, entity); checkControlPermission(subject, entity); } controlScheduleManager.scheduleAction(id, subject, action, schedule, order); } /** * Get the supported actions for an appdef entity from the local * ControlPluginManager */ @Transactional(readOnly=true) public List<String> getActions(AuthzSubject subject, AppdefEntityID id) throws PermissionException, PluginNotFoundException, AppdefEntityNotFoundException, GroupNotCompatibleException { if (id.isGroup()) { List<AppdefEntityID> groupMembers = GroupUtil .getCompatGroupMembers(subject, id, null, PageControl.PAGE_ALL); // For each entity in the list, sanity check permissions for (AppdefEntityID entity : groupMembers) { checkControlPermission(subject, entity); } } else { checkControlPermission(subject, id); } String pluginName = platformManager.getPlatformPluginName(id); return controlPluginManager.getActions(pluginName); } /** * Get the supported actions for an appdef entity from the local * ControlPluginManager */ @Transactional(readOnly=true) public List<String> getActions(AuthzSubject subject, AppdefEntityTypeID aetid) throws PluginNotFoundException { String pluginName = aetid.getAppdefResourceType().getName(); return controlPluginManager.getActions(pluginName); } /** * Check if a compatible group's members have been enabled for control. A * group is enabled for control if and only if all of its members have been * enabled for control. * @return flag - true if group is enabled */ @Transactional(readOnly=true) public boolean isGroupControlEnabled(AuthzSubject subject, AppdefEntityID id) throws AppdefEntityNotFoundException, PermissionException { if (!id.isGroup()) { throw new IllegalArgumentException("Expecting entity of type " + "group."); } List<AppdefEntityID> members; try { members = GroupUtil.getCompatGroupMembers(subject, id, null); } catch (GroupNotCompatibleException ex) { // only compatible groups are controllable return false; } if (members.isEmpty()) { return false; } for (AppdefEntityID member : members) { try { checkControlEnabled(subject, member); return true; } catch (PluginException e) { // continue } } return false; } /** * Checks with the plugin manager to find out if an entity's resource * provides support for control. * @param resType - appdef entity (of all kinds inc. groups) * @return flag - true if supported */ @Transactional(readOnly=true) public boolean isControlSupported(AuthzSubject subject, String resType) { try { controlPluginManager.getPlugin(resType); return true; } catch (PluginNotFoundException e) { return false; } } /** * Checks with the plugin manager to find out if an entity's resource * provides support for control. * @param resType - appdef entity (of all kinds inc. groups) * @return flag - true if supported */ @Transactional(readOnly=true) public boolean isControlSupported(AuthzSubject subject, AppdefEntityID id, String resType) { try { if (id.isGroup()) { List<AppdefEntityID> members = GroupUtil.getCompatGroupMembers(subject, id, null); if (members.isEmpty()) { return false; } AppdefEntityID groupMemberID = (AppdefEntityID) members.get(0); checkControlPermission(subject, groupMemberID); // HHQ-5788 - we dont display control tab for compatible groups of type "platform" if (groupMemberID.getType() == AppdefEntityConstants.APPDEF_TYPE_PLATFORM) { return false; } // Check with the plugin manager whether resource provides support for control controlPluginManager.getPlugin(resType); } else { checkControlPermission(subject, id); // Check with the plugin manager whether resource provides support for control controlPluginManager.getPlugin(resType); // Check if an entity has been enabled for control // f.e some of the platforms have a "reset vm" control action and some do not checkControlEnabled(subject, id) ; } return true; } catch (PluginNotFoundException e) { // return false } catch (PermissionException e) { // return false } catch (AppdefEntityNotFoundException e) { // return false } catch (GroupNotCompatibleException e) { // return false }catch(PluginException pe) { // return false } return false; } /** * Check if a an entity has been enabled for control. * @return flag - true if enabled */ @Transactional(readOnly=true) public boolean isControlEnabled(AuthzSubject subject, AppdefEntityID id) { try { checkControlEnabled(subject, id); return true; } catch (PluginException e) { return false; } } /** * Check if an entity has been enabled for control */ @Transactional(readOnly=true) public void checkControlEnabled(AuthzSubject subject, AppdefEntityID id) throws PluginException { ConfigResponseDB config; try { config = configManager.getConfigResponse(id); } catch (IllegalArgumentException iae) { throw new PluginException(iae); } catch (Exception e) { throw new PluginException(e); } if (config == null || config.getControlResponse() == null) { throw new PluginException("Control not " + "configured for " + id); } } /** * Send an agent a plugin configuration. This is needed when agents restart, * since they do not persist control plugin configuration. * * @param pluginName Name of the plugin to get the config for * @param merge If true, merge the product and control config data */ @Transactional(readOnly=true) public byte[] getPluginConfiguration(String pluginName, boolean merge) throws PluginException { try { AppdefEntityID id = new AppdefEntityID(pluginName); AuthzSubject overlord = authzSubjectManager.getOverlordPojo(); ConfigResponse config = configManager.getMergedConfigResponse(overlord, ProductPlugin.TYPE_CONTROL, id, merge); return config.encode(); } catch (Exception e) { // XXX: Could be a bit more specific here when catching // exceptions, but ideally this should always // succeed since the agent knows when to pull the // config. throw new PluginException("Unable to get plugin configuration: " + e.getMessage()); } } /** * Receive status information about a previous control action */ @Transactional(propagation=Propagation.REQUIRES_NEW) public void sendCommandResult(int id, int result, long startTime, long endTime, String message) { String status; if (result == 0) { status = ControlConstants.STATUS_COMPLETED; } else { status = ControlConstants.STATUS_FAILED; } String msg; if (message != null && message.length() > 500) { // Show last 500 characters from the command output msg = message.substring(message.length() - 500); } else { msg = message; } Integer pk = new Integer(id); ControlHistory cLocal = null ; for(int i=0; i < 3; i++) { cLocal = controlHistoryDao.get(pk); if(cLocal != null) break ; }//EO while there are more retries if (cLocal == null) { // We know the ID, this should not happen throw new SystemException( "Failure getting control history id=" + id + ". Could not update history {status=" + status + ", startTime=" + startTime + ", endTime=" + endTime + ", message=" + msg + "}"); } cLocal.setStatus(status); cLocal.setStartTime(startTime); cLocal.setEndTime(endTime); cLocal.setMessage(msg); // Send a control event ControlEvent event = new ControlEvent(cLocal.getSubject(), cLocal.getEntityType().intValue(), cLocal .getEntityId(), cLocal.getAction(), cLocal.getScheduled().booleanValue(), cLocal.getDateScheduled(), status); event.setMessage(msg); messagePublisher.publishMessage(EventConstants.EVENTS_TOPIC, event); } @Transactional public void removeControlHistory(AppdefEntityID id) { controlHistoryDao.removeByEntity(id.getType(), id.getID()); } /** * Accept an array of appdef entity Ids and verify control permission on * each entity for specified subject. Return only the set of entities that * have authorization. * * @return List of entities subject is authz to control NOTE: Returns an * empty list when no resources are found. */ public List<AppdefEntityID> batchCheckControlPermissions(AuthzSubject caller, AppdefEntityID[] entities) throws AppdefEntityNotFoundException, PermissionException { return doBatchCheckControlPermissions(caller, entities); } protected List<AppdefEntityID> doBatchCheckControlPermissions(AuthzSubject caller, AppdefEntityID[] entities) throws AppdefEntityNotFoundException, PermissionException { List<ResourceValue> resList = new ArrayList<ResourceValue>(); List<String> opList = new ArrayList<String>(); List<AppdefEntityID> retVal = new ArrayList<AppdefEntityID>(); ResourceValue[] resArr; String[] opArr; // package up the args for verification for (AppdefEntityID entity : entities) { // Special case groups. If the group is compatible, // pull the members and check each of them. According // to Moseley, if any member of a group is control unauthz // then the entire group is unauthz. if (entity.isGroup()) { if (isGroupControlEnabled(caller, entity)) { retVal.add(entity); } continue; } // Build up the arguments -- operation name array correlated // with resource (i.e. type specific operation names) opList.add(getControlPermissionByType(entity)); ResourceValue rv = new ResourceValue(); rv.setInstanceId(entity.getId()); rv.setResourceType(resourceTypeDao.findByName(AppdefUtil.appdefTypeIdToAuthzTypeStr(entity.getType()))); resList.add(rv); } if (resList.size() > 0) { opArr = (String[]) opList.toArray(new String[0]); resArr = (ResourceValue[]) resList.toArray(new ResourceValue[0]); // fetch authz resources and add to return list try { PermissionManager pm = PermissionManagerFactory.getInstance(); Resource[] authz = pm.findOperationScopeBySubjectBatch(caller, resArr, opArr); for (int x = 0; x < authz.length; x++) { retVal.add(AppdefUtil.newAppdefEntityId(authz[x])); } } catch (ApplicationException e) { // returns empty list as advertised } } return retVal; } // Authz Helper Methods /** * Check control modify permission for an appdef entity Control Modify ops * are treated as regular modify operations */ protected void checkModifyPermission(AuthzSubject caller, AppdefEntityID id) throws PermissionException { int type = id.getType(); switch (type) { case AppdefEntityConstants.APPDEF_TYPE_PLATFORM: permissionManager.checkModifyPermission(caller, id); return; case AppdefEntityConstants.APPDEF_TYPE_SERVER: permissionManager.checkModifyPermission(caller, id); return; case AppdefEntityConstants.APPDEF_TYPE_SERVICE: permissionManager.checkModifyPermission(caller, id); return; case AppdefEntityConstants.APPDEF_TYPE_APPLICATION: permissionManager.checkModifyPermission(caller, id); return; default: throw new InvalidAppdefTypeException("Unknown type: " + type); } } /** Check control permission for an appdef entity */ protected void checkControlPermission(AuthzSubject caller, AppdefEntityID id) throws PermissionException { int type = id.getType(); switch (type) { case AppdefEntityConstants.APPDEF_TYPE_PLATFORM: permissionManager.checkControlPermission(caller, id); return; case AppdefEntityConstants.APPDEF_TYPE_SERVER: permissionManager.checkControlPermission(caller, id); return; case AppdefEntityConstants.APPDEF_TYPE_SERVICE: permissionManager.checkControlPermission(caller, id); return; case AppdefEntityConstants.APPDEF_TYPE_APPLICATION: permissionManager.checkControlPermission(caller, id); return; default: throw new InvalidAppdefTypeException("Unknown type: " + type); } } // Lookup the appropriate control permission based on entity type. // Groups are fetched and appropriate type is returned. private String getControlPermissionByType(AppdefEntityID id) { switch (id.getType()) { case AppdefEntityConstants.APPDEF_TYPE_PLATFORM: return AuthzConstants.platformOpControlPlatform; case AppdefEntityConstants.APPDEF_TYPE_SERVER: return AuthzConstants.serverOpControlServer; case AppdefEntityConstants.APPDEF_TYPE_SERVICE: return AuthzConstants.serviceOpControlService; case AppdefEntityConstants.APPDEF_TYPE_APPLICATION: return AuthzConstants.appOpControlApplication; default: throw new IllegalArgumentException("Invalid appdef type:" + id.getType()); } } }