/* * Copyright © 2015-2016 Cask Data, Inc. * * Licensed 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 co.cask.cdap.internal.app.services; import co.cask.cdap.api.ProgramSpecification; import co.cask.cdap.api.app.ApplicationSpecification; import co.cask.cdap.api.artifact.ApplicationClass; import co.cask.cdap.api.artifact.ArtifactId; import co.cask.cdap.api.artifact.ArtifactScope; import co.cask.cdap.api.artifact.ArtifactVersion; import co.cask.cdap.api.flow.FlowSpecification; import co.cask.cdap.api.flow.FlowletConnection; import co.cask.cdap.api.metrics.MetricDeleteQuery; import co.cask.cdap.api.metrics.MetricStore; import co.cask.cdap.api.schedule.SchedulableProgramType; import co.cask.cdap.api.workflow.WorkflowSpecification; import co.cask.cdap.app.deploy.Manager; import co.cask.cdap.app.deploy.ManagerFactory; import co.cask.cdap.app.program.Programs; import co.cask.cdap.app.runtime.ProgramRuntimeService; import co.cask.cdap.app.store.Store; import co.cask.cdap.common.ApplicationNotFoundException; import co.cask.cdap.common.ArtifactAlreadyExistsException; import co.cask.cdap.common.ArtifactNotFoundException; import co.cask.cdap.common.CannotBeDeletedException; import co.cask.cdap.common.InvalidArtifactException; import co.cask.cdap.common.NotFoundException; import co.cask.cdap.common.conf.CConfiguration; import co.cask.cdap.common.conf.Constants; import co.cask.cdap.common.namespace.NamespacedLocationFactory; import co.cask.cdap.config.PreferencesStore; import co.cask.cdap.data2.metadata.store.MetadataStore; import co.cask.cdap.data2.registry.UsageRegistry; import co.cask.cdap.data2.transaction.queue.QueueAdmin; import co.cask.cdap.data2.transaction.stream.StreamConsumerFactory; import co.cask.cdap.internal.app.deploy.ProgramTerminator; import co.cask.cdap.internal.app.deploy.pipeline.AppDeploymentInfo; import co.cask.cdap.internal.app.deploy.pipeline.ApplicationWithPrograms; import co.cask.cdap.internal.app.deploy.pipeline.ProgramGenerationStage; import co.cask.cdap.internal.app.runtime.artifact.ArtifactDetail; import co.cask.cdap.internal.app.runtime.artifact.ArtifactRepository; import co.cask.cdap.internal.app.runtime.artifact.WriteConflictException; import co.cask.cdap.internal.app.runtime.flow.FlowUtils; import co.cask.cdap.internal.app.runtime.schedule.Scheduler; import co.cask.cdap.proto.ApplicationDetail; import co.cask.cdap.proto.ApplicationRecord; import co.cask.cdap.proto.Id; import co.cask.cdap.proto.ProgramType; import co.cask.cdap.proto.ProgramTypes; import co.cask.cdap.proto.artifact.AppRequest; import co.cask.cdap.proto.artifact.ArtifactSummary; import co.cask.cdap.proto.id.ApplicationId; import co.cask.cdap.proto.id.Ids; import co.cask.cdap.proto.id.ProgramId; import co.cask.cdap.proto.security.Action; import co.cask.cdap.security.authorization.AuthorizerInstantiatorService; import co.cask.cdap.security.spi.authentication.SecurityRequestContext; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.util.concurrent.AbstractIdleService; import com.google.gson.Gson; import com.google.inject.Inject; import org.apache.twill.filesystem.Location; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * Service that manage lifecycle of Applications. */ public class ApplicationLifecycleService extends AbstractIdleService { private static final Logger LOG = LoggerFactory.getLogger(ApplicationLifecycleService.class); private static final Gson GSON = new Gson(); /** * Runtime program service for running and managing programs. */ private final ProgramRuntimeService runtimeService; /** * Store manages non-runtime lifecycle. */ private final Store store; private final CConfiguration configuration; private final Scheduler scheduler; private final QueueAdmin queueAdmin; private final NamespacedLocationFactory namespacedLocationFactory; private final StreamConsumerFactory streamConsumerFactory; private final UsageRegistry usageRegistry; private final PreferencesStore preferencesStore; private final MetricStore metricStore; private final ArtifactRepository artifactRepository; private final ManagerFactory<AppDeploymentInfo, ApplicationWithPrograms> managerFactory; private final MetadataStore metadataStore; private final AuthorizerInstantiatorService authorizerInstantiatorService; @Inject ApplicationLifecycleService(ProgramRuntimeService runtimeService, Store store, CConfiguration configuration, Scheduler scheduler, QueueAdmin queueAdmin, NamespacedLocationFactory namespacedLocationFactory, StreamConsumerFactory streamConsumerFactory, UsageRegistry usageRegistry, PreferencesStore preferencesStore, MetricStore metricStore, ArtifactRepository artifactRepository, ManagerFactory<AppDeploymentInfo, ApplicationWithPrograms> managerFactory, MetadataStore metadataStore, AuthorizerInstantiatorService authorizerInstantiatorService) { this.runtimeService = runtimeService; this.store = store; this.configuration = configuration; this.scheduler = scheduler; this.queueAdmin = queueAdmin; this.namespacedLocationFactory = namespacedLocationFactory; this.streamConsumerFactory = streamConsumerFactory; this.usageRegistry = usageRegistry; this.preferencesStore = preferencesStore; this.metricStore = metricStore; this.artifactRepository = artifactRepository; this.managerFactory = managerFactory; this.metadataStore = metadataStore; this.authorizerInstantiatorService = authorizerInstantiatorService; } @Override protected void startUp() throws Exception { LOG.info("Starting ApplicationLifecycleService"); } @Override protected void shutDown() throws Exception { LOG.info("Shutting down ApplicationLifecycleService"); } /** * Get all applications in the specified namespace, filtered to only include applications with an artifact name * in the set of specified names and an artifact version equal to the specified version. If the specified set * is empty, no filtering is performed on artifact name. If the specified version is null, no filtering is done * on artifact version. * * @param namespace the namespace to get apps from * @param artifactNames the set of valid artifact names. If empty, all artifact names are valid * @param artifactVersion the artifact version to match. If null, all artifact versions are valid * @return list of all applications in the namespace that match the specified artifact names and version */ public List<ApplicationRecord> getApps(Id.Namespace namespace, Set<String> artifactNames, @Nullable String artifactVersion) { return getApps(namespace, getAppPredicate(artifactNames, artifactVersion)); } /** * Get all applications in the specified namespace that satisfy the specified predicate. * * @param namespace the namespace to get apps from * @param predicate the predicate that must be satisfied in order to be returned * @return list of all applications in the namespace that satisfy the specified predicate */ public List<ApplicationRecord> getApps(Id.Namespace namespace, Predicate<ApplicationRecord> predicate) { List<ApplicationRecord> appRecords = new ArrayList<>(); for (ApplicationSpecification appSpec : store.getAllApplications(namespace)) { // possible if this particular app was deploy prior to v3.2 and upgrade failed for some reason. ArtifactId artifactId = appSpec.getArtifactId(); ArtifactSummary artifactSummary = artifactId == null ? new ArtifactSummary(appSpec.getName(), null) : ArtifactSummary.from(artifactId); ApplicationRecord record = new ApplicationRecord(artifactSummary, appSpec.getName(), appSpec.getDescription()); if (predicate.apply(record)) { appRecords.add(record); } } return appRecords; } /** * Get detail about the specified application * * @param appId the id of the application to get * @return detail about the specified application * @throws ApplicationNotFoundException if the specified application does not exist */ public ApplicationDetail getAppDetail(Id.Application appId) throws ApplicationNotFoundException { ApplicationSpecification appSpec = store.getApplication(appId); if (appSpec == null) { throw new ApplicationNotFoundException(appId); } return ApplicationDetail.fromSpec(appSpec); } /** * Update an existing application. An application's configuration and artifact version can be updated. * * @param appId the id of the application to update * @param appRequest the request to update the application, including new config and artifact * @param programTerminator a program terminator that will stop programs that are removed when updating an app. * For example, if an update removes a flow, the terminator defines how to stop that flow. * @return information about the deployed application * @throws ApplicationNotFoundException if the specified application does not exist * @throws ArtifactNotFoundException if the requested artifact does not exist * @throws InvalidArtifactException if the specified artifact is invalid. For example, if the artifact name changed, * if the version is an invalid version, or the artifact contains no app classes * @throws Exception if there was an exception during the deployment pipeline. This exception will often wrap * the actual exception */ public ApplicationWithPrograms updateApp(Id.Application appId, AppRequest appRequest, ProgramTerminator programTerminator) throws Exception { // check that app exists ApplicationSpecification currentSpec = store.getApplication(appId); if (currentSpec == null) { throw new ApplicationNotFoundException(appId); } // App exists. Check if the current user has admin privileges on it before updating. The user's write privileges on // the namespace will get enforced in the deployApp method. authorizerInstantiatorService.get().enforce(appId.toEntityId(), SecurityRequestContext.toPrincipal(), Action.ADMIN); ArtifactId currentArtifact = currentSpec.getArtifactId(); // if no artifact is given, use the current one. ArtifactId newArtifactId = currentArtifact; // otherwise, check requested artifact is valid and use it ArtifactSummary requestedArtifact = appRequest.getArtifact(); if (requestedArtifact != null) { // cannot change artifact name, only artifact version. if (!currentArtifact.getName().equals(requestedArtifact.getName())) { throw new InvalidArtifactException(String.format( " Only artifact version updates are allowed. Cannot change from artifact '%s' to '%s'.", currentArtifact.getName(), requestedArtifact.getName())); } if (!currentArtifact.getScope().equals(requestedArtifact.getScope())) { throw new InvalidArtifactException("Only artifact version updates are allowed. " + "Cannot change from a non-system artifact to a system artifact or vice versa."); } // check requested artifact version is valid ArtifactVersion requestedVersion = new ArtifactVersion(requestedArtifact.getVersion()); if (requestedVersion.getVersion() == null) { throw new InvalidArtifactException(String.format( "Requested artifact version '%s' is invalid", requestedArtifact.getVersion())); } newArtifactId = new ArtifactId(currentArtifact.getName(), requestedVersion, currentArtifact.getScope()); } Object requestedConfigObj = appRequest.getConfig(); // if config is null, use the previous config String requestedConfigStr = requestedConfigObj == null ? currentSpec.getConfiguration() : GSON.toJson(requestedConfigObj); Id.Artifact artifactId = Id.Artifact.from( newArtifactId.getScope() == ArtifactScope.SYSTEM ? Id.Namespace.SYSTEM : appId.getNamespace(), newArtifactId.getName(), newArtifactId.getVersion()); return deployApp(appId.getNamespace(), appId.getId(), artifactId, requestedConfigStr, programTerminator); } /** * Deploy an application by first adding the application jar to the artifact repository, then creating an application * using that newly added artifact. * * @param namespace the namespace to deploy the application and artifact in * @param appName the name of the app. If null, the name will be set based on the application spec * @param artifactId the id of the artifact to add and create the application from * @param jarFile the application jar to add as an artifact and create the application from * @param configStr the configuration to send to the application when generating the application specification * @param programTerminator a program terminator that will stop programs that are removed when updating an app. * For example, if an update removes a flow, the terminator defines how to stop that flow. * @return information about the deployed application * @throws WriteConflictException if there was a write conflict adding the artifact. Should be a transient error * @throws InvalidArtifactException the the artifact is invalid. For example, if it does not contain any app classes * @throws ArtifactAlreadyExistsException if the specified artifact already exists * @throws IOException if there was an IO error writing the artifact */ public ApplicationWithPrograms deployAppAndArtifact(Id.Namespace namespace, @Nullable String appName, Id.Artifact artifactId, File jarFile, @Nullable String configStr, ProgramTerminator programTerminator) throws Exception { ArtifactDetail artifactDetail = artifactRepository.addArtifact(artifactId, jarFile); try { return deployApp(namespace, appName, configStr, programTerminator, artifactDetail); } catch (Exception e) { // if we added the artifact, but failed to deploy the application, delete the artifact to bring us back // to the state we were in before this call. try { artifactRepository.deleteArtifact(artifactId); } catch (IOException e2) { // if the delete fails, nothing we can do, just log it and continue on LOG.warn("Failed to delete artifact {} after deployment of artifact and application failed.", artifactId, e2); e.addSuppressed(e2); } throw e; } } /** * Deploy an application using the specified artifact and configuration. When an app is deployed, the Application * class is instantiated and configure() is called in order to generate an {@link ApplicationSpecification}. * Programs, datasets, and streams are created based on the specification before the spec is persisted in the * {@link Store}. This method can create a new application as well as update an existing one. * * @param namespace the namespace to deploy the app to * @param appName the name of the app. If null, the name will be set based on the application spec * @param artifactId the id of the artifact to create the application from * @param configStr the configuration to send to the application when generating the application specification * @param programTerminator a program terminator that will stop programs that are removed when updating an app. * For example, if an update removes a flow, the terminator defines how to stop that flow. * @return information about the deployed application * @throws InvalidArtifactException if the artifact does not contain any application classes * @throws ArtifactNotFoundException if the specified artifact does not exist * @throws IOException if there was an IO error reading artifact detail from the meta store * @throws Exception if there was an exception during the deployment pipeline. This exception will often wrap * the actual exception */ public ApplicationWithPrograms deployApp(Id.Namespace namespace, @Nullable String appName, Id.Artifact artifactId, @Nullable String configStr, ProgramTerminator programTerminator) throws Exception { ArtifactDetail artifactDetail = artifactRepository.getArtifact(artifactId); return deployApp(namespace, appName, configStr, programTerminator, artifactDetail); } /** * Remove all the applications inside the given {@link Id.Namespace} * * @param namespaceId the {@link Id.Namespace} under which all application should be deleted * @throws Exception */ public void removeAll(final Id.Namespace namespaceId) throws Exception { List<ApplicationSpecification> allSpecs = new ArrayList<>( store.getAllApplications(namespaceId)); //Check if any program associated with this namespace is running boolean appRunning = runtimeService.checkAnyRunning(new Predicate<Id.Program>() { @Override public boolean apply(Id.Program programId) { return programId.getApplication().getNamespace().equals(namespaceId); } }, ProgramType.values()); if (appRunning) { throw new CannotBeDeletedException(namespaceId, "One of the program associated with this namespace is still " + "running"); } //All Apps are STOPPED, delete them for (ApplicationSpecification appSpec : allSpecs) { Id.Application id = Id.Application.from(namespaceId.getId(), appSpec.getName()); deleteApp(id, appSpec); } } /** * Delete an application specified by appId. * * @param appId the {@link Id.Application} of the application to be removed * @throws Exception */ public void removeApplication(final Id.Application appId) throws Exception { //Check if all are stopped. boolean appRunning = runtimeService.checkAnyRunning(new Predicate<Id.Program>() { @Override public boolean apply(Id.Program programId) { return programId.getApplication().equals(appId); } }, ProgramType.values()); if (appRunning) { throw new CannotBeDeletedException(appId); } ApplicationSpecification spec = store.getApplication(appId); if (spec == null) { throw new NotFoundException(appId); } deleteApp(appId, spec); } /** * Look up the app archive location from the store. Most of this logic is in case that jar isn't actually * there. In that case we try to find it in the expected place. * * @param appId the id of the application to find * @return the location of the jar for the application * @throws FileNotFoundException if the jar file could not be found * @throws IOException if there was some error reading from the meta store or filesystem */ private Location findAppJarLocation(Id.Application appId) throws IOException { Location recordedLocation = store.getApplicationArchiveLocation(appId); if (recordedLocation == null) { throw new FileNotFoundException(String.format( "Could not find the location of jar for app '%s' in namespace '%s' in the metastore.", appId.getId(), appId.getNamespaceId())); } if (recordedLocation.exists()) { return recordedLocation; } // bad metadata... not sure how it gets into this state but we have seen it // make an educated guess for where it could be Location expectedDirectory = ProgramGenerationStage.getAppArchiveDirLocation(configuration, appId, namespacedLocationFactory); if (expectedDirectory.exists() && expectedDirectory.isDirectory()) { // should be only one file there... expect it to start with the app name and end in .jar for (Location file : expectedDirectory.list()) { if (file.getName().startsWith(appId.getId()) && file.getName().endsWith(".jar")) { return file; } } } // if we couldn't find it there either, error out throw new FileNotFoundException(String.format( "Could not find jar for app '%s' in namespace '%s'. Expected it to be at %s.", appId.getId(), appId.getNamespaceId(), recordedLocation)); } /** * Delete the jar location of the program. * * @param appId applicationId. * @throws IOException if there are errors with location IO */ private void deleteProgramLocations(Id.Application appId) throws IOException { Iterable<ProgramSpecification> programSpecs = getProgramSpecs(appId); String appFabricDir = configuration.get(Constants.AppFabric.OUTPUT_DIR); for (ProgramSpecification spec : programSpecs) { ProgramType type = ProgramTypes.fromSpecification(spec); Id.Program programId = Id.Program.from(appId, type, spec.getName()); try { Location location = Programs.programLocation(namespacedLocationFactory, appFabricDir, programId); location.delete(); } catch (FileNotFoundException e) { LOG.warn("Program jar for program {} not found.", programId.toString(), e); } } // Delete webapp // TODO: this will go away once webapp gets a spec try { Id.Program programId = Id.Program.from(appId.getNamespaceId(), appId.getId(), ProgramType.WEBAPP, ProgramType.WEBAPP.name().toLowerCase()); Location location = Programs.programLocation(namespacedLocationFactory, appFabricDir, programId); location.delete(); } catch (FileNotFoundException e) { // expected exception when webapp is not present. } } private Iterable<ProgramSpecification> getProgramSpecs(Id.Application appId) { ApplicationSpecification appSpec = store.getApplication(appId); return Iterables.concat(appSpec.getFlows().values(), appSpec.getMapReduce().values(), appSpec.getServices().values(), appSpec.getSpark().values(), appSpec.getWorkers().values(), appSpec.getWorkflows().values()); } /** * Delete the metrics for an application, or if null is provided as the application ID, for all apps. * * @param applicationId the application to delete metrics for. * If null, metrics for all applications in the namespace are deleted. */ private void deleteMetrics(String namespaceId, String applicationId) throws Exception { Collection<ApplicationSpecification> applications = Lists.newArrayList(); if (applicationId == null) { applications = this.store.getAllApplications(new Id.Namespace(namespaceId)); } else { ApplicationSpecification spec = this.store.getApplication (new Id.Application(new Id.Namespace(namespaceId), applicationId)); applications.add(spec); } long endTs = System.currentTimeMillis() / 1000; Map<String, String> tags = Maps.newHashMap(); tags.put(Constants.Metrics.Tag.NAMESPACE, namespaceId); for (ApplicationSpecification application : applications) { // add or replace application name in the tagMap tags.put(Constants.Metrics.Tag.APP, application.getName()); MetricDeleteQuery deleteQuery = new MetricDeleteQuery(0, endTs, tags); metricStore.delete(deleteQuery); } } /** * Delete stored Preferences of the application and all its programs. * * @param appId applicationId */ private void deletePreferences(Id.Application appId) { Iterable<ProgramSpecification> programSpecs = getProgramSpecs(appId); for (ProgramSpecification spec : programSpecs) { preferencesStore.deleteProperties(appId.getNamespaceId(), appId.getId(), ProgramTypes.fromSpecification(spec).getCategoryName(), spec.getName()); LOG.trace("Deleted Preferences of Program : {}, {}, {}, {}", appId.getNamespaceId(), appId.getId(), ProgramTypes.fromSpecification(spec).getCategoryName(), spec.getName()); } preferencesStore.deleteProperties(appId.getNamespaceId(), appId.getId()); LOG.trace("Deleted Preferences of Application : {}, {}", appId.getNamespaceId(), appId.getId()); } private ApplicationWithPrograms deployApp(Id.Namespace namespace, @Nullable String appName, @Nullable String configStr, ProgramTerminator programTerminator, ArtifactDetail artifactDetail) throws Exception { // Enforce that the current principal has write access to the namespace the app is being deployed to authorizerInstantiatorService.get().enforce(namespace.toEntityId(), SecurityRequestContext.toPrincipal(), Action.WRITE); Id.Artifact artifactId = Id.Artifact.from(namespace, artifactDetail.getDescriptor().getArtifactId()); Set<ApplicationClass> appClasses = artifactDetail.getMeta().getClasses().getApps(); if (appClasses.isEmpty()) { throw new InvalidArtifactException(String.format("No application classes found in artifact '%s'.", artifactId)); } String className = appClasses.iterator().next().getClassName(); Location location = artifactDetail.getDescriptor().getLocation(); // deploy application with newly added artifact AppDeploymentInfo deploymentInfo = new AppDeploymentInfo(artifactId, className, location, configStr); Manager<AppDeploymentInfo, ApplicationWithPrograms> manager = managerFactory.create(programTerminator); // TODO: (CDAP-3258) Manager needs MUCH better error handling. ApplicationWithPrograms applicationWithPrograms = manager.deploy(namespace, appName, deploymentInfo).get(); // Deployment successful. Grant all privileges on this app to the current principal. authorizerInstantiatorService.get().grant(namespace.toEntityId().app(applicationWithPrograms.getId().getId()), SecurityRequestContext.toPrincipal(), ImmutableSet.of(Action.ALL)); return applicationWithPrograms; } // deletes without performs checks that no programs are running /** * Delete the specified application without performing checks that its programs are stopped. * * @param appId the id of the application to delete * @param spec the spec of the application to delete * @throws Exception */ private void deleteApp(Id.Application appId, ApplicationSpecification spec) throws Exception { // enfore ADMIN privileges on the app authorizerInstantiatorService.get().enforce(appId.toEntityId(), SecurityRequestContext.toPrincipal(), Action.ADMIN); // first remove all privileges on the app revokePrivileges(appId.toEntityId(), spec); //Delete the schedules for (WorkflowSpecification workflowSpec : spec.getWorkflows().values()) { Id.Program workflowProgramId = Id.Program.from(appId, ProgramType.WORKFLOW, workflowSpec.getName()); scheduler.deleteSchedules(workflowProgramId, SchedulableProgramType.WORKFLOW); } deleteMetrics(appId.getNamespaceId(), appId.getId()); //Delete all preferences of the application and of all its programs deletePreferences(appId); // Delete all streams and queues state of each flow // TODO: This should be unified with the DeletedProgramHandlerStage for (FlowSpecification flowSpecification : spec.getFlows().values()) { Id.Program flowProgramId = Id.Program.from(appId, ProgramType.FLOW, flowSpecification.getName()); // Collects stream name to all group ids consuming that stream Multimap<String, Long> streamGroups = HashMultimap.create(); for (FlowletConnection connection : flowSpecification.getConnections()) { if (connection.getSourceType() == FlowletConnection.Type.STREAM) { long groupId = FlowUtils.generateConsumerGroupId(flowProgramId, connection.getTargetName()); streamGroups.put(connection.getSourceName(), groupId); } } // Remove all process states and group states for each stream String namespace = String.format("%s.%s", flowProgramId.getApplicationId(), flowProgramId.getId()); for (Map.Entry<String, Collection<Long>> entry : streamGroups.asMap().entrySet()) { streamConsumerFactory.dropAll(Id.Stream.from(appId.getNamespaceId(), entry.getKey()), namespace, entry.getValue()); } queueAdmin.dropAllForFlow(Id.Flow.from(appId, flowSpecification.getName())); } deleteProgramLocations(appId); ApplicationSpecification appSpec = store.getApplication(appId); deleteAppMetadata(appId, appSpec); store.removeApplication(appId); try { usageRegistry.unregister(appId); } catch (Exception e) { LOG.warn("Failed to unregister usage of app: {}", appId, e); } } // TODO: CDAP-5427 - This should be a single operation private void revokePrivileges(ApplicationId appId, ApplicationSpecification appSpec) throws Exception { for (ProgramId programId : getAllPrograms(appId, appSpec)) { authorizerInstantiatorService.get().revoke(programId); } authorizerInstantiatorService.get().revoke(appId); } /** * Delete the metadata for the application and the programs. */ private void deleteAppMetadata(Id.Application appId, ApplicationSpecification appSpec) { // Remove metadata for the Application itself. metadataStore.removeMetadata(appId); // Remove metadata for the programs of the Application // TODO: Need to remove this we support prefix search of metadata type. // See https://issues.cask.co/browse/CDAP-3669 for (ProgramId programId : getAllPrograms(appId.toEntityId(), appSpec)) { metadataStore.removeMetadata(programId.toId()); } } private Set<ProgramId> getAllPrograms(ApplicationId appId, ApplicationSpecification appSpec) { Set<ProgramId> result = new HashSet<>(); Map<ProgramType, Set<String>> programTypeToNames = new HashMap<>(); programTypeToNames.put(ProgramType.FLOW, appSpec.getFlows().keySet()); programTypeToNames.put(ProgramType.MAPREDUCE, appSpec.getMapReduce().keySet()); programTypeToNames.put(ProgramType.WORKFLOW, appSpec.getWorkflows().keySet()); programTypeToNames.put(ProgramType.SERVICE, appSpec.getServices().keySet()); programTypeToNames.put(ProgramType.SPARK, appSpec.getSpark().keySet()); programTypeToNames.put(ProgramType.WORKER, appSpec.getWorkers().keySet()); for (Map.Entry<ProgramType, Set<String>> entry : programTypeToNames.entrySet()) { Set<String> programNames = entry.getValue(); for (String programName : programNames) { result.add(Ids.namespace(appId.getNamespace()) .app(appId.getApplication()) .program(entry.getKey(), programName)); } } return result; } // get filter for app specs by artifact name and version. if they are null, it means don't filter. private Predicate<ApplicationRecord> getAppPredicate(Set<String> artifactNames, @Nullable String artifactVersion) { if (artifactNames.isEmpty() && artifactVersion == null) { return Predicates.alwaysTrue(); } else if (artifactNames.isEmpty()) { return new ArtifactVersionPredicate(artifactVersion); } else if (artifactVersion == null) { return new ArtifactNamesPredicate(artifactNames); } else { return Predicates.and(new ArtifactNamesPredicate(artifactNames), new ArtifactVersionPredicate(artifactVersion)); } } /** * Returns true if the application artifact is in a whitelist of names */ private static class ArtifactNamesPredicate implements Predicate<ApplicationRecord> { private final Set<String> names; public ArtifactNamesPredicate(Set<String> names) { this.names = names; } @Override public boolean apply(ApplicationRecord input) { return names.contains(input.getArtifact().getName()); } } /** * Returns true if the application artifact is a specific version */ private static class ArtifactVersionPredicate implements Predicate<ApplicationRecord> { private final String version; public ArtifactVersionPredicate(String version) { this.version = version; } @Override public boolean apply(ApplicationRecord input) { return version.equals(input.getArtifact().getVersion()); } } }