/** * Copyright (C) 2008 Progress Software, Inc. All rights reserved. * http://fusesource.com * * The software in this package is published under the terms of the AGPL license * a copy of which has been included with this distribution in the license.txt file. */ package org.fusesource.cloudmix.controller.provisioning; import com.sun.jersey.api.NotFoundException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.fusesource.cloudmix.common.CloudmixHelper; import org.fusesource.cloudmix.common.ControllerDataProvider; import org.fusesource.cloudmix.common.GridClient; import org.fusesource.cloudmix.common.GridController; import org.fusesource.cloudmix.common.URIs; import org.fusesource.cloudmix.common.controller.AgentController; import org.fusesource.cloudmix.common.controller.FeatureController; import org.fusesource.cloudmix.common.controller.ProfileController; import org.fusesource.cloudmix.common.dto.AgentDetails; import org.fusesource.cloudmix.common.dto.ConfigurationUpdate; import org.fusesource.cloudmix.common.dto.Dependency; import org.fusesource.cloudmix.common.dto.FeatureDetails; import org.fusesource.cloudmix.common.dto.ProcessList; import org.fusesource.cloudmix.common.dto.ProfileDetails; import org.fusesource.cloudmix.common.dto.ProfileStatus; import org.fusesource.cloudmix.common.dto.ProvisioningAction; import org.fusesource.cloudmix.common.dto.ProvisioningHistory; import org.fusesource.cloudmix.common.util.ObjectHelper; import org.fusesource.cloudmix.controller.properties.ExpressionFactory; import org.fusesource.cloudmix.controller.properties.PropertiesEvaluator; import org.fusesource.cloudmix.controller.properties.mvel.MvelExpressionFactory; import org.mortbay.util.UrlEncoded; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; /** * @version $Revision$ */ public class DefaultGridController implements GridController, GridClient { private static final transient Log LOG = LogFactory.getLog(DefaultGridController.class); private AtomicLong counter = new AtomicLong(0); private long agentTimeout = 3000L; private ControllerDataProvider dataProvider = new SimpleControllerDataProvider(this); private PropertiesEvaluator propertiesEvaluator; private ExpressionFactory expressionFactory = new MvelExpressionFactory(); @Override public String toString() { return "DefaultGridController[agentTimout: " + agentTimeout + "]"; } public URI getRootUri() { return URIs.createURI(CloudmixHelper.getDefaultRootUrl()); } public String addAgentDetails(AgentDetails details) { if (details.getId() == null || details.getId().equals("")) { details.setId(constructAgentId(details)); } AgentController agent = new AgentController(this, details); ProvisioningHistory history = new ProvisioningHistory(); // lets create a history from the current process lists... ProcessList processList = details.getProcesses(); if (processList != null) { processList.populateHistory(history); } agent.setHistory(history); // lets set the features set too history.populate(agent.getFeatures()); AgentController rc = dataProvider.addAgent(details.getId(), agent); if (rc == null) { rc = agent; } rc.markActive(); return rc.getDetails().getId(); } public void updateAgentDetails(String agentId, AgentDetails agentDetails) { AgentController ac = dataProvider.getAgent(agentId); if (ac == null) { LOG.warn("Could not find agent with ID " + agentId); return; } agentDetails.setId(agentId); // make sure the ID's match up ac.setDetails(agentDetails); dataProvider.updateAgent(agentId, ac); } private String constructAgentId(AgentDetails details) { StringBuilder sb = new StringBuilder(); sb.append(details.getProfile()); sb.append('_'); String hostname = details.getHostname(); LOG.info("constructing agent.id with hostname: " + hostname); int idx = hostname.indexOf('.'); if (idx > 0) { hostname = hostname.substring(0, idx); } LOG.info("abbreviated hostname: " + hostname); sb.append(hostname); sb.append('_'); sb.append(details.getContainerType()); sb.append('_'); sb.append(counter.incrementAndGet()); String unEncId = sb.toString(); try { return URLEncoder.encode(unEncId, "UTF-8"); } catch (UnsupportedEncodingException uee) { return unEncId; } } public void removeAgentDetails(String agentId) { AgentController remove = dataProvider.removeAgent(agentId); if (remove == null) { throw new NotFoundException("Agent '" + agentId + "' does not exist"); } } public Collection<AgentDetails> getAllAgentDetails() { List<AgentDetails> rc = new ArrayList<AgentDetails>(); // Return all the agents that have not timed out. for (AgentController agent : agentTrackers()) { rc.add(agent.getDetails()); } return rc; } public AgentDetails getAgentDetails(String agentId) { AgentController agent = agentTracker(agentId); if (agent == null) { throw new NotFoundException("Agent '" + agentId + "' does not exist"); } return agent.getDetails(); } public ProvisioningHistory getAgentHistory(String agentId) { AgentController agent = agentTracker(agentId); if (agent == null) { throw new NotFoundException("Agent '" + agentId + "' does not exist"); } agent.markActive(); return agent.getHistory(); } public FeatureDetails getFeature(String featureId) { FeatureController rc = dataProvider.getFeature(featureId); if (rc == null) { throw new NotFoundException("Feature '" + featureId + "' does not exist"); } return rc.getDetails(); } public Collection<FeatureDetails> getFeatureDetails() { List<FeatureDetails> answer = new ArrayList<FeatureDetails>(); Collection<FeatureController> featureControllers = dataProvider.getFeatures(); for (FeatureController featureController : featureControllers) { answer.add(featureController.getDetails()); } return answer; } public void addFeature(FeatureDetails featureDetails) { dataProvider.addFeature(featureDetails.getId(), new FeatureController(this, featureDetails)); } public void removeFeature(String featureId) { FeatureController remove = dataProvider.removeFeature(featureId); if (remove == null) { throw new NotFoundException("Feature '" + featureId + "' does not exist"); } } public void addAgentToFeature(String featureId, String agentId, Map<String, String> cfgOverridesProps) { AgentController agent = agentTracker(agentId); if (agent == null) { throw new NotFoundException("Agent '" + agentId + "' does not exist"); } addAgentToFeature(agent, featureId, cfgOverridesProps); } protected List<ProvisioningAction> addAgentToFeature(AgentController agent, String featureId, Map<String, String> cfgOverridesProps) { if (featureId == null) { throw new IllegalArgumentException("featuredId should not be null"); } if (agent.getFeatures().add(featureId)) { // If the agent did not have this added yet.. List<ProvisioningAction> actions = getInstallActionsFor(agent, featureId, cfgOverridesProps); for (ProvisioningAction action : actions) { agent.getHistory().addAction(action); } return actions; } else { return Collections.emptyList(); } } public void removeAgentFromFeature(String featureId, String agentId) { AgentController agent = agentTracker(agentId); if (agent == null) { throw new NotFoundException("Agent '" + agentId + "' does not exist"); } // There is a chance that the feature cannot be found // in which case there is no issue with removing it :) // FeatureController feature = dataProvider.getFeature(featureId); // if (feature == null) { // throw new NotFoundException("Feature '" + featureId + "' does not exist"); // } List<ProvisioningAction> actions = null; if (agent.getFeatures().remove(featureId)) { // If the agent did not have this removed yet.. actions = getUninstallActionsFor(agent, featureId); for (ProvisioningAction action : actions) { agent.getHistory().addAction(action); } } } public List<String> getAgentsAssignedToFeature(String featureId) { return getAgentsAssignedToFeature(featureId, null, false); } public List<String> getAgentsAssignedToFeature(String featureId, String profileId, boolean onlyIfDeployed) { List<String> rc = new ArrayList<String>(); // Return all the agents that have not timed out and that have the feature assigned. for (AgentController agent : agentTrackers()) { // TODO check for wildcard profile!!! //if (profileId != null && !profileId.equals(agent.getDetails().getProfile())) { if (profileId != null && !agent.getDetails().matchesProfile(profileId)) { continue; } if (agent.getFeatures().contains(featureId)) { boolean addFeature = false; if (onlyIfDeployed) { // Restrict to deployed features reported back by the agent Set<String> installedFeatures = agent.getDetails().getCurrentFeatures(); if (installedFeatures != null) { for (String f : installedFeatures) { if (f.equals(featureId)) { addFeature = true; } } } } else { addFeature = true; } if (addFeature) { rc.add(agent.getDetails().getId()); } } } return rc; } public void addProfile(ProfileDetails profileDetails) { dataProvider.addProfile(profileDetails.getId(), new ProfileController(this, profileDetails)); } public void removeProfile(ProfileDetails profile) { String id = profile.getId(); ObjectHelper.notNull(id, "profile.id"); removeProfile(id); } public void removeProfile(String profileId) { ProfileController remove = dataProvider.removeProfile(profileId); if (remove == null) { throw new NotFoundException("Profile '" + profileId + "' does not exist"); } // lets delete any features associated with this profile! deleteFeaturesForProfile(profileId); } /** * Deletes the features for a given profile. * <p/> * An implementation might have a more optimal way of implementing this than * brute force iterating through all features. */ protected void deleteFeaturesForProfile(String profileId) { Collection<FeatureDetails> features = getFeatureDetails(); for (FeatureDetails feature : features) { String ownerId = feature.getOwnedByProfileId(); if (ownerId != null && ownerId.equals(profileId)) { removeFeature(feature.getId()); } } } // TODO note the difference in APIs between this and getProfileDetails public List<ProfileDetails> getProfiles() { return new ArrayList<ProfileDetails>(getProfileDetails()); } public Collection<ProfileDetails> getProfileDetails() { List<ProfileDetails> answer = new ArrayList<ProfileDetails>(); Collection<ProfileController> profileControllers = dataProvider.getProfiles(); for (ProfileController profileController : profileControllers) { answer.add(profileController.getDetails()); } return answer; } public ProfileDetails getProfile(String profileId) { ProfileController rc = getProfileController(profileId); if (rc == null) { throw new NotFoundException("Profile '" + profileId + "' does not exist"); } return rc.getDetails(); } public ProfileStatus getProfileStatus(String profileId) { ProfileController rc = getProfileController(profileId); if (rc == null) { throw new NotFoundException("Profile '" + profileId + "' does not exist"); } return rc.getStatus(); } public Properties getProperties(String profileId) { return getPropertiesEvaluator().evaluateProperties(profileId); } protected ProfileController getProfileController(String profileId) { ProfileController answer = dataProvider.getProfile(profileId); if (answer == null) { String encodedId = UrlEncoded.encodeString(profileId); answer = dataProvider.getProfile(encodedId); } return answer; } public FeatureController getFeatureController(String featureId) { return dataProvider.getFeature(featureId); } public FeatureController featureController(FeatureDetails featureDetails) { return getFeatureController(featureDetails.getId()); } public FeatureController getFeatureController(Dependency featureDetails) { return getFeatureController(featureDetails.getFeatureId()); } public int getFeatureInstanceCount(String featureId, String profileId, boolean onlyIfDeployed) { List<String> agents = getAgentsAssignedToFeature(featureId, profileId, onlyIfDeployed); return agents.size(); } // Properties //------------------------------------------------------------------------- public long getAgentTimeout() { return agentTimeout; } public void setAgentTimeout(long machineTimeout) { this.agentTimeout = machineTimeout; } public PropertiesEvaluator getPropertiesEvaluator() { if (propertiesEvaluator == null) { propertiesEvaluator = new PropertiesEvaluator(this, expressionFactory); } return propertiesEvaluator; } public void setPropertiesEvaluator(PropertiesEvaluator propertiesEvaluator) { this.propertiesEvaluator = propertiesEvaluator; } public ExpressionFactory getExpressionFactory() { return expressionFactory; } public void setExpressionFactory(ExpressionFactory expressionFactory) { this.expressionFactory = expressionFactory; } public ControllerDataProvider getDataProvider() { return dataProvider; } public void setDataProvider(ControllerDataProvider dp) { dataProvider = dp; dataProvider.setGrid(this); } // GridClient API //------------------------------------------------------------------------- public ProvisioningHistory pollAgentHistory(String agentId) { // we are local so no need to poll return getAgentHistory(agentId); } public List<FeatureDetails> getFeatures() { return new ArrayList<FeatureDetails>(getFeatureDetails()); } public void removeFeature(FeatureDetails feature) { removeFeature(feature.getId()); } // Implementation methods //------------------------------------------------------------------------- /** * This could get much more complicated. Should it dive into dependencies? * The more work we do here, the dumber the agent can stay. * * @param agent * @return */ protected List<ProvisioningAction> getInstallActionsFor(AgentController agent, String featureId, Map<String, String> cfgOverridesProps) { List<ProvisioningAction> rc = new ArrayList<ProvisioningAction>(); ProvisioningAction action = new ProvisioningAction(); action.setId(agent.getNextHistoryId()); action.setCommand("install"); action.setFeature(featureId); FeatureController fc = dataProvider.getFeature(featureId); if (fc == null) { fc = dataProvider.getFeature(encodeURL(featureId)); } action.setResource(fc.getResource()); if (cfgOverridesProps != null && cfgOverridesProps.size() > 0) { for (String key : cfgOverridesProps.keySet()) { action.addCfgOverride(new ConfigurationUpdate(key, cfgOverridesProps.get(key))); } } rc.add(action); return rc; } protected List<ProvisioningAction> getUninstallActionsFor(AgentController agent, String featureId) { List<ProvisioningAction> rc = new ArrayList<ProvisioningAction>(); ProvisioningAction action = new ProvisioningAction(); action.setId(agent.getNextHistoryId()); action.setCommand("uninstall"); action.setFeature(featureId); // this should not be needed for uninstallation // action.setResource(dataProvider.getFeature(featureId).getResource()); rc.add(action); return rc; } protected AgentController agentTracker(String agentId) { return dataProvider.getAgent(agentId); } protected Collection<AgentController> agentTrackers() { Collection<AgentController> agents = dataProvider.getAgents(); List<AgentController> l = new ArrayList<AgentController>(agents.size()); for (AgentController agent : agents) { if (agent.isActive(System.currentTimeMillis())) { l.add(agent); } } LOG.debug("Default GridController, live agents available: " + l.size()); return l; } protected Collection<AgentController> agentTrackers(String profileID) { Collection<AgentController> agents = agentTrackers(); List<AgentController> l = new ArrayList<AgentController>(agents.size()); for (AgentController agent : agents) { if (profileID.equals(agent.getDetails().getProfile())) { l.add(agent); } } return l; } protected Collection<FeatureController> featureControllers() { return dataProvider.getFeatures(); } protected Collection<ProfileController> profileControllers() { return dataProvider.getProfiles(); } protected static String encodeURL(String name) { try { return URLEncoder.encode(name, "UTF-8"); } catch (UnsupportedEncodingException e) { LOG.warn("Problem encoding URL", e); return name; } } protected static String decodeURL(String url) { try { return URLDecoder.decode(url, "UTF-8"); } catch (UnsupportedEncodingException e) { LOG.warn("Problem decoding URL", e); return url; } } }