package com.thinkbiganalytics.feedmgr.nifi;
/*-
* #%L
* thinkbig-feed-manager-controller
* %%
* Copyright (C) 2017 ThinkBig Analytics
* %%
* 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.
* #L%
*/
import com.thinkbiganalytics.nifi.rest.client.LegacyNifiRestClient;
import com.thinkbiganalytics.nifi.rest.client.layout.AlignProcessGroupComponents;
import com.thinkbiganalytics.nifi.rest.client.layout.ProcessGroupAndConnections;
import com.thinkbiganalytics.nifi.rest.model.NiFiPropertyDescriptorTransform;
import com.thinkbiganalytics.nifi.rest.support.NifiConstants;
import com.thinkbiganalytics.nifi.rest.support.NifiTemplateNameUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.web.api.dto.PortDTO;
import org.apache.nifi.web.api.dto.ProcessGroupDTO;
import org.apache.nifi.web.api.dto.status.ProcessGroupStatusDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/**
* This code will find all Process groups matching the Version name of <Name> - {13 digit timestamp} and if the group doesn't have anything in its Queue, it will delete it from NiFi
*/
public class CleanupStaleFeedRevisions {
private static final Logger log = LoggerFactory.getLogger(CleanupStaleFeedRevisions.class);
LegacyNifiRestClient restClient;
/**
* the name of the parent group to look at or the word 'all' to access everything
**/
private String processGroupId;
private Set<PortDTO> stoppedPorts = new HashSet<>();
private Set<ProcessGroupDTO> deletedProcessGroups = new HashSet<>();
private NiFiPropertyDescriptorTransform propertyDescriptorTransform;
/**
* Cleanup versioned process groups that are no longer active
*
* @param restClient the nifi rest client
* @param processGroupId a parent process group (i.e. a category) to look at, or the word 'all' to clean up everything
* @param propertyDescriptorTransform the transformation bean
*/
public CleanupStaleFeedRevisions(LegacyNifiRestClient restClient, String processGroupId, NiFiPropertyDescriptorTransform propertyDescriptorTransform) {
this.processGroupId = processGroupId;
this.restClient = restClient;
this.propertyDescriptorTransform = propertyDescriptorTransform;
}
/**
* Cleanup all versioned feed process groups
* if the {@link this#processGroupId} == 'all' then it will clean up everything, otherwise it will cleanup just the children under the {@link this#processGroupId}
*/
public void cleanup() {
deletedProcessGroups.clear();
Set<String> categoriesToCleanup = new HashSet<>();
if ("all".equalsIgnoreCase(processGroupId)) {
ProcessGroupDTO root = restClient.getNiFiRestClient().processGroups().findRoot();
root.getContents().getProcessGroups().stream().forEach(categoryGroup -> {
categoriesToCleanup.add(categoryGroup.getId());
});
} else {
categoriesToCleanup.add(processGroupId);
}
categoriesToCleanup.stream().forEach(categoryProcessGroupId -> doCleanup(categoryProcessGroupId));
log.info("Successfully Cleaned up versioned ProcessGroups, deleting {} groups ", deletedProcessGroups.size());
}
/**
* Return the list of groups that were deleted as part of the {@link this#cleanup()} activity
*
* @return the list of groups that were deleted as part of the {@link this#cleanup()} activity
*/
public Set<ProcessGroupDTO> getDeletedProcessGroups() {
return deletedProcessGroups;
}
private void doCleanup(String processGroupId) {
stoppedPorts.clear();
AlignProcessGroupComponents alignProcessGroupComponents = new AlignProcessGroupComponents(restClient.getNiFiRestClient(), processGroupId);
alignProcessGroupComponents.groupItems();
final Map<String, ProcessGroupAndConnections> groups = alignProcessGroupComponents.getProcessGroupWithConnectionsMap();
Set<ProcessGroupAndConnections> deletedItems = new HashSet<>();
groups.values().stream()
.filter(groupAndConnections -> NifiTemplateNameUtil.isVersionedProcessGroup(groupAndConnections.getProcessGroup().getName()))
.filter(groupAndConnections -> canDelete(groupAndConnections.getProcessGroup()))
.forEach(groupAndConnections -> {
cleanup(groupAndConnections);
deletedItems.add(groupAndConnections);
});
startPorts();
//relayout the group
if (!deletedItems.isEmpty()) {
new AlignProcessGroupComponents(restClient.getNiFiRestClient(), processGroupId).autoLayout();
}
}
private void cleanup(ProcessGroupAndConnections groupAndConnections) {
//stop the ports
stopPorts(groupAndConnections.getPorts());
groupAndConnections.getConnections().stream().forEach(connectionDTO -> restClient.deleteConnection(connectionDTO, false));
log.info("About to delete {}", groupAndConnections.getProcessGroup().getName());
restClient.deleteProcessGroup(groupAndConnections.getProcessGroup());
deletedProcessGroups.add(groupAndConnections.getProcessGroup());
}
private void stopPorts(Set<PortDTO> ports) {
if (!ports.isEmpty()) {
ports.stream()
.filter(portDTO -> !stoppedPorts.contains(portDTO))
.forEach(portDTO1 -> {
if (isInputPort(portDTO1)) {
restClient.stopInputPort(portDTO1.getParentGroupId(), portDTO1.getId());
stoppedPorts.add(portDTO1);
} else {
restClient.stopOutputPort(portDTO1.getParentGroupId(), portDTO1.getId());
stoppedPorts.add(portDTO1);
}
});
}
}
private void startPorts() {
Set<PortDTO> startedPorts = new HashSet<>();
if (!stoppedPorts.isEmpty()) {
stoppedPorts.stream()
.forEach(portDTO1 -> {
if (isInputPort(portDTO1)) {
restClient.startInputPort(portDTO1.getParentGroupId(), portDTO1.getId());
startedPorts.add(portDTO1);
} else {
restClient.startOutputPort(portDTO1.getParentGroupId(), portDTO1.getId());
startedPorts.add(portDTO1);
}
});
}
stoppedPorts.removeAll(startedPorts);
}
private boolean isInputPort(PortDTO portDTO) {
return NifiConstants.NIFI_PORT_TYPE.INPUT_PORT.name().equalsIgnoreCase(portDTO.getType());
}
private boolean isOutputPort(PortDTO portDTO) {
return NifiConstants.NIFI_PORT_TYPE.OUTPUT_PORT.name().equalsIgnoreCase(portDTO.getType());
}
private boolean canDelete(ProcessGroupDTO groupDTO) {
Optional<ProcessGroupStatusDTO> statusDTO = restClient.getNiFiRestClient().processGroups().getStatus(groupDTO.getId());
if (statusDTO.isPresent()) {
return !hasItemsInQueue(statusDTO.get());
} else {
return false;
}
}
private boolean hasItemsInQueue(ProcessGroupStatusDTO statusDTO) {
String queuedCount = propertyDescriptorTransform.getQueuedCount(statusDTO);
return StringUtils.isNotBlank(queuedCount) && !queuedCount.equalsIgnoreCase("0");
}
}