Repository: ambari Updated Branches: refs/heads/branch-2.6 17f89c01f -> eecd8513a
AMBARI-22337 each service should be able to implement server actions, package them add a jar to be loaded during EU (port from trunk to branch-26)(dili) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/eecd8513 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/eecd8513 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/eecd8513 Branch: refs/heads/branch-2.6 Commit: eecd8513a304641617cc8602f5805eee8aaf3c69 Parents: 17f89c0 Author: Di Li <d...@apache.org> Authored: Mon Nov 20 13:54:31 2017 -0500 Committer: Di Li <d...@apache.org> Committed: Mon Nov 20 13:54:31 2017 -0500 ---------------------------------------------------------------------- ambari-server/pom.xml | 12 ++ .../serveraction/ServerActionExecutor.java | 147 +++++++++++++++++-- .../ambari/server/stack/ServiceDirectory.java | 29 ++++ .../ambari/server/stack/ServiceModule.java | 8 + .../apache/ambari/server/state/ServiceInfo.java | 14 ++ .../ambari/server/stack/ServiceModuleTest.java | 30 ++++ .../server/stack/StackManagerExtensionTest.java | 6 + 7 files changed, 232 insertions(+), 14 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/eecd8513/ambari-server/pom.xml ---------------------------------------------------------------------- diff --git a/ambari-server/pom.xml b/ambari-server/pom.xml index 342b3c0..eece739 100644 --- a/ambari-server/pom.xml +++ b/ambari-server/pom.xml @@ -165,6 +165,18 @@ </target> </configuration> </execution> + <execution> + <id>generate-test-oozie2-server-actions-dir</id> + <phase>process-test-classes</phase> + <goals> + <goal>run</goal> + </goals> + <configuration> + <target> + <mkdir dir="target/test-classes/extensions/EXT/0.1/services/OOZIE2/server_actions/tmp"/> + </target> + </configuration> + </execution> </executions> </plugin> <plugin> http://git-wip-us.apache.org/repos/asf/ambari/blob/eecd8513/ambari-server/src/main/java/org/apache/ambari/server/serveraction/ServerActionExecutor.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/ServerActionExecutor.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/ServerActionExecutor.java index 50e3cfe..e219dc3 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/ServerActionExecutor.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/ServerActionExecutor.java @@ -18,10 +18,17 @@ package org.apache.ambari.server.serveraction; +import java.io.File; +import java.io.FilenameFilter; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.StringTokenizer; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; @@ -38,11 +45,17 @@ import org.apache.ambari.server.actionmanager.HostRoleStatus; import org.apache.ambari.server.actionmanager.Stage; import org.apache.ambari.server.agent.CommandReport; import org.apache.ambari.server.agent.ExecutionCommand; +import org.apache.ambari.server.api.services.AmbariMetaInfo; import org.apache.ambari.server.configuration.Configuration; +import org.apache.ambari.server.controller.AmbariManagementController; import org.apache.ambari.server.security.authorization.internal.InternalAuthenticationToken; +import org.apache.ambari.server.state.ServiceInfo; +import org.apache.ambari.server.state.UpgradeContext.UpgradeServiceSummary; +import org.apache.ambari.server.state.UpgradeContext.UpgradeSummary; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.ClassUtils; import com.google.inject.Inject; import com.google.inject.Injector; @@ -495,7 +508,6 @@ public class ServerActionExecutor { throw new AmbariException("Missing ExecutionCommand data"); } else { Map<String, String> roleParams = executionCommand.getRoleParams(); - if (roleParams == null) { throw new AmbariException("Missing RoleParams data"); } else { @@ -504,8 +516,30 @@ public class ServerActionExecutor { if (actionClassname == null) { throw new AmbariException("Missing action classname for server action"); } else { - ServerAction action = createServerAction(actionClassname); - + Map<String, ServiceInfo> services = new HashMap<String, ServiceInfo>(); + UpgradeSummary upgradeSummary = executionCommand.getUpgradeSummary(); + if (upgradeSummary != null) { + Map<String, UpgradeServiceSummary> upgradeServiceSummaries = upgradeSummary.services; + LOG.debug("UpgradeServiceSummary: " + upgradeServiceSummaries); + AmbariManagementController ambariManagementController = injector.getInstance(AmbariManagementController.class); + AmbariMetaInfo ambariMetaInfo = ambariManagementController.getAmbariMetaInfo(); + String serviceName = executionCommand.getServiceName(); + if (serviceName != null && !serviceName.isEmpty()){ + LOG.info(String.format("Server action %s is associated with service %s", actionClassname, serviceName)); + //Execution stage of a given service, only need to examine stack information for this one service + UpgradeServiceSummary serviceSummary = upgradeServiceSummaries.get(serviceName); + addServiceInfo(services, ambariMetaInfo, serviceSummary.sourceStackId, serviceName); + } else { + LOG.info(String.format("Server action %s is not associated with a service", actionClassname)); + //Load all Jars + for(String key: upgradeServiceSummaries.keySet()){ + UpgradeServiceSummary serviceSummary = upgradeServiceSummaries.get(key); + addServiceInfo(services, ambariMetaInfo, serviceSummary.sourceStackId, key); + } + } + LOG.info(String.format("Attempt to load server action classes from %s", services.keySet().toString())); + } + ServerAction action = createServerAction(actionClassname, services); if (action == null) { throw new AmbariException("Failed to create server action: " + actionClassname); } else { @@ -520,6 +554,30 @@ public class ServerActionExecutor { } } + private void addServiceInfo(Map<String, ServiceInfo> services, AmbariMetaInfo ambariMetaInfo, String stackId, String serviceName) { + List<String> stackInfo = getStackInfo(stackId); + LOG.debug(String.format("Stack info list: %s", stackInfo)); + if (stackInfo.size() > 1) { + try { + ServiceInfo service = ambariMetaInfo.getService(stackInfo.get(0), stackInfo.get(1), serviceName); + LOG.debug(String.format("Adding %s to the list of services for loading external Jars...", service.getName())); + services.put(serviceName, service); + } catch (AmbariException e) { + LOG.error(String.format("Failed to obtain service info for stack %s, service name %s", stackId, serviceName), e); + } + } + } + + private List<String> getStackInfo(String stackId) { + LOG.debug(String.format("Stack id: %s", stackId)); + StringTokenizer tokens = new StringTokenizer(stackId, "-"); + List<String> info = new ArrayList<String>(); + while (tokens.hasMoreElements()) { + info.add((String)tokens.nextElement()); + } + return info; + } + /** * Attempts to create an instance of the ServerAction class implementation specified in * classname. @@ -528,24 +586,85 @@ public class ServerActionExecutor { * @return the instantiated ServerAction implementation * @throws AmbariException */ - private ServerAction createServerAction(String classname) throws AmbariException { - try { - Class<?> actionClass = Class.forName(classname); + private ServerAction createServerAction(String classname, Map<String, ServiceInfo> services) throws AmbariException { + Class<?> actionClass = null; + actionClass = getServerActionClass(classname); + if (actionClass == null) { + LOG.debug(String.format("Did not find %s in Ambari, try to load it from external directories", classname)); + actionClass = getServiceLevelServerActionClass(classname, services); + } - if (actionClass == null) { - throw new AmbariException("Unable to load server action class: " + classname); + if (actionClass == null) { + throw new AmbariException("Unable to load server action class: " + classname); + } else { + LOG.debug(String.format("Ready to init server action %s", classname)); + Class<? extends ServerAction> serverActionClass = actionClass.asSubclass(ServerAction.class); + if (serverActionClass == null) { + throw new AmbariException("Unable to execute server action class, invalid type: " + classname); } else { - Class<? extends ServerAction> serverActionClass = actionClass.asSubclass(ServerAction.class); + return injector.getInstance(serverActionClass); + } + } + } - if (serverActionClass == null) { - throw new AmbariException("Unable to execute server action class, invalid type: " + classname); - } else { - return injector.getInstance(serverActionClass); + /** + * Load server action classes defined in the service level Jar files + * */ + private Class<?> getServiceLevelServerActionClass(String classname, Map<String, ServiceInfo> services) { + List<URL> urls = new ArrayList<>(); + for (ServiceInfo service : services.values()) { + LOG.debug(String.format("Checking service %s", service)); + File dir = service.getServerActionsFolder(); + if ( dir != null) { + LOG.debug(String.format("Service %s, external dir %s",service.getName(), dir.getAbsolutePath())); + File[] jars = dir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + LOG.debug(String.format("Checking folder %s", name)); + return name.endsWith(".jar"); + } + }); + for (File jar : jars) { + try { + URL url = jar.toURI().toURL(); + urls.add(url); + LOG.info("Adding server action jar to classpath: {}", url); + } + catch (Exception e) { + LOG.error("Failed to add server action jar to classpath: {}", jar.getAbsolutePath(), e); + } } + } else { + LOG.error(String.format("%s service server actions folder returned null", service)); + } + } + + ClassLoader classLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]), ClassUtils.getDefaultClassLoader()); + Class<?> actionClass = null; + try { + actionClass = ClassUtils.resolveClassName(classname, classLoader); + LOG.debug(String.format("Found external server action %s", classname)); + } catch(IllegalArgumentException illegalArgumentException) { + LOG.error(String.format("Unable to find server action %s in external server action directories", classname), illegalArgumentException); + } + + return actionClass; + } + + /** + * Load server action classes defined in Ambari source code + * */ + private Class<?> getServerActionClass(String classname) throws AmbariException{ + Class<?> actionClass = null; + try { + actionClass = Class.forName(classname); + if (actionClass == null) { + LOG.warn(String.format("Unable to load server action class: %s from Ambari", classname)); } } catch (ClassNotFoundException e) { - throw new AmbariException("Unable to load server action class: " + classname, e); + LOG.error(String.format("Unable to load server action class: %s", classname), e); } + return actionClass; } /** http://git-wip-us.apache.org/repos/asf/ambari/blob/eecd8513/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java index abef459..8af0c9a 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java @@ -92,6 +92,11 @@ public abstract class ServiceDirectory extends StackDefinitionDirectory { protected File checksDir; /** + * server side action directory path + */ + protected File serverActionsDir; + + /** * service metainfo file object representation */ private ServiceMetainfoXml metaInfoXml; @@ -117,6 +122,11 @@ public abstract class ServiceDirectory extends StackDefinitionDirectory { protected static final String CHECKS_FOLDER_NAME = "checks"; /** + * Server actions directory name + */ + protected static final String SERVER_ACTIONS_FOLDER_NAME = "server_actions"; + + /** * service metainfo file name */ private static final String SERVICE_METAINFO_FILE_NAME = "metainfo.xml"; @@ -171,6 +181,15 @@ public abstract class ServiceDirectory extends StackDefinitionDirectory { } /** + * Obtain the server side actions directory path. + * + * @return server side actions directory path + */ + public File getServerActionsDir() { + return serverActionsDir; + } + + /** * Obtain the metrics file. * * @return metrics file @@ -302,6 +321,7 @@ public abstract class ServiceDirectory extends StackDefinitionDirectory { calculatePackageDirectory(stack, service); calculateUpgradesDirectory(stack, service); calculateChecksDirectory(stack, service); + calculateServerActionsDirectory(stack, service); } /** @@ -377,6 +397,15 @@ public abstract class ServiceDirectory extends StackDefinitionDirectory { } /** + * Sets the serverActionsDir if the dir exists and is not empty + * @param stack + * @param service + */ + protected void calculateServerActionsDirectory(String stack, String service) { + serverActionsDir = resolveDirectory(SERVER_ACTIONS_FOLDER_NAME, stack, service); + } + + /** * Unmarshal the metainfo file into its object representation. * * @throws AmbariException if the metainfo file doesn't exist or http://git-wip-us.apache.org/repos/asf/ambari/blob/eecd8513/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java index 6699e0e..7a8fa64 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java @@ -144,6 +144,7 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem serviceInfo.setServicePackageFolder(serviceDirectory.getPackageDir()); serviceInfo.setServiceUpgradesFolder(serviceDirectory.getUpgradesDir()); serviceInfo.setChecksFolder(serviceDirectory.getChecksDir()); + serviceInfo.setServerActionsFolder(serviceDirectory.getServerActionsDir()); serviceInfo.setAdvisorFile(serviceDirectory.getAdvisorFile()); serviceInfo.setAdvisorName(serviceDirectory.getAdvisorName(serviceInfo.getName())); @@ -259,6 +260,13 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem serviceInfo.setChecksFolder(parent.getChecksFolder()); } + /* + * Use parent's server actions if the current one does not have any. + */ + if (serviceInfo.getServerActionsFolder() == null) { + serviceInfo.setServerActionsFolder(parent.getServerActionsFolder()); + } + /** * If current stack version does not specify the credential store information * for the service, then use parent definition. http://git-wip-us.apache.org/repos/asf/ambari/blob/eecd8513/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java index b7afe53..a5ea449 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java @@ -282,6 +282,12 @@ public class ServiceInfo implements Validable{ @XmlTransient private File checksFolder; + /** + * Stores the path to the server actions folder which contains server actions jars for the given service. + */ + @XmlTransient + private File serverActionsFolder; + public boolean isDeleted() { return isDeleted; } @@ -744,6 +750,14 @@ public String getVersion() { this.checksFolder = checksFolder; } + public File getServerActionsFolder() { + return serverActionsFolder; + } + + public void setServerActionsFolder(File serverActionsFolder) { + this.serverActionsFolder = serverActionsFolder; + } + /** * Exposes (and initializes on first use) map of os-specific details. * @return map of OS specific details keyed by family http://git-wip-us.apache.org/repos/asf/ambari/blob/eecd8513/ambari-server/src/test/java/org/apache/ambari/server/stack/ServiceModuleTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/stack/ServiceModuleTest.java b/ambari-server/src/test/java/org/apache/ambari/server/stack/ServiceModuleTest.java index 5efbc89..f8bfac8 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/stack/ServiceModuleTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/stack/ServiceModuleTest.java @@ -467,6 +467,36 @@ public class ServiceModuleTest { } @Test + public void testResolve_ServerActionDirectory() throws Exception { + File serverActions = new File("server_actions"); + + // check directory specified in child only + ServiceInfo info = new ServiceInfo(); + ServiceInfo parentInfo = new ServiceInfo(); + ServiceModule child = createServiceModule(info); + ServiceModule parent = createServiceModule(parentInfo); + child.getModuleInfo().setServerActionsFolder(serverActions); + resolveService(child, parent); + assertEquals(serverActions.getPath(), child.getModuleInfo().getServerActionsFolder().getPath()); + + // check directory specified in parent only + child = createServiceModule(info); + parent = createServiceModule(parentInfo); + parent.getModuleInfo().setServerActionsFolder(serverActions); + resolveService(child, parent); + assertEquals(serverActions.getPath(), child.getModuleInfo().getServerActionsFolder().getPath()); + + // check directory set in both + info.setServerActionsFolder(serverActions); + child = createServiceModule(info); + child.getModuleInfo().setServerActionsFolder(serverActions); + parent = createServiceModule(parentInfo); + parent.getModuleInfo().setServerActionsFolder(new File("other")); + resolveService(child, parent); + assertEquals(serverActions.getPath(), child.getModuleInfo().getServerActionsFolder().getPath()); + } + + @Test public void testResolve_CustomCommands() throws Exception { List<CustomCommandDefinition> customCommands = new ArrayList<CustomCommandDefinition>(); CustomCommandDefinition cmd1 = new CustomCommandDefinition(); http://git-wip-us.apache.org/repos/asf/ambari/blob/eecd8513/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerExtensionTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerExtensionTest.java b/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerExtensionTest.java index 0676568..8165398 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerExtensionTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerExtensionTest.java @@ -110,6 +110,9 @@ public class StackManagerExtensionTest { File checks = oozie.getChecksFolder(); assertNotNull(checks); assertTrue("Checks dir is " + checks.getPath(), checks.getPath().contains("extensions/EXT/0.1/services/OOZIE2/checks")); + File serverActions = oozie.getServerActionsFolder(); + assertNotNull(serverActions); + assertTrue("Server actions dir is " + serverActions.getPath(), serverActions.getPath().contains("extensions/EXT/0.1/services/OOZIE2/server_actions")); List<ThemeInfo> themes = oozie.getThemes(); assertNotNull(themes); assertTrue("Number of themes is " + themes.size(), themes.size() == 1); @@ -127,6 +130,9 @@ public class StackManagerExtensionTest { checks = oozie.getChecksFolder(); assertNotNull(checks); assertTrue("Checks dir is " + checks.getPath(), checks.getPath().contains("extensions/EXT/0.1/services/OOZIE2/checks")); + serverActions = oozie.getServerActionsFolder(); + assertNotNull(serverActions); + assertTrue("Server actions dir is " + serverActions.getPath(), serverActions.getPath().contains("extensions/EXT/0.1/services/OOZIE2/server_actions")); themes = oozie.getThemes(); assertNotNull(themes); assertTrue("Number of themes is " + themes.size(), themes.size() == 0);