NIFI-11122 Corrected provided parameter context inheritance after creation

This closes #6913

Signed-off-by: David Handermann <exceptionfactory@apache.org>
This commit is contained in:
Joe Gresock 2023-02-01 10:30:23 -05:00 committed by exceptionfactory
parent 027e2b9bc1
commit 2609fd5f55
No known key found for this signature in database
GPG Key ID: 29B6A52D2AAE8DBA
5 changed files with 185 additions and 4 deletions

View File

@ -348,7 +348,22 @@ public class StandardParameterProviderNode extends AbstractComponentNode impleme
if (groupConfiguration == null) {
continue;
}
reference.verifyCanSetParameters(getFetchedParameterUpdateMap(reference, groupConfiguration));
final Map<String, Parameter> parameterUpdates = getFetchedParameterUpdateMap(reference, groupConfiguration);
final Collection<String> removedParametersWithReferences = new HashSet<>();
for (final Map.Entry<String, Parameter> entry : parameterUpdates.entrySet()) {
final String parameterName = entry.getKey();
if (entry.getValue() == null) {
reference.getParameter(parameterName).ifPresent(currentParameter -> {
if (reference.hasReferencingComponents(currentParameter)) {
removedParametersWithReferences.add(parameterName);
}
});
}
}
// Any deletions of parameters with referencing components should be removed from consideration,
// since we do not remove provided parameters that are deleted until they are no longer referenced
removedParametersWithReferences.forEach(parameterUpdates::remove);
reference.verifyCanSetParameters(parameterUpdates);
}
} finally {
readLock.unlock();

View File

@ -640,7 +640,8 @@ public class StandardParameterContext implements ParameterContext {
// If a current parameter is not in the proposed parameters, it was effectively removed
if (!effectiveProposedParameters.containsKey(currentParameterDescriptor)) {
final Parameter parameter = entry.getValue();
if (parameter.isProvided() && hasReferencingComponents(parameter)) {
final boolean isParameterInherited = !parameter.getParameterContextId().equals(id);
if (!isParameterInherited && parameter.isProvided() && hasReferencingComponents(parameter)) {
logger.info("Provided parameter [{}] was removed from the source, but it is referenced by a component, so the parameter will be preserved",
currentParameterDescriptor.getName());
} else {
@ -651,7 +652,8 @@ public class StandardParameterContext implements ParameterContext {
return effectiveParameterUpdates;
}
private boolean hasReferencingComponents(final Parameter parameter) {
@Override
public boolean hasReferencingComponents(final Parameter parameter) {
if (parameter == null) {
return false;
}

View File

@ -174,6 +174,12 @@ public interface ParameterContext extends ParameterLookup, ComponentAuthorizable
*/
List<String> getInheritedParameterContextNames();
/**
* @param parameter A parameter
* @return True if the parameter has referencing components
*/
boolean hasReferencingComponents(Parameter parameter);
/**
* Returns true if this ParameterContext inherits from the given parameter context, either
* directly or indirectly.

View File

@ -337,7 +337,8 @@ public class StandardParameterContextDAO implements ParameterContextDAO {
}
} else {
final boolean hasProvidedParameters = parameters.stream()
.anyMatch(parameter -> parameter.getParameter().getProvided() != null && parameter.getParameter().getProvided());
.anyMatch(parameter -> parameter.getParameter().getProvided() != null && parameter.getParameter().getProvided()
&& !parameter.getParameter().getInherited());
if (hasProvidedParameters) {
throw new IllegalArgumentException(String.format("Provided Parameters may not be set on Context [%s] because its " +
"parameters can only be user-entered", parameterContextDTO.getName()));

View File

@ -0,0 +1,157 @@
/*
* 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.nifi.web.dao.impl;
import org.apache.nifi.authorization.AuthorizationRequest;
import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserDetails;
import org.apache.nifi.authorization.user.StandardNiFiUser;
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.parameter.Parameter;
import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.parameter.ParameterDescriptor;
import org.apache.nifi.parameter.ParameterReferenceManager;
import org.apache.nifi.parameter.StandardParameterContext;
import org.apache.nifi.parameter.StandardParameterContextManager;
import org.apache.nifi.parameter.StandardParameterReferenceManager;
import org.apache.nifi.web.api.dto.ParameterContextDTO;
import org.apache.nifi.web.api.dto.ParameterContextReferenceDTO;
import org.apache.nifi.web.api.dto.ParameterDTO;
import org.apache.nifi.web.api.entity.ParameterContextReferenceEntity;
import org.apache.nifi.web.api.entity.ParameterEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class TestStandardParameterContextDAO {
private StandardParameterContextDAO dao;
@Mock(answer = RETURNS_DEEP_STUBS)
private FlowController flowController;
@Mock
private Authentication authentication;
@Mock
private Authorizer authorizer;
@BeforeEach
void setUp() {
dao = new StandardParameterContextDAO();
dao.setFlowController(flowController);
dao.setAuthorizer(authorizer);
when(authorizer.authorize(any(AuthorizationRequest.class))).thenReturn(AuthorizationResult.approved());
final SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(authentication);
final NiFiUser user = new StandardNiFiUser.Builder().identity("user").build();
final NiFiUserDetails userDetail = new NiFiUserDetails(user);
when(authentication.getPrincipal()).thenReturn(userDetail);
final ParameterReferenceManager parameterReferenceManager = new StandardParameterReferenceManager(flowController.getFlowManager());
final FlowManager flowManager = flowController.getFlowManager();
final StandardParameterContextManager parameterContextLookup = new StandardParameterContextManager();
when(flowManager.getParameterContextManager()).thenReturn(parameterContextLookup);
parameterContextLookup.addParameterContext(new StandardParameterContext.Builder().id("id")
.name("Context")
.parameterReferenceManager(parameterReferenceManager)
.build());
final ParameterContext inheritedContext = new StandardParameterContext.Builder().id("inherited-id")
.parameterReferenceManager(parameterReferenceManager)
.name("Inherited")
.build();
final Map<String, Parameter> parameters = new HashMap<>();
parameters.put("inherited-param", new Parameter(new ParameterDescriptor.Builder().name("inherited-param").build(),
"value", null, true));
inheritedContext.setParameters(parameters);
parameterContextLookup.addParameterContext(inheritedContext);
}
@Test
public void testVerifyUpdateInheritedProvidedParameter() {
final ParameterContextDTO dto = new ParameterContextDTO();
dto.setId("id");
dto.setName("Context");
final List<ParameterContextReferenceEntity> refs = new ArrayList<>();
final ParameterContextReferenceEntity ref = new ParameterContextReferenceEntity();
ref.setId("inherited-id");
ref.setComponent(new ParameterContextReferenceDTO());
ref.getComponent().setId("inherited-id");
ref.getComponent().setName("Inherited");
refs.add(ref);
dto.setInheritedParameterContexts(refs);
// Updating a provided parameter that is inherited is allowed
dao.verifyUpdate(dto, true);
}
@Test
public void testVerifyUpdateNonInheritedProvidedParameter() {
final ParameterContextDTO dto = new ParameterContextDTO();
dto.setId("id");
dto.setName("Context");
final Set<ParameterEntity> parameters = new HashSet<>();
final ParameterEntity parameter = new ParameterEntity();
parameter.setCanWrite(true);
final ParameterDTO parameterDto = new ParameterDTO();
parameterDto.setProvided(true);
parameterDto.setName("param");
parameterDto.setValue("value");
parameterDto.setInherited(false);
parameter.setParameter(parameterDto);
parameters.add(parameter);
dto.setParameters(parameters);
final List<ParameterContextReferenceEntity> refs = new ArrayList<>();
final ParameterContextReferenceEntity ref = new ParameterContextReferenceEntity();
ref.setId("inherited-id");
ref.setComponent(new ParameterContextReferenceDTO());
ref.getComponent().setId("inherited-id");
ref.getComponent().setName("Inherited");
refs.add(ref);
dto.setInheritedParameterContexts(refs);
// Updating a provided parameter that is not inherited should fail
assertThrows(IllegalArgumentException.class, () -> dao.verifyUpdate(dto, true));
}
}