Skip to content

Commit

Permalink
NIFI-3785 Added feature to move a controller service to it's parent o…
Browse files Browse the repository at this point in the history
…r child process group
  • Loading branch information
Freedom9339 committed Nov 6, 2023
1 parent 97dd543 commit 958c591
Show file tree
Hide file tree
Showing 8 changed files with 508 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2063,6 +2063,16 @@ ControllerServiceReferencingComponentsEntity updateControllerServiceReferencingC
*/
ControllerServiceEntity updateControllerService(Revision revision, ControllerServiceDTO controllerServiceDTO);

/**
* Moves the specified controller service.
*
* @param revision Revision to compare with current base revision
* @param controllerServiceDTO The controller service DTO
* @param newProcessGroupID The id of the process group the controller service is being moved to
* @return The controller service DTO
*/
ControllerServiceEntity moveControllerService(final Revision revision, final ControllerServiceDTO controllerServiceDTO, final String newProcessGroupID);

/**
* Performs verification of the given Configuration for the Controller Service with the given ID
* @param controllerServiceId the id of the controller service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2937,6 +2937,119 @@ public ControllerServiceEntity updateControllerService(final Revision revision,
return entityFactory.createControllerServiceEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, bulletinEntities);
}

@Override
public ControllerServiceEntity moveControllerService(final Revision revision, final ControllerServiceDTO controllerServiceDTO, final String newProcessGroupID) {
// get the component, ensure we have access to it, and perform the move request
final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(controllerServiceDTO.getId());
final RevisionUpdate<ControllerServiceDTO> snapshot = updateComponent(revision,
controllerService,
() -> moveControllerServiceWork(controllerService, newProcessGroupID),
cs -> {
awaitValidationCompletion(cs);
final ControllerServiceDTO dto = dtoFactory.createControllerServiceDto(cs);
final ControllerServiceReference ref = controllerService.getReferences();
final ControllerServiceReferencingComponentsEntity referencingComponentsEntity = createControllerServiceReferencingComponentsEntity(ref);
dto.setReferencingComponents(referencingComponentsEntity.getControllerServiceReferencingComponents());
return dto;
});

final PermissionsDTO permissions = dtoFactory.createPermissionsDto(controllerService);
final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(controllerService));
final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(controllerServiceDTO.getId()));
final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
return entityFactory.createControllerServiceEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, bulletinEntities);
}

private ControllerServiceNode moveControllerServiceWork(final ControllerServiceNode controllerService, final String newProcessGroupID) {
final ProcessGroup oldParentGroup = controllerService.getProcessGroup();
Set<ComponentNode> referencedComponents = controllerService.getReferences().getReferencingComponents();
isReferencesDisabled(referencedComponents);
oldParentGroup.removeControllerService(controllerService);
ProcessGroup newParent;
if (!oldParentGroup.isRootGroup() && oldParentGroup.getParent().getIdentifier().equals(newProcessGroupID)) {
// move to parent process group
newParent = oldParentGroup.getParent();
newParent.addControllerService(controllerService);

// unset any references the controller services has to other controller services that are now out of scope
Map<String, String> updatedProps = new HashMap<>();
Set<Map.Entry<PropertyDescriptor, PropertyConfiguration>> properties = controllerService.getProperties().entrySet();
for (var prop : properties) {
var value = prop.getValue();
if (value !=null) {
ControllerServiceNode controller;
try {
controller = controllerServiceDAO.getControllerService((value.getRawValue()));
} catch (Exception e){
continue;
}
if (controller != null) {
if (!hasProcessGroup(controller.getProcessGroup(), newParent.getIdentifier())) {
controller.removeReference(controllerService, prop.getKey());
updatedProps.put(prop.getKey().getName(), null);
}
}
}
}
if (!updatedProps.isEmpty())
controllerService.setProperties(updatedProps, true, Collections.emptySet());

} else {
// move to child process group
newParent = oldParentGroup.getProcessGroup(newProcessGroupID);
newParent.addControllerService(controllerService);

// unset any references for processors that are outside the new scope
for (ComponentNode node : referencedComponents) {
if (!hasProcessGroup(newParent, node.getProcessGroupIdentifier())) {
Set<Map.Entry<PropertyDescriptor, PropertyConfiguration>> properties = node.getProperties().entrySet();
Map<String, String> updatedProps = new HashMap<>();
for (Map.Entry<PropertyDescriptor, PropertyConfiguration> prop : properties) {
final PropertyConfiguration value = prop.getValue();
if (value != null && value.getRawValue().equals(controllerService.getIdentifier())) {
controllerService.removeReference(node, prop.getKey());
node.getComponent().onPropertyModified(prop.getKey(), controllerService.getIdentifier(), null);
updatedProps.put(prop.getKey().getName(), null);
}
}
node.setProperties(updatedProps, true, Collections.emptySet());
}
}
}

return controllerService;
}
private boolean hasProcessGroup(ProcessGroup root, String id){
if (root.getProcessGroup(id) != null || root.getIdentifier().equals(id)) {
return true;
}

for (ProcessGroup child : root.getProcessGroups()) {
if (hasProcessGroup(child, id)) {
return true;
}
}

return false;
}

private void isReferencesDisabled(Set<ComponentNode> nodes) {
for (ComponentNode node : nodes) {
if (node instanceof ProcessorNode processorNode) {
if (processorNode.isRunning())
throw new IllegalStateException("Cannot move controller service. Referenced processor " + processorNode + " is running");
}
if (node instanceof ControllerServiceNode controllerNode) {
if (controllerNode.isActive())
throw new IllegalStateException("Cannot move controller service. Referenced controller services " + controllerNode + " is enabled");
}
if (node instanceof ReportingTaskNode taskNode) {
if (taskNode.isRunning())
throw new IllegalStateException("Cannot move controller service. Referenced task " + taskNode + " is running");
}
}
}

@Override
public List<ConfigVerificationResultDTO> performControllerServiceConfigVerification(final String controllerServiceId, final Map<String, String> properties, final Map<String, String> variables) {
return controllerServiceDAO.verifyConfiguration(controllerServiceId, properties, variables);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import org.apache.nifi.authorization.AuthorizeParameterReference;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.ComponentAuthorizable;
import org.apache.nifi.authorization.ProcessGroupAuthorizable;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.OperationAuthorizable;
Expand Down Expand Up @@ -692,6 +693,109 @@ public Response updateControllerService(
);
}

/**
* Moves the specified Controller Service to paren/child process groups.
*
* @param httpServletRequest request
* @param id The id of the controller service to update.
* @param requestControllerServiceEntity A controllerServiceEntity.
* @return A controllerServiceEntity.
*/
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}/move")
@ApiOperation(value = "Move Controller Service to the specified Process Group.",
response = ControllerServiceEntity.class,
authorizations = {
@Authorization(value = "Read - /flow"),
@Authorization(value = "Write - /{component-type}/{uuid} or /operation/{component-type}/{uuid} - For source and destination process groups")
})
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response moveControllerServices(
@Context HttpServletRequest httpServletRequest,
@ApiParam(value = "The controller service id.", required = true)
@PathParam("id") String id,
@ApiParam(value = "The controller service entity", required = true)
final ControllerServiceEntity requestControllerServiceEntity) {

if (requestControllerServiceEntity == null) {
throw new IllegalArgumentException("Controller service must be specified.");
}

if (requestControllerServiceEntity.getRevision() == null) {
throw new IllegalArgumentException("Revision must be specified.");
}

if (requestControllerServiceEntity.getParentGroupId() == null) {
throw new IllegalArgumentException("ParentGroupId must be specified.");
}

final ControllerServiceDTO requestControllerServiceDTO = serviceFacade.getControllerService(id, true).getComponent();
ControllerServiceState requestControllerServiceState = null;
try {
requestControllerServiceState = ControllerServiceState.valueOf(requestControllerServiceDTO.getState());
} catch (final IllegalArgumentException iae) {
// ignore
}

// ensure an action has been specified
if (requestControllerServiceState == null) {
throw new IllegalArgumentException("Must specify the updated state. To update the referencing Controller Services the "
+ "state should be DISABLED.");
}

// ensure the controller service state is not DISABLING
if (!ControllerServiceState.DISABLED.equals(requestControllerServiceState) ) {
throw new IllegalArgumentException("Cannot set the referencing services to ENABLING or DISABLING");
}

final Revision requestRevision = getRevision(requestControllerServiceEntity, id);
return withWriteLock(
serviceFacade,
serviceFacade.getControllerService(id, true),
requestRevision,
lookup -> {
// authorize the service
final ComponentAuthorizable authorizable = lookup.getControllerService(requestControllerServiceDTO.getId());
authorizable.getAuthorizable().authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());

// authorize the current and new process groups
final ProcessGroupAuthorizable authorizableProcessGroupNew = lookup.getProcessGroup(requestControllerServiceEntity.getParentGroupId());
authorizableProcessGroupNew.getAuthorizable().authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());

final ProcessGroupAuthorizable authorizableProcessGroupOld = lookup.getProcessGroup(requestControllerServiceDTO.getParentGroupId());
authorizableProcessGroupOld.getAuthorizable().authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());

// authorize any referenced services
AuthorizeControllerServiceReference.authorizeControllerServiceReferences(requestControllerServiceDTO.getProperties(), authorizable, authorizer, lookup);
AuthorizeParameterReference.authorizeParameterReferences(requestControllerServiceDTO.getProperties(), authorizer, authorizable.getParameterContext(),
NiFiUserUtils.getNiFiUser());

// authorize referencing components
requestControllerServiceDTO.getReferencingComponents().forEach(e -> {
final Authorizable controllerService = lookup.getControllerServiceReferencingComponent(requestControllerServiceDTO.getId(), e.getId());
OperationAuthorizable.authorizeOperation(controllerService, authorizer, NiFiUserUtils.getNiFiUser());
});
},
null,
(revision, controllerServiceEntity) -> {
final ControllerServiceDTO controllerService = controllerServiceEntity.getComponent();
final ControllerServiceEntity entity = serviceFacade.moveControllerService(revision, controllerService, requestControllerServiceEntity.getParentGroupId());
populateRemainingControllerServiceEntityContent(entity);
return generateOkResponse(entity).build();
}
);
}

/**
* Removes the specified controller service.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
<jsp:include page="/WEB-INF/partials/canvas/search-users-dialog.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/disable-controller-service-dialog.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/enable-controller-service-dialog.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/move-controller-service-dialog.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/fetch-parameters-dialog.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/new-controller-service-dialog.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/new-reporting-task-dialog.jsp"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<%--
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.
--%>
<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
<div id="move-controller-service-dialog" layout="column" class="hidden large-dialog">
<div class="dialog-content">
<div class="settings-left">
<div id="move-controller-service-service-container" class="setting">
<div class="setting-name">Service</div>
<div class="setting-field">
<span id="move-controller-service-id" class="hidden"></span>
<div id="move-controller-service-name"></div>
<div id="move-controller-service-bulletins"></div>
<div class="clear"></div>
</div>
</div>
<div id="move-controller-service-scope-container" class="setting">
<div class="setting-name">Move To</div>
<div class="setting-field">
<div id="move-controller-service-scope"></div>
</div>
</div>
</div>
<div class="spacer">&nbsp;</div>
<div class="settings-right">
<div class="setting">
<div class="setting-name">
Referencing Components
<div class="fa fa-question-circle" alt="Info" title="Other components referencing this controller service."></div>
</div>
<div class="setting-field">
<div id="move-controller-service-referencing-components"></div>
</div>
</div>
</div>
</div>
<div class="controller-service-canceling hidden unset">
Canceling...
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -217,20 +217,55 @@ div.controller-service-canceling {
white-space: nowrap;
}

/*
Move dialog
*/

#move-controller-service-scope-container {
height: 48px;
}

#move-controller-service-name {
float: left;
max-width: 280px;
text-overflow: ellipsis;
}

#move-controller-service-bulletins {
float: left;
margin-left: 5px;
width: 12px;
height: 12px;
background-color: transparent;
display: none;
}

#move-controller-service-scope {
float: left;
width: 225px;
}

#move-controller-service-referencing-components {
border: 0 solid #ccc;
padding: 2px;
overflow: auto;
white-space: nowrap;
}

/* enable, disable progress */

div.disable-referencing-components, div.enable-referencing-components {
div.disable-referencing-components, div.enable-referencing-components, div.move-referencing-components {
width: 16px;
height: 16px;
background-color: transparent;
float: right;
}

#enable-controller-service-progress, #disable-controller-service-progress {
#enable-controller-service-progress, #disable-controller-service-progress, #move-controller-service-progress {
margin-top: 2px;
}

#enable-controller-service-progress li, #disable-controller-service-progress li {
#enable-controller-service-progress li, #disable-controller-service-progress li, #move-controller-service-progress li {
line-height: 16px;
}

Expand Down
Loading

0 comments on commit 958c591

Please sign in to comment.