AMBARI-21343. Cleanup relevant Kerberos identities when a component is removed (amagyar)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/8b5c7db6 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/8b5c7db6 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/8b5c7db6 Branch: refs/heads/feature-branch-AMBARI-21307 Commit: 8b5c7db602a0e1e2dfb214ec1d51884c16219467 Parents: 9d224f7 Author: Attila Magyar <amag...@hortonworks.com> Authored: Thu Jun 29 11:05:25 2017 +0200 Committer: Attila Magyar <amag...@hortonworks.com> Committed: Thu Jun 29 11:05:25 2017 +0200 ---------------------------------------------------------------------- .../ambari/server/controller/AmbariServer.java | 4 + .../controller/DeleteIdentityHandler.java | 283 +++++++++++++++++++ .../server/controller/KerberosHelper.java | 3 + .../server/controller/KerberosHelperImpl.java | 31 +- .../OrderedRequestStageContainer.java | 45 +++ .../utilities/KerberosIdentityCleaner.java | 135 +++++++++ .../AbstractPrepareKerberosServerAction.java | 19 +- .../server/serveraction/kerberos/Component.java | 74 +++++ .../kerberos/FinalizeKerberosServerAction.java | 27 +- .../kerberos/KerberosServerAction.java | 27 ++ .../kerberos/AbstractKerberosDescriptor.java | 15 + .../kerberos/KerberosComponentDescriptor.java | 15 + .../state/kerberos/KerberosDescriptor.java | 8 - .../kerberos/KerberosIdentityDescriptor.java | 30 ++ .../kerberos/KerberosServiceDescriptor.java | 6 + .../utilities/KerberosIdentityCleanerTest.java | 204 +++++++++++++ ambari-web/app/controllers/main/service/item.js | 6 +- 17 files changed, 894 insertions(+), 38 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java index aeba739..8988be0 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java @@ -76,6 +76,7 @@ import org.apache.ambari.server.controller.internal.UserPrivilegeResourceProvide import org.apache.ambari.server.controller.internal.ViewPermissionResourceProvider; import org.apache.ambari.server.controller.metrics.ThreadPoolEnabledPropertyProvider; import org.apache.ambari.server.controller.utilities.KerberosChecker; +import org.apache.ambari.server.controller.utilities.KerberosIdentityCleaner; import org.apache.ambari.server.metrics.system.MetricsService; import org.apache.ambari.server.orm.GuiceJpaInitializer; import org.apache.ambari.server.orm.PersistenceType; @@ -941,6 +942,9 @@ public class AmbariServer { BaseService.init(injector.getInstance(RequestAuditLogger.class)); RetryHelper.init(injector.getInstance(Clusters.class), configs.getOperationsRetryAttempts()); + + KerberosIdentityCleaner identityCleaner = injector.getInstance(KerberosIdentityCleaner.class); + identityCleaner.register(); } /** http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/controller/DeleteIdentityHandler.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/DeleteIdentityHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/DeleteIdentityHandler.java new file mode 100644 index 0000000..aa098b6 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/DeleteIdentityHandler.java @@ -0,0 +1,283 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ambari.server.controller; + +import static com.google.common.collect.Sets.newHashSet; +import static org.apache.ambari.server.controller.KerberosHelperImpl.BASE_LOG_DIR; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; + +import org.apache.ambari.server.AmbariException; +import org.apache.ambari.server.Role; +import org.apache.ambari.server.RoleCommand; +import org.apache.ambari.server.actionmanager.HostRoleStatus; +import org.apache.ambari.server.actionmanager.Stage; +import org.apache.ambari.server.actionmanager.StageFactory; +import org.apache.ambari.server.agent.CommandReport; +import org.apache.ambari.server.controller.internal.RequestResourceFilter; +import org.apache.ambari.server.serveraction.ServerAction; +import org.apache.ambari.server.serveraction.kerberos.AbstractPrepareKerberosServerAction; +import org.apache.ambari.server.serveraction.kerberos.Component; +import org.apache.ambari.server.serveraction.kerberos.DestroyPrincipalsServerAction; +import org.apache.ambari.server.serveraction.kerberos.KDCType; +import org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandler; +import org.apache.ambari.server.serveraction.kerberos.KerberosServerAction; +import org.apache.ambari.server.state.Cluster; +import org.apache.ambari.server.state.kerberos.KerberosDescriptor; +import org.apache.ambari.server.state.svccomphost.ServiceComponentHostServerActionEvent; +import org.apache.ambari.server.utils.StageUtils; + +/** + * I delete kerberos identities (principals and keytabs) of a given component. + */ +class DeleteIdentityHandler { + private final AmbariCustomCommandExecutionHelper customCommandExecutionHelper; + private final Integer taskTimeout; + private final StageFactory stageFactory; + private final AmbariManagementController ambariManagementController; + + public DeleteIdentityHandler(AmbariCustomCommandExecutionHelper customCommandExecutionHelper, Integer taskTimeout, StageFactory stageFactory, AmbariManagementController ambariManagementController) { + this.customCommandExecutionHelper = customCommandExecutionHelper; + this.taskTimeout = taskTimeout; + this.stageFactory = stageFactory; + this.ambariManagementController = ambariManagementController; + } + + /** + * Creates and adds stages to the given stage container for deleting kerberos identities. + * The service component that belongs to the identity doesn't need to be installed. + */ + public void addDeleteIdentityStages(Cluster cluster, OrderedRequestStageContainer stageContainer, CommandParams commandParameters, boolean manageIdentities) + throws AmbariException + { + ServiceComponentHostServerActionEvent event = new ServiceComponentHostServerActionEvent("AMBARI_SERVER", StageUtils.getHostName(), System.currentTimeMillis()); + String hostParamsJson = StageUtils.getGson().toJson(customCommandExecutionHelper.createDefaultHostParams(cluster, cluster.getDesiredStackVersion())); + stageContainer.setClusterHostInfo(StageUtils.getGson().toJson(StageUtils.getClusterHostInfo(cluster))); + if (manageIdentities) { + addPrepareDeleteIdentity(cluster, hostParamsJson, event, commandParameters, stageContainer); + addDestroyPrincipals(cluster, hostParamsJson, event, commandParameters, stageContainer); + addDeleteKeytab(cluster, newHashSet(commandParameters.component.getHostName()), hostParamsJson, commandParameters, stageContainer); + } + addFinalize(cluster, hostParamsJson, event, stageContainer, commandParameters); + } + + private void addPrepareDeleteIdentity(Cluster cluster, + String hostParamsJson, ServiceComponentHostServerActionEvent event, + CommandParams commandParameters, + OrderedRequestStageContainer stageContainer) + throws AmbariException + { + Stage stage = createServerActionStage(stageContainer.getLastStageId(), + cluster, + stageContainer.getId(), + "Prepare delete identities", + "{}", + hostParamsJson, + PrepareDeleteIdentityServerAction.class, + event, + commandParameters.asMap(), + "Prepare delete identities", + taskTimeout); + stageContainer.addStage(stage); + } + + private void addDestroyPrincipals(Cluster cluster, + String hostParamsJson, ServiceComponentHostServerActionEvent event, + CommandParams commandParameters, + OrderedRequestStageContainer stageContainer) + throws AmbariException + { + Stage stage = createServerActionStage(stageContainer.getLastStageId(), + cluster, + stageContainer.getId(), + "Destroy Principals", + "{}", + hostParamsJson, + DestroyPrincipalsServerAction.class, + event, + commandParameters.asMap(), + "Destroy Principals", + Math.max(ServerAction.DEFAULT_LONG_RUNNING_TASK_TIMEOUT_SECONDS, taskTimeout)); + stageContainer.addStage(stage); + } + + private void addDeleteKeytab(Cluster cluster, + Set<String> hostFilter, + String hostParamsJson, + CommandParams commandParameters, + OrderedRequestStageContainer stageContainer) + throws AmbariException + { + Stage stage = createNewStage(stageContainer.getLastStageId(), + cluster, + stageContainer.getId(), + "Delete Keytabs", + commandParameters.asJson(), + hostParamsJson); + + Map<String, String> requestParams = new HashMap<>(); + List<RequestResourceFilter> requestResourceFilters = new ArrayList<>(); + RequestResourceFilter reqResFilter = new RequestResourceFilter("KERBEROS", "KERBEROS_CLIENT", new ArrayList<>(hostFilter)); + requestResourceFilters.add(reqResFilter); + + ActionExecutionContext actionExecContext = new ActionExecutionContext( + cluster.getClusterName(), + "REMOVE_KEYTAB", + requestResourceFilters, + requestParams); + customCommandExecutionHelper.addExecutionCommandsToStage(actionExecContext, stage, requestParams, null); + stageContainer.addStage(stage); + } + + private void addFinalize(Cluster cluster, + String hostParamsJson, ServiceComponentHostServerActionEvent event, + OrderedRequestStageContainer requestStageContainer, + CommandParams commandParameters) + throws AmbariException + { + Stage stage = createServerActionStage(requestStageContainer.getLastStageId(), + cluster, + requestStageContainer.getId(), + "Finalize Operations", + "{}", + hostParamsJson, + DeleteDataDirAction.class, + event, + commandParameters.asMap(), + "Finalize Operations", 300); + requestStageContainer.addStage(stage); + } + + + public static class CommandParams { + private final Component component; + private final List<String> identities; + private final String authName; + private final File dataDirectory; + private final String defaultRealm; + private final KDCType kdcType; + + public CommandParams(Component component, List<String> identities, String authName, File dataDirectory, String defaultRealm, KDCType kdcType) { + this.component = component; + this.identities = identities; + this.authName = authName; + this.dataDirectory = dataDirectory; + this.defaultRealm = defaultRealm; + this.kdcType = kdcType; + } + + public Map<String, String> asMap() { + Map<String, String> commandParameters = new HashMap<>(); + commandParameters.put(KerberosServerAction.AUTHENTICATED_USER_NAME, authName); + commandParameters.put(KerberosServerAction.DEFAULT_REALM, defaultRealm); + commandParameters.put(KerberosServerAction.KDC_TYPE, kdcType.name()); + commandParameters.put(KerberosServerAction.IDENTITY_FILTER, StageUtils.getGson().toJson(identities)); + commandParameters.put(KerberosServerAction.COMPONENT_FILTER, StageUtils.getGson().toJson(component)); + commandParameters.put(KerberosServerAction.DATA_DIRECTORY, dataDirectory.getAbsolutePath()); + return commandParameters; + } + + public String asJson() { + return StageUtils.getGson().toJson(asMap()); + } + } + + private static class PrepareDeleteIdentityServerAction extends AbstractPrepareKerberosServerAction { + @Override + public CommandReport execute(ConcurrentMap<String, Object> requestSharedDataContext) throws AmbariException, InterruptedException { + KerberosDescriptor kerberosDescriptor = getKerberosDescriptor(); + processServiceComponents( + getCluster(), + kerberosDescriptor, + Collections.singletonList(getComponentFilter()), + getIdentityFilter(), + dataDirectory(), + calculateConfig(kerberosDescriptor), + new HashMap<String, Map<String, String>>(), + false, + new HashMap<String, Set<String>>()); + return createCommandReport(0, HostRoleStatus.COMPLETED, "{}", actionLog.getStdOut(), actionLog.getStdErr()); + } + + protected Component getComponentFilter() { + return StageUtils.getGson().fromJson(getCommandParameterValue(KerberosServerAction.COMPONENT_FILTER), Component.class); + } + + private Map<String, Map<String, String>> calculateConfig(KerberosDescriptor kerberosDescriptor) throws AmbariException { + return getKerberosHelper().calculateConfigurations(getCluster(), null, kerberosDescriptor.getProperties()); + } + + private String dataDirectory() { + return getCommandParameterValue(getCommandParameters(), DATA_DIRECTORY); + } + + private KerberosDescriptor getKerberosDescriptor() throws AmbariException { + return getKerberosHelper().getKerberosDescriptor(getCluster()); + } + } + + private Stage createNewStage(long id, Cluster cluster, long requestId, String requestContext, String commandParams, String hostParams) { + Stage stage = stageFactory.createNew(requestId, + BASE_LOG_DIR + File.pathSeparator + requestId, + cluster.getClusterName(), + cluster.getClusterId(), + requestContext, + commandParams, + hostParams); + stage.setStageId(id); + return stage; + } + + private Stage createServerActionStage(long id, Cluster cluster, long requestId, + String requestContext, + String commandParams, String hostParams, + Class<? extends ServerAction> actionClass, + ServiceComponentHostServerActionEvent event, + Map<String, String> commandParameters, String commandDetail, + Integer timeout) throws AmbariException { + + Stage stage = createNewStage(id, cluster, requestId, requestContext, commandParams, hostParams); + stage.addServerActionCommand(actionClass.getName(), null, Role.AMBARI_SERVER_ACTION, + RoleCommand.EXECUTE, cluster.getClusterName(), event, commandParameters, commandDetail, + ambariManagementController.findConfigurationTagsWithOverrides(cluster, null), timeout, + false, false); + + return stage; + } + + private static class DeleteDataDirAction extends KerberosServerAction { + + @Override + public CommandReport execute(ConcurrentMap<String, Object> requestSharedDataContext) throws AmbariException, InterruptedException { + deleteDataDirectory(getCommandParameterValue(DATA_DIRECTORY)); + return createCommandReport(0, HostRoleStatus.COMPLETED, "{}", actionLog.getStdOut(), actionLog.getStdErr()); + } + + @Override + protected CommandReport processIdentity(Map<String, String> identityRecord, String evaluatedPrincipal, KerberosOperationHandler operationHandler, Map<String, String> kerberosConfiguration, Map<String, Object> requestSharedDataContext) throws AmbariException { + return null; + } + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java index ca2dda5..cc0c048 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java @@ -27,6 +27,7 @@ import java.util.Set; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.controller.internal.RequestStageContainer; import org.apache.ambari.server.security.credential.PrincipalKeyCredential; +import org.apache.ambari.server.serveraction.kerberos.Component; import org.apache.ambari.server.serveraction.kerberos.KerberosAdminAuthenticationException; import org.apache.ambari.server.serveraction.kerberos.KerberosIdentityDataFileWriter; import org.apache.ambari.server.serveraction.kerberos.KerberosInvalidConfigurationException; @@ -232,6 +233,8 @@ public interface KerberosHelper { RequestStageContainer requestStageContainer, Boolean manageIdentities) throws AmbariException, KerberosOperationException; + void deleteIdentity(Cluster cluster, Component component, List<String> identities) throws AmbariException, KerberosOperationException; + /** * Updates the relevant configurations for the components specified in the service filter. * <p/> http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java index d57fcd2..b30f8f6 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java @@ -64,6 +64,7 @@ import org.apache.ambari.server.security.credential.PrincipalKeyCredential; import org.apache.ambari.server.security.encryption.CredentialStoreService; import org.apache.ambari.server.serveraction.ServerAction; import org.apache.ambari.server.serveraction.kerberos.CleanupServerAction; +import org.apache.ambari.server.serveraction.kerberos.Component; import org.apache.ambari.server.serveraction.kerberos.ConfigureAmbariIdentitiesServerAction; import org.apache.ambari.server.serveraction.kerberos.CreateKeytabFilesServerAction; import org.apache.ambari.server.serveraction.kerberos.CreatePrincipalsServerAction; @@ -130,7 +131,7 @@ import com.google.inject.persist.Transactional; @Singleton public class KerberosHelperImpl implements KerberosHelper { - private static final String BASE_LOG_DIR = "/tmp/ambari"; + public static final String BASE_LOG_DIR = "/tmp/ambari"; private static final Logger LOG = LoggerFactory.getLogger(KerberosHelperImpl.class); @@ -296,6 +297,34 @@ public class KerberosHelperImpl implements KerberosHelper { requestStageContainer, new DeletePrincipalsAndKeytabsHandler()); } + /** + * Deletes the kerberos identities of the given component, even if the component is already deleted. + */ + @Override + public void deleteIdentity(Cluster cluster, Component component, List<String> identities) throws AmbariException, KerberosOperationException { + if (identities.isEmpty()) { + return; + } + KerberosDetails kerberosDetails = getKerberosDetails(cluster, null); + validateKDCCredentials(kerberosDetails, cluster); + File dataDirectory = createTemporaryDirectory(); + RoleCommandOrder roleCommandOrder = ambariManagementController.getRoleCommandOrder(cluster); + DeleteIdentityHandler handler = new DeleteIdentityHandler(customCommandExecutionHelper, configuration.getDefaultServerTaskTimeout(), stageFactory, ambariManagementController); + DeleteIdentityHandler.CommandParams commandParameters = new DeleteIdentityHandler.CommandParams( + component, + identities, + ambariManagementController.getAuthName(), + dataDirectory, + kerberosDetails.getDefaultRealm(), + kerberosDetails.getKdcType()); + OrderedRequestStageContainer stageContainer = new OrderedRequestStageContainer( + roleGraphFactory, + roleCommandOrder, + new RequestStageContainer(actionManager.getNextRequestId(), null, requestFactory, actionManager)); + handler.addDeleteIdentityStages(cluster, stageContainer, commandParameters, kerberosDetails.manageIdentities()); + stageContainer.getRequestStageContainer().persist(); + } + @Override public void configureServices(Cluster cluster, Map<String, Collection<String>> serviceFilter) throws AmbariException, KerberosInvalidConfigurationException { http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/controller/OrderedRequestStageContainer.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/OrderedRequestStageContainer.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/OrderedRequestStageContainer.java new file mode 100644 index 0000000..6d8b5a3 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/OrderedRequestStageContainer.java @@ -0,0 +1,45 @@ +package org.apache.ambari.server.controller; + +import org.apache.ambari.server.AmbariException; +import org.apache.ambari.server.actionmanager.Stage; +import org.apache.ambari.server.controller.internal.RequestStageContainer; +import org.apache.ambari.server.metadata.RoleCommandOrder; +import org.apache.ambari.server.stageplanner.RoleGraph; +import org.apache.ambari.server.stageplanner.RoleGraphFactory; + +/** + * An extension of RequestStageContainer that takes the role command order into consideration when adding stages + */ +public class OrderedRequestStageContainer { + private final RoleGraphFactory roleGraphFactory; + private final RoleCommandOrder roleCommandOrder; + private final RequestStageContainer requestStageContainer; + + public OrderedRequestStageContainer(RoleGraphFactory roleGraphFactory, RoleCommandOrder roleCommandOrder, RequestStageContainer requestStageContainer) { + this.roleGraphFactory = roleGraphFactory; + this.roleCommandOrder = roleCommandOrder; + this.requestStageContainer = requestStageContainer; + } + + public void addStage(Stage stage) throws AmbariException { + RoleGraph roleGraph = roleGraphFactory.createNew(roleCommandOrder); + roleGraph.build(stage); + requestStageContainer.addStages(roleGraph.getStages()); + } + + public long getLastStageId() { + return requestStageContainer.getLastStageId(); + } + + public long getId() { + return requestStageContainer.getId(); + } + + public RequestStageContainer getRequestStageContainer() { + return requestStageContainer; + } + + public void setClusterHostInfo(String clusterHostInfo) { + this.requestStageContainer.setClusterHostInfo(clusterHostInfo); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleaner.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleaner.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleaner.java new file mode 100644 index 0000000..0a8462f --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleaner.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ambari.server.controller.utilities; + +import static org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptor.nullToEmpty; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.ambari.server.AmbariException; +import org.apache.ambari.server.controller.KerberosHelper; +import org.apache.ambari.server.events.ServiceComponentUninstalledEvent; +import org.apache.ambari.server.events.publishers.AmbariEventPublisher; +import org.apache.ambari.server.serveraction.kerberos.Component; +import org.apache.ambari.server.serveraction.kerberos.KerberosMissingAdminCredentialsException; +import org.apache.ambari.server.state.Cluster; +import org.apache.ambari.server.state.Clusters; +import org.apache.ambari.server.state.SecurityType; +import org.apache.ambari.server.state.Service; +import org.apache.ambari.server.state.kerberos.KerberosComponentDescriptor; +import org.apache.ambari.server.state.kerberos.KerberosDescriptor; +import org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor; +import org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.eventbus.Subscribe; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +@Singleton +public class KerberosIdentityCleaner { + private final static Logger LOG = LoggerFactory.getLogger(KerberosIdentityCleaner.class); + private final AmbariEventPublisher eventPublisher; + private final KerberosHelper kerberosHelper; + private final Clusters clusters; + + @Inject + public KerberosIdentityCleaner(AmbariEventPublisher eventPublisher, KerberosHelper kerberosHelper, Clusters clusters) { + this.eventPublisher = eventPublisher; + this.kerberosHelper = kerberosHelper; + this.clusters = clusters; + } + + public void register() { + this.eventPublisher.register(this); + } + + /** + * Removes kerberos identities (principals and keytabs) after a component was uninstalled. + * Keeps the identity if either the principal or the keytab is used by an other service + */ + @Subscribe + public void componentRemoved(ServiceComponentUninstalledEvent event) throws KerberosMissingAdminCredentialsException { + try { + Cluster cluster = clusters.getCluster(event.getClusterId()); + if (cluster.getSecurityType() != SecurityType.KERBEROS) { + return; + } + KerberosComponentDescriptor descriptor = componentDescriptor(cluster, event.getServiceName(), event.getComponentName()); + if (descriptor == null) { + LOG.info("No kerberos descriptor for {}", event); + return; + } + List<String> identitiesToRemove = identityNames(skipSharedIdentities(descriptor.getIdentitiesSkipReferences(), cluster, event)); + LOG.info("Deleting identities {} after an event {}", identitiesToRemove, event); + kerberosHelper.deleteIdentity(cluster, new Component(event.getHostName(), event.getServiceName(), event.getComponentName()), identitiesToRemove); + } catch (Exception e) { + LOG.error("Error while deleting kerberos identity after an event: " + event, e); + } + } + + private KerberosComponentDescriptor componentDescriptor(Cluster cluster, String serviceName, String componentName) throws AmbariException { + KerberosServiceDescriptor serviceDescriptor = kerberosHelper.getKerberosDescriptor(cluster).getService(serviceName); + return serviceDescriptor == null ? null : serviceDescriptor.getComponent(componentName); + } + + private List<String> identityNames(List<KerberosIdentityDescriptor> identities) { + List<String> result = new ArrayList<>(); + for (KerberosIdentityDescriptor each : identities) { result.add(each.getName()); } + return result; + } + + private List<KerberosIdentityDescriptor> skipSharedIdentities(List<KerberosIdentityDescriptor> candidates, Cluster cluster, ServiceComponentUninstalledEvent event) throws AmbariException { + List<KerberosIdentityDescriptor> activeIdentities = activeIdentities(cluster, kerberosHelper.getKerberosDescriptor(cluster), event); + List<KerberosIdentityDescriptor> result = new ArrayList<>(); + for (KerberosIdentityDescriptor candidate : candidates) { + if (!candidate.isShared(activeIdentities)) { + result.add(candidate); + } else { + LOG.debug("Skip removing shared identity: {}", candidate.getName()); + } + } + return result; + } + + private List<KerberosIdentityDescriptor> activeIdentities(Cluster cluster, KerberosDescriptor root, ServiceComponentUninstalledEvent event) { + List<KerberosIdentityDescriptor> result = new ArrayList<>(); + result.addAll(nullToEmpty(root.getIdentities())); + for (Map.Entry<String, Service> serviceEntry : cluster.getServices().entrySet()) { + KerberosServiceDescriptor serviceDescriptor = root.getService(serviceEntry.getKey()); + if (serviceDescriptor == null) { + continue; + } + result.addAll(nullToEmpty(serviceDescriptor.getIdentities())); + for (String componentName : serviceEntry.getValue().getServiceComponents().keySet()) { + if (!sameComponent(event, componentName, serviceEntry.getKey())) { + result.addAll(serviceDescriptor.getComponentIdentities(componentName)); + } + } + } + return result; + } + + private boolean sameComponent(ServiceComponentUninstalledEvent event, String componentName, String serviceName) { + return event.getServiceName().equals(serviceName) && event.getComponentName().equals(componentName); + } +} + http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/AbstractPrepareKerberosServerAction.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/AbstractPrepareKerberosServerAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/AbstractPrepareKerberosServerAction.java index 7aac346..dd2b223 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/AbstractPrepareKerberosServerAction.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/AbstractPrepareKerberosServerAction.java @@ -21,6 +21,7 @@ package org.apache.ambari.server.serveraction.kerberos; import java.io.File; import java.io.IOException; import java.lang.reflect.Type; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -65,7 +66,7 @@ public abstract class AbstractPrepareKerberosServerAction extends KerberosServer throw new UnsupportedOperationException(); } - KerberosHelper getKerberosHelper() { + protected KerberosHelper getKerberosHelper() { return kerberosHelper; } @@ -76,6 +77,20 @@ public abstract class AbstractPrepareKerberosServerAction extends KerberosServer Map<String, Map<String, String>> kerberosConfigurations, boolean includeAmbariIdentity, Map<String, Set<String>> propertiesToBeIgnored) throws AmbariException { + List<Component> components = new ArrayList<>(); + for (ServiceComponentHost each : schToProcess) { + components.add(Component.fromServiceComponentHost(each)); + } + processServiceComponents(cluster, kerberosDescriptor, components, identityFilter, dataDirectory, currentConfigurations, kerberosConfigurations, includeAmbariIdentity, propertiesToBeIgnored); + } + + protected void processServiceComponents(Cluster cluster, KerberosDescriptor kerberosDescriptor, + List<Component> schToProcess, + Collection<String> identityFilter, String dataDirectory, + Map<String, Map<String, String>> currentConfigurations, + Map<String, Map<String, String>> kerberosConfigurations, + boolean includeAmbariIdentity, + Map<String, Set<String>> propertiesToBeIgnored) throws AmbariException { actionLog.writeStdOut("Processing Kerberos identities and configurations"); @@ -113,7 +128,7 @@ public abstract class AbstractPrepareKerberosServerAction extends KerberosServer // Iterate over the components installed on the current host to get the service and // component-level Kerberos descriptors in order to determine which principals, // keytab files, and configurations need to be created or updated. - for (ServiceComponentHost sch : schToProcess) { + for (Component sch : schToProcess) { String hostName = sch.getHostName(); String serviceName = sch.getServiceName(); http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/Component.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/Component.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/Component.java new file mode 100644 index 0000000..4f1ee52 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/Component.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ambari.server.serveraction.kerberos; + +import org.apache.ambari.server.state.ServiceComponentHost; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + +public class Component { + private final String hostName; + private final String serviceName; + private final String serviceComponentName; + + public static Component fromServiceComponentHost(ServiceComponentHost serviceComponentHost) { + return new Component( + serviceComponentHost.getHostName(), + serviceComponentHost.getServiceName(), + serviceComponentHost.getServiceComponentName()); + } + + public Component(String hostName, String serviceName, String serviceComponentName) { + this.hostName = hostName; + this.serviceName = serviceName; + this.serviceComponentName = serviceComponentName; + } + + public String getHostName() { + return hostName; + } + + public String getServiceName() { + return serviceName; + } + + public String getServiceComponentName() { + return serviceComponentName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Component component = (Component) o; + return new EqualsBuilder() + .append(hostName, component.hostName) + .append(serviceName, component.serviceName) + .append(serviceComponentName, component.serviceComponentName) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(hostName) + .append(serviceName) + .append(serviceComponentName) + .toHashCode(); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/FinalizeKerberosServerAction.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/FinalizeKerberosServerAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/FinalizeKerberosServerAction.java index 2742390..10ad48b 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/FinalizeKerberosServerAction.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/FinalizeKerberosServerAction.java @@ -18,8 +18,6 @@ package org.apache.ambari.server.serveraction.kerberos; -import java.io.File; -import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -36,7 +34,6 @@ import org.apache.ambari.server.state.SecurityState; import org.apache.ambari.server.state.ServiceComponentHost; import org.apache.ambari.server.utils.ShellCommandUtil; import org.apache.ambari.server.utils.StageUtils; -import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -208,29 +205,9 @@ public class FinalizeKerberosServerAction extends KerberosServerAction { processIdentities(requestSharedDataContext); requestSharedDataContext.remove(this.getClass().getName() + "_visited"); } - - // Make sure this is a relevant directory. We don't want to accidentally allow _ANY_ directory - // to be deleted. - if ((dataDirectoryPath != null) && dataDirectoryPath.contains("/" + DATA_DIRECTORY_PREFIX)) { - File dataDirectory = new File(dataDirectoryPath); - File dataDirectoryParent = dataDirectory.getParentFile(); - - // Make sure this directory has a parent and it is writeable, else we wont be able to - // delete the directory - if ((dataDirectoryParent != null) && dataDirectory.isDirectory() && - dataDirectoryParent.isDirectory() && dataDirectoryParent.canWrite()) { - try { - FileUtils.deleteDirectory(dataDirectory); - } catch (IOException e) { - // We should log this exception, but don't let it fail the process since if we got to this - // KerberosServerAction it is expected that the the overall process was a success. - String message = String.format("The data directory (%s) was not deleted due to an error condition - {%s}", - dataDirectory.getAbsolutePath(), e.getMessage()); - LOG.warn(message, e); - } - } - } + deleteDataDirectory(dataDirectoryPath); return createCommandReport(0, HostRoleStatus.COMPLETED, "{}", actionLog.getStdOut(), actionLog.getStdErr()); } + } http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java index d404133..2e331bb 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java @@ -35,6 +35,7 @@ import org.apache.ambari.server.serveraction.AbstractServerAction; import org.apache.ambari.server.state.Cluster; import org.apache.ambari.server.state.Clusters; import org.apache.ambari.server.utils.StageUtils; +import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,6 +83,8 @@ public abstract class KerberosServerAction extends AbstractServerAction { */ public static final String IDENTITY_FILTER = "identity_filter"; + public static final String COMPONENT_FILTER = "component_filter"; + /** * A (command parameter) property name used to hold the relevant KDC type value. See * {@link org.apache.ambari.server.serveraction.kerberos.KDCType} for valid values @@ -536,4 +539,28 @@ public abstract class KerberosServerAction extends AbstractServerAction { return commandReport; } + + protected void deleteDataDirectory(String dataDirectoryPath) { + // Make sure this is a relevant directory. We don't want to accidentally allow _ANY_ directory + // to be deleted. + if ((dataDirectoryPath != null) && dataDirectoryPath.contains("/" + DATA_DIRECTORY_PREFIX)) { + File dataDirectory = new File(dataDirectoryPath); + File dataDirectoryParent = dataDirectory.getParentFile(); + + // Make sure this directory has a parent and it is writeable, else we wont be able to + // delete the directory + if ((dataDirectoryParent != null) && dataDirectory.isDirectory() && + dataDirectoryParent.isDirectory() && dataDirectoryParent.canWrite()) { + try { + FileUtils.deleteDirectory(dataDirectory); + } catch (IOException e) { + // We should log this exception, but don't let it fail the process since if we got to this + // KerberosServerAction it is expected that the the overall process was a success. + String message = String.format("The data directory (%s) was not deleted due to an error condition - {%s}", + dataDirectory.getAbsolutePath(), e.getMessage()); + LOG.warn(message, e); + } + } + } + } } http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptor.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptor.java b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptor.java index 397f384..38100ac 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptor.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptor.java @@ -18,6 +18,9 @@ package org.apache.ambari.server.state.kerberos; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -181,6 +184,18 @@ public abstract class AbstractKerberosDescriptor { return root; } + public static <T> Collection<T> nullToEmpty(Collection<T> collection) { + return collection == null ? Collections.<T>emptyList() : collection; + } + + public static <T> List<T> nullToEmpty(List<T> list) { + return list == null ? Collections.<T>emptyList() : list; + } + + public static <K,V> Map<K,V> nullToEmpty(Map<K,V> collection) { + return collection == null ? Collections.<K,V>emptyMap() : collection; + } + @Override public int hashCode() { return 37 * http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosComponentDescriptor.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosComponentDescriptor.java b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosComponentDescriptor.java index 768a17e..41d1f65 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosComponentDescriptor.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosComponentDescriptor.java @@ -17,7 +17,9 @@ */ package org.apache.ambari.server.state.kerberos; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; /** @@ -111,6 +113,19 @@ public class KerberosComponentDescriptor extends AbstractKerberosDescriptorConta return null; } + /** + * @return identities which are not references to other identities + */ + public List<KerberosIdentityDescriptor> getIdentitiesSkipReferences() { + List<KerberosIdentityDescriptor> result = new ArrayList<>(); + for (KerberosIdentityDescriptor each : nullToEmpty(getIdentities())) { + if (!each.getReferencedServiceName().isPresent() && each.getName() != null && !each.getName().startsWith("/")) { + result.add(each); + } + } + return result; + } + @Override public int hashCode() { return 35 * super.hashCode(); http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosDescriptor.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosDescriptor.java b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosDescriptor.java index f9dfa4a..eba1b3a 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosDescriptor.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosDescriptor.java @@ -461,12 +461,4 @@ public class KerberosDescriptor extends AbstractKerberosDescriptorContainer { } } } - - private static <T> Collection<T> nullToEmpty(Collection<T> collection) { - return collection == null ? Collections.<T>emptyList() : collection; - } - - private static <K,V> Map<K,V> nullToEmpty(Map<K,V> collection) { - return collection == null ? Collections.<K,V>emptyMap() : collection; - } } http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosIdentityDescriptor.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosIdentityDescriptor.java b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosIdentityDescriptor.java index e180f7a..2023793 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosIdentityDescriptor.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosIdentityDescriptor.java @@ -17,8 +17,10 @@ */ package org.apache.ambari.server.state.kerberos; +import java.util.List; import java.util.Map; +import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.collections.Predicate; import org.apache.ambari.server.collections.PredicateUtils; @@ -369,6 +371,34 @@ public class KerberosIdentityDescriptor extends AbstractKerberosDescriptor { } } + /** + * @return true if this identity either has the same principal or keytab as any of the given identities. + */ + public boolean isShared(List<KerberosIdentityDescriptor> identities) throws AmbariException { + for (KerberosIdentityDescriptor each : identities) { + if (hasSamePrincipal(each) || hasSameKeytab(each)) { + return true; + } + } + return false; + } + + private boolean hasSameKeytab(KerberosIdentityDescriptor that) { + try { + return this.getKeytabDescriptor().getFile().equals(that.getKeytabDescriptor().getFile()); + } catch (NullPointerException e) { + return false; + } + } + + private boolean hasSamePrincipal(KerberosIdentityDescriptor that) { + try { + return this.getPrincipalDescriptor().getValue().equals(that.getPrincipalDescriptor().getValue()); + } catch (NullPointerException e) { + return false; + } + } + @Override public int hashCode() { return super.hashCode() + http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosServiceDescriptor.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosServiceDescriptor.java b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosServiceDescriptor.java index 8507bfa..0777327 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosServiceDescriptor.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosServiceDescriptor.java @@ -272,6 +272,12 @@ public class KerberosServiceDescriptor extends AbstractKerberosDescriptorContain return map; } + public List<KerberosIdentityDescriptor> getComponentIdentities(String componentName) { + return getComponent(componentName) != null + ? nullToEmpty(getComponent(componentName).getIdentities()) + : Collections.<KerberosIdentityDescriptor>emptyList(); + } + @Override public int hashCode() { return super.hashCode() + http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/test/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleanerTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleanerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleanerTest.java new file mode 100644 index 0000000..d22c92e --- /dev/null +++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleanerTest.java @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ambari.server.controller.utilities; + +import static com.google.common.collect.Lists.newArrayList; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.reset; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.ambari.server.controller.KerberosHelper; +import org.apache.ambari.server.events.ServiceComponentUninstalledEvent; +import org.apache.ambari.server.events.publishers.AmbariEventPublisher; +import org.apache.ambari.server.serveraction.kerberos.Component; +import org.apache.ambari.server.serveraction.kerberos.KerberosMissingAdminCredentialsException; +import org.apache.ambari.server.state.Cluster; +import org.apache.ambari.server.state.Clusters; +import org.apache.ambari.server.state.SecurityType; +import org.apache.ambari.server.state.Service; +import org.apache.ambari.server.state.ServiceComponent; +import org.apache.ambari.server.state.kerberos.KerberosDescriptor; +import org.apache.ambari.server.state.kerberos.KerberosDescriptorFactory; +import org.easymock.EasyMockRule; +import org.easymock.EasyMockSupport; +import org.easymock.Mock; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public class KerberosIdentityCleanerTest extends EasyMockSupport { + @Rule public EasyMockRule mocks = new EasyMockRule(this); + private static final String HOST = "c6401"; + private static final String OOZIE = "OOZIE"; + private static final String OOZIE_SERVER = "OOZIE_SERVER"; + private static final String OOZIE_2 = "OOZIE2"; + private static final String OOZIE_SERVER_2 = "OOZIE_SERVER2"; + private static final String YARN_2 = "YARN2"; + private static final String RESOURCE_MANAGER_2 = "RESOURCE_MANAGER2"; + private static final String YARN = "YARN"; + private static final String RESOURCE_MANAGER = "RESOURCE_MANAGER"; + private static final long CLUSTER_ID = 1; + @Mock private KerberosHelper kerberosHelper; + @Mock private Clusters clusters; + @Mock private Cluster cluster; + private Map<String, Service> installedServices = new HashMap<>(); + private KerberosDescriptorFactory kerberosDescriptorFactory = new KerberosDescriptorFactory(); + private KerberosIdentityCleaner kerberosIdentityCleaner; + private KerberosDescriptor kerberosDescriptor; + + @Test + public void removesAllKerberosIdentitesOfComponentAfterComponentWasUninstalled() throws Exception { + installComponent(OOZIE, OOZIE_SERVER); + kerberosHelper.deleteIdentity(cluster, new Component(HOST, OOZIE, OOZIE_SERVER), newArrayList("oozie_server1", "oozie_server2")); + expectLastCall().once(); + replayAll(); + uninstallComponent(OOZIE, OOZIE_SERVER, HOST); + verifyAll(); + } + + @Test + public void skipsRemovingIdentityWhenServiceDoesNotExist() throws Exception { + replayAll(); + uninstallComponent("NO_SUCH_SERVICE", OOZIE_SERVER, HOST); + verifyAll(); + } + + @Test + public void skipsRemovingIdentityThatIsSharedByPrincipalName() throws Exception { + installComponent(OOZIE, OOZIE_SERVER); + installComponent(OOZIE_2, OOZIE_SERVER_2); + kerberosHelper.deleteIdentity(cluster, new Component(HOST, OOZIE, OOZIE_SERVER), newArrayList("oozie_server1")); + expectLastCall().once(); + replayAll(); + uninstallComponent(OOZIE, OOZIE_SERVER, HOST); + verifyAll(); + } + + @Test + public void skipsRemovingIdentityThatIsSharedByKeyTabFilePath() throws Exception { + installComponent(YARN, RESOURCE_MANAGER); + installComponent(YARN_2, RESOURCE_MANAGER_2); + kerberosHelper.deleteIdentity(cluster, new Component(HOST, YARN, RESOURCE_MANAGER), newArrayList("rm_unique")); + expectLastCall().once(); + replayAll(); + uninstallComponent(YARN, RESOURCE_MANAGER, HOST); + verifyAll(); + } + + @Test + public void skipsRemovingIdentityWhenClusterIsNotKerberized() throws Exception { + reset(cluster); + expect(cluster.getSecurityType()).andReturn(SecurityType.NONE).anyTimes(); + replayAll(); + uninstallComponent(OOZIE, OOZIE_SERVER, HOST); + verifyAll(); + } + + private void installComponent(String serviceName, final String componentName) { + Service service = createMock(serviceName + "_" + componentName, Service.class); + installedServices.put(serviceName, service); + expect(service.getServiceComponents()).andReturn(new HashMap<String, ServiceComponent>() {{ + put(componentName, null); + }}).anyTimes(); + } + + private void uninstallComponent(String service, String component, String host) throws KerberosMissingAdminCredentialsException { + kerberosIdentityCleaner.componentRemoved(new ServiceComponentUninstalledEvent(CLUSTER_ID, "any", "any", service, component, host, false)); + } + + @Before + public void setUp() throws Exception { + kerberosIdentityCleaner = new KerberosIdentityCleaner(new AmbariEventPublisher(), kerberosHelper, clusters); + kerberosDescriptor = kerberosDescriptorFactory.createInstance("{" + + " 'services': [" + + " {" + + " 'name': 'OOZIE'," + + " 'components': [" + + " {" + + " 'name': 'OOZIE_SERVER'," + + " 'identities': [" + + " {" + + " 'name': '/HDFS/NAMENODE/hdfs'" + + " }," + + " {" + + " 'name': 'oozie_server1'" + + " }," +"" + + " {" + + " 'name': 'oozie_server2'," + + " 'principal': { 'value': 'oozie/_h...@example.com' }" + + " }" + + " ]" + + " }" + + " ]" + + " }," + + " {" + + " 'name': 'OOZIE2'," + + " 'components': [" + + " {" + + " 'name': 'OOZIE_SERVER2'," + + " 'identities': [" + + " {" + + " 'name': 'oozie_server3'," + + " 'principal': { 'value': 'oozie/_h...@example.com' }" + + " }" +"" + + " ]" + + " }" + + " ]" + + " }," + + " {" + + " 'name': 'YARN'," + + " 'components': [" + + " {" + + " 'name': 'RESOURCE_MANAGER'," + + " 'identities': [" + + " {" + + " 'name': 'rm_unique'" + + " }," + + " {" + + " 'name': 'rm1-shared'," + + " 'keytab' : { 'file' : 'shared' }" + + " }" + + " ]" + + " }" + + " ]" + + " }," + + " {" + + " 'name': 'YARN2'," + + " 'components': [" + + " {" + + " 'name': 'RESOURCE_MANAGER2'," + + " 'identities': [" + + " {" + + " 'name': 'rm2-shared'," + + " 'keytab' : { 'file' : 'shared' }" + + " }" + + " ]" + + " }" + + " ]" + + " }" + + " ]" + + "}"); + expect(clusters.getCluster(CLUSTER_ID)).andReturn(cluster).anyTimes(); + expect(cluster.getSecurityType()).andReturn(SecurityType.KERBEROS).anyTimes(); + expect(kerberosHelper.getKerberosDescriptor(cluster)).andReturn(kerberosDescriptor).anyTimes(); + expect(cluster.getServices()).andReturn(installedServices).anyTimes(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-web/app/controllers/main/service/item.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/service/item.js b/ambari-web/app/controllers/main/service/item.js index 37713dc..197eb8e 100644 --- a/ambari-web/app/controllers/main/service/item.js +++ b/ambari-web/app/controllers/main/service/item.js @@ -1388,8 +1388,10 @@ App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDow this._super(); } }); - self.set('deleteServiceProgressPopup', progressPopup); - self.deleteServiceCall(serviceNames); + App.get('router.mainAdminKerberosController').getKDCSessionState(function() { + self.set('deleteServiceProgressPopup', progressPopup); + self.deleteServiceCall(serviceNames); + }); this._super(); },