NIFI-11514 Flow JSON support and deprecating YAML format. Revised parameter generation. Generic refactors.

Signed-off-by: Ferenc Erdei <erdei.ferenc90@gmail.com>
This closes #7344
This commit is contained in:
Ferenc Kis 2023-04-25 14:25:29 +02:00 committed by Ferenc Erdei
parent 0d119f6f39
commit 3aa02a022e
No known key found for this signature in database
GPG Key ID: 023D856C60E92F96
334 changed files with 9097 additions and 26937 deletions

View File

@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.apache.nifi.c2.client.api; package org.apache.nifi.c2.client.api;
import java.util.Optional; import java.util.Optional;
@ -47,7 +48,15 @@ public interface C2Client {
* @param callbackUrl url where the content should be downloaded from * @param callbackUrl url where the content should be downloaded from
* @return the actual downloaded content. Will be empty if no content can be downloaded * @return the actual downloaded content. Will be empty if no content can be downloaded
*/ */
Optional<byte[]> retrieveUpdateContent(String callbackUrl); Optional<byte[]> retrieveUpdateConfigurationContent(String callbackUrl);
/**
* Retrieve the asset from the C2 Server
*
* @param callbackUrl url where the asset should be downloaded from
* @return the actual downloaded asset. Will be empty if no content can be downloaded
*/
Optional<byte[]> retrieveUpdateAssetContent(String callbackUrl);
/** /**
* Uploads a binary bundle to C2 server * Uploads a binary bundle to C2 server

View File

@ -14,8 +14,17 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.apache.nifi.c2.client; package org.apache.nifi.c2.client;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toUnmodifiableMap;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import java.util.Arrays;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
/** /**
* Configuration for a C2 Client. * Configuration for a C2 Client.
*/ */
@ -42,6 +51,7 @@ public class C2ClientConfig {
private final String truststoreType; private final String truststoreType;
private final long callTimeout; private final long callTimeout;
private final long readTimeout; private final long readTimeout;
private final Map<String, String> httpHeaders;
private final long connectTimeout; private final long connectTimeout;
private final int maxIdleConnections; private final int maxIdleConnections;
private final long keepAliveDuration; private final long keepAliveDuration;
@ -71,6 +81,7 @@ public class C2ClientConfig {
this.truststoreType = builder.truststoreType; this.truststoreType = builder.truststoreType;
this.readTimeout = builder.readTimeout; this.readTimeout = builder.readTimeout;
this.connectTimeout = builder.connectTimeout; this.connectTimeout = builder.connectTimeout;
this.httpHeaders = builder.httpHeaders;
this.maxIdleConnections = builder.maxIdleConnections; this.maxIdleConnections = builder.maxIdleConnections;
this.keepAliveDuration = builder.keepAliveDuration; this.keepAliveDuration = builder.keepAliveDuration;
this.c2RequestCompression = builder.c2RequestCompression; this.c2RequestCompression = builder.c2RequestCompression;
@ -165,6 +176,10 @@ public class C2ClientConfig {
return connectTimeout; return connectTimeout;
} }
public Map<String, String> getHttpHeaders() {
return httpHeaders;
}
public String getC2RequestCompression() { public String getC2RequestCompression() {
return c2RequestCompression; return c2RequestCompression;
} }
@ -186,6 +201,9 @@ public class C2ClientConfig {
*/ */
public static class Builder { public static class Builder {
private static final String HTTP_HEADERS_SEPARATOR = "#";
private static final String HTTP_HEADER_KEY_VALUE_SEPARATOR = ":";
private String c2Url; private String c2Url;
private String c2AckUrl; private String c2AckUrl;
private String c2RestPathBase; private String c2RestPathBase;
@ -208,6 +226,7 @@ public class C2ClientConfig {
private String truststoreType; private String truststoreType;
private long readTimeout; private long readTimeout;
private long connectTimeout; private long connectTimeout;
private Map<String, String> httpHeaders;
private int maxIdleConnections; private int maxIdleConnections;
private long keepAliveDuration; private long keepAliveDuration;
private String c2RequestCompression; private String c2RequestCompression;
@ -227,10 +246,12 @@ public class C2ClientConfig {
this.c2RestPathBase = c2RestPathBase; this.c2RestPathBase = c2RestPathBase;
return this; return this;
} }
public Builder c2RestPathHeartbeat(String c2RestPathHeartbeat) { public Builder c2RestPathHeartbeat(String c2RestPathHeartbeat) {
this.c2RestPathHeartbeat = c2RestPathHeartbeat; this.c2RestPathHeartbeat = c2RestPathHeartbeat;
return this; return this;
} }
public Builder c2RestPathAcknowledge(String c2RestPathAcknowledge) { public Builder c2RestPathAcknowledge(String c2RestPathAcknowledge) {
this.c2RestPathAcknowledge = c2RestPathAcknowledge; this.c2RestPathAcknowledge = c2RestPathAcknowledge;
return this; return this;
@ -321,6 +342,21 @@ public class C2ClientConfig {
return this; return this;
} }
public Builder httpHeaders(String httpHeaders) {
this.httpHeaders = ofNullable(httpHeaders)
.filter(StringUtils::isNotBlank)
.map(headers -> headers.split(HTTP_HEADERS_SEPARATOR))
.stream()
.flatMap(Arrays::stream)
.map(String::trim)
.map(header -> header.split(HTTP_HEADER_KEY_VALUE_SEPARATOR))
.filter(split -> split.length == 2)
.collect(toUnmodifiableMap(
split -> ofNullable(split[0]).map(String::trim).orElse(EMPTY),
split -> ofNullable(split[1]).map(String::trim).orElse(EMPTY)));
return this;
}
public Builder maxIdleConnections(int maxIdleConnections) { public Builder maxIdleConnections(int maxIdleConnections) {
this.maxIdleConnections = maxIdleConnections; this.maxIdleConnections = maxIdleConnections;
return this; return this;

View File

@ -20,8 +20,10 @@ package org.apache.nifi.c2.client.http;
import static okhttp3.MultipartBody.FORM; import static okhttp3.MultipartBody.FORM;
import java.io.IOException; import java.io.IOException;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import okhttp3.Headers;
import okhttp3.MediaType; import okhttp3.MediaType;
import okhttp3.MultipartBody; import okhttp3.MultipartBody;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -84,33 +86,13 @@ public class C2HttpClient implements C2Client {
} }
@Override @Override
public Optional<byte[]> retrieveUpdateContent(String callbackUrl) { public Optional<byte[]> retrieveUpdateConfigurationContent(String callbackUrl) {
Optional<byte[]> updateContent = Optional.empty(); return retrieveContent(callbackUrl, clientConfig.getHttpHeaders());
Request.Builder requestBuilder = new Request.Builder()
.get()
.url(callbackUrl);
Request request = requestBuilder.build();
try (Response response = httpClientReference.get().newCall(request).execute()) {
Optional<ResponseBody> body = Optional.ofNullable(response.body());
if (!response.isSuccessful()) {
StringBuilder messageBuilder = new StringBuilder(String.format("Configuration retrieval failed: HTTP %d", response.code()));
body.map(Object::toString).ifPresent(messageBuilder::append);
throw new C2ServerException(messageBuilder.toString());
} }
if (body.isPresent()) { @Override
updateContent = Optional.of(body.get().bytes()); public Optional<byte[]> retrieveUpdateAssetContent(String callbackUrl) {
} else { return retrieveContent(callbackUrl, Map.of());
logger.warn("No body returned when pulling a new configuration");
}
} catch (Exception e) {
logger.warn("Configuration retrieval failed", e);
}
return updateContent;
} }
@Override @Override
@ -181,4 +163,34 @@ public class C2HttpClient implements C2Client {
logger.error("Could not transmit ack to C2 server {}", c2UrlProvider.getAcknowledgeUrl(), e); logger.error("Could not transmit ack to C2 server {}", c2UrlProvider.getAcknowledgeUrl(), e);
} }
} }
private Optional<byte[]> retrieveContent(String callbackUrl, Map<String, String> httpHeaders) {
Optional<byte[]> content = Optional.empty();
Request.Builder requestBuilder = new Request.Builder()
.get()
.headers(Headers.of(httpHeaders))
.url(callbackUrl);
Request request = requestBuilder.build();
try (Response response = httpClientReference.get().newCall(request).execute()) {
Optional<ResponseBody> body = Optional.ofNullable(response.body());
if (!response.isSuccessful()) {
StringBuilder messageBuilder = new StringBuilder(String.format("Update content retrieval failed: HTTP %d", response.code()));
body.map(Object::toString).ifPresent(messageBuilder::append);
throw new C2ServerException(messageBuilder.toString());
}
if (body.isPresent()) {
content = Optional.of(body.get().bytes());
} else {
logger.warn("No body returned when pulling new content");
}
} catch (Exception e) {
logger.warn("Update content retrieval failed", e);
}
return content;
}
} }

View File

@ -124,7 +124,7 @@ public class C2HttpClientTest {
mockWebServer.enqueue(new MockResponse().setBody("updateContent").setResponseCode(HTTP_STATUS_BAD_REQUEST)); mockWebServer.enqueue(new MockResponse().setBody("updateContent").setResponseCode(HTTP_STATUS_BAD_REQUEST));
C2HttpClient c2HttpClient = C2HttpClient.create(c2ClientConfig, serializer); C2HttpClient c2HttpClient = C2HttpClient.create(c2ClientConfig, serializer);
Optional<byte[]> response = c2HttpClient.retrieveUpdateContent(baseUrl + UPDATE_PATH); Optional<byte[]> response = c2HttpClient.retrieveUpdateConfigurationContent(baseUrl + UPDATE_PATH);
assertFalse(response.isPresent()); assertFalse(response.isPresent());
@ -138,7 +138,7 @@ public class C2HttpClientTest {
mockWebServer.enqueue(new MockResponse().setBody(content).setResponseCode(HTTP_STATUS_OK)); mockWebServer.enqueue(new MockResponse().setBody(content).setResponseCode(HTTP_STATUS_OK));
C2HttpClient c2HttpClient = C2HttpClient.create(c2ClientConfig, serializer); C2HttpClient c2HttpClient = C2HttpClient.create(c2ClientConfig, serializer);
Optional<byte[]> response = c2HttpClient.retrieveUpdateContent(baseUrl + UPDATE_PATH); Optional<byte[]> response = c2HttpClient.retrieveUpdateConfigurationContent(baseUrl + UPDATE_PATH);
assertTrue(response.isPresent()); assertTrue(response.isPresent());
assertArrayEquals(content.getBytes(StandardCharsets.UTF_8), response.get()); assertArrayEquals(content.getBytes(StandardCharsets.UTF_8), response.get());

View File

@ -33,8 +33,9 @@ import static org.apache.nifi.c2.protocol.api.OperationType.TRANSFER;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -45,9 +46,11 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipUtils;
import org.apache.nifi.c2.client.api.C2Client; import org.apache.nifi.c2.client.api.C2Client;
import org.apache.nifi.c2.protocol.api.C2Operation; import org.apache.nifi.c2.protocol.api.C2Operation;
import org.apache.nifi.c2.protocol.api.C2OperationAck; import org.apache.nifi.c2.protocol.api.C2OperationAck;
@ -124,11 +127,11 @@ public class TransferDebugOperationHandler implements C2OperationHandler {
return operationAck(operation, operationState(NOT_APPLIED, C2_CALLBACK_URL_NOT_FOUND)); return operationAck(operation, operationState(NOT_APPLIED, C2_CALLBACK_URL_NOT_FOUND));
} }
List<Path> contentFilteredFilePaths = null; List<Path> preparedFiles = null;
C2OperationState operationState; C2OperationState operationState;
try { try {
contentFilteredFilePaths = filterContent(operation.getIdentifier(), bundleFilePaths); preparedFiles = prepareFiles(operation.getIdentifier(), bundleFilePaths);
operationState = createDebugBundle(contentFilteredFilePaths) operationState = createDebugBundle(preparedFiles)
.map(bundle -> c2Client.uploadBundle(callbackUrl.get(), bundle) .map(bundle -> c2Client.uploadBundle(callbackUrl.get(), bundle)
.map(errorMessage -> operationState(NOT_APPLIED, errorMessage)) .map(errorMessage -> operationState(NOT_APPLIED, errorMessage))
.orElseGet(() -> operationState(FULLY_APPLIED, SUCCESSFUL_UPLOAD))) .orElseGet(() -> operationState(FULLY_APPLIED, SUCCESSFUL_UPLOAD)))
@ -137,7 +140,7 @@ public class TransferDebugOperationHandler implements C2OperationHandler {
LOG.error("Unexpected error happened", e); LOG.error("Unexpected error happened", e);
operationState = operationState(NOT_APPLIED, UNABLE_TO_CREATE_BUNDLE); operationState = operationState(NOT_APPLIED, UNABLE_TO_CREATE_BUNDLE);
} finally { } finally {
ofNullable(contentFilteredFilePaths).ifPresent(this::cleanup); ofNullable(preparedFiles).ifPresent(this::cleanup);
} }
LOG.debug("Returning operation ack for operation {} with state {} and details {}", operation.getIdentifier(), operationState.getState(), operationState.getDetails()); LOG.debug("Returning operation ack for operation {} with state {} and details {}", operation.getIdentifier(), operationState.getState(), operationState.getDetails());
@ -158,21 +161,40 @@ public class TransferDebugOperationHandler implements C2OperationHandler {
return state; return state;
} }
private List<Path> filterContent(String operationId, List<Path> bundleFilePaths) { private List<Path> prepareFiles(String operationId, List<Path> bundleFilePaths) throws IOException {
List<Path> contentFilteredFilePaths = new ArrayList<>(); List<Path> preparedFiles = new ArrayList<>();
for (Path path : bundleFilePaths) { for (Path bundleFile : bundleFilePaths) {
String fileName = path.getFileName().toString();
try (Stream<String> fileStream = lines(path, Charset.defaultCharset())) {
Path tempDirectory = createTempDirectory(operationId); Path tempDirectory = createTempDirectory(operationId);
Path tempFile = Paths.get(tempDirectory.toAbsolutePath().toString(), fileName); String fileName = bundleFile.getFileName().toString();
Files.write(tempFile, (Iterable<String>) fileStream.filter(contentFilter)::iterator);
contentFilteredFilePaths.add(tempFile); Path preparedFile = GzipUtils.isCompressedFilename(fileName)
? handleGzipFile(bundleFile, Paths.get(tempDirectory.toAbsolutePath().toString(), GzipUtils.getUncompressedFilename(fileName)))
: handleUncompressedFile(bundleFile, Paths.get(tempDirectory.toAbsolutePath().toString(), fileName));
preparedFiles.add(preparedFile);
}
return preparedFiles;
}
private Path handleGzipFile(Path sourceFile, Path targetFile) throws IOException {
try (GZIPInputStream gzipInputStream = new GZIPInputStream(new FileInputStream(sourceFile.toFile()));
FileOutputStream fileOutputStream = new FileOutputStream(targetFile.toFile())) {
// no content filter is applied here as flow.json.gz has encoded properties
gzipInputStream.transferTo(fileOutputStream);
return targetFile;
} catch (IOException e) { } catch (IOException e) {
LOG.error("Error during filtering file content: " + path.toAbsolutePath(), e); LOG.error("Error during filtering gzip file content: " + sourceFile.toAbsolutePath(), e);
throw new UncheckedIOException(e); throw e;
} }
} }
return contentFilteredFilePaths;
private Path handleUncompressedFile(Path sourceFile, Path targetFile) throws IOException {
try (Stream<String> fileStream = lines(sourceFile, Charset.defaultCharset())) {
Files.write(targetFile, (Iterable<String>) fileStream.filter(contentFilter)::iterator);
return targetFile;
} catch (IOException e) {
LOG.error("Error during filtering uncompressed file content: " + sourceFile.toAbsolutePath(), e);
throw e;
}
} }
private Optional<byte[]> createDebugBundle(List<Path> filePaths) { private Optional<byte[]> createDebugBundle(List<Path> filePaths) {

View File

@ -123,7 +123,7 @@ public class UpdateAssetOperationHandler implements C2OperationHandler {
LOG.info("Initiating asset update from url {} with name {}, force update is {}", callbackUrl, assetFileName, forceDownload); LOG.info("Initiating asset update from url {} with name {}, force update is {}", callbackUrl, assetFileName, forceDownload);
C2OperationState operationState = assetUpdatePrecondition.test(assetFileName, forceDownload) C2OperationState operationState = assetUpdatePrecondition.test(assetFileName, forceDownload)
? c2Client.retrieveUpdateContent(callbackUrl.get()) ? c2Client.retrieveUpdateAssetContent(callbackUrl.get())
.map(content -> assetPersistFunction.apply(assetFileName, content) .map(content -> assetPersistFunction.apply(assetFileName, content)
? operationState(FULLY_APPLIED, SUCCESSFULLY_UPDATE_ASSET) ? operationState(FULLY_APPLIED, SUCCESSFULLY_UPDATE_ASSET)
: operationState(NOT_APPLIED, FAILED_TO_PERSIST_ASSET_TO_DISK)) : operationState(NOT_APPLIED, FAILED_TO_PERSIST_ASSET_TO_DISK))

View File

@ -29,7 +29,6 @@ import static org.apache.nifi.c2.protocol.api.OperationType.UPDATE;
import java.net.URI; import java.net.URI;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.apache.nifi.c2.client.api.C2Client; import org.apache.nifi.c2.client.api.C2Client;
@ -43,22 +42,26 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class UpdateConfigurationOperationHandler implements C2OperationHandler { public class UpdateConfigurationOperationHandler implements C2OperationHandler {
private static final Logger logger = LoggerFactory.getLogger(UpdateConfigurationOperationHandler.class);
private static final Pattern FLOW_ID_PATTERN = Pattern.compile("/[^/]+?/[^/]+?/[^/]+?/([^/]+)?/?.*");
static final String FLOW_ID = "flowId";
static final String LOCATION = "location";
public static final String FLOW_URL_KEY = "flowUrl"; public static final String FLOW_URL_KEY = "flowUrl";
public static final String FLOW_RELATIVE_URL_KEY = "relativeFlowUrl"; public static final String FLOW_RELATIVE_URL_KEY = "relativeFlowUrl";
static final String FLOW_ID = "flowId";
static final String LOCATION = "location";
private static final Logger logger = LoggerFactory.getLogger(UpdateConfigurationOperationHandler.class);
private static final Pattern FLOW_ID_PATTERN = Pattern.compile("/[^/]+?/[^/]+?/[^/]+?/([^/]+)?/?.*");
private final C2Client client; private final C2Client client;
private final Function<byte[], Boolean> updateFlow; private final UpdateConfigurationStrategy updateConfigurationStrategy;
private final FlowIdHolder flowIdHolder; private final FlowIdHolder flowIdHolder;
private final OperandPropertiesProvider operandPropertiesProvider; private final OperandPropertiesProvider operandPropertiesProvider;
public UpdateConfigurationOperationHandler(C2Client client, FlowIdHolder flowIdHolder, Function<byte[], Boolean> updateFlow, public UpdateConfigurationOperationHandler(C2Client client, FlowIdHolder flowIdHolder, UpdateConfigurationStrategy updateConfigurationStrategy,
OperandPropertiesProvider operandPropertiesProvider) { OperandPropertiesProvider operandPropertiesProvider) {
this.client = client; this.client = client;
this.updateFlow = updateFlow; this.updateConfigurationStrategy = updateConfigurationStrategy;
this.flowIdHolder = flowIdHolder; this.flowIdHolder = flowIdHolder;
this.operandPropertiesProvider = operandPropertiesProvider; this.operandPropertiesProvider = operandPropertiesProvider;
} }
@ -80,7 +83,7 @@ public class UpdateConfigurationOperationHandler implements C2OperationHandler {
@Override @Override
public boolean requiresRestart() { public boolean requiresRestart() {
return true; return false;
} }
@Override @Override
@ -108,19 +111,22 @@ public class UpdateConfigurationOperationHandler implements C2OperationHandler {
logger.info("Will perform flow update from {} for operation #{}. Previous flow id was {}, replacing with new id {}", logger.info("Will perform flow update from {} for operation #{}. Previous flow id was {}, replacing with new id {}",
callbackUrl, operationId, ofNullable(flowIdHolder.getFlowId()).orElse("not set"), flowId); callbackUrl, operationId, ofNullable(flowIdHolder.getFlowId()).orElse("not set"), flowId);
C2OperationState state = updateFlow(operationId, callbackUrl.get());
if (state.getState() == FULLY_APPLIED) {
flowIdHolder.setFlowId(flowId); flowIdHolder.setFlowId(flowId);
return operationAck(operationId, updateFlow(operationId, callbackUrl.get())); }
return operationAck(operationId, state);
} }
private C2OperationState updateFlow(String opIdentifier, String callbackUrl) { private C2OperationState updateFlow(String opIdentifier, String callbackUrl) {
Optional<byte[]> updateContent = client.retrieveUpdateContent(callbackUrl); Optional<byte[]> updateContent = client.retrieveUpdateConfigurationContent(callbackUrl);
if (!updateContent.isPresent()) { if (!updateContent.isPresent()) {
logger.error("Update content retrieval resulted in empty content so flow update was omitted for operation #{}.", opIdentifier); logger.error("Update content retrieval resulted in empty content so flow update was omitted for operation #{}.", opIdentifier);
return operationState(NOT_APPLIED, "Update content retrieval resulted in empty content"); return operationState(NOT_APPLIED, "Update content retrieval resulted in empty content");
} }
if (!updateFlow.apply(updateContent.get())) { if (!updateConfigurationStrategy.update(updateContent.get())) {
logger.error("Update resulted in error for operation #{}.", opIdentifier); logger.error("Update resulted in error for operation #{}.", opIdentifier);
return operationState(NOT_APPLIED, "Update resulted in error"); return operationState(NOT_APPLIED, "Update resulted in error");
} }

View File

@ -15,19 +15,19 @@
* limitations under the License. * limitations under the License.
*/ */
package org.apache.nifi.minifi.c2.provider.nifi.rest; package org.apache.nifi.c2.client.service.operation;
import org.junit.jupiter.api.Test; /**
* Common interface for classes implementing approach for MiNiFi flow update
*/
@FunctionalInterface
public interface UpdateConfigurationStrategy {
import java.io.IOException; /**
* Updates the MiNiFi agent's flow with the flow passed as parameter
import static org.junit.jupiter.api.Assertions.assertEquals; *
import static org.mockito.Mockito.mock; * @param flow the MiNiFi flow config JSON represented as a byte array
* @return true if the flow update was true, false otherwise
public class TemplatesIteratorExceptionTest { */
@Test boolean update(byte[] flow);
public void testCauseConstructor() {
IOException ioException = mock(IOException.class);
assertEquals(ioException, new TemplatesIteratorException(ioException).getCause());
}
} }

View File

@ -160,7 +160,7 @@ public class UpdateAssetOperationHandlerTest {
// given // given
C2Operation operation = operation(ASSET_URL, ASSET_FILE_NAME, FORCE_DOWNLOAD); C2Operation operation = operation(ASSET_URL, ASSET_FILE_NAME, FORCE_DOWNLOAD);
when(assetUpdatePrecondition.test(ASSET_FILE_NAME, parseBoolean(FORCE_DOWNLOAD))).thenReturn(TRUE); when(assetUpdatePrecondition.test(ASSET_FILE_NAME, parseBoolean(FORCE_DOWNLOAD))).thenReturn(TRUE);
when(c2Client.retrieveUpdateContent(ASSET_URL)).thenReturn(empty()); when(c2Client.retrieveUpdateAssetContent(ASSET_URL)).thenReturn(empty());
// when // when
C2OperationAck result = testHandler.handle(operation); C2OperationAck result = testHandler.handle(operation);
@ -177,7 +177,7 @@ public class UpdateAssetOperationHandlerTest {
C2Operation operation = operation(ASSET_URL, ASSET_FILE_NAME, FORCE_DOWNLOAD); C2Operation operation = operation(ASSET_URL, ASSET_FILE_NAME, FORCE_DOWNLOAD);
when(assetUpdatePrecondition.test(ASSET_FILE_NAME, parseBoolean(FORCE_DOWNLOAD))).thenReturn(TRUE); when(assetUpdatePrecondition.test(ASSET_FILE_NAME, parseBoolean(FORCE_DOWNLOAD))).thenReturn(TRUE);
byte[] mockUpdateContent = new byte[0]; byte[] mockUpdateContent = new byte[0];
when(c2Client.retrieveUpdateContent(ASSET_URL)).thenReturn(Optional.of(mockUpdateContent)); when(c2Client.retrieveUpdateAssetContent(ASSET_URL)).thenReturn(Optional.of(mockUpdateContent));
when(assetPersistFunction.apply(ASSET_FILE_NAME, mockUpdateContent)).thenReturn(FALSE); when(assetPersistFunction.apply(ASSET_FILE_NAME, mockUpdateContent)).thenReturn(FALSE);
// when // when
@ -195,7 +195,7 @@ public class UpdateAssetOperationHandlerTest {
C2Operation operation = operation(ASSET_URL, ASSET_FILE_NAME, FORCE_DOWNLOAD); C2Operation operation = operation(ASSET_URL, ASSET_FILE_NAME, FORCE_DOWNLOAD);
when(assetUpdatePrecondition.test(ASSET_FILE_NAME, parseBoolean(FORCE_DOWNLOAD))).thenReturn(TRUE); when(assetUpdatePrecondition.test(ASSET_FILE_NAME, parseBoolean(FORCE_DOWNLOAD))).thenReturn(TRUE);
byte[] mockUpdateContent = new byte[0]; byte[] mockUpdateContent = new byte[0];
when(c2Client.retrieveUpdateContent(ASSET_URL)).thenReturn(Optional.of(mockUpdateContent)); when(c2Client.retrieveUpdateAssetContent(ASSET_URL)).thenReturn(Optional.of(mockUpdateContent));
when(assetPersistFunction.apply(ASSET_FILE_NAME, mockUpdateContent)).thenReturn(TRUE); when(assetPersistFunction.apply(ASSET_FILE_NAME, mockUpdateContent)).thenReturn(TRUE);
// when // when

View File

@ -30,7 +30,6 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function;
import org.apache.nifi.c2.client.api.C2Client; import org.apache.nifi.c2.client.api.C2Client;
import org.apache.nifi.c2.client.service.FlowIdHolder; import org.apache.nifi.c2.client.service.FlowIdHolder;
import org.apache.nifi.c2.protocol.api.C2Operation; import org.apache.nifi.c2.protocol.api.C2Operation;
@ -82,9 +81,9 @@ public class UpdateConfigurationOperationHandlerTest {
@Test @Test
void testHandleFlowIdInArg() { void testHandleFlowIdInArg() {
Function<byte[], Boolean> successUpdate = x -> true; UpdateConfigurationStrategy successUpdate = flow -> true;
when(flowIdHolder.getFlowId()).thenReturn(FLOW_ID); when(flowIdHolder.getFlowId()).thenReturn(FLOW_ID);
when(client.retrieveUpdateContent(any())).thenReturn(Optional.of("content".getBytes())); when(client.retrieveUpdateConfigurationContent(any())).thenReturn(Optional.of("content".getBytes()));
when(client.getCallbackUrl(any(), any())).thenReturn(Optional.of(INCORRECT_LOCATION)); when(client.getCallbackUrl(any(), any())).thenReturn(Optional.of(INCORRECT_LOCATION));
UpdateConfigurationOperationHandler handler = new UpdateConfigurationOperationHandler(client, flowIdHolder, successUpdate, operandPropertiesProvider); UpdateConfigurationOperationHandler handler = new UpdateConfigurationOperationHandler(client, flowIdHolder, successUpdate, operandPropertiesProvider);
C2Operation operation = new C2Operation(); C2Operation operation = new C2Operation();
@ -117,9 +116,9 @@ public class UpdateConfigurationOperationHandlerTest {
@Test @Test
void testHandleReturnsNotAppliedWithContentApplyIssues() { void testHandleReturnsNotAppliedWithContentApplyIssues() {
Function<byte[], Boolean> failedToUpdate = x -> false; UpdateConfigurationStrategy failedToUpdate = flow -> false;
when(flowIdHolder.getFlowId()).thenReturn("previous_flow_id"); when(flowIdHolder.getFlowId()).thenReturn("previous_flow_id");
when(client.retrieveUpdateContent(any())).thenReturn(Optional.of("content".getBytes())); when(client.retrieveUpdateConfigurationContent(any())).thenReturn(Optional.of("content".getBytes()));
when(client.getCallbackUrl(any(), any())).thenReturn(Optional.of(CORRECT_LOCATION)); when(client.getCallbackUrl(any(), any())).thenReturn(Optional.of(CORRECT_LOCATION));
UpdateConfigurationOperationHandler handler = new UpdateConfigurationOperationHandler(client, flowIdHolder, failedToUpdate, operandPropertiesProvider); UpdateConfigurationOperationHandler handler = new UpdateConfigurationOperationHandler(client, flowIdHolder, failedToUpdate, operandPropertiesProvider);
C2Operation operation = new C2Operation(); C2Operation operation = new C2Operation();
@ -134,10 +133,10 @@ public class UpdateConfigurationOperationHandlerTest {
@Test @Test
void testHandleReturnsFullyApplied() { void testHandleReturnsFullyApplied() {
Function<byte[], Boolean> successUpdate = x -> true; UpdateConfigurationStrategy successUpdate = flow -> true;
when(flowIdHolder.getFlowId()).thenReturn("previous_flow_id"); when(flowIdHolder.getFlowId()).thenReturn("previous_flow_id");
when(client.getCallbackUrl(any(), any())).thenReturn(Optional.of(CORRECT_LOCATION)); when(client.getCallbackUrl(any(), any())).thenReturn(Optional.of(CORRECT_LOCATION));
when(client.retrieveUpdateContent(any())).thenReturn(Optional.of("content".getBytes())); when(client.retrieveUpdateConfigurationContent(any())).thenReturn(Optional.of("content".getBytes()));
UpdateConfigurationOperationHandler handler = new UpdateConfigurationOperationHandler(client, flowIdHolder, successUpdate, operandPropertiesProvider); UpdateConfigurationOperationHandler handler = new UpdateConfigurationOperationHandler(client, flowIdHolder, successUpdate, operandPropertiesProvider);
C2Operation operation = new C2Operation(); C2Operation operation = new C2Operation();
operation.setIdentifier(OPERATION_ID); operation.setIdentifier(OPERATION_ID);

View File

@ -39,7 +39,7 @@ Specific goals for MiNiFi are comprised of:
Perspectives of the role of MiNiFi should be from the perspective of the agent acting immediately at, or directly adjacent to, source sensors, systems, or servers. Perspectives of the role of MiNiFi should be from the perspective of the agent acting immediately at, or directly adjacent to, source sensors, systems, or servers.
## Requirements ## Requirements
* JRE 1.8 * JRE 17
## Getting Started ## Getting Started
@ -55,7 +55,7 @@ To run MiNiFi:
- View the logs located in the logs folder - View the logs located in the logs folder
$ tail -F ~/example-minifi-deploy/logs/minifi-app.log $ tail -F ~/example-minifi-deploy/logs/minifi-app.log
- For help building your first data flow and sending data to a NiFi instance see the System Admin Guide located in the docs folder or making use of the minifi-toolkit, which aids in adapting NiFi templates to MiNiFi YAML configuration file format. - For help building your first data flow and sending data to a NiFi instance see the System Admin Guide located in the docs folder or making use of the minifi-toolkit, which aids in converting legacy YAML templates to the new JSON configuration file format.
## Getting Help ## Getting Help
If you have questions, you can reach out to our mailing list: dev@nifi.apache.org If you have questions, you can reach out to our mailing list: dev@nifi.apache.org

View File

@ -79,13 +79,13 @@ limitations under the License.
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.nifi.minifi</groupId> <groupId>org.apache.nifi.minifi</groupId>
<artifactId>minifi-commons-schema</artifactId> <artifactId>minifi-commons-api</artifactId>
<version>${project.version}</version> <version>2.0.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.nifi.minifi</groupId> <groupId>org.apache.nifi.minifi</groupId>
<artifactId>minifi-commons-api</artifactId> <artifactId>minifi-commons-framework</artifactId>
<version>${project.version}</version> <version>2.0.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>

View File

@ -46,6 +46,8 @@ import org.apache.nifi.minifi.bootstrap.service.ReloadService;
import org.apache.nifi.minifi.bootstrap.util.ProcessUtils; import org.apache.nifi.minifi.bootstrap.util.ProcessUtils;
import org.apache.nifi.minifi.bootstrap.util.UnixProcessUtils; import org.apache.nifi.minifi.bootstrap.util.UnixProcessUtils;
import org.apache.nifi.minifi.commons.api.MiNiFiCommandState; import org.apache.nifi.minifi.commons.api.MiNiFiCommandState;
import org.apache.nifi.minifi.commons.service.FlowEnrichService;
import org.apache.nifi.properties.ApplicationProperties;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -71,7 +73,6 @@ public class RunMiNiFi implements ConfigurationFileHolder {
public static final Logger DEFAULT_LOGGER = LoggerFactory.getLogger(RunMiNiFi.class); public static final Logger DEFAULT_LOGGER = LoggerFactory.getLogger(RunMiNiFi.class);
public static final String CONF_DIR_KEY = "conf.dir"; public static final String CONF_DIR_KEY = "conf.dir";
public static final String MINIFI_CONFIG_FILE_KEY = "nifi.minifi.config";
public static final String STATUS_FILE_PID_KEY = "pid"; public static final String STATUS_FILE_PID_KEY = "pid";
public static final int UNINITIALIZED = -1; public static final int UNINITIALIZED = -1;
private static final String STATUS_FILE_PORT_KEY = "port"; private static final String STATUS_FILE_PORT_KEY = "port";
@ -115,7 +116,8 @@ public class RunMiNiFi implements ConfigurationFileHolder {
MiNiFiStatusProvider miNiFiStatusProvider = new MiNiFiStatusProvider(miNiFiCommandSender, processUtils); MiNiFiStatusProvider miNiFiStatusProvider = new MiNiFiStatusProvider(miNiFiCommandSender, processUtils);
periodicStatusReporterManager = periodicStatusReporterManager =
new PeriodicStatusReporterManager(bootstrapProperties, miNiFiStatusProvider, miNiFiCommandSender, miNiFiParameters); new PeriodicStatusReporterManager(bootstrapProperties, miNiFiStatusProvider, miNiFiCommandSender, miNiFiParameters);
MiNiFiConfigurationChangeListener configurationChangeListener = new MiNiFiConfigurationChangeListener(this, DEFAULT_LOGGER, bootstrapFileProvider); MiNiFiConfigurationChangeListener configurationChangeListener = new MiNiFiConfigurationChangeListener(this, DEFAULT_LOGGER, bootstrapFileProvider,
new FlowEnrichService(new ApplicationProperties(bootstrapProperties)));
configurationChangeCoordinator = new ConfigurationChangeCoordinator(bootstrapFileProvider, this, singleton(configurationChangeListener)); configurationChangeCoordinator = new ConfigurationChangeCoordinator(bootstrapFileProvider, this, singleton(configurationChangeListener));
currentPortProvider = new CurrentPortProvider(miNiFiCommandSender, miNiFiParameters, processUtils); currentPortProvider = new CurrentPortProvider(miNiFiCommandSender, miNiFiParameters, processUtils);

View File

@ -18,27 +18,21 @@
package org.apache.nifi.minifi.bootstrap.command; package org.apache.nifi.minifi.bootstrap.command;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static org.apache.nifi.minifi.commons.api.MiNiFiCommandState.FULLY_APPLIED;
import static org.apache.nifi.minifi.commons.api.MiNiFiCommandState.NOT_APPLIED_WITH_RESTART;
import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.CMD_LOGGER; import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.CMD_LOGGER;
import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.CONF_DIR_KEY; import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.CONF_DIR_KEY;
import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.DEFAULT_LOGGER; import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.DEFAULT_LOGGER;
import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.MINIFI_CONFIG_FILE_KEY;
import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.STATUS_FILE_PID_KEY; import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.STATUS_FILE_PID_KEY;
import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.UNINITIALIZED; import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.UNINITIALIZED;
import static org.apache.nifi.minifi.bootstrap.Status.ERROR; import static org.apache.nifi.minifi.bootstrap.Status.ERROR;
import static org.apache.nifi.minifi.bootstrap.Status.OK; import static org.apache.nifi.minifi.bootstrap.Status.OK;
import static org.apache.nifi.minifi.bootstrap.util.ConfigTransformer.asByteArrayInputStream; import static org.apache.nifi.minifi.commons.api.MiNiFiCommandState.FULLY_APPLIED;
import static org.apache.nifi.minifi.bootstrap.util.ConfigTransformer.generateConfigFiles; import static org.apache.nifi.minifi.commons.api.MiNiFiCommandState.NOT_APPLIED_WITH_RESTART;
import static org.apache.nifi.minifi.commons.api.MiNiFiConstants.RAW_EXTENSION;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Properties; import java.util.Properties;
@ -46,6 +40,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.io.FilenameUtils;
import org.apache.nifi.minifi.bootstrap.MiNiFiParameters; import org.apache.nifi.minifi.bootstrap.MiNiFiParameters;
import org.apache.nifi.minifi.bootstrap.RunMiNiFi; import org.apache.nifi.minifi.bootstrap.RunMiNiFi;
import org.apache.nifi.minifi.bootstrap.ShutdownHook; import org.apache.nifi.minifi.bootstrap.ShutdownHook;
@ -56,10 +51,13 @@ import org.apache.nifi.minifi.bootstrap.service.BootstrapFileProvider;
import org.apache.nifi.minifi.bootstrap.service.CurrentPortProvider; import org.apache.nifi.minifi.bootstrap.service.CurrentPortProvider;
import org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider; import org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider;
import org.apache.nifi.minifi.bootstrap.service.MiNiFiListener; import org.apache.nifi.minifi.bootstrap.service.MiNiFiListener;
import org.apache.nifi.minifi.bootstrap.service.MiNiFiPropertiesGenerator;
import org.apache.nifi.minifi.bootstrap.service.MiNiFiStdLogHandler; import org.apache.nifi.minifi.bootstrap.service.MiNiFiStdLogHandler;
import org.apache.nifi.minifi.bootstrap.service.PeriodicStatusReporterManager; import org.apache.nifi.minifi.bootstrap.service.PeriodicStatusReporterManager;
import org.apache.nifi.minifi.commons.api.MiNiFiProperties;
public class StartRunner implements CommandRunner { public class StartRunner implements CommandRunner {
private static final int STARTUP_WAIT_SECONDS = 60; private static final int STARTUP_WAIT_SECONDS = 60;
private final CurrentPortProvider currentPortProvider; private final CurrentPortProvider currentPortProvider;
@ -71,15 +69,18 @@ public class StartRunner implements CommandRunner {
private final Lock lock = new ReentrantLock(); private final Lock lock = new ReentrantLock();
private final Condition startupCondition = lock.newCondition(); private final Condition startupCondition = lock.newCondition();
private final RunMiNiFi runMiNiFi; private final RunMiNiFi runMiNiFi;
private volatile ShutdownHook shutdownHook;
private final MiNiFiExecCommandProvider miNiFiExecCommandProvider; private final MiNiFiExecCommandProvider miNiFiExecCommandProvider;
private final ConfigurationChangeListener configurationChangeListener; private final ConfigurationChangeListener configurationChangeListener;
private final MiNiFiPropertiesGenerator miNiFiPropertiesGenerator;
private volatile ShutdownHook shutdownHook;
private int listenPort; private int listenPort;
public StartRunner(CurrentPortProvider currentPortProvider, BootstrapFileProvider bootstrapFileProvider, public StartRunner(CurrentPortProvider currentPortProvider, BootstrapFileProvider bootstrapFileProvider,
PeriodicStatusReporterManager periodicStatusReporterManager, MiNiFiStdLogHandler miNiFiStdLogHandler, MiNiFiParameters miNiFiParameters, File bootstrapConfigFile, PeriodicStatusReporterManager periodicStatusReporterManager, MiNiFiStdLogHandler miNiFiStdLogHandler,
RunMiNiFi runMiNiFi, MiNiFiExecCommandProvider miNiFiExecCommandProvider, ConfigurationChangeListener configurationChangeListener) { MiNiFiParameters miNiFiParameters, File bootstrapConfigFile, RunMiNiFi runMiNiFi,
MiNiFiExecCommandProvider miNiFiExecCommandProvider, ConfigurationChangeListener configurationChangeListener) {
this.currentPortProvider = currentPortProvider; this.currentPortProvider = currentPortProvider;
this.bootstrapFileProvider = bootstrapFileProvider; this.bootstrapFileProvider = bootstrapFileProvider;
this.periodicStatusReporterManager = periodicStatusReporterManager; this.periodicStatusReporterManager = periodicStatusReporterManager;
@ -89,10 +90,12 @@ public class StartRunner implements CommandRunner {
this.runMiNiFi = runMiNiFi; this.runMiNiFi = runMiNiFi;
this.miNiFiExecCommandProvider = miNiFiExecCommandProvider; this.miNiFiExecCommandProvider = miNiFiExecCommandProvider;
this.configurationChangeListener = configurationChangeListener; this.configurationChangeListener = configurationChangeListener;
this.miNiFiPropertiesGenerator = new MiNiFiPropertiesGenerator();
} }
/** /**
* Starts (and restarts) MiNiFi process during the whole lifecycle of the bootstrap process. * Starts (and restarts) MiNiFi process during the whole lifecycle of the bootstrap process.
*
* @param args the input arguments * @param args the input arguments
* @return status code * @return status code
*/ */
@ -107,7 +110,7 @@ public class StartRunner implements CommandRunner {
return OK.getStatusCode(); return OK.getStatusCode();
} }
private void start() throws IOException, InterruptedException { private void start() throws IOException, InterruptedException, ConfigurationChangeException {
Integer port = currentPortProvider.getCurrentPort(); Integer port = currentPortProvider.getCurrentPort();
if (port != null) { if (port != null) {
CMD_LOGGER.info("Apache MiNiFi is already running, listening to Bootstrap on port {}", port); CMD_LOGGER.info("Apache MiNiFi is already running, listening to Bootstrap on port {}", port);
@ -121,10 +124,11 @@ public class StartRunner implements CommandRunner {
Properties bootstrapProperties = bootstrapFileProvider.getBootstrapProperties(); Properties bootstrapProperties = bootstrapFileProvider.getBootstrapProperties();
String confDir = bootstrapProperties.getProperty(CONF_DIR_KEY); String confDir = bootstrapProperties.getProperty(CONF_DIR_KEY);
initConfigFiles(bootstrapProperties, confDir);
generateMiNiFiProperties(bootstrapProperties, confDir);
regenerateFlowConfiguration(bootstrapProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_FLOW_CONFIG.getKey()));
Process process = startMiNiFi(); Process process = startMiNiFi();
try { try {
while (true) { while (true) {
if (process.isAlive()) { if (process.isAlive()) {
@ -144,7 +148,7 @@ public class StartRunner implements CommandRunner {
Thread.sleep(5000L); Thread.sleep(5000L);
continue; continue;
} }
process = restartMiNifi(bootstrapProperties, confDir); process = restartMiNifi(confDir);
// failed to start process // failed to start process
if (process == null) { if (process == null) {
return; return;
@ -161,18 +165,13 @@ public class StartRunner implements CommandRunner {
} }
} }
private Process restartMiNifi(Properties bootstrapProperties, String confDir) throws IOException { private Process restartMiNifi(String confDir) throws IOException {
Process process; Process process;
boolean previouslyStarted = runMiNiFi.isNiFiStarted(); boolean previouslyStarted = runMiNiFi.isNiFiStarted();
boolean configChangeSuccessful = true; boolean configChangeSuccessful = true;
if (!previouslyStarted) { if (!previouslyStarted) {
File swapConfigFile = bootstrapFileProvider.getConfigYmlSwapFile();
File bootstrapSwapConfigFile = bootstrapFileProvider.getBootstrapConfSwapFile(); File bootstrapSwapConfigFile = bootstrapFileProvider.getBootstrapConfSwapFile();
if (swapConfigFile.exists()) { if (bootstrapSwapConfigFile.exists()) {
if (!revertFlowConfig(bootstrapProperties, confDir, swapConfigFile)) {
return null;
}
} else if(bootstrapSwapConfigFile.exists()) {
if (!revertBootstrapConfig(confDir, bootstrapSwapConfigFile)) { if (!revertBootstrapConfig(confDir, bootstrapSwapConfigFile)) {
return null; return null;
} }
@ -202,36 +201,14 @@ public class StartRunner implements CommandRunner {
return process; return process;
} }
private boolean revertFlowConfig(Properties bootstrapProperties, String confDir, File swapConfigFile) throws IOException {
DEFAULT_LOGGER.info("Flow Swap file exists, MiNiFi failed trying to change configuration. Reverting to old configuration.");
try {
ByteBuffer tempConfigFile = generateConfigFiles(Files.newInputStream(swapConfigFile.toPath()), confDir, bootstrapProperties);
runMiNiFi.getConfigFileReference().set(tempConfigFile.asReadOnlyBuffer());
} catch (ConfigurationChangeException e) {
DEFAULT_LOGGER.error("The flow swap file is malformed, unable to restart from prior state. Will not attempt to restart MiNiFi. Swap File should be cleaned up manually.");
return false;
}
Files.copy(swapConfigFile.toPath(), Paths.get(bootstrapProperties.getProperty(MINIFI_CONFIG_FILE_KEY)), REPLACE_EXISTING);
DEFAULT_LOGGER.info("Replacing flow config file with swap file and deleting swap file");
if (!swapConfigFile.delete()) {
DEFAULT_LOGGER.warn("The flow swap file failed to delete after replacing using it to revert to the old configuration. It should be cleaned up manually.");
}
runMiNiFi.setReloading(false);
return true;
}
private boolean revertBootstrapConfig(String confDir, File bootstrapSwapConfigFile) throws IOException { private boolean revertBootstrapConfig(String confDir, File bootstrapSwapConfigFile) throws IOException {
DEFAULT_LOGGER.info("Bootstrap Swap file exists, MiNiFi failed trying to change configuration. Reverting to old configuration."); DEFAULT_LOGGER.info("Bootstrap Swap file exists, MiNiFi failed trying to change configuration. Reverting to old configuration.");
Files.copy(bootstrapSwapConfigFile.toPath(), bootstrapConfigFile.toPath(), REPLACE_EXISTING); Files.copy(bootstrapSwapConfigFile.toPath(), bootstrapConfigFile.toPath(), REPLACE_EXISTING);
try { try {
ByteBuffer tempConfigFile = generateConfigFiles(asByteArrayInputStream(runMiNiFi.getConfigFileReference().get().duplicate()), confDir, bootstrapFileProvider.getBootstrapProperties()); miNiFiPropertiesGenerator.generateMinifiProperties(confDir, bootstrapFileProvider.getBootstrapProperties());
runMiNiFi.getConfigFileReference().set(tempConfigFile.asReadOnlyBuffer());
} catch (ConfigurationChangeException e) { } catch (ConfigurationChangeException e) {
DEFAULT_LOGGER.error("The bootstrap swap file is malformed, unable to restart from prior state. Will not attempt to restart MiNiFi. Swap File should be cleaned up manually."); DEFAULT_LOGGER.error("Unable to create MiNiFi properties file. Will not attempt to restart MiNiFi. Swap File should be cleaned up manually.");
return false; return false;
} }
@ -262,7 +239,6 @@ public class StartRunner implements CommandRunner {
try { try {
Thread.sleep(1000L); Thread.sleep(1000L);
if (runMiNiFi.getReloading() && runMiNiFi.isNiFiStarted()) { if (runMiNiFi.getReloading() && runMiNiFi.isNiFiStarted()) {
deleteSwapFile(bootstrapFileProvider.getConfigYmlSwapFile());
deleteSwapFile(bootstrapFileProvider.getBootstrapConfSwapFile()); deleteSwapFile(bootstrapFileProvider.getBootstrapConfSwapFile());
runMiNiFi.setReloading(false); runMiNiFi.setReloading(false);
} }
@ -283,24 +259,26 @@ public class StartRunner implements CommandRunner {
} }
} }
private void initConfigFiles(Properties bootstrapProperties, String confDir) throws IOException { private void generateMiNiFiProperties(Properties bootstrapProperties, String confDir) {
File configFile = new File(bootstrapProperties.getProperty(MINIFI_CONFIG_FILE_KEY)); DEFAULT_LOGGER.debug("Generating minifi.properties from bootstrap.conf");
try (InputStream inputStream = new FileInputStream(configFile)) { try {
ByteBuffer tempConfigFile = generateConfigFiles(inputStream, confDir, bootstrapProperties); miNiFiPropertiesGenerator.generateMinifiProperties(confDir, bootstrapProperties);
runMiNiFi.getConfigFileReference().set(tempConfigFile.asReadOnlyBuffer());
} catch (FileNotFoundException e) {
String fileNotFoundMessage = "The config file defined in " + MINIFI_CONFIG_FILE_KEY + " does not exists.";
DEFAULT_LOGGER.error(fileNotFoundMessage, e);
throw new StartupFailureException(fileNotFoundMessage);
} catch (ConfigurationChangeException e) { } catch (ConfigurationChangeException e) {
String malformedConfigFileMessage = "The config file is malformed, unable to start."; throw new StartupFailureException("Unable to create MiNiFi properties file", e);
DEFAULT_LOGGER.error(malformedConfigFileMessage, e); }
throw new StartupFailureException(malformedConfigFileMessage); }
private void regenerateFlowConfiguration(String flowConfigFileLocation) throws ConfigurationChangeException, IOException {
Path flowConfigFile = Path.of(flowConfigFileLocation).toAbsolutePath();
String currentFlowConfigFileBaseName = FilenameUtils.getBaseName(flowConfigFile.toString());
Path rawConfigFile = flowConfigFile.getParent().resolve(currentFlowConfigFileBaseName + RAW_EXTENSION);
if (Files.exists(rawConfigFile)) {
DEFAULT_LOGGER.debug("Regenerating flow configuration {} from raw flow configuration {}", flowConfigFile, rawConfigFile);
configurationChangeListener.handleChange(Files.newInputStream(rawConfigFile));
} }
} }
private Process startMiNiFi() throws IOException { private Process startMiNiFi() throws IOException {
MiNiFiListener listener = new MiNiFiListener(); MiNiFiListener listener = new MiNiFiListener();
listenPort = listener.start(runMiNiFi, bootstrapFileProvider, configurationChangeListener); listenPort = listener.start(runMiNiFi, bootstrapFileProvider, configurationChangeListener);
@ -309,7 +287,7 @@ public class StartRunner implements CommandRunner {
return startMiNiFiProcess(getProcessBuilder()); return startMiNiFiProcess(getProcessBuilder());
} }
private ProcessBuilder getProcessBuilder() throws IOException{ private ProcessBuilder getProcessBuilder() throws IOException {
ProcessBuilder builder = new ProcessBuilder(); ProcessBuilder builder = new ProcessBuilder();
File workingDir = getWorkingDir(); File workingDir = getWorkingDir();

View File

@ -14,18 +14,22 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.apache.nifi.minifi.bootstrap.configuration; package org.apache.nifi.minifi.bootstrap.configuration;
import static java.util.Optional.ofNullable;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.toList;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Optional;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream;
import org.apache.nifi.minifi.bootstrap.RunMiNiFi; import org.apache.nifi.minifi.bootstrap.RunMiNiFi;
import org.apache.nifi.minifi.bootstrap.configuration.ingestors.interfaces.ChangeIngestor; import org.apache.nifi.minifi.bootstrap.configuration.ingestors.interfaces.ChangeIngestor;
import org.apache.nifi.minifi.bootstrap.service.BootstrapFileProvider; import org.apache.nifi.minifi.bootstrap.service.BootstrapFileProvider;
@ -35,64 +39,72 @@ import org.slf4j.LoggerFactory;
public class ConfigurationChangeCoordinator implements Closeable, ConfigurationChangeNotifier { public class ConfigurationChangeCoordinator implements Closeable, ConfigurationChangeNotifier {
public static final String NOTIFIER_PROPERTY_PREFIX = "nifi.minifi.notifier"; public static final String NOTIFIER_INGESTORS_KEY = "nifi.minifi.notifier.ingestors";
public static final String NOTIFIER_INGESTORS_KEY = NOTIFIER_PROPERTY_PREFIX + ".ingestors";
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationChangeCoordinator.class);
private final Set<ConfigurationChangeListener> configurationChangeListeners; private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationChangeCoordinator.class);
private final Set<ChangeIngestor> changeIngestors = new HashSet<>(); private static final String COMMA = ",";
private final BootstrapFileProvider bootstrapFileProvider; private final BootstrapFileProvider bootstrapFileProvider;
private final RunMiNiFi runMiNiFi; private final RunMiNiFi runMiNiFi;
private final Set<ConfigurationChangeListener> configurationChangeListeners;
private final Set<ChangeIngestor> changeIngestors;
public ConfigurationChangeCoordinator(BootstrapFileProvider bootstrapFileProvider, RunMiNiFi runMiNiFi, public ConfigurationChangeCoordinator(BootstrapFileProvider bootstrapFileProvider, RunMiNiFi runMiNiFi,
Set<ConfigurationChangeListener> miNiFiConfigurationChangeListeners) { Set<ConfigurationChangeListener> miNiFiConfigurationChangeListeners) {
this.bootstrapFileProvider = bootstrapFileProvider; this.bootstrapFileProvider = bootstrapFileProvider;
this.runMiNiFi = runMiNiFi; this.runMiNiFi = runMiNiFi;
this.configurationChangeListeners = Optional.ofNullable(miNiFiConfigurationChangeListeners).map(Collections::unmodifiableSet).orElse(Collections.emptySet()); this.configurationChangeListeners = ofNullable(miNiFiConfigurationChangeListeners).map(Collections::unmodifiableSet).orElse(Collections.emptySet());
this.changeIngestors = new HashSet<>();
}
@Override
public Collection<ListenerHandleResult> notifyListeners(ByteBuffer newFlowConfig) {
LOGGER.info("Notifying Listeners of a change");
return configurationChangeListeners.stream()
.map(listener -> notifyListener(newFlowConfig, listener))
.collect(toList());
}
@Override
public void close() {
closeIngestors();
} }
/** /**
* Begins the associated notification service provided by the given implementation. In most implementations, no action will occur until this method is invoked. * Begins the associated notification service provided by the given implementation. In most implementations, no action will occur until this method is invoked.
*/ */
public void start() throws IOException{ public void start() throws IOException {
initialize(); initialize();
changeIngestors.forEach(ChangeIngestor::start); changeIngestors.forEach(ChangeIngestor::start);
} }
/** private ListenerHandleResult notifyListener(ByteBuffer newFlowConfig, ConfigurationChangeListener listener) {
* Provides an immutable collection of listeners for the notifier instance
*
* @return a collection of those listeners registered for notifications
*/
public Set<ConfigurationChangeListener> getChangeListeners() {
return Collections.unmodifiableSet(configurationChangeListeners);
}
/**
* Provide the mechanism by which listeners are notified
*/
public Collection<ListenerHandleResult> notifyListeners(ByteBuffer newConfig) {
LOGGER.info("Notifying Listeners of a change");
Collection<ListenerHandleResult> listenerHandleResults = new ArrayList<>(configurationChangeListeners.size());
for (final ConfigurationChangeListener listener : getChangeListeners()) {
ListenerHandleResult result;
try { try {
listener.handleChange(new ByteBufferInputStream(newConfig.duplicate())); listener.handleChange(new ByteBufferInputStream(newFlowConfig.duplicate()));
result = new ListenerHandleResult(listener); ListenerHandleResult listenerHandleResult = new ListenerHandleResult(listener);
LOGGER.info("Listener notification result {}", listenerHandleResult);
return listenerHandleResult;
} catch (ConfigurationChangeException ex) { } catch (ConfigurationChangeException ex) {
result = new ListenerHandleResult(listener, ex); ListenerHandleResult listenerHandleResult = new ListenerHandleResult(listener, ex);
LOGGER.error("Listener notification result {} with failure {}", listenerHandleResult, ex);
return listenerHandleResult;
} }
listenerHandleResults.add(result);
LOGGER.info("Listener notification result: {}", result);
}
return listenerHandleResults;
} }
private void initialize() throws IOException {
closeIngestors();
@Override Properties bootstrapProperties = bootstrapFileProvider.getBootstrapProperties();
public void close() { ofNullable(bootstrapProperties.getProperty(NOTIFIER_INGESTORS_KEY))
.filter(not(String::isBlank))
.map(ingestors -> ingestors.split(COMMA))
.stream()
.flatMap(Stream::of)
.map(String::trim)
.forEach(ingestorClassname -> instantiateIngestor(bootstrapProperties, ingestorClassname));
}
private void closeIngestors() {
try { try {
for (ChangeIngestor changeIngestor : changeIngestors) { for (ChangeIngestor changeIngestor : changeIngestors) {
changeIngestor.close(); changeIngestor.close();
@ -103,15 +115,7 @@ public class ConfigurationChangeCoordinator implements Closeable, ConfigurationC
} }
} }
private void initialize() throws IOException { private void instantiateIngestor(Properties bootstrapProperties, String ingestorClassname) {
close();
Properties bootstrapProperties = bootstrapFileProvider.getBootstrapProperties();
// cleanup previously initialized ingestors
String ingestorsCsv = bootstrapProperties.getProperty(NOTIFIER_INGESTORS_KEY);
if (ingestorsCsv != null && !ingestorsCsv.isEmpty()) {
for (String ingestorClassname : ingestorsCsv.split(",")) {
ingestorClassname = ingestorClassname.trim();
try { try {
Class<?> ingestorClass = Class.forName(ingestorClassname); Class<?> ingestorClass = Class.forName(ingestorClassname);
ChangeIngestor changeIngestor = (ChangeIngestor) ingestorClass.newInstance(); ChangeIngestor changeIngestor = (ChangeIngestor) ingestorClass.newInstance();
@ -122,6 +126,4 @@ public class ConfigurationChangeCoordinator implements Closeable, ConfigurationC
LOGGER.error("Instantiating [{}] ingestor failed", ingestorClassname, e); LOGGER.error("Instantiating [{}] ingestor failed", ingestorClassname, e);
} }
} }
}
}
} }

View File

@ -19,8 +19,7 @@ package org.apache.nifi.minifi.bootstrap.configuration;
import java.io.InputStream; import java.io.InputStream;
/** /**
* Interface for handling events detected and driven by an associated {@link ConfigurationChangeCoordinator} to which the listener * Interface for handling events detected and driven by an associated {@link ConfigurationChangeCoordinator}
* has registered via {@link ConfigurationChangeCoordinator#registerListener(ConfigurationChangeListener)}.
*/ */
public interface ConfigurationChangeListener { public interface ConfigurationChangeListener {

View File

@ -19,11 +19,11 @@ package org.apache.nifi.minifi.bootstrap.configuration;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Collection; import java.util.Collection;
import java.util.Set;
public interface ConfigurationChangeNotifier { public interface ConfigurationChangeNotifier {
Set<ConfigurationChangeListener> getChangeListeners(); /**
* Provides the mechanism by which listeners are notified
Collection<ListenerHandleResult> notifyListeners(ByteBuffer is); */
Collection<ListenerHandleResult> notifyListeners(ByteBuffer newFlowConfig);
} }

View File

@ -17,10 +17,8 @@
package org.apache.nifi.minifi.bootstrap.configuration.differentiators; package org.apache.nifi.minifi.bootstrap.configuration.differentiators;
import java.io.DataInputStream; import static java.util.Optional.ofNullable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder; import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder;
@ -29,59 +27,30 @@ import org.slf4j.LoggerFactory;
public abstract class WholeConfigDifferentiator { public abstract class WholeConfigDifferentiator {
public static final String WHOLE_CONFIG_KEY = "Whole Config";
private final static Logger logger = LoggerFactory.getLogger(WholeConfigDifferentiator.class); private final static Logger logger = LoggerFactory.getLogger(WholeConfigDifferentiator.class);
public static final String WHOLE_CONFIG_KEY = "Whole Config"; protected volatile ConfigurationFileHolder configurationFileHolder;
volatile ConfigurationFileHolder configurationFileHolder;
boolean compareInputStreamToConfigFile(InputStream inputStream) throws IOException {
logger.debug("Checking if change is different");
AtomicReference<ByteBuffer> currentConfigFileReference = configurationFileHolder.getConfigFileReference();
ByteBuffer currentConfigFile = currentConfigFileReference.get();
ByteBuffer byteBuffer = ByteBuffer.allocate(currentConfigFile.limit());
DataInputStream dataInputStream = new DataInputStream(inputStream);
try {
dataInputStream.readFully(byteBuffer.array());
} catch (EOFException e) {
logger.debug("New config is shorter than the current. Must be different.");
return true;
}
logger.debug("Read the input");
if (dataInputStream.available() != 0) {
return true;
} else {
return byteBuffer.compareTo(currentConfigFile) != 0;
}
}
public void initialize(ConfigurationFileHolder configurationFileHolder) { public void initialize(ConfigurationFileHolder configurationFileHolder) {
this.configurationFileHolder = configurationFileHolder; this.configurationFileHolder = configurationFileHolder;
} }
public static class ByteBufferInputDifferentiator extends WholeConfigDifferentiator implements Differentiator<ByteBuffer> {
public static class InputStreamInput extends WholeConfigDifferentiator implements Differentiator<InputStream> { public boolean isNew(ByteBuffer newFlowConfig) {
public boolean isNew(InputStream inputStream) throws IOException { AtomicReference<ByteBuffer> currentFlowConfigReference = configurationFileHolder.getConfigFileReference();
return compareInputStreamToConfigFile(inputStream); ByteBuffer currentFlowConfig = currentFlowConfigReference.get();
boolean compareResult = ofNullable(currentFlowConfig)
.map(newFlowConfig::compareTo)
.map(result -> result != 0)
.orElse(Boolean.TRUE);
logger.debug("New flow is different from existing flow: {}", compareResult);
return compareResult;
} }
} }
public static class ByteBufferInput extends WholeConfigDifferentiator implements Differentiator<ByteBuffer> {
public boolean isNew(ByteBuffer inputBuffer) {
AtomicReference<ByteBuffer> currentConfigFileReference = configurationFileHolder.getConfigFileReference();
ByteBuffer currentConfigFile = currentConfigFileReference.get();
return inputBuffer.compareTo(currentConfigFile) != 0;
}
}
public static Differentiator<InputStream> getInputStreamDifferentiator() {
return new InputStreamInput();
}
public static Differentiator<ByteBuffer> getByteBufferDifferentiator() { public static Differentiator<ByteBuffer> getByteBufferDifferentiator() {
return new ByteBufferInput(); return new ByteBufferInputDifferentiator();
} }
} }

View File

@ -26,25 +26,22 @@ import java.util.concurrent.atomic.AtomicReference;
import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder; import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier; import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier;
import org.apache.nifi.minifi.bootstrap.configuration.ingestors.interfaces.ChangeIngestor; import org.apache.nifi.minifi.bootstrap.configuration.ingestors.interfaces.ChangeIngestor;
import org.slf4j.Logger;
public abstract class AbstractPullChangeIngestor implements Runnable, ChangeIngestor { public abstract class AbstractPullChangeIngestor implements Runnable, ChangeIngestor {
// 5 minute default pulling period protected static final String DEFAULT_POLLING_PERIOD_MILLISECONDS = "300000";
protected static final String DEFAULT_POLLING_PERIOD = "300000";
protected static Logger logger;
protected final AtomicInteger pollingPeriodMS = new AtomicInteger(); protected final AtomicInteger pollingPeriodMS = new AtomicInteger();
private final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
protected volatile ConfigurationChangeNotifier configurationChangeNotifier;
protected volatile ConfigurationFileHolder configurationFileHolder;
protected final AtomicReference<Properties> properties = new AtomicReference<>(); protected final AtomicReference<Properties> properties = new AtomicReference<>();
private final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
protected volatile ConfigurationChangeNotifier configurationChangeNotifier;
@Override @Override
public void initialize(Properties properties, ConfigurationFileHolder configurationFileHolder, ConfigurationChangeNotifier configurationChangeNotifier) { public void initialize(Properties properties, ConfigurationFileHolder configurationFileHolder, ConfigurationChangeNotifier configurationChangeNotifier) {
this.configurationChangeNotifier = configurationChangeNotifier; this.configurationChangeNotifier = configurationChangeNotifier;
this.properties.set(properties); this.properties.set(properties);
this.configurationFileHolder = configurationFileHolder;
} }
@Override @Override

View File

@ -14,24 +14,29 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.apache.nifi.minifi.bootstrap.configuration.ingestors; package org.apache.nifi.minifi.bootstrap.configuration.ingestors;
import static java.nio.ByteBuffer.wrap;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.function.Predicate.not;
import static org.apache.commons.io.FilenameUtils.getBaseName;
import static org.apache.commons.io.IOUtils.toByteArray;
import static org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeCoordinator.NOTIFIER_INGESTORS_KEY; import static org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeCoordinator.NOTIFIER_INGESTORS_KEY;
import static org.apache.nifi.minifi.bootstrap.configuration.differentiators.WholeConfigDifferentiator.WHOLE_CONFIG_KEY; import static org.apache.nifi.minifi.bootstrap.configuration.differentiators.WholeConfigDifferentiator.WHOLE_CONFIG_KEY;
import static org.apache.nifi.minifi.commons.api.MiNiFiConstants.RAW_EXTENSION;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent; import java.nio.file.WatchEvent;
import java.nio.file.WatchKey; import java.nio.file.WatchKey;
import java.nio.file.WatchService; import java.nio.file.WatchService;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Properties; import java.util.Properties;
@ -39,13 +44,12 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.apache.commons.io.IOUtils;
import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder; import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier; import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier;
import org.apache.nifi.minifi.bootstrap.configuration.differentiators.Differentiator; import org.apache.nifi.minifi.bootstrap.configuration.differentiators.Differentiator;
import org.apache.nifi.minifi.bootstrap.configuration.differentiators.WholeConfigDifferentiator; import org.apache.nifi.minifi.bootstrap.configuration.differentiators.WholeConfigDifferentiator;
import org.apache.nifi.minifi.bootstrap.configuration.ingestors.interfaces.ChangeIngestor; import org.apache.nifi.minifi.bootstrap.configuration.ingestors.interfaces.ChangeIngestor;
import org.apache.nifi.minifi.bootstrap.util.ConfigTransformer; import org.apache.nifi.minifi.commons.api.MiNiFiProperties;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -56,165 +60,160 @@ import org.slf4j.LoggerFactory;
*/ */
public class FileChangeIngestor implements Runnable, ChangeIngestor { public class FileChangeIngestor implements Runnable, ChangeIngestor {
private static final Map<String, Supplier<Differentiator<ByteBuffer>>> DIFFERENTIATOR_CONSTRUCTOR_MAP; private static final Map<String, Supplier<Differentiator<ByteBuffer>>> DIFFERENTIATOR_CONSTRUCTOR_MAP = Map.of(
WHOLE_CONFIG_KEY, WholeConfigDifferentiator::getByteBufferDifferentiator
);
static { static final String CONFIG_FILE_BASE_KEY = NOTIFIER_INGESTORS_KEY + ".file";
HashMap<String, Supplier<Differentiator<ByteBuffer>>> tempMap = new HashMap<>(); static final String CONFIG_FILE_PATH_KEY = CONFIG_FILE_BASE_KEY + ".config.path";
tempMap.put(WHOLE_CONFIG_KEY, WholeConfigDifferentiator::getByteBufferDifferentiator); static final String POLLING_PERIOD_INTERVAL_KEY = CONFIG_FILE_BASE_KEY + ".polling.period.seconds";
static final int DEFAULT_POLLING_PERIOD_INTERVAL = 15;
DIFFERENTIATOR_CONSTRUCTOR_MAP = Collections.unmodifiableMap(tempMap);
}
protected static final int DEFAULT_POLLING_PERIOD_INTERVAL = 15;
protected static final TimeUnit DEFAULT_POLLING_PERIOD_UNIT = TimeUnit.SECONDS;
private final static Logger logger = LoggerFactory.getLogger(FileChangeIngestor.class); private final static Logger logger = LoggerFactory.getLogger(FileChangeIngestor.class);
private static final String CONFIG_FILE_BASE_KEY = NOTIFIER_INGESTORS_KEY + ".file";
protected static final String CONFIG_FILE_PATH_KEY = CONFIG_FILE_BASE_KEY + ".config.path"; private static final TimeUnit DEFAULT_POLLING_PERIOD_UNIT = SECONDS;
protected static final String POLLING_PERIOD_INTERVAL_KEY = CONFIG_FILE_BASE_KEY + ".polling.period.seconds"; private static final String DIFFERENTIATOR_KEY = CONFIG_FILE_BASE_KEY + ".differentiator";
public static final String DIFFERENTIATOR_KEY = CONFIG_FILE_BASE_KEY + ".differentiator";
private volatile Differentiator<ByteBuffer> differentiator;
private volatile ConfigurationChangeNotifier configurationChangeNotifier;
private ScheduledExecutorService executorService;
private Path configFilePath; private Path configFilePath;
private WatchService watchService; private WatchService watchService;
private long pollingSeconds; private long pollingSeconds;
private volatile Differentiator<ByteBuffer> differentiator;
private volatile ConfigurationChangeNotifier configurationChangeNotifier;
private volatile ConfigurationFileHolder configurationFileHolder;
private volatile Properties properties;
private ScheduledExecutorService executorService;
protected static WatchService initializeWatcher(Path filePath) { @Override
public void initialize(Properties properties, ConfigurationFileHolder configurationFileHolder, ConfigurationChangeNotifier configurationChangeNotifier) {
Path configFile = ofNullable(properties.getProperty(CONFIG_FILE_PATH_KEY))
.filter(not(String::isBlank))
.map(Path::of)
.map(Path::toAbsolutePath)
.orElseThrow(() -> new IllegalArgumentException("Property, " + CONFIG_FILE_PATH_KEY + ", for the path of the config file must be specified"));
try { try {
final WatchService fsWatcher = FileSystems.getDefault().newWatchService(); this.configurationChangeNotifier = configurationChangeNotifier;
final Path watchDirectory = filePath.getParent(); this.configFilePath = configFile;
watchDirectory.register(fsWatcher, ENTRY_MODIFY); this.pollingSeconds = ofNullable(properties.getProperty(POLLING_PERIOD_INTERVAL_KEY, Long.toString(DEFAULT_POLLING_PERIOD_INTERVAL)))
.map(Long::parseLong)
.filter(duration -> duration > 0)
.map(duration -> SECONDS.convert(duration, DEFAULT_POLLING_PERIOD_UNIT))
.orElseThrow(() -> new IllegalArgumentException("Cannot specify a polling period with duration <=0"));
this.watchService = initializeWatcher(configFile);
this.differentiator = ofNullable(properties.getProperty(DIFFERENTIATOR_KEY))
.filter(not(String::isBlank))
.map(differentiator -> ofNullable(DIFFERENTIATOR_CONSTRUCTOR_MAP.get(differentiator))
.map(Supplier::get)
.orElseThrow(unableToFindDifferentiatorExceptionSupplier(differentiator)))
.orElseGet(WholeConfigDifferentiator::getByteBufferDifferentiator);
this.differentiator.initialize(configurationFileHolder);
} catch (Exception e) {
throw new IllegalStateException("Could not successfully initialize file change notifier", e);
}
return fsWatcher; checkConfigFileLocationCorrectness(properties, configFile);
} catch (IOException ioe) { }
throw new IllegalStateException("Unable to initialize a file system watcher for the path " + filePath, ioe);
@Override
public void start() {
executorService = Executors.newScheduledThreadPool(1, runnable -> {
Thread notifierThread = Executors.defaultThreadFactory().newThread(runnable);
notifierThread.setName("File Change Notifier Thread");
notifierThread.setDaemon(true);
return notifierThread;
});
executorService.scheduleWithFixedDelay(this, 0, pollingSeconds, DEFAULT_POLLING_PERIOD_UNIT);
}
@Override
public void run() {
logger.debug("Checking for a change in {}", configFilePath);
if (targetFileChanged()) {
logger.debug("Target file changed, checking if it's different than current flow");
try (FileInputStream flowCandidateInputStream = new FileInputStream(configFilePath.toFile())) {
ByteBuffer newFlowConfig = wrap(toByteArray(flowCandidateInputStream));
if (differentiator.isNew(newFlowConfig)) {
logger.debug("Current flow and new flow is different, notifying listener");
configurationChangeNotifier.notifyListeners(newFlowConfig);
logger.debug("Listeners have been notified");
}
} catch (Exception e) {
logger.error("Could not successfully notify listeners.", e);
}
} else {
logger.debug("No change detected in {}", configFilePath);
} }
} }
protected boolean targetChanged() { @Override
boolean targetChanged; public void close() {
if (executorService != null) {
executorService.shutdownNow();
}
}
Optional<WatchKey> watchKey = Optional.ofNullable(watchService.poll()); boolean targetFileChanged() {
logger.trace("Attempting to acquire watch key");
targetChanged = watchKey Optional<WatchKey> watchKey = ofNullable(watchService.poll());
logger.trace("Watch key acquire with value {}", watchKey);
boolean targetChanged = watchKey
.map(WatchKey::pollEvents) .map(WatchKey::pollEvents)
.orElse(emptyList()) .orElse(emptyList())
.stream() .stream()
.anyMatch(watchEvent -> ENTRY_MODIFY == watchEvent.kind() .anyMatch(watchEvent -> ENTRY_MODIFY == watchEvent.kind()
&& ((WatchEvent<Path>) watchEvent).context().equals(configFilePath.getName(configFilePath.getNameCount() - 1))); && ((WatchEvent<Path>) watchEvent).context().equals(configFilePath.getName(configFilePath.getNameCount() - 1)));
logger.debug("Target file changed: {}", targetChanged);
// After completing inspection, reset for detection of subsequent change events // After completing inspection, reset for detection of subsequent change events
watchKey.map(WatchKey::reset) watchKey.map(WatchKey::reset)
.filter(valid -> !valid) .filter(valid -> !valid)
.ifPresent(valid -> { .ifPresent(valid -> {
logger.error("Unable to reinitialize file system watcher.");
throw new IllegalStateException("Unable to reinitialize file system watcher."); throw new IllegalStateException("Unable to reinitialize file system watcher.");
}); });
logger.trace("Watch key has been reset successfully");
return targetChanged; return targetChanged;
} }
@Override private WatchService initializeWatcher(Path filePath) {
public void run() {
logger.debug("Checking for a change");
if (targetChanged()) {
logger.debug("Target changed, checking if it's different than current flow.");
try (FileInputStream configFile = new FileInputStream(configFilePath.toFile())) {
ByteBuffer readOnlyNewConfig =
ConfigTransformer.overrideNonFlowSectionsFromOriginalSchema(
IOUtils.toByteArray(configFile), configurationFileHolder.getConfigFileReference().get().duplicate(), properties);
if (differentiator.isNew(readOnlyNewConfig)) {
logger.debug("New change, notifying listener");
configurationChangeNotifier.notifyListeners(readOnlyNewConfig);
logger.debug("Listeners notified");
}
} catch (Exception e) {
logger.error("Could not successfully notify listeners.", e);
}
}
}
@Override
public void initialize(Properties properties, ConfigurationFileHolder configurationFileHolder, ConfigurationChangeNotifier configurationChangeNotifier) {
this.properties = properties;
this.configurationFileHolder = configurationFileHolder;
final String rawPath = properties.getProperty(CONFIG_FILE_PATH_KEY);
final String rawPollingDuration = properties.getProperty(POLLING_PERIOD_INTERVAL_KEY, Long.toString(DEFAULT_POLLING_PERIOD_INTERVAL));
if (rawPath == null || rawPath.isEmpty()) {
throw new IllegalArgumentException("Property, " + CONFIG_FILE_PATH_KEY + ", for the path of the config file must be specified.");
}
try { try {
setConfigFilePath(Paths.get(rawPath)); WatchService fileSystemWatcher = FileSystems.getDefault().newWatchService();
setPollingPeriod(Long.parseLong(rawPollingDuration), DEFAULT_POLLING_PERIOD_UNIT); Path watchDirectory = filePath.getParent();
setWatchService(initializeWatcher(configFilePath)); watchDirectory.register(fileSystemWatcher, ENTRY_MODIFY);
} catch (Exception e) { logger.trace("Watch service registered for {}", watchDirectory);
throw new IllegalStateException("Could not successfully initialize file change notifier.", e); return fileSystemWatcher;
} catch (IOException ioe) {
throw new IllegalStateException("Unable to initialize a file system watcher for the path " + filePath, ioe);
}
} }
this.configurationChangeNotifier = configurationChangeNotifier; private Supplier<IllegalArgumentException> unableToFindDifferentiatorExceptionSupplier(String differentiator) {
return () -> new IllegalArgumentException("Property, " + DIFFERENTIATOR_KEY + ", has value " + differentiator
final String differentiatorName = properties.getProperty(DIFFERENTIATOR_KEY); + " which does not correspond to any in the FileChangeIngestor Map:" + DIFFERENTIATOR_CONSTRUCTOR_MAP.keySet());
if (differentiatorName != null && !differentiatorName.isEmpty()) {
Supplier<Differentiator<ByteBuffer>> differentiatorSupplier = DIFFERENTIATOR_CONSTRUCTOR_MAP.get(differentiatorName);
if (differentiatorSupplier == null) {
throw new IllegalArgumentException("Property, " + DIFFERENTIATOR_KEY + ", has value " + differentiatorName + " which does not " +
"correspond to any in the PullHttpChangeIngestor Map:" + DIFFERENTIATOR_CONSTRUCTOR_MAP.keySet());
}
differentiator = differentiatorSupplier.get();
} else {
differentiator = WholeConfigDifferentiator.getByteBufferDifferentiator();
}
differentiator.initialize(configurationFileHolder);
} }
protected void setConfigFilePath(Path configFilePath) { private void checkConfigFileLocationCorrectness(Properties properties, Path configFile) {
Path flowConfigFile = Path.of(properties.getProperty(MiNiFiProperties.NIFI_MINIFI_FLOW_CONFIG.getKey())).toAbsolutePath();
Path rawFlowConfigFile = flowConfigFile.getParent().resolve(getBaseName(flowConfigFile.toString()) + RAW_EXTENSION);
if (flowConfigFile.equals(configFile) || rawFlowConfigFile.equals(configFile)) {
throw new IllegalStateException("File ingestor config file (" + CONFIG_FILE_PATH_KEY
+ ") must point to a different file than MiNiFi flow config file and raw flow config file");
}
}
// Methods exposed only for enable testing
void setConfigFilePath(Path configFilePath) {
this.configFilePath = configFilePath; this.configFilePath = configFilePath;
} }
protected void setWatchService(WatchService watchService) { void setWatchService(WatchService watchService) {
this.watchService = watchService; this.watchService = watchService;
} }
protected void setConfigurationChangeNotifier(ConfigurationChangeNotifier configurationChangeNotifier) { void setConfigurationChangeNotifier(ConfigurationChangeNotifier configurationChangeNotifier) {
this.configurationChangeNotifier = configurationChangeNotifier; this.configurationChangeNotifier = configurationChangeNotifier;
} }
protected void setDifferentiator(Differentiator<ByteBuffer> differentiator) { void setDifferentiator(Differentiator<ByteBuffer> differentiator) {
this.differentiator = differentiator; this.differentiator = differentiator;
} }
protected void setPollingPeriod(long duration, TimeUnit unit) {
if (duration < 0) {
throw new IllegalArgumentException("Cannot specify a polling period with duration <=0");
}
this.pollingSeconds = TimeUnit.SECONDS.convert(duration, unit);
}
@Override
public void start() {
executorService = Executors.newScheduledThreadPool(1, r -> {
Thread t = Executors.defaultThreadFactory().newThread(r);
t.setName("File Change Notifier Thread");
t.setDaemon(true);
return t;
});
this.executorService.scheduleWithFixedDelay(this, 0, pollingSeconds, DEFAULT_POLLING_PERIOD_UNIT);
}
@Override
public void close() {
if (this.executorService != null) {
this.executorService.shutdownNow();
}
}
} }

View File

@ -17,8 +17,21 @@
package org.apache.nifi.minifi.bootstrap.configuration.ingestors; package org.apache.nifi.minifi.bootstrap.configuration.ingestors;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.parseBoolean;
import static java.lang.Long.parseLong;
import static java.nio.ByteBuffer.wrap;
import static java.util.Optional.ofNullable;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toMap;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeCoordinator.NOTIFIER_INGESTORS_KEY; import static org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeCoordinator.NOTIFIER_INGESTORS_KEY;
import static org.apache.nifi.minifi.bootstrap.configuration.differentiators.WholeConfigDifferentiator.WHOLE_CONFIG_KEY; import static org.apache.nifi.minifi.bootstrap.configuration.differentiators.WholeConfigDifferentiator.WHOLE_CONFIG_KEY;
import static org.eclipse.jetty.http.HttpScheme.HTTP;
import static org.eclipse.jetty.http.HttpScheme.HTTPS;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
@ -26,49 +39,35 @@ import java.net.InetSocketAddress;
import java.net.Proxy; import java.net.Proxy;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.KeyStore; import java.security.KeyStore;
import java.util.Collections; import java.util.Arrays;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier; import java.util.function.Supplier;
import javax.net.ssl.SSLContext; import java.util.stream.Stream;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import okhttp3.Authenticator;
import okhttp3.Credentials; import okhttp3.Credentials;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder; import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier; import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier;
import org.apache.nifi.minifi.bootstrap.configuration.differentiators.Differentiator; import org.apache.nifi.minifi.bootstrap.configuration.differentiators.Differentiator;
import org.apache.nifi.minifi.bootstrap.configuration.differentiators.WholeConfigDifferentiator; import org.apache.nifi.minifi.bootstrap.configuration.differentiators.WholeConfigDifferentiator;
import org.apache.nifi.minifi.bootstrap.util.ConfigTransformer;
import org.apache.nifi.minifi.commons.schema.common.StringUtil;
import org.apache.nifi.security.ssl.StandardKeyStoreBuilder; import org.apache.nifi.security.ssl.StandardKeyStoreBuilder;
import org.apache.nifi.security.ssl.StandardSslContextBuilder; import org.apache.nifi.security.ssl.StandardSslContextBuilder;
import org.apache.nifi.security.ssl.StandardTrustManagerBuilder; import org.apache.nifi.security.ssl.StandardTrustManagerBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class PullHttpChangeIngestor extends AbstractPullChangeIngestor { public class PullHttpChangeIngestor extends AbstractPullChangeIngestor {
private static final int NOT_MODIFIED_STATUS_CODE = 304;
private static final Map<String, Supplier<Differentiator<ByteBuffer>>> DIFFERENTIATOR_CONSTRUCTOR_MAP;
static {
HashMap<String, Supplier<Differentiator<ByteBuffer>>> tempMap = new HashMap<>();
tempMap.put(WHOLE_CONFIG_KEY, WholeConfigDifferentiator::getByteBufferDifferentiator);
DIFFERENTIATOR_CONSTRUCTOR_MAP = Collections.unmodifiableMap(tempMap);
}
private static final String DEFAULT_CONNECT_TIMEOUT_MS = "5000";
private static final String DEFAULT_READ_TIMEOUT_MS = "15000";
public static final String PULL_HTTP_BASE_KEY = NOTIFIER_INGESTORS_KEY + ".pull.http"; public static final String PULL_HTTP_BASE_KEY = NOTIFIER_INGESTORS_KEY + ".pull.http";
public static final String PULL_HTTP_POLLING_PERIOD_KEY = PULL_HTTP_BASE_KEY + ".period.ms"; public static final String PULL_HTTP_POLLING_PERIOD_KEY = PULL_HTTP_BASE_KEY + ".period.ms";
public static final String PORT_KEY = PULL_HTTP_BASE_KEY + ".port"; public static final String PORT_KEY = PULL_HTTP_BASE_KEY + ".port";
@ -90,116 +89,126 @@ public class PullHttpChangeIngestor extends AbstractPullChangeIngestor {
public static final String DIFFERENTIATOR_KEY = PULL_HTTP_BASE_KEY + ".differentiator"; public static final String DIFFERENTIATOR_KEY = PULL_HTTP_BASE_KEY + ".differentiator";
public static final String USE_ETAG_KEY = PULL_HTTP_BASE_KEY + ".use.etag"; public static final String USE_ETAG_KEY = PULL_HTTP_BASE_KEY + ".use.etag";
public static final String OVERRIDE_SECURITY = PULL_HTTP_BASE_KEY + ".override.security"; public static final String OVERRIDE_SECURITY = PULL_HTTP_BASE_KEY + ".override.security";
public static final String HTTP_HEADERS = PULL_HTTP_BASE_KEY + ".headers";
private static final Logger logger = LoggerFactory.getLogger(PullHttpChangeIngestor.class);
private static final Map<String, Supplier<Differentiator<ByteBuffer>>> DIFFERENTIATOR_CONSTRUCTOR_MAP = Map.of(
WHOLE_CONFIG_KEY, WholeConfigDifferentiator::getByteBufferDifferentiator
);
private static final int NOT_MODIFIED_STATUS_CODE = 304;
private static final String DEFAULT_CONNECT_TIMEOUT_MS = "5000";
private static final String DEFAULT_READ_TIMEOUT_MS = "15000";
private static final String DOUBLE_QUOTES = "\"";
private static final String ETAG_HEADER = "ETag";
private static final String PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization";
private static final String DEFAULT_PATH = "/";
private static final int BAD_REQUEST_STATUS_CODE = 400;
private static final String IF_NONE_MATCH_HEADER_KEY = "If-None-Match";
private static final String HTTP_HEADERS_SEPARATOR = ",";
private static final String HTTP_HEADER_KEY_VALUE_SEPARATOR = ":";
private final AtomicReference<OkHttpClient> httpClientReference = new AtomicReference<>(); private final AtomicReference<OkHttpClient> httpClientReference = new AtomicReference<>();
private final AtomicReference<Integer> portReference = new AtomicReference<>(); private final AtomicReference<Integer> portReference = new AtomicReference<>();
private final AtomicReference<String> hostReference = new AtomicReference<>(); private final AtomicReference<String> hostReference = new AtomicReference<>();
private final AtomicReference<String> pathReference = new AtomicReference<>(); private final AtomicReference<String> pathReference = new AtomicReference<>();
private final AtomicReference<String> queryReference = new AtomicReference<>(); private final AtomicReference<String> queryReference = new AtomicReference<>();
private final AtomicReference<Map<String, String>> httpHeadersReference = new AtomicReference<>();
private volatile Differentiator<ByteBuffer> differentiator; private volatile Differentiator<ByteBuffer> differentiator;
private volatile String connectionScheme; private volatile String connectionScheme;
private volatile String lastEtag = ""; private volatile String lastEtag = "";
private volatile boolean useEtag = false; private volatile boolean useEtag = false;
public PullHttpChangeIngestor() {
logger = LoggerFactory.getLogger(PullHttpChangeIngestor.class);
}
@Override @Override
public void initialize(Properties properties, ConfigurationFileHolder configurationFileHolder, ConfigurationChangeNotifier configurationChangeNotifier) { public void initialize(Properties properties, ConfigurationFileHolder configurationFileHolder, ConfigurationChangeNotifier configurationChangeNotifier) {
super.initialize(properties, configurationFileHolder, configurationChangeNotifier); super.initialize(properties, configurationFileHolder, configurationChangeNotifier);
pollingPeriodMS.set(Integer.parseInt(properties.getProperty(PULL_HTTP_POLLING_PERIOD_KEY, DEFAULT_POLLING_PERIOD))); pollingPeriodMS.set(Integer.parseInt(properties.getProperty(PULL_HTTP_POLLING_PERIOD_KEY, DEFAULT_POLLING_PERIOD_MILLISECONDS)));
if (pollingPeriodMS.get() < 1) { if (pollingPeriodMS.get() < 1) {
throw new IllegalArgumentException("Property, " + PULL_HTTP_POLLING_PERIOD_KEY + ", for the polling period ms must be set with a positive integer."); throw new IllegalArgumentException("Property, " + PULL_HTTP_POLLING_PERIOD_KEY + ", for the polling period ms must be set with a positive integer");
} }
final String host = properties.getProperty(HOST_KEY); String host = ofNullable(properties.getProperty(HOST_KEY))
if (host == null || host.isEmpty()) { .filter(StringUtils::isNotBlank)
throw new IllegalArgumentException("Property, " + HOST_KEY + ", for the hostname to pull configurations from must be specified."); .orElseThrow(() -> new IllegalArgumentException("Property, " + HOST_KEY + ", for the hostname to pull configurations from must be specified"));
} String path = properties.getProperty(PATH_KEY, DEFAULT_PATH);
String query = properties.getProperty(QUERY_KEY, EMPTY);
Map<String, String> httpHeaders = ofNullable(properties.getProperty(HTTP_HEADERS))
.filter(StringUtils::isNotBlank)
.map(headers -> headers.split(HTTP_HEADERS_SEPARATOR))
.stream()
.flatMap(Arrays::stream)
.map(String::trim)
.map(header -> header.split(HTTP_HEADER_KEY_VALUE_SEPARATOR))
.filter(split -> split.length == 2)
.collect(toMap(split -> ofNullable(split[0]).map(String::trim).orElse(EMPTY), split -> ofNullable(split[1]).map(String::trim).orElse(EMPTY)));
logger.debug("Configured HTTP headers: {}", httpHeaders);
final String path = properties.getProperty(PATH_KEY, "/"); ofNullable(properties.get(PORT_KEY))
final String query = properties.getProperty(QUERY_KEY, ""); .map(String.class::cast)
.map(Integer::parseInt)
final String portString = (String) properties.get(PORT_KEY); .ifPresentOrElse(
final Integer port; portReference::set,
if (portString == null) { () -> {
throw new IllegalArgumentException("Property, " + PORT_KEY + ", for the hostname to pull configurations from must be specified."); throw new IllegalArgumentException("Property, " + PORT_KEY + ", for the hostname to pull configurations from must be specified");
} else { });
port = Integer.parseInt(portString);
}
portReference.set(port);
hostReference.set(host); hostReference.set(host);
pathReference.set(path); pathReference.set(path);
queryReference.set(query); queryReference.set(query);
httpHeadersReference.set(httpHeaders);
final String useEtagString = (String) properties.getOrDefault(USE_ETAG_KEY, "false"); useEtag = parseBoolean((String) properties.getOrDefault(USE_ETAG_KEY, FALSE.toString()));
if ("true".equalsIgnoreCase(useEtagString) || "false".equalsIgnoreCase(useEtagString)) {
useEtag = Boolean.parseBoolean(useEtagString);
} else {
throw new IllegalArgumentException("Property, " + USE_ETAG_KEY + ", to specify whether to use the ETag header, must either be a value boolean value (\"true\" or \"false\") or left to " +
"the default value of \"false\". It is set to \"" + useEtagString + "\".");
}
httpClientReference.set(null); httpClientReference.set(null);
final OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder(); OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
.connectTimeout(parseLong(properties.getProperty(CONNECT_TIMEOUT_KEY, DEFAULT_CONNECT_TIMEOUT_MS)), MILLISECONDS)
.readTimeout(parseLong(properties.getProperty(READ_TIMEOUT_KEY, DEFAULT_READ_TIMEOUT_MS)), MILLISECONDS)
.followRedirects(true);
// Set timeouts String proxyHost = properties.getProperty(PROXY_HOST_KEY);
okHttpClientBuilder.connectTimeout(Long.parseLong(properties.getProperty(CONNECT_TIMEOUT_KEY, DEFAULT_CONNECT_TIMEOUT_MS)), TimeUnit.MILLISECONDS); if (isNotBlank(proxyHost)) {
okHttpClientBuilder.readTimeout(Long.parseLong(properties.getProperty(READ_TIMEOUT_KEY, DEFAULT_READ_TIMEOUT_MS)), TimeUnit.MILLISECONDS); ofNullable(properties.getProperty(PROXY_PORT_KEY))
.filter(StringUtils::isNotBlank)
.map(Integer::parseInt)
.map(port -> new InetSocketAddress(proxyHost, port))
.map(inetSocketAddress -> new Proxy(Proxy.Type.HTTP, inetSocketAddress))
.ifPresentOrElse(
okHttpClientBuilder::proxy,
() -> {
throw new IllegalArgumentException("Proxy port required if proxy specified");
});
// Set whether to follow redirects ofNullable(properties.getProperty(PROXY_USERNAME))
okHttpClientBuilder.followRedirects(true); .filter(StringUtils::isNotBlank)
.ifPresent(proxyUserName ->
String proxyHost = properties.getProperty(PROXY_HOST_KEY, ""); ofNullable(properties.getProperty(PROXY_PASSWORD))
if (!proxyHost.isEmpty()) { .map(proxyPassword -> Credentials.basic(proxyUserName, proxyPassword))
String proxyPort = properties.getProperty(PROXY_PORT_KEY); .map(credentials -> (Authenticator) (route, response) -> response.request().newBuilder().addHeader(PROXY_AUTHORIZATION_HEADER, credentials).build())
if (proxyPort == null || proxyPort.isEmpty()) { .ifPresentOrElse(
throw new IllegalArgumentException("Proxy port required if proxy specified."); okHttpClientBuilder::proxyAuthenticator,
} () -> {
okHttpClientBuilder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, Integer.parseInt(proxyPort)))); throw new IllegalArgumentException("Must specify proxy password with proxy username");
String proxyUsername = properties.getProperty(PROXY_USERNAME); }));
if (proxyUsername != null) {
String proxyPassword = properties.getProperty(PROXY_PASSWORD);
if (proxyPassword == null) {
throw new IllegalArgumentException("Must specify proxy password with proxy username.");
}
okHttpClientBuilder.proxyAuthenticator((route, response) -> response.request().newBuilder().addHeader("Proxy-Authorization", Credentials.basic(proxyUsername, proxyPassword)).build());
}
} }
// check if the ssl path is set and add the factory if so
if (properties.containsKey(KEYSTORE_LOCATION_KEY)) { if (properties.containsKey(KEYSTORE_LOCATION_KEY)) {
try { connectionScheme = HTTPS.toString();
setSslSocketFactory(okHttpClientBuilder, properties); setSslSocketFactory(okHttpClientBuilder, properties);
connectionScheme = "https";
} catch (Exception e) {
throw new IllegalStateException(e);
}
} else { } else {
connectionScheme = "http"; connectionScheme = HTTP.toString();
} }
httpClientReference.set(okHttpClientBuilder.build()); httpClientReference.set(okHttpClientBuilder.build());
final String differentiatorName = properties.getProperty(DIFFERENTIATOR_KEY); differentiator = ofNullable(properties.getProperty(DIFFERENTIATOR_KEY))
.filter(not(String::isBlank))
if (differentiatorName != null && !differentiatorName.isEmpty()) { .map(differentiator -> ofNullable(DIFFERENTIATOR_CONSTRUCTOR_MAP.get(differentiator))
Supplier<Differentiator<ByteBuffer>> differentiatorSupplier = DIFFERENTIATOR_CONSTRUCTOR_MAP.get(differentiatorName); .map(Supplier::get)
if (differentiatorSupplier == null) { .orElseThrow(unableToFindDifferentiatorExceptionSupplier(differentiator)))
throw new IllegalArgumentException("Property, " + DIFFERENTIATOR_KEY + ", has value " + differentiatorName + " which does not " + .orElseGet(WholeConfigDifferentiator::getByteBufferDifferentiator);
"correspond to any in the PullHttpChangeIngestor Map:" + DIFFERENTIATOR_CONSTRUCTOR_MAP.keySet());
}
differentiator = differentiatorSupplier.get();
} else {
differentiator = WholeConfigDifferentiator.getByteBufferDifferentiator();
}
differentiator.initialize(configurationFileHolder); differentiator.initialize(configurationFileHolder);
} }
@Override @Override
public void run() { public void run() {
logger.debug("Attempting to pull new config"); logger.debug("Attempting to pull new config");
@ -207,144 +216,106 @@ public class PullHttpChangeIngestor extends AbstractPullChangeIngestor {
.host(hostReference.get()) .host(hostReference.get())
.port(portReference.get()) .port(portReference.get())
.encodedPath(pathReference.get()); .encodedPath(pathReference.get());
final String query = queryReference.get(); ofNullable(queryReference.get())
if (!StringUtil.isNullOrEmpty(query)) { .filter(StringUtils::isNotBlank)
builder = builder.encodedQuery(query); .ifPresent(builder::encodedQuery);
} HttpUrl url = builder.scheme(connectionScheme).build();
final HttpUrl url = builder
.scheme(connectionScheme)
.build();
final Request.Builder requestBuilder = new Request.Builder()
.get()
.url(url);
Request.Builder requestBuilder = new Request.Builder().get().url(url);
if (useEtag) { if (useEtag) {
requestBuilder.addHeader("If-None-Match", lastEtag); requestBuilder.addHeader(IF_NONE_MATCH_HEADER_KEY, lastEtag);
} }
httpHeadersReference.get().forEach(requestBuilder::addHeader);
final Request request = requestBuilder.build(); Request request = requestBuilder.build();
logger.debug("Sending request: {}", request);
ResponseBody body = null;
try (Response response = httpClientReference.get().newCall(request).execute()) { try (Response response = httpClientReference.get().newCall(request).execute()) {
logger.debug("Response received: {}", response.toString()); logger.debug("Response received: {}", response);
int code = response.code(); int code = response.code();
if (code == NOT_MODIFIED_STATUS_CODE) { if (code == NOT_MODIFIED_STATUS_CODE) {
return; return;
} }
if (code >= BAD_REQUEST_STATUS_CODE) {
if (code >= 400) {
throw new IOException("Got response code " + code + " while trying to pull configuration: " + response.body().string()); throw new IOException("Got response code " + code + " while trying to pull configuration: " + response.body().string());
} }
body = response.body(); ResponseBody body = response.body();
if (body == null) { if (body == null) {
logger.warn("No body returned when pulling a new configuration"); logger.warn("No body returned when pulling a new configuration");
return; return;
} }
// checking if some parts of the configuration must be preserved ByteBuffer newFlowConfig = wrap(body.bytes()).duplicate();
ByteBuffer readOnlyNewConfig = if (differentiator.isNew(newFlowConfig)) {
ConfigTransformer.overrideNonFlowSectionsFromOriginalSchema(body.bytes(), configurationFileHolder.getConfigFileReference().get().duplicate(), properties.get());
if (differentiator.isNew(readOnlyNewConfig)) {
logger.debug("New change received, notifying listener"); logger.debug("New change received, notifying listener");
configurationChangeNotifier.notifyListeners(readOnlyNewConfig); configurationChangeNotifier.notifyListeners(newFlowConfig);
logger.debug("Listeners notified"); logger.debug("Listeners notified");
} else { } else {
logger.debug("Pulled config same as currently running."); logger.debug("Pulled config same as currently running");
} }
if (useEtag) { if (useEtag) {
lastEtag = (new StringBuilder("\"")) lastEtag = Stream.of(DOUBLE_QUOTES, response.header(ETAG_HEADER).trim(), DOUBLE_QUOTES).collect(joining());
.append(response.header("ETag").trim())
.append("\"").toString();
} }
} catch (Exception e) { } catch (Exception e) {
logger.warn("Hit an exception while trying to pull", e); logger.warn("Hit an exception while trying to pull", e);
} }
} }
private void setSslSocketFactory(OkHttpClient.Builder okHttpClientBuilder, Properties properties) throws Exception { private void setSslSocketFactory(OkHttpClient.Builder okHttpClientBuilder, Properties properties) {
final String keystoreLocation = properties.getProperty(KEYSTORE_LOCATION_KEY); String keystorePass = properties.getProperty(KEYSTORE_PASSWORD_KEY);
final String keystorePass = properties.getProperty(KEYSTORE_PASSWORD_KEY); KeyStore keyStore = buildKeyStore(properties, KEYSTORE_LOCATION_KEY, KEYSTORE_PASSWORD_KEY, KEYSTORE_TYPE_KEY);
final String keystoreType = properties.getProperty(KEYSTORE_TYPE_KEY); KeyStore truststore = buildKeyStore(properties, TRUSTSTORE_LOCATION_KEY, TRUSTSTORE_PASSWORD_KEY, TRUSTSTORE_TYPE_KEY);
assertKeystorePropertiesSet(keystoreLocation, keystorePass, keystoreType);
final KeyStore keyStore; SSLSocketFactory sslSocketFactory = new StandardSslContextBuilder()
try (final FileInputStream keyStoreStream = new FileInputStream(keystoreLocation)) {
keyStore = new StandardKeyStoreBuilder()
.type(keystoreType)
.inputStream(keyStoreStream)
.password(keystorePass.toCharArray())
.build();
}
final String truststoreLocation = properties.getProperty(TRUSTSTORE_LOCATION_KEY);
final String truststorePass = properties.getProperty(TRUSTSTORE_PASSWORD_KEY);
final String truststoreType = properties.getProperty(TRUSTSTORE_TYPE_KEY);
assertTruststorePropertiesSet(truststoreLocation, truststorePass, truststoreType);
final KeyStore truststore;
try (final FileInputStream trustStoreStream = new FileInputStream(truststoreLocation)) {
truststore = new StandardKeyStoreBuilder()
.type(truststoreType)
.inputStream(trustStoreStream)
.password(truststorePass.toCharArray())
.build();
}
final X509TrustManager trustManager = new StandardTrustManagerBuilder().trustStore(truststore).build();
final SSLContext sslContext = new StandardSslContextBuilder()
.keyStore(keyStore) .keyStore(keyStore)
.keyPassword(keystorePass.toCharArray()) .keyPassword(keystorePass.toCharArray())
.trustStore(truststore) .trustStore(truststore)
.build(); .build()
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); .getSocketFactory();
X509TrustManager trustManager = new StandardTrustManagerBuilder().trustStore(truststore).build();
okHttpClientBuilder.sslSocketFactory(sslSocketFactory, trustManager); okHttpClientBuilder.sslSocketFactory(sslSocketFactory, trustManager);
} }
private void assertKeystorePropertiesSet(String location, String password, String type) { private KeyStore buildKeyStore(Properties properties, String locationKey, String passKey, String typeKey) {
if (location == null || location.isEmpty()) { String keystoreLocation = ofNullable(properties.getProperty(locationKey))
throw new IllegalArgumentException(KEYSTORE_LOCATION_KEY + " is null or is empty"); .filter(StringUtils::isNotBlank)
} .orElseThrow(() -> new IllegalArgumentException(locationKey + " is null or empty"));
String keystorePass = ofNullable(properties.getProperty(passKey))
.filter(StringUtils::isNotBlank)
.orElseThrow(() -> new IllegalArgumentException(passKey + " is null or empty"));
String keystoreType = ofNullable(properties.getProperty(typeKey))
.filter(StringUtils::isNotBlank)
.orElseThrow(() -> new IllegalArgumentException(typeKey + " is null or empty"));
if (password == null || password.isEmpty()) { try (FileInputStream keyStoreStream = new FileInputStream(keystoreLocation)) {
throw new IllegalArgumentException(KEYSTORE_LOCATION_KEY + " is set but " + KEYSTORE_PASSWORD_KEY + " is not (or is empty). If the location is set, the password must also be."); return new StandardKeyStoreBuilder()
} .type(keystoreType)
.inputStream(keyStoreStream)
if (type == null || type.isEmpty()) { .password(keystorePass.toCharArray())
throw new IllegalArgumentException(KEYSTORE_LOCATION_KEY + " is set but " + KEYSTORE_TYPE_KEY + " is not (or is empty). If the location is set, the type must also be."); .build();
} catch (IOException e) {
throw new IllegalStateException("Unable to create keyStore", e);
} }
} }
private void assertTruststorePropertiesSet(String location, String password, String type) { private Supplier<IllegalArgumentException> unableToFindDifferentiatorExceptionSupplier(String differentiator) {
if (location == null || location.isEmpty()) { return () -> new IllegalArgumentException("Property, " + DIFFERENTIATOR_KEY + ", has value " + differentiator
throw new IllegalArgumentException(TRUSTSTORE_LOCATION_KEY + " is not set or is empty"); + " which does not correspond to any in the FileChangeIngestor Map:" + DIFFERENTIATOR_CONSTRUCTOR_MAP.keySet());
} }
if (password == null || password.isEmpty()) { // Methods exposed only for enable testing
throw new IllegalArgumentException(TRUSTSTORE_LOCATION_KEY + " is set but " + TRUSTSTORE_PASSWORD_KEY + " is not (or is empty). If the location is set, the password must also be."); void setDifferentiator(Differentiator<ByteBuffer> differentiator) {
}
if (type == null || type.isEmpty()) {
throw new IllegalArgumentException(TRUSTSTORE_LOCATION_KEY + " is set but " + TRUSTSTORE_TYPE_KEY + " is not (or is empty). If the location is set, the type must also be.");
}
}
protected void setDifferentiator(Differentiator<ByteBuffer> differentiator) {
this.differentiator = differentiator; this.differentiator = differentiator;
} }
public void setLastEtag(String lastEtag) { void setLastEtag(String lastEtag) {
this.lastEtag = lastEtag; this.lastEtag = lastEtag;
} }
public void setUseEtag(boolean useEtag) { void setUseEtag(boolean useEtag) {
this.useEtag = useEtag; this.useEtag = useEtag;
} }
} }

View File

@ -17,6 +17,10 @@
package org.apache.nifi.minifi.bootstrap.configuration.ingestors; package org.apache.nifi.minifi.bootstrap.configuration.ingestors;
import static java.nio.ByteBuffer.wrap;
import static java.util.Optional.ofNullable;
import static java.util.function.Predicate.not;
import static org.apache.commons.io.IOUtils.toByteArray;
import static org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeCoordinator.NOTIFIER_INGESTORS_KEY; import static org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeCoordinator.NOTIFIER_INGESTORS_KEY;
import static org.apache.nifi.minifi.bootstrap.configuration.differentiators.WholeConfigDifferentiator.WHOLE_CONFIG_KEY; import static org.apache.nifi.minifi.bootstrap.configuration.differentiators.WholeConfigDifferentiator.WHOLE_CONFIG_KEY;
@ -29,15 +33,12 @@ import java.nio.ByteBuffer;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.Security; import java.security.Security;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.function.Supplier; import java.util.function.Supplier;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.nifi.jetty.configuration.connector.StandardServerConnectorFactory; import org.apache.nifi.jetty.configuration.connector.StandardServerConnectorFactory;
import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder; import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier; import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier;
@ -45,7 +46,6 @@ import org.apache.nifi.minifi.bootstrap.configuration.ListenerHandleResult;
import org.apache.nifi.minifi.bootstrap.configuration.differentiators.Differentiator; import org.apache.nifi.minifi.bootstrap.configuration.differentiators.Differentiator;
import org.apache.nifi.minifi.bootstrap.configuration.differentiators.WholeConfigDifferentiator; import org.apache.nifi.minifi.bootstrap.configuration.differentiators.WholeConfigDifferentiator;
import org.apache.nifi.minifi.bootstrap.configuration.ingestors.interfaces.ChangeIngestor; import org.apache.nifi.minifi.bootstrap.configuration.ingestors.interfaces.ChangeIngestor;
import org.apache.nifi.minifi.bootstrap.util.ConfigTransformer;
import org.apache.nifi.security.ssl.StandardKeyStoreBuilder; import org.apache.nifi.security.ssl.StandardKeyStoreBuilder;
import org.apache.nifi.security.ssl.StandardSslContextBuilder; import org.apache.nifi.security.ssl.StandardSslContextBuilder;
import org.apache.nifi.security.util.TlsPlatform; import org.apache.nifi.security.util.TlsPlatform;
@ -62,25 +62,17 @@ import org.slf4j.LoggerFactory;
public class RestChangeIngestor implements ChangeIngestor { public class RestChangeIngestor implements ChangeIngestor {
private static final Map<String, Supplier<Differentiator<ByteBuffer>>> DIFFERENTIATOR_CONSTRUCTOR_MAP;
static { static {
HashMap<String, Supplier<Differentiator<ByteBuffer>>> tempMap = new HashMap<>();
tempMap.put(WHOLE_CONFIG_KEY, WholeConfigDifferentiator::getByteBufferDifferentiator);
DIFFERENTIATOR_CONSTRUCTOR_MAP = Collections.unmodifiableMap(tempMap);
Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
} }
public static final String GET_TEXT = "This is a config change listener for an Apache NiFi - MiNiFi instance.\n" + public static final String GET_TEXT = "This is a config change listener for an Apache NiFi - MiNiFi instance.\n" +
"Use this rest server to upload a conf.yml to configure the MiNiFi instance.\n" + "Use this rest server to upload a flow.json to configure the MiNiFi instance.\n" +
"Send a POST http request to '/' to upload the file."; "Send a POST http request to '/' to upload the file.";
public static final String OTHER_TEXT = "This is not a support HTTP operation. Please use GET to get more information or POST to upload a new config.yml file.\n"; public static final String OTHER_TEXT = "This is not a supported HTTP operation. Please use GET to get more information or POST to upload a new flow.json file.\n";
public static final String POST = "POST"; public static final String POST = "POST";
public static final String GET = "GET"; public static final String GET = "GET";
private final static Logger logger = LoggerFactory.getLogger(RestChangeIngestor.class); public static final String RECEIVE_HTTP_BASE_KEY = NOTIFIER_INGESTORS_KEY + ".receive.http";
private static final String RECEIVE_HTTP_BASE_KEY = NOTIFIER_INGESTORS_KEY + ".receive.http";
public static final String PORT_KEY = RECEIVE_HTTP_BASE_KEY + ".port"; public static final String PORT_KEY = RECEIVE_HTTP_BASE_KEY + ".port";
public static final String HOST_KEY = RECEIVE_HTTP_BASE_KEY + ".host"; public static final String HOST_KEY = RECEIVE_HTTP_BASE_KEY + ".host";
public static final String TRUSTSTORE_LOCATION_KEY = RECEIVE_HTTP_BASE_KEY + ".truststore.location"; public static final String TRUSTSTORE_LOCATION_KEY = RECEIVE_HTTP_BASE_KEY + ".truststore.location";
@ -91,12 +83,17 @@ public class RestChangeIngestor implements ChangeIngestor {
public static final String KEYSTORE_TYPE_KEY = RECEIVE_HTTP_BASE_KEY + ".keystore.type"; public static final String KEYSTORE_TYPE_KEY = RECEIVE_HTTP_BASE_KEY + ".keystore.type";
public static final String NEED_CLIENT_AUTH_KEY = RECEIVE_HTTP_BASE_KEY + ".need.client.auth"; public static final String NEED_CLIENT_AUTH_KEY = RECEIVE_HTTP_BASE_KEY + ".need.client.auth";
public static final String DIFFERENTIATOR_KEY = RECEIVE_HTTP_BASE_KEY + ".differentiator"; public static final String DIFFERENTIATOR_KEY = RECEIVE_HTTP_BASE_KEY + ".differentiator";
private final static Logger logger = LoggerFactory.getLogger(RestChangeIngestor.class);
private static final Map<String, Supplier<Differentiator<ByteBuffer>>> DIFFERENTIATOR_CONSTRUCTOR_MAP = Map.of(
WHOLE_CONFIG_KEY, WholeConfigDifferentiator::getByteBufferDifferentiator
);
private final Server jetty; private final Server jetty;
private volatile Differentiator<ByteBuffer> differentiator; private volatile Differentiator<ByteBuffer> differentiator;
private volatile ConfigurationChangeNotifier configurationChangeNotifier; private volatile ConfigurationChangeNotifier configurationChangeNotifier;
private volatile ConfigurationFileHolder configurationFileHolder;
private volatile Properties properties;
public RestChangeIngestor() { public RestChangeIngestor() {
QueuedThreadPool queuedThreadPool = new QueuedThreadPool(); QueuedThreadPool queuedThreadPool = new QueuedThreadPool();
@ -106,36 +103,23 @@ public class RestChangeIngestor implements ChangeIngestor {
@Override @Override
public void initialize(Properties properties, ConfigurationFileHolder configurationFileHolder, ConfigurationChangeNotifier configurationChangeNotifier) { public void initialize(Properties properties, ConfigurationFileHolder configurationFileHolder, ConfigurationChangeNotifier configurationChangeNotifier) {
this.configurationFileHolder = configurationFileHolder; logger.info("Initializing RestChangeIngestor");
this.properties = properties; this.differentiator = ofNullable(properties.getProperty(DIFFERENTIATOR_KEY))
logger.info("Initializing"); .filter(not(String::isBlank))
String differentiatorName = properties.getProperty(DIFFERENTIATOR_KEY); .map(differentiator -> ofNullable(DIFFERENTIATOR_CONSTRUCTOR_MAP.get(differentiator))
.map(Supplier::get)
.orElseThrow(unableToFindDifferentiatorExceptionSupplier(differentiator)))
.orElseGet(WholeConfigDifferentiator::getByteBufferDifferentiator);
this.differentiator.initialize(configurationFileHolder);
if (differentiatorName != null && !differentiatorName.isEmpty()) { ofNullable(properties.getProperty(KEYSTORE_LOCATION_KEY))
Supplier<Differentiator<ByteBuffer>> differentiatorSupplier = DIFFERENTIATOR_CONSTRUCTOR_MAP.get(differentiatorName); .ifPresentOrElse(keyStoreLocation -> createSecureConnector(properties), () -> createConnector(properties));
if (differentiatorSupplier == null) {
throw new IllegalArgumentException("Property, " + DIFFERENTIATOR_KEY + ", has value " + differentiatorName + " which does not " +
"correspond to any in the PullHttpChangeIngestor Map:" + DIFFERENTIATOR_CONSTRUCTOR_MAP.keySet());
}
differentiator = differentiatorSupplier.get();
} else {
differentiator = WholeConfigDifferentiator.getByteBufferDifferentiator();
}
differentiator.initialize(configurationFileHolder);
// create the secure connector if keystore location is specified
if (properties.getProperty(KEYSTORE_LOCATION_KEY) != null) {
createSecureConnector(properties);
} else {
// create the unsecure connector otherwise
createConnector(properties);
}
this.configurationChangeNotifier = configurationChangeNotifier; this.configurationChangeNotifier = configurationChangeNotifier;
HandlerCollection handlerCollection = new HandlerCollection(true); HandlerCollection handlerCollection = new HandlerCollection(true);
handlerCollection.addHandler(new JettyHandler()); handlerCollection.addHandler(new JettyHandler());
jetty.setHandler(handlerCollection); this.jetty.setHandler(handlerCollection);
} }
@Override @Override
@ -231,7 +215,13 @@ public class RestChangeIngestor implements ChangeIngestor {
logger.info("HTTPS Connector added for Host [{}] and Port [{}]", https.getHost(), https.getPort()); logger.info("HTTPS Connector added for Host [{}] and Port [{}]", https.getHost(), https.getPort());
} }
protected void setDifferentiator(Differentiator<ByteBuffer> differentiator) { private Supplier<IllegalArgumentException> unableToFindDifferentiatorExceptionSupplier(String differentiator) {
return () -> new IllegalArgumentException("Property, " + DIFFERENTIATOR_KEY + ", has value " + differentiator
+ " which does not correspond to any in the FileChangeIngestor Map:" + DIFFERENTIATOR_CONSTRUCTOR_MAP.keySet());
}
// Method exposed only for enable testing
void setDifferentiator(Differentiator<ByteBuffer> differentiator) {
this.differentiator = differentiator; this.differentiator = differentiator;
} }
@ -249,13 +239,9 @@ public class RestChangeIngestor implements ChangeIngestor {
int statusCode; int statusCode;
String responseText; String responseText;
try { try {
ByteBuffer readOnlyNewConfig = ByteBuffer newFlowConfig = wrap(toByteArray(request.getInputStream())).duplicate();
ConfigTransformer.overrideNonFlowSectionsFromOriginalSchema( if (differentiator.isNew(newFlowConfig)) {
IOUtils.toByteArray(request.getInputStream()), configurationFileHolder.getConfigFileReference().get().duplicate(), properties); Collection<ListenerHandleResult> listenerHandleResults = configurationChangeNotifier.notifyListeners(newFlowConfig);
if (differentiator.isNew(readOnlyNewConfig)) {
Collection<ListenerHandleResult> listenerHandleResults = configurationChangeNotifier.notifyListeners(readOnlyNewConfig);
statusCode = 200; statusCode = 200;
for (ListenerHandleResult result : listenerHandleResults) { for (ListenerHandleResult result : listenerHandleResults) {
@ -312,6 +298,5 @@ public class RestChangeIngestor implements ChangeIngestor {
logger.info("request content type = " + request.getContentType()); logger.info("request content type = " + request.getContentType());
logger.info("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); logger.info("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
} }
} }
} }

View File

@ -17,6 +17,8 @@
package org.apache.nifi.minifi.bootstrap.service; package org.apache.nifi.minifi.bootstrap.service;
import static org.apache.commons.lang3.StringUtils.SPACE;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.IOException; import java.io.IOException;
@ -40,12 +42,10 @@ public class BootstrapCodec {
private final RunMiNiFi runner; private final RunMiNiFi runner;
private final Logger logger = LoggerFactory.getLogger(BootstrapCodec.class); private final Logger logger = LoggerFactory.getLogger(BootstrapCodec.class);
private final UpdatePropertiesService updatePropertiesService; private final UpdatePropertiesService updatePropertiesService;
private final UpdateConfigurationService updateConfigurationService;
public BootstrapCodec(RunMiNiFi runner, BootstrapFileProvider bootstrapFileProvider, ConfigurationChangeListener configurationChangeListener) { public BootstrapCodec(RunMiNiFi runner, BootstrapFileProvider bootstrapFileProvider, ConfigurationChangeListener configurationChangeListener) {
this.runner = runner; this.runner = runner;
this.updatePropertiesService = new UpdatePropertiesService(runner, logger, bootstrapFileProvider); this.updatePropertiesService = new UpdatePropertiesService(runner, logger, bootstrapFileProvider);
this.updateConfigurationService = new UpdateConfigurationService(runner, configurationChangeListener, bootstrapFileProvider);
} }
public void communicate(InputStream in, OutputStream out) throws IOException { public void communicate(InputStream in, OutputStream out) throws IOException {
@ -53,18 +53,13 @@ public class BootstrapCodec {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out)); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
String line = reader.readLine(); String line = reader.readLine();
String[] splits = Optional.ofNullable(line).map(l -> l.split(" ")).orElse(new String[0]); String[] splits = Optional.ofNullable(line)
if (splits.length == 0) { .map(l -> l.split(SPACE))
throw new IOException("Received invalid command from MiNiFi: " + line); .filter(items -> items.length > 0)
} .orElseThrow(() -> new IOException("Received invalid command from MiNiFi: " + line));
BootstrapCommand cmd = BootstrapCommand.safeValueOf(splits[0]); BootstrapCommand cmd = BootstrapCommand.safeValueOf(splits[0]);
String[] args; String[] args = splits.length == 1 ? new String[0] : Arrays.copyOfRange(splits, 1, splits.length);
if (splits.length == 1) {
args = new String[0];
} else {
args = Arrays.copyOfRange(splits, 1, splits.length);
}
try { try {
processRequest(cmd, args, writer); processRequest(cmd, args, writer);
@ -90,21 +85,11 @@ public class BootstrapCodec {
case UPDATE_PROPERTIES: case UPDATE_PROPERTIES:
handlePropertiesUpdateCommand(writer); handlePropertiesUpdateCommand(writer);
break; break;
case UPDATE_CONFIGURATION:
handleUpdateConfigurationCommand(writer);
break;
default: default:
throw new InvalidCommandException("Unknown command: " + cmd); throw new InvalidCommandException("Unknown command: " + cmd);
} }
} }
private void handleUpdateConfigurationCommand(BufferedWriter writer) throws IOException {
logger.debug("Received 'UPDATE_CONFIGURATION' command from MINIFI");
writeOk(writer);
runner.setCommandInProgress(true);
updateConfigurationService.handleUpdate().ifPresent(runner::sendAcknowledgeToMiNiFi);
}
private void handlePropertiesUpdateCommand(BufferedWriter writer) throws IOException { private void handlePropertiesUpdateCommand(BufferedWriter writer) throws IOException {
logger.debug("Received 'UPDATE_PROPERTIES' command from MINIFI"); logger.debug("Received 'UPDATE_PROPERTIES' command from MINIFI");
writeOk(writer); writeOk(writer);
@ -170,7 +155,7 @@ public class BootstrapCodec {
} }
private enum BootstrapCommand { private enum BootstrapCommand {
PORT, STARTED, SHUTDOWN, RELOAD, UPDATE_PROPERTIES, UPDATE_CONFIGURATION, UNKNOWN; PORT, STARTED, SHUTDOWN, RELOAD, UPDATE_PROPERTIES, UNKNOWN;
public static BootstrapCommand safeValueOf(String value) { public static BootstrapCommand safeValueOf(String value) {
try { try {

View File

@ -94,14 +94,6 @@ public class BootstrapFileProvider {
return reloadFile; return reloadFile;
} }
public File getConfigYmlSwapFile() {
File confDir = bootstrapConfigFile.getParentFile();
File swapFile = new File(confDir, "swap.yml");
LOGGER.debug("Swap File: {}", swapFile);
return swapFile;
}
public File getBootstrapConfSwapFile() { public File getBootstrapConfSwapFile() {
File confDir = bootstrapConfigFile.getParentFile(); File confDir = bootstrapConfigFile.getParentFile();
File swapFile = new File(confDir, "bootstrap-swap.conf"); File swapFile = new File(confDir, "bootstrap-swap.conf");

View File

@ -14,82 +14,48 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.apache.nifi.minifi.bootstrap.service; package org.apache.nifi.minifi.bootstrap.service;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static java.nio.ByteBuffer.wrap;
import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.CONF_DIR_KEY; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.MINIFI_CONFIG_FILE_KEY; import static org.apache.commons.io.IOUtils.closeQuietly;
import static org.apache.nifi.minifi.bootstrap.util.ConfigTransformer.generateConfigFiles; import static org.apache.commons.io.IOUtils.toByteArray;
import static org.apache.nifi.minifi.commons.api.MiNiFiConstants.BACKUP_EXTENSION;
import static org.apache.nifi.minifi.commons.api.MiNiFiConstants.RAW_EXTENSION;
import static org.apache.nifi.minifi.commons.util.FlowUpdateUtils.backup;
import static org.apache.nifi.minifi.commons.util.FlowUpdateUtils.persist;
import static org.apache.nifi.minifi.commons.util.FlowUpdateUtils.removeIfExists;
import static org.apache.nifi.minifi.commons.util.FlowUpdateUtils.revert;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.file.Files; import java.nio.file.Path;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.FilenameUtils;
import org.apache.nifi.minifi.bootstrap.RunMiNiFi; import org.apache.nifi.minifi.bootstrap.RunMiNiFi;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeException; import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeException;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeListener; import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeListener;
import org.apache.nifi.minifi.commons.api.MiNiFiProperties;
import org.apache.nifi.minifi.commons.service.FlowEnrichService;
import org.slf4j.Logger; import org.slf4j.Logger;
public class MiNiFiConfigurationChangeListener implements ConfigurationChangeListener { public class MiNiFiConfigurationChangeListener implements ConfigurationChangeListener {
private static final ReentrantLock handlingLock = new ReentrantLock();
private final RunMiNiFi runner; private final RunMiNiFi runner;
private final Logger logger; private final Logger logger;
private final BootstrapFileProvider bootstrapFileProvider; private final BootstrapFileProvider bootstrapFileProvider;
private final FlowEnrichService flowEnrichService;
private static final ReentrantLock handlingLock = new ReentrantLock(); public MiNiFiConfigurationChangeListener(RunMiNiFi runner, Logger logger, BootstrapFileProvider bootstrapFileProvider, FlowEnrichService flowEnrichService) {
public MiNiFiConfigurationChangeListener(RunMiNiFi runner, Logger logger, BootstrapFileProvider bootstrapFileProvider) {
this.runner = runner; this.runner = runner;
this.logger = logger; this.logger = logger;
this.bootstrapFileProvider = bootstrapFileProvider; this.bootstrapFileProvider = bootstrapFileProvider;
} this.flowEnrichService = flowEnrichService;
@Override
public void handleChange(InputStream configInputStream) throws ConfigurationChangeException {
logger.info("Received notification of a change");
if (!handlingLock.tryLock()) {
throw new ConfigurationChangeException("Instance is already handling another change");
}
// Store the incoming stream as a byte array to be shared among components that need it
try {
Properties bootstrapProperties = bootstrapFileProvider.getBootstrapProperties();
File configFile = new File(bootstrapProperties.getProperty(MINIFI_CONFIG_FILE_KEY));
File swapConfigFile = bootstrapFileProvider.getConfigYmlSwapFile();
logger.info("Persisting old configuration to {}", swapConfigFile.getAbsolutePath());
try (FileInputStream configFileInputStream = new FileInputStream(configFile)) {
Files.copy(configFileInputStream, swapConfigFile.toPath(), REPLACE_EXISTING);
}
// write out new config to file
Files.copy(configInputStream, configFile.toPath(), REPLACE_EXISTING);
// Create an input stream to feed to the config transformer
try (FileInputStream newConfigIs = new FileInputStream(configFile)) {
try {
String confDir = bootstrapProperties.getProperty(CONF_DIR_KEY);
transformConfigurationFiles(confDir, newConfigIs, configFile, swapConfigFile);
} catch (Exception e) {
logger.debug("Transformation of new config file failed after swap file was created, deleting it.");
if (!swapConfigFile.delete()) {
logger.warn("The swap file failed to delete after a failed handling of a change. It should be cleaned up manually.");
}
throw e;
}
}
} catch (Exception e) {
throw new ConfigurationChangeException("Unable to perform reload of received configuration change", e);
} finally {
IOUtils.closeQuietly(configInputStream);
handlingLock.unlock();
}
} }
@Override @Override
@ -97,33 +63,53 @@ public class MiNiFiConfigurationChangeListener implements ConfigurationChangeLis
return "MiNiFiConfigurationChangeListener"; return "MiNiFiConfigurationChangeListener";
} }
private void transformConfigurationFiles(String confDir, FileInputStream newConfigIs, File configFile, File swapConfigFile) throws Exception { @Override
try { public void handleChange(InputStream flowConfigInputStream) throws ConfigurationChangeException {
logger.info("Performing transformation for input and saving outputs to {}", confDir); logger.info("Received notification of a change");
ByteBuffer tempConfigFile = generateConfigFiles(newConfigIs, confDir, bootstrapFileProvider.getBootstrapProperties());
runner.getConfigFileReference().set(tempConfigFile.asReadOnlyBuffer()); if (!handlingLock.tryLock()) {
reloadNewConfiguration(swapConfigFile, confDir); throw new ConfigurationChangeException("Instance is already handling another change");
} catch (Exception e) {
logger.debug("Transformation of new config file failed after replacing original with the swap file, reverting.");
try (FileInputStream swapConfigFileStream = new FileInputStream(swapConfigFile)) {
Files.copy(swapConfigFileStream, configFile.toPath(), REPLACE_EXISTING);
} }
throw e;
Path currentFlowConfigFile = null;
Path backupFlowConfigFile = null;
Path currentRawFlowConfigFile = null;
Path backupRawFlowConfigFile = null;
try {
Properties bootstrapProperties = bootstrapFileProvider.getBootstrapProperties();
currentFlowConfigFile = Path.of(bootstrapProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_FLOW_CONFIG.getKey())).toAbsolutePath();
backupFlowConfigFile = Path.of(currentFlowConfigFile + BACKUP_EXTENSION);
String currentFlowConfigFileBaseName = FilenameUtils.getBaseName(currentFlowConfigFile.toString());
currentRawFlowConfigFile = currentFlowConfigFile.getParent().resolve(currentFlowConfigFileBaseName + RAW_EXTENSION);
backupRawFlowConfigFile = currentFlowConfigFile.getParent().resolve(currentFlowConfigFileBaseName + RAW_EXTENSION + BACKUP_EXTENSION);
backup(currentFlowConfigFile, backupFlowConfigFile);
backup(currentRawFlowConfigFile, backupRawFlowConfigFile);
byte[] rawFlow = toByteArray(flowConfigInputStream);
byte[] enrichedFlow = flowEnrichService.enrichFlow(rawFlow);
persist(enrichedFlow, currentFlowConfigFile, true);
restartInstance();
persist(rawFlow, currentRawFlowConfigFile, false);
setActiveFlowReference(wrap(rawFlow));
logger.info("MiNiFi has finished reloading successfully and applied the new flow configuration");
} catch (Exception e) {
logger.error("Configuration update failed. Reverting to previous flow", e);
revert(backupFlowConfigFile, currentFlowConfigFile);
revert(backupRawFlowConfigFile, currentRawFlowConfigFile);
throw new ConfigurationChangeException("Unable to perform reload of received configuration change", e);
} finally {
removeIfExists(backupFlowConfigFile);
removeIfExists(backupRawFlowConfigFile);
closeQuietly(flowConfigInputStream);
handlingLock.unlock();
} }
} }
private void reloadNewConfiguration(File swapConfigFile, String confDir) throws Exception { private void setActiveFlowReference(ByteBuffer flowConfig) {
try { logger.debug("Setting active flow reference {} with content:\n{}", flowConfig, new String(flowConfig.array(), UTF_8));
logger.info("Reloading instance with new configuration"); runner.getConfigFileReference().set(flowConfig);
restartInstance();
} catch (Exception e) {
logger.debug("Transformation of new config file failed after transformation into Flow.xml and nifi.properties, reverting.");
try (FileInputStream swapConfigFileStream = new FileInputStream(swapConfigFile)) {
ByteBuffer resetConfigFile = generateConfigFiles(swapConfigFileStream, confDir, bootstrapFileProvider.getBootstrapProperties());
runner.getConfigFileReference().set(resetConfigFile.asReadOnlyBuffer());
}
throw e;
}
} }
private void restartInstance() throws IOException { private void restartInstance() throws IOException {

View File

@ -17,15 +17,19 @@
package org.apache.nifi.minifi.bootstrap.service; package org.apache.nifi.minifi.bootstrap.service;
import static java.lang.String.join;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.joining;
import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.CONF_DIR_KEY; import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.CONF_DIR_KEY;
import static org.apache.nifi.util.NiFiProperties.PROPERTIES_FILE_PATH;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Map.Entry;
import java.util.Optional; import java.util.Optional;
import java.util.Properties; import java.util.Properties;
import java.util.stream.Stream;
public class MiNiFiExecCommandProvider { public class MiNiFiExecCommandProvider {
@ -40,12 +44,28 @@ public class MiNiFiExecCommandProvider {
public static final String DEFAULT_BOOTSTRAP_LOG_FILE_NAME = "minifi-bootstrap"; public static final String DEFAULT_BOOTSTRAP_LOG_FILE_NAME = "minifi-bootstrap";
public static final String DEFAULT_LOG_FILE_EXTENSION = "log"; public static final String DEFAULT_LOG_FILE_EXTENSION = "log";
private static final String PROPERTIES_FILE_KEY = "props.file";
private static final String LIB_DIR_KEY = "lib.dir";
private static final String JAVA_COMMAND_KEY = "java";
private static final String JAVA_ARG_KEY_PREFIX = "java.arg";
private static final String DEFAULT_JAVA_CMD = "java"; private static final String DEFAULT_JAVA_CMD = "java";
private static final String DEFAULT_LIB_DIR = "./lib"; private static final String DEFAULT_LIB_DIR = "./lib";
private static final String DEFAULT_CONF_DIR = "./conf"; private static final String DEFAULT_CONF_DIR = "./conf";
private static final String DEFAULT_CONFIG_FILE = DEFAULT_CONF_DIR + "/bootstrap.conf"; private static final String DEFAULT_MINIFI_PROPERTIES_FILE = "minifi.properties";
private static final String WINDOWS_FILE_EXTENSION = ".exe"; private static final String WINDOWS_FILE_EXTENSION = ".exe";
private static final String LINUX_FILE_EXTENSION = "";
private static final String JAR_FILE_EXTENSION = ".jar";
private static final String JAVA_HOME_ENVIRONMENT_VARIABLE = "JAVA_HOME";
private static final String MINIFI_CLASS_NAME = "MiNiFi";
private static final String MINIFI_FULLY_QUALIFIED_CLASS_NAME = "org.apache.nifi.minifi." + MINIFI_CLASS_NAME;
private static final String SYSTEM_PROPERTY_TEMPLATE = "-D%s=%s";
private static final String NIFI_BOOTSTRAP_LISTEN_PORT = "nifi.bootstrap.listen.port";
private static final String APP = "app";
private static final String CLASSPATH = "-classpath";
private static final String BIN_DIRECTORY = "bin";
private final BootstrapFileProvider bootstrapFileProvider; private final BootstrapFileProvider bootstrapFileProvider;
@ -53,6 +73,16 @@ public class MiNiFiExecCommandProvider {
this.bootstrapFileProvider = bootstrapFileProvider; this.bootstrapFileProvider = bootstrapFileProvider;
} }
public static String getMiNiFiPropertiesPath(Properties props, File confDir) {
return ofNullable(props.getProperty(PROPERTIES_FILE_KEY))
.orElseGet(() -> ofNullable(confDir)
.filter(File::exists)
.map(File::getAbsolutePath)
.map(parent -> Path.of(parent, DEFAULT_MINIFI_PROPERTIES_FILE).toAbsolutePath().toString())
.orElseGet(() -> Path.of(DEFAULT_CONF_DIR, DEFAULT_MINIFI_PROPERTIES_FILE).toAbsolutePath().toString()))
.trim();
}
/** /**
* Returns the process arguments required for the bootstrap to start the MiNiFi process. * Returns the process arguments required for the bootstrap to start the MiNiFi process.
* *
@ -62,116 +92,82 @@ public class MiNiFiExecCommandProvider {
* @throws IOException throws IOException if any of the configuration file read fails * @throws IOException throws IOException if any of the configuration file read fails
*/ */
public List<String> getMiNiFiExecCommand(int listenPort, File workingDir) throws IOException { public List<String> getMiNiFiExecCommand(int listenPort, File workingDir) throws IOException {
Properties props = bootstrapFileProvider.getBootstrapProperties(); Properties bootstrapProperties = bootstrapFileProvider.getBootstrapProperties();
File confDir = getFile(props.getProperty(CONF_DIR_KEY, DEFAULT_CONF_DIR).trim(), workingDir);
File libDir = getFile(props.getProperty("lib.dir", DEFAULT_LIB_DIR).trim(), workingDir); File confDir = getFile(bootstrapProperties.getProperty(CONF_DIR_KEY, DEFAULT_CONF_DIR).trim(), workingDir);
File libDir = getFile(bootstrapProperties.getProperty(LIB_DIR_KEY, DEFAULT_LIB_DIR).trim(), workingDir);
String minifiLogDir = System.getProperty(LOG_DIR, DEFAULT_LOG_DIR).trim(); String minifiLogDir = System.getProperty(LOG_DIR, DEFAULT_LOG_DIR).trim();
String minifiAppLogFileName = System.getProperty(APP_LOG_FILE_NAME, DEFAULT_APP_LOG_FILE_NAME).trim(); String minifiAppLogFileName = System.getProperty(APP_LOG_FILE_NAME, DEFAULT_APP_LOG_FILE_NAME).trim();
String minifiAppLogFileExtension = System.getProperty(APP_LOG_FILE_EXTENSION, DEFAULT_LOG_FILE_EXTENSION).trim(); String minifiAppLogFileExtension = System.getProperty(APP_LOG_FILE_EXTENSION, DEFAULT_LOG_FILE_EXTENSION).trim();
String minifiBootstrapLogFileName = System.getProperty(BOOTSTRAP_LOG_FILE_NAME, DEFAULT_BOOTSTRAP_LOG_FILE_NAME).trim(); String minifiBootstrapLogFileName = System.getProperty(BOOTSTRAP_LOG_FILE_NAME, DEFAULT_BOOTSTRAP_LOG_FILE_NAME).trim();
String minifiBootstrapLogFileExtension = System.getProperty(BOOTSTRAP_LOG_FILE_EXTENSION, DEFAULT_LOG_FILE_EXTENSION).trim(); String minifiBootstrapLogFileExtension = System.getProperty(BOOTSTRAP_LOG_FILE_EXTENSION, DEFAULT_LOG_FILE_EXTENSION).trim();
List<String> cmd = new ArrayList<>(); List<String> javaCommand = List.of(getJavaCommand(bootstrapProperties), CLASSPATH, buildClassPath(confDir, libDir));
cmd.add(getJavaCommand(props)); List<String> javaAdditionalArgs = getJavaAdditionalArgs(bootstrapProperties);
cmd.add("-classpath"); List<String> systemProperties = List.of(
cmd.add(buildClassPath(props, confDir, libDir)); systemProperty(PROPERTIES_FILE_PATH, getMiNiFiPropertiesPath(bootstrapProperties, confDir)),
cmd.addAll(getJavaAdditionalArgs(props)); systemProperty(NIFI_BOOTSTRAP_LISTEN_PORT, listenPort),
cmd.add("-Dnifi.properties.file.path=" + getMiNiFiPropsFileName(props, confDir)); systemProperty(APP, MINIFI_CLASS_NAME),
cmd.add("-Dnifi.bootstrap.listen.port=" + listenPort); systemProperty(LOG_DIR, minifiLogDir),
cmd.add("-Dapp=MiNiFi"); systemProperty(APP_LOG_FILE_NAME, minifiAppLogFileName),
cmd.add("-D" + LOG_DIR + "=" + minifiLogDir); systemProperty(APP_LOG_FILE_EXTENSION, minifiAppLogFileExtension),
cmd.add("-D" + APP_LOG_FILE_NAME + "=" + minifiAppLogFileName); systemProperty(BOOTSTRAP_LOG_FILE_NAME, minifiBootstrapLogFileName),
cmd.add("-D" + APP_LOG_FILE_EXTENSION + "=" + minifiAppLogFileExtension); systemProperty(BOOTSTRAP_LOG_FILE_EXTENSION, minifiBootstrapLogFileExtension)
cmd.add("-D" + BOOTSTRAP_LOG_FILE_NAME + "=" + minifiBootstrapLogFileName); );
cmd.add("-D" + BOOTSTRAP_LOG_FILE_EXTENSION + "=" + minifiBootstrapLogFileExtension);
cmd.add("org.apache.nifi.minifi.MiNiFi");
return cmd; return List.of(javaCommand, javaAdditionalArgs, systemProperties, List.of(MINIFI_FULLY_QUALIFIED_CLASS_NAME))
} .stream()
.flatMap(List::stream)
private String getJavaCommand(Properties props) { .toList();
String javaCmd = props.getProperty("java");
if (javaCmd == null) {
javaCmd = DEFAULT_JAVA_CMD;
}
if (javaCmd.equals(DEFAULT_JAVA_CMD)) {
Optional.ofNullable(System.getenv("JAVA_HOME"))
.map(javaHome -> getJavaCommandBasedOnExtension(javaHome, WINDOWS_FILE_EXTENSION)
.orElseGet(() -> getJavaCommandBasedOnExtension(javaHome, "").orElse(DEFAULT_JAVA_CMD)));
}
return javaCmd;
}
private Optional<String> getJavaCommandBasedOnExtension(String javaHome, String extension) {
String javaCmd = null;
File javaFile = new File(javaHome + File.separatorChar + "bin" + File.separatorChar + "java" + extension);
if (javaFile.exists() && javaFile.canExecute()) {
javaCmd = javaFile.getAbsolutePath();
}
return Optional.ofNullable(javaCmd);
}
private String buildClassPath(Properties props, File confDir, File libDir) {
File[] libFiles = libDir.listFiles((dir, filename) -> filename.toLowerCase().endsWith(".jar"));
if (libFiles == null || libFiles.length == 0) {
throw new RuntimeException("Could not find lib directory at " + libDir.getAbsolutePath());
}
File[] confFiles = confDir.listFiles();
if (confFiles == null || confFiles.length == 0) {
throw new RuntimeException("Could not find conf directory at " + confDir.getAbsolutePath());
}
List<String> cpFiles = new ArrayList<>(confFiles.length + libFiles.length);
cpFiles.add(confDir.getAbsolutePath());
for (File file : libFiles) {
cpFiles.add(file.getAbsolutePath());
}
StringBuilder classPathBuilder = new StringBuilder();
for (int i = 0; i < cpFiles.size(); i++) {
String filename = cpFiles.get(i);
classPathBuilder.append(filename);
if (i < cpFiles.size() - 1) {
classPathBuilder.append(File.pathSeparatorChar);
}
}
return classPathBuilder.toString();
}
private List<String> getJavaAdditionalArgs(Properties props) {
List<String> javaAdditionalArgs = new ArrayList<>();
for (Entry<Object, Object> entry : props.entrySet()) {
String key = (String) entry.getKey();
String value = (String) entry.getValue();
if (key.startsWith("java.arg")) {
javaAdditionalArgs.add(value);
}
}
return javaAdditionalArgs;
}
private String getMiNiFiPropsFileName(Properties props, File confDir) {
String minifiPropsFilename = props.getProperty("props.file");
if (minifiPropsFilename == null) {
if (confDir.exists()) {
minifiPropsFilename = new File(confDir, "nifi.properties").getAbsolutePath();
} else {
minifiPropsFilename = DEFAULT_CONFIG_FILE;
}
}
return minifiPropsFilename.trim();
} }
private File getFile(String filename, File workingDir) { private File getFile(String filename, File workingDir) {
File file = new File(filename); File file = new File(filename);
if (!file.isAbsolute()) { return file.isAbsolute() ? file : new File(workingDir, filename).getAbsoluteFile();
file = new File(workingDir, filename);
} }
return file;
private String getJavaCommand(Properties bootstrapProperties) {
String javaCommand = bootstrapProperties.getProperty(JAVA_COMMAND_KEY, DEFAULT_JAVA_CMD);
return javaCommand.equals(DEFAULT_JAVA_CMD)
? ofNullable(System.getenv(JAVA_HOME_ENVIRONMENT_VARIABLE))
.flatMap(javaHome ->
getJavaCommandBasedOnExtension(javaHome, javaCommand, WINDOWS_FILE_EXTENSION)
.or(() -> getJavaCommandBasedOnExtension(javaHome, javaCommand, LINUX_FILE_EXTENSION)))
.orElse(DEFAULT_JAVA_CMD)
: javaCommand;
}
private Optional<String> getJavaCommandBasedOnExtension(String javaHome, String javaCommand, String extension) {
return Optional.of(new File(join(File.separator, javaHome, BIN_DIRECTORY, javaCommand + extension)))
.filter(File::exists)
.filter(File::canExecute)
.map(File::getAbsolutePath);
}
private String buildClassPath(File confDir, File libDir) {
File[] libFiles = ofNullable(libDir.listFiles((dir, filename) -> filename.toLowerCase().endsWith(JAR_FILE_EXTENSION)))
.filter(files -> files.length > 0)
.orElseThrow(() -> new RuntimeException("Could not find lib directory at " + libDir.getAbsolutePath()));
ofNullable(confDir.listFiles())
.filter(files -> files.length > 0)
.orElseThrow(() -> new RuntimeException("Could not find conf directory at " + confDir.getAbsolutePath()));
return Stream.concat(Stream.of(confDir), Stream.of(libFiles))
.map(File::getAbsolutePath)
.collect(joining(File.pathSeparator));
}
private List<String> getJavaAdditionalArgs(Properties props) {
return props.entrySet()
.stream()
.filter(entry -> ((String) entry.getKey()).startsWith(JAVA_ARG_KEY_PREFIX))
.map(entry -> (String) entry.getValue())
.toList();
}
private String systemProperty(String key, Object value) {
return String.format(SYSTEM_PROPERTY_TEMPLATE, key, value);
} }
} }

View File

@ -0,0 +1,276 @@
/*
* 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.minifi.bootstrap.service;
import static java.lang.String.join;
import static java.lang.System.getProperty;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.nifi.minifi.bootstrap.service.BootstrapFileProvider.getBootstrapConfFile;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.APP_LOG_FILE_EXTENSION;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.APP_LOG_FILE_NAME;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.BOOTSTRAP_LOG_FILE_EXTENSION;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.BOOTSTRAP_LOG_FILE_NAME;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.DEFAULT_APP_LOG_FILE_NAME;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.DEFAULT_BOOTSTRAP_LOG_FILE_NAME;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.DEFAULT_LOG_DIR;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.DEFAULT_LOG_FILE_EXTENSION;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.LOG_DIR;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.getMiNiFiPropertiesPath;
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.MINIFI_APP_LOG_FILE;
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.MINIFI_BOOTSTRAP_FILE_PATH;
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.MINIFI_BOOTSTRAP_LOG_FILE;
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.MINIFI_LOG_DIRECTORY;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.function.Predicate;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeException;
import org.apache.nifi.minifi.bootstrap.util.OrderedProperties;
import org.apache.nifi.minifi.commons.api.MiNiFiProperties;
import org.apache.nifi.util.NiFiProperties;
public class MiNiFiPropertiesGenerator {
public static final String PROPERTIES_FILE_APACHE_2_0_LICENSE =
" Licensed to the Apache Software Foundation (ASF) under one or more\n" +
"# contributor license agreements. See the NOTICE file distributed with\n" +
"# this work for additional information regarding copyright ownership.\n" +
"# The ASF licenses this file to You under the Apache License, Version 2.0\n" +
"# (the \"License\"); you may not use this file except in compliance with\n" +
"# the License. You may obtain a copy of the License at\n" +
"#\n" +
"# http://www.apache.org/licenses/LICENSE-2.0\n" +
"#\n" +
"# Unless required by applicable law or agreed to in writing, software\n" +
"# distributed under the License is distributed on an \"AS IS\" BASIS,\n" +
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
"# See the License for the specific language governing permissions and\n" +
"# limitations under the License.\n" +
"\n";
static final List<Triple<String, String, String>> NIFI_PROPERTIES_WITH_DEFAULT_VALUES_AND_COMMENTS = List.of(
Triple.of(NiFiProperties.FLOW_CONFIGURATION_ARCHIVE_ENABLED, "false", EMPTY),
Triple.of(NiFiProperties.FLOW_CONFIGURATION_ARCHIVE_DIR, "./conf/archive/", EMPTY),
Triple.of(NiFiProperties.AUTO_RESUME_STATE, "true", EMPTY),
Triple.of(NiFiProperties.FLOW_CONTROLLER_GRACEFUL_SHUTDOWN_PERIOD, "10 sec", EMPTY),
Triple.of(NiFiProperties.WRITE_DELAY_INTERVAL, "500 ms", EMPTY),
Triple.of(NiFiProperties.ADMINISTRATIVE_YIELD_DURATION, "30 sec", EMPTY),
Triple.of(NiFiProperties.VARIABLE_REGISTRY_PROPERTIES, EMPTY, EMPTY),
Triple.of(NiFiProperties.BORED_YIELD_DURATION, "10 millis",
"# If a component has no work to do (is \"bored\"), how long should we wait before checking again for work"),
Triple.of(NiFiProperties.LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE, "./conf/login-identity-providers.xml", EMPTY),
Triple.of(NiFiProperties.TEMPLATE_DIRECTORY, "./conf/templates", EMPTY),
Triple.of(NiFiProperties.UI_BANNER_TEXT, EMPTY, EMPTY),
Triple.of(NiFiProperties.UI_AUTO_REFRESH_INTERVAL, "30 sec", EMPTY),
Triple.of(NiFiProperties.NAR_LIBRARY_DIRECTORY, "./lib", EMPTY),
Triple.of(NiFiProperties.NAR_WORKING_DIRECTORY, "./work/nar/", EMPTY),
Triple.of(NiFiProperties.NAR_LIBRARY_AUTOLOAD_DIRECTORY, "./extensions", EMPTY),
Triple.of(NiFiProperties.COMPONENT_DOCS_DIRECTORY, "./work/docs/components", EMPTY),
Triple.of(NiFiProperties.STATE_MANAGEMENT_CONFIG_FILE, "./conf/state-management.xml", "# State Management"),
Triple.of(NiFiProperties.STATE_MANAGEMENT_LOCAL_PROVIDER_ID, "local-provider", "# The ID of the local state provider"),
Triple.of(NiFiProperties.REPOSITORY_DATABASE_DIRECTORY, "./database_repository", "# H2 Settings"),
Triple.of(NiFiProperties.H2_URL_APPEND, ";LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE", EMPTY),
Triple.of(NiFiProperties.FLOWFILE_REPOSITORY_IMPLEMENTATION, "org.apache.nifi.controller.repository.WriteAheadFlowFileRepository", "# FlowFile Repository"),
Triple.of(NiFiProperties.FLOWFILE_REPOSITORY_DIRECTORY, "./flowfile_repository", EMPTY),
Triple.of(NiFiProperties.FLOWFILE_REPOSITORY_CHECKPOINT_INTERVAL, "20 secs", EMPTY),
Triple.of(NiFiProperties.FLOWFILE_REPOSITORY_ALWAYS_SYNC, "false", EMPTY),
Triple.of(NiFiProperties.FLOWFILE_SWAP_MANAGER_IMPLEMENTATION, "org.apache.nifi.controller.FileSystemSwapManager", EMPTY),
Triple.of(NiFiProperties.QUEUE_SWAP_THRESHOLD, "20000", EMPTY),
Triple.of(NiFiProperties.CONTENT_REPOSITORY_IMPLEMENTATION, "org.apache.nifi.controller.repository.FileSystemRepository", "# Content Repository"),
Triple.of(NiFiProperties.MAX_APPENDABLE_CLAIM_SIZE, "50 KB", EMPTY),
Triple.of(NiFiProperties.CONTENT_ARCHIVE_MAX_RETENTION_PERIOD, "7 days", EMPTY),
Triple.of(NiFiProperties.CONTENT_ARCHIVE_MAX_USAGE_PERCENTAGE, "50%", EMPTY),
Triple.of(NiFiProperties.CONTENT_ARCHIVE_ENABLED, "false", EMPTY),
Triple.of(NiFiProperties.REPOSITORY_CONTENT_PREFIX + ".default", "./content_repository", EMPTY),
Triple.of(NiFiProperties.PROVENANCE_REPO_IMPLEMENTATION_CLASS, "org.apache.nifi.provenance.NoOpProvenanceRepository",
"# Provenance Repository Properties"),
Triple.of(NiFiProperties.PROVENANCE_ROLLOVER_TIME, EMPTY, EMPTY),
Triple.of(NiFiProperties.PROVENANCE_INDEX_SHARD_SIZE, EMPTY, EMPTY),
Triple.of(NiFiProperties.PROVENANCE_MAX_STORAGE_SIZE, EMPTY, EMPTY),
Triple.of(NiFiProperties.PROVENANCE_MAX_STORAGE_TIME, EMPTY, EMPTY),
Triple.of(NiFiProperties.COMPONENT_STATUS_REPOSITORY_IMPLEMENTATION, "org.apache.nifi.controller.status.history.VolatileComponentStatusRepository",
"# Component Status Repository"),
Triple.of(NiFiProperties.COMPONENT_STATUS_SNAPSHOT_FREQUENCY, "1 min", EMPTY),
Triple.of(NiFiProperties.WEB_HTTP_HOST, EMPTY, EMPTY),
Triple.of(NiFiProperties.WEB_HTTP_PORT, EMPTY, EMPTY),
Triple.of(NiFiProperties.WEB_HTTPS_HOST, EMPTY, EMPTY),
Triple.of(NiFiProperties.WEB_HTTPS_PORT, EMPTY, EMPTY),
Triple.of(NiFiProperties.WEB_WORKING_DIR, "./work/jetty", EMPTY),
Triple.of(NiFiProperties.WEB_THREADS, "200", EMPTY),
Triple.of(NiFiProperties.SECURITY_KEYSTORE, EMPTY, EMPTY),
Triple.of(NiFiProperties.SECURITY_KEYSTORE_TYPE, EMPTY, EMPTY),
Triple.of(NiFiProperties.SECURITY_KEYSTORE_PASSWD, EMPTY, EMPTY),
Triple.of(NiFiProperties.SECURITY_KEY_PASSWD, EMPTY, EMPTY),
Triple.of(NiFiProperties.SECURITY_TRUSTSTORE, EMPTY, EMPTY),
Triple.of(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, EMPTY, EMPTY),
Triple.of(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD, EMPTY, EMPTY),
Triple.of(NiFiProperties.SECURITY_OCSP_RESPONDER_URL, EMPTY, EMPTY),
Triple.of(NiFiProperties.SECURITY_OCSP_RESPONDER_CERTIFICATE, EMPTY, EMPTY),
Triple.of(NiFiProperties.CLUSTER_IS_NODE, "false", EMPTY),
Triple.of(NiFiProperties.FLOW_CONFIGURATION_FILE, "./conf/flow.xml.gz", EMPTY)
);
static final Map<String, String> MINIFI_TO_NIFI_PROPERTY_MAPPING = Map.of(
MiNiFiProperties.NIFI_MINIFI_FLOW_CONFIG.getKey(), NiFiProperties.FLOW_CONFIGURATION_JSON_FILE,
MiNiFiProperties.NIFI_MINIFI_SECURITY_KEYSTORE.getKey(), NiFiProperties.SECURITY_KEYSTORE,
MiNiFiProperties.NIFI_MINIFI_SECURITY_KEYSTORE_TYPE.getKey(), NiFiProperties.SECURITY_KEYSTORE_TYPE,
MiNiFiProperties.NIFI_MINIFI_SECURITY_KEYSTORE_PASSWD.getKey(), NiFiProperties.SECURITY_KEYSTORE_PASSWD,
MiNiFiProperties.NIFI_MINIFI_SECURITY_KEY_PASSWD.getKey(), NiFiProperties.SECURITY_KEY_PASSWD,
MiNiFiProperties.NIFI_MINIFI_SECURITY_TRUSTSTORE.getKey(), NiFiProperties.SECURITY_TRUSTSTORE,
MiNiFiProperties.NIFI_MINIFI_SECURITY_TRUSTSTORE_TYPE.getKey(), NiFiProperties.SECURITY_TRUSTSTORE_TYPE,
MiNiFiProperties.NIFI_MINIFI_SECURITY_TRUSTSTORE_PASSWD.getKey(), NiFiProperties.SECURITY_TRUSTSTORE_PASSWD
);
static final String DEFAULT_SENSITIVE_PROPERTIES_ENCODING_ALGORITHM = "NIFI_PBKDF2_AES_GCM_256";
private static final Base64.Encoder KEY_ENCODER = Base64.getEncoder().withoutPadding();
private static final int SENSITIVE_PROPERTIES_KEY_LENGTH = 24;
private static final String C2_PROPERTY_PREFIX = "c2.";
private static final String NIFI_PREFIX = "nifi.";
public static final String FILE_EXTENSION_DELIMITER = ".";
public void generateMinifiProperties(String configDirectory, Properties bootstrapProperties) throws ConfigurationChangeException {
String minifiPropertiesFileName = Path.of(getMiNiFiPropertiesPath(bootstrapProperties, new File(configDirectory))).getFileName().toString();
Path minifiPropertiesFile = Path.of(configDirectory, minifiPropertiesFileName);
Map<String, String> existingSensitivePropertiesConfiguration = extractSensitivePropertiesConfiguration(minifiPropertiesFile);
OrderedProperties minifiProperties = prepareMinifiProperties(bootstrapProperties, existingSensitivePropertiesConfiguration);
persistMinifiProperties(minifiPropertiesFile, minifiProperties);
}
private Map<String, String> extractSensitivePropertiesConfiguration(Path minifiPropertiesFile) throws ConfigurationChangeException {
if (!Files.exists(minifiPropertiesFile)) {
return Map.of();
}
Properties minifiProperties = new Properties();
try (InputStream inputStream = Files.newInputStream(minifiPropertiesFile)) {
minifiProperties.load(inputStream);
} catch (IOException e) {
throw new ConfigurationChangeException("Unable to load MiNiFi properties from " + minifiPropertiesFile, e);
}
return Map.of(
NiFiProperties.SENSITIVE_PROPS_KEY, minifiProperties.getProperty(NiFiProperties.SENSITIVE_PROPS_KEY),
NiFiProperties.SENSITIVE_PROPS_ALGORITHM, minifiProperties.getProperty(NiFiProperties.SENSITIVE_PROPS_ALGORITHM)
);
}
private OrderedProperties prepareMinifiProperties(Properties bootstrapProperties, Map<String, String> existingSensitivePropertiesConfiguration) {
OrderedProperties minifiProperties = new OrderedProperties();
NIFI_PROPERTIES_WITH_DEFAULT_VALUES_AND_COMMENTS
.forEach(triple -> minifiProperties.setProperty(triple.getLeft(), triple.getMiddle(), triple.getRight()));
getNonBlankPropertiesWithPredicate(bootstrapProperties, entry -> MINIFI_TO_NIFI_PROPERTY_MAPPING.containsKey(entry.getKey()))
.forEach(entry -> minifiProperties.setProperty(MINIFI_TO_NIFI_PROPERTY_MAPPING.get(entry.getKey()), entry.getValue()));
getSensitiveProperties(bootstrapProperties, existingSensitivePropertiesConfiguration)
.forEach(entry -> minifiProperties.setProperty(entry.getKey(), entry.getValue()));
getNonBlankPropertiesWithPredicate(bootstrapProperties, entry -> ((String) entry.getKey()).startsWith(C2_PROPERTY_PREFIX))
.forEach(entry -> minifiProperties.setProperty(entry.getKey(), entry.getValue()));
getNonBlankPropertiesWithPredicate(bootstrapProperties, entry -> ((String) entry.getKey()).startsWith(NIFI_PREFIX))
.forEach(entry -> minifiProperties.setProperty(entry.getKey(), entry.getValue()));
bootstrapFileAndLogProperties()
.forEach(entry -> minifiProperties.setProperty(entry.getKey(), entry.getValue()));
return minifiProperties;
}
private List<Pair<String, String>> getNonBlankPropertiesWithPredicate(Properties bootstrapProperties, Predicate<Map.Entry> predicate) {
return ofNullable(bootstrapProperties)
.map(Properties::entrySet)
.orElseGet(Set::of)
.stream()
.filter(predicate)
.filter(entry -> isNotBlank((String) entry.getValue()))
.map(entry -> Pair.of((String) entry.getKey(), (String) entry.getValue()))
.sorted((o1, o2) -> Comparator.<String>naturalOrder().compare(o1.getKey(), o2.getKey()))
.toList();
}
private List<Pair<String, String>> getSensitiveProperties(Properties bootstrapProperties, Map<String, String> existingSensitivePropertiesConfiguration) {
return existingSensitivePropertiesConfiguration.isEmpty()
? List.of(
Pair.of(NiFiProperties.SENSITIVE_PROPS_KEY,
ofNullable(bootstrapProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_SENSITIVE_PROPS_KEY.getKey()))
.filter(StringUtils::isNotBlank)
.orElseGet(this::generateSensitivePropertiesKey)),
Pair.of(NiFiProperties.SENSITIVE_PROPS_ALGORITHM,
ofNullable(bootstrapProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_SENSITIVE_PROPS_ALGORITHM.getKey()))
.filter(StringUtils::isNotBlank)
.orElse(DEFAULT_SENSITIVE_PROPERTIES_ENCODING_ALGORITHM)))
: existingSensitivePropertiesConfiguration.entrySet()
.stream()
.map(entry -> Pair.of(entry.getKey(), entry.getValue()))
.toList();
}
private String generateSensitivePropertiesKey() {
SecureRandom secureRandom = new SecureRandom();
byte[] sensitivePropertiesKeyBinary = new byte[SENSITIVE_PROPERTIES_KEY_LENGTH];
secureRandom.nextBytes(sensitivePropertiesKeyBinary);
return KEY_ENCODER.encodeToString(sensitivePropertiesKeyBinary);
}
private List<Pair<String, String>> bootstrapFileAndLogProperties() {
return List.of(
Pair.of(MINIFI_BOOTSTRAP_FILE_PATH, getBootstrapConfFile().getAbsolutePath()),
Pair.of(MINIFI_LOG_DIRECTORY, getProperty(LOG_DIR, DEFAULT_LOG_DIR).trim()),
Pair.of(MINIFI_APP_LOG_FILE, join(FILE_EXTENSION_DELIMITER,
getProperty(APP_LOG_FILE_NAME, DEFAULT_APP_LOG_FILE_NAME).trim(),
getProperty(APP_LOG_FILE_EXTENSION, DEFAULT_LOG_FILE_EXTENSION).trim())),
Pair.of(MINIFI_BOOTSTRAP_LOG_FILE, join(FILE_EXTENSION_DELIMITER,
getProperty(BOOTSTRAP_LOG_FILE_NAME, DEFAULT_BOOTSTRAP_LOG_FILE_NAME).trim(),
getProperty(BOOTSTRAP_LOG_FILE_EXTENSION, DEFAULT_LOG_FILE_EXTENSION).trim()))
);
}
private void persistMinifiProperties(Path minifiPropertiesFile, OrderedProperties minifiProperties) throws ConfigurationChangeException {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
FileOutputStream fileOutputStream = new FileOutputStream(minifiPropertiesFile.toString())) {
minifiProperties.store(byteArrayOutputStream, PROPERTIES_FILE_APACHE_2_0_LICENSE);
byteArrayOutputStream.writeTo(fileOutputStream);
} catch (Exception e) {
throw new ConfigurationChangeException("Failed to write MiNiFi properties to " + minifiPropertiesFile, e);
}
}
}

View File

@ -14,9 +14,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.apache.nifi.minifi.bootstrap.service; package org.apache.nifi.minifi.bootstrap.service;
import static org.apache.nifi.minifi.commons.schema.common.BootstrapPropertyKeys.STATUS_REPORTER_COMPONENTS_KEY; import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.NIFI_MINIFI_STATUS_REPORTER_COMPONENTS;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
@ -54,7 +55,7 @@ public class PeriodicStatusReporterManager implements QueryableStatusAggregator
public void startPeriodicNotifiers() { public void startPeriodicNotifiers() {
periodicStatusReporters = initializePeriodicNotifiers(); periodicStatusReporters = initializePeriodicNotifiers();
for (PeriodicStatusReporter periodicStatusReporter: periodicStatusReporters) { for (PeriodicStatusReporter periodicStatusReporter : periodicStatusReporters) {
periodicStatusReporter.start(); periodicStatusReporter.start();
LOGGER.debug("Started {} notifier", periodicStatusReporter.getClass().getCanonicalName()); LOGGER.debug("Started {} notifier", periodicStatusReporter.getClass().getCanonicalName());
} }
@ -96,7 +97,7 @@ public class PeriodicStatusReporterManager implements QueryableStatusAggregator
LOGGER.debug("Initiating bootstrap periodic status reporters..."); LOGGER.debug("Initiating bootstrap periodic status reporters...");
Set<PeriodicStatusReporter> statusReporters = new HashSet<>(); Set<PeriodicStatusReporter> statusReporters = new HashSet<>();
String reportersCsv = bootstrapProperties.getProperty(STATUS_REPORTER_COMPONENTS_KEY); String reportersCsv = bootstrapProperties.getProperty(NIFI_MINIFI_STATUS_REPORTER_COMPONENTS.getKey());
if (reportersCsv != null && !reportersCsv.isEmpty()) { if (reportersCsv != null && !reportersCsv.isEmpty()) {
for (String reporterClassname : reportersCsv.split(",")) { for (String reporterClassname : reportersCsv.split(",")) {

View File

@ -1,95 +0,0 @@
/*
* 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.minifi.bootstrap.service;
import static java.util.Optional.ofNullable;
import static org.apache.nifi.minifi.commons.api.MiNiFiConstants.CONFIG_UPDATED_FILE_NAME;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.Optional;
import org.apache.commons.io.IOUtils;
import org.apache.nifi.minifi.bootstrap.RunMiNiFi;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeListener;
import org.apache.nifi.minifi.bootstrap.configuration.differentiators.Differentiator;
import org.apache.nifi.minifi.bootstrap.configuration.differentiators.WholeConfigDifferentiator;
import org.apache.nifi.minifi.bootstrap.util.ByteBufferInputStream;
import org.apache.nifi.minifi.bootstrap.util.ConfigTransformer;
import org.apache.nifi.minifi.commons.api.MiNiFiCommandState;
import org.apache.nifi.util.NiFiProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UpdateConfigurationService {
private static final Logger logger = LoggerFactory.getLogger(UpdateConfigurationService.class);
private static final String FALLBACK_CONFIG_FILE_DIR = "./conf/";
private final Differentiator<ByteBuffer> differentiator;
private final RunMiNiFi runMiNiFi;
private final ConfigurationChangeListener miNiFiConfigurationChangeListener;
private final BootstrapFileProvider bootstrapFileProvider;
public UpdateConfigurationService(RunMiNiFi runMiNiFi, ConfigurationChangeListener miNiFiConfigurationChangeListener, BootstrapFileProvider bootstrapFileProvider) {
this.differentiator = WholeConfigDifferentiator.getByteBufferDifferentiator();
this.differentiator.initialize(runMiNiFi);
this.runMiNiFi = runMiNiFi;
this.miNiFiConfigurationChangeListener = miNiFiConfigurationChangeListener;
this.bootstrapFileProvider = bootstrapFileProvider;
}
public Optional<MiNiFiCommandState> handleUpdate() {
logger.info("Handling configuration update");
MiNiFiCommandState commandState = null;
try (FileInputStream configFile = new FileInputStream(getConfigFilePath().toFile())) {
ByteBuffer readOnlyNewConfig = ConfigTransformer.overrideNonFlowSectionsFromOriginalSchema(
IOUtils.toByteArray(configFile), runMiNiFi.getConfigFileReference().get().duplicate(), bootstrapFileProvider.getBootstrapProperties());
if (differentiator.isNew(readOnlyNewConfig)) {
miNiFiConfigurationChangeListener.handleChange(new ByteBufferInputStream(readOnlyNewConfig.duplicate()));
} else {
logger.info("The given configuration does not contain any update. No operation required");
commandState = MiNiFiCommandState.NO_OPERATION;
}
} catch (Exception e) {
commandState = MiNiFiCommandState.NOT_APPLIED_WITHOUT_RESTART;
logger.error("Could not handle configuration update", e);
}
return Optional.ofNullable(commandState);
}
private Path getConfigFilePath() {
return ofNullable(safeGetPropertiesFilePath())
.map(File::new)
.map(File::getParent)
.map(parentDir -> new File(parentDir + CONFIG_UPDATED_FILE_NAME))
.orElse(new File(FALLBACK_CONFIG_FILE_DIR + CONFIG_UPDATED_FILE_NAME)).toPath();
}
private String safeGetPropertiesFilePath() {
String propertyFile = null;
try {
propertyFile = bootstrapFileProvider.getBootstrapProperties().getProperty(NiFiProperties.PROPERTIES_FILE_PATH, null);
} catch (IOException e) {
logger.error("Failed to get properties file path");
}
return propertyFile;
}
}

View File

@ -19,13 +19,10 @@ package org.apache.nifi.minifi.bootstrap.service;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.CONF_DIR_KEY; import static org.apache.nifi.minifi.bootstrap.RunMiNiFi.CONF_DIR_KEY;
import static org.apache.nifi.minifi.bootstrap.util.ConfigTransformer.asByteArrayInputStream;
import static org.apache.nifi.minifi.bootstrap.util.ConfigTransformer.generateConfigFiles;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.Optional; import java.util.Optional;
import java.util.Properties; import java.util.Properties;
@ -38,11 +35,13 @@ public class UpdatePropertiesService {
private final RunMiNiFi runner; private final RunMiNiFi runner;
private final Logger logger; private final Logger logger;
private final BootstrapFileProvider bootstrapFileProvider; private final BootstrapFileProvider bootstrapFileProvider;
private final MiNiFiPropertiesGenerator miNiFiPropertiesGenerator;
public UpdatePropertiesService(RunMiNiFi runner, Logger logger, BootstrapFileProvider bootstrapFileProvider) { public UpdatePropertiesService(RunMiNiFi runner, Logger logger, BootstrapFileProvider bootstrapFileProvider) {
this.runner = runner; this.runner = runner;
this.logger = logger; this.logger = logger;
this.bootstrapFileProvider = bootstrapFileProvider; this.bootstrapFileProvider = bootstrapFileProvider;
this.miNiFiPropertiesGenerator = new MiNiFiPropertiesGenerator();
} }
public Optional<MiNiFiCommandState> handleUpdate() { public Optional<MiNiFiCommandState> handleUpdate() {
@ -72,9 +71,7 @@ public class UpdatePropertiesService {
throws IOException, ConfigurationChangeException { throws IOException, ConfigurationChangeException {
Optional<MiNiFiCommandState> commandState = Optional.empty(); Optional<MiNiFiCommandState> commandState = Optional.empty();
try { try {
ByteBuffer byteBuffer = generateConfigFiles(asByteArrayInputStream(runner.getConfigFileReference().get().duplicate()), miNiFiPropertiesGenerator.generateMinifiProperties(bootstrapProperties.getProperty(CONF_DIR_KEY), bootstrapProperties);
bootstrapProperties.getProperty(CONF_DIR_KEY), bootstrapProperties);
runner.getConfigFileReference().set(byteBuffer.asReadOnlyBuffer());
restartInstance(); restartInstance();
} catch (Exception e) { } catch (Exception e) {
commandState = Optional.of(MiNiFiCommandState.NOT_APPLIED_WITHOUT_RESTART); commandState = Optional.of(MiNiFiCommandState.NOT_APPLIED_WITHOUT_RESTART);
@ -84,10 +81,7 @@ public class UpdatePropertiesService {
} }
// read reverted properties // read reverted properties
bootstrapProperties = bootstrapFileProvider.getBootstrapProperties(); bootstrapProperties = bootstrapFileProvider.getBootstrapProperties();
miNiFiPropertiesGenerator.generateMinifiProperties(bootstrapProperties.getProperty(CONF_DIR_KEY), bootstrapProperties);
ByteBuffer byteBuffer = generateConfigFiles(
asByteArrayInputStream(runner.getConfigFileReference().get().duplicate()), bootstrapProperties.getProperty(CONF_DIR_KEY), bootstrapProperties);
runner.getConfigFileReference().set(byteBuffer.asReadOnlyBuffer());
logger.debug("Transformation of new config file failed after swap file was created, deleting it."); logger.debug("Transformation of new config file failed after swap file was created, deleting it.");
if (!bootstrapSwapConfigFile.delete()) { if (!bootstrapSwapConfigFile.delete()) {

View File

@ -17,6 +17,12 @@
package org.apache.nifi.minifi.bootstrap.status.reporters; package org.apache.nifi.minifi.bootstrap.status.reporters;
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.NIFI_MINIFI_STATUS_REPORTER_LOG_LEVEL;
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.NIFI_MINIFI_STATUS_REPORTER_LOG_PERIOD;
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.NIFI_MINIFI_STATUS_REPORTER_LOG_QUERY;
import java.io.IOException;
import java.util.Properties;
import org.apache.nifi.logging.LogLevel; import org.apache.nifi.logging.LogLevel;
import org.apache.nifi.minifi.bootstrap.QueryableStatusAggregator; import org.apache.nifi.minifi.bootstrap.QueryableStatusAggregator;
import org.apache.nifi.minifi.bootstrap.status.PeriodicStatusReporter; import org.apache.nifi.minifi.bootstrap.status.PeriodicStatusReporter;
@ -24,61 +30,49 @@ import org.apache.nifi.minifi.commons.status.FlowStatusReport;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Properties;
import static org.apache.nifi.minifi.commons.schema.common.BootstrapPropertyKeys.STATUS_REPORTER_PROPERTY_PREFIX;
public class StatusLogger extends PeriodicStatusReporter { public class StatusLogger extends PeriodicStatusReporter {
private volatile QueryableStatusAggregator queryableStatusAggregator; private volatile QueryableStatusAggregator queryableStatusAggregator;
private volatile LogLevel logLevel; private volatile LogLevel logLevel;
private volatile String statusQuery; private volatile String statusQuery;
protected static Logger logger = LoggerFactory.getLogger(StatusLogger.class); protected static Logger logger = LoggerFactory.getLogger(StatusLogger.class);
public static final String LOGGER_STATUS_REPORTER_PROPERTY_PREFIX = STATUS_REPORTER_PROPERTY_PREFIX + ".log";
public static final String REPORT_PERIOD_KEY = LOGGER_STATUS_REPORTER_PROPERTY_PREFIX + ".period";
public static final String LOGGING_LEVEL_KEY = LOGGER_STATUS_REPORTER_PROPERTY_PREFIX + ".level";
public static final String QUERY_KEY = LOGGER_STATUS_REPORTER_PROPERTY_PREFIX + ".query";
static final String ENCOUNTERED_IO_EXCEPTION = "Encountered an IO Exception while attempting to query the flow status."; static final String ENCOUNTERED_IO_EXCEPTION = "Encountered an IO Exception while attempting to query the flow status.";
@Override @Override
public void initialize(Properties properties, QueryableStatusAggregator queryableStatusAggregator) { public void initialize(Properties properties, QueryableStatusAggregator queryableStatusAggregator) {
this.queryableStatusAggregator = queryableStatusAggregator; this.queryableStatusAggregator = queryableStatusAggregator;
String periodString = properties.getProperty(REPORT_PERIOD_KEY); String periodString = properties.getProperty(NIFI_MINIFI_STATUS_REPORTER_LOG_PERIOD.getKey());
if (periodString == null) { if (periodString == null) {
throw new IllegalStateException(REPORT_PERIOD_KEY + " is null but it is required. Please configure it."); throw new IllegalStateException(NIFI_MINIFI_STATUS_REPORTER_LOG_PERIOD.getKey() + " is null but it is required. Please configure it.");
} }
try { try {
setPeriod(Integer.parseInt(periodString)); setPeriod(Integer.parseInt(periodString));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new IllegalStateException(REPORT_PERIOD_KEY + " is not a valid number.", e); throw new IllegalStateException(NIFI_MINIFI_STATUS_REPORTER_LOG_PERIOD.getKey() + " is not a valid number.", e);
} }
String loglevelString = properties.getProperty(LOGGING_LEVEL_KEY); String loglevelString = properties.getProperty(NIFI_MINIFI_STATUS_REPORTER_LOG_LEVEL.getKey());
if (loglevelString == null) { if (loglevelString == null) {
throw new IllegalStateException(LOGGING_LEVEL_KEY + " is null but it is required. Please configure it."); throw new IllegalStateException(NIFI_MINIFI_STATUS_REPORTER_LOG_LEVEL.getKey() + " is null but it is required. Please configure it.");
} }
try { try {
logLevel = LogLevel.valueOf(loglevelString.toUpperCase()); logLevel = LogLevel.valueOf(loglevelString.toUpperCase());
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new IllegalStateException("Value set for " + LOGGING_LEVEL_KEY + " is not a valid log level."); throw new IllegalStateException("Value set for " + NIFI_MINIFI_STATUS_REPORTER_LOG_LEVEL.getKey() + " is not a valid log level.");
} }
if (LogLevel.FATAL.equals(logLevel)){ if (LogLevel.FATAL.equals(logLevel)) {
throw new IllegalStateException("Cannot log status at the FATAL level. Please configure " + LOGGING_LEVEL_KEY + " to another value."); throw new IllegalStateException("Cannot log status at the FATAL level. Please configure " + NIFI_MINIFI_STATUS_REPORTER_LOG_LEVEL.getKey() + " to another value.");
} }
statusQuery = properties.getProperty(QUERY_KEY); statusQuery = properties.getProperty(NIFI_MINIFI_STATUS_REPORTER_LOG_QUERY.getKey());
if (statusQuery == null) { if (statusQuery == null) {
throw new IllegalStateException(QUERY_KEY + " is null but it is required. Please configure it."); throw new IllegalStateException(NIFI_MINIFI_STATUS_REPORTER_LOG_QUERY.getKey() + " is null but it is required. Please configure it.");
} }
reportRunner = new ReportStatusRunner(); reportRunner = new ReportStatusRunner();

View File

@ -1,102 +0,0 @@
/*
* 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.minifi.bootstrap.util;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.minifi.commons.schema.ProvenanceReportingSchema;
import org.apache.nifi.minifi.commons.schema.SecurityPropertiesSchema;
import org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import static org.apache.nifi.minifi.commons.schema.common.BootstrapPropertyKeys.BOOTSTRAP_KEYS_TO_YML_KEYS;
import static org.apache.nifi.minifi.commons.schema.common.BootstrapPropertyKeys.BOOTSTRAP_PROVENANCE_REPORTING_KEYS;
import static org.apache.nifi.minifi.commons.schema.common.BootstrapPropertyKeys.BOOTSTRAP_SECURITY_PROPERTY_KEYS;
import static org.apache.nifi.minifi.commons.schema.common.BootstrapPropertyKeys.BOOTSTRAP_SENSITIVE_PROPERTY_KEYS;
import static org.apache.nifi.minifi.commons.schema.common.BootstrapPropertyKeys.USE_PARENT_SSL;
public class BootstrapTransformer {
public static Optional<SecurityPropertiesSchema> buildSecurityPropertiesFromBootstrap(final Properties bootstrapProperties) {
Optional<SecurityPropertiesSchema> securityPropsOptional = Optional.empty();
final Map<String, Object> securityProperties = new HashMap<>();
if (bootstrapProperties != null) {
BOOTSTRAP_SECURITY_PROPERTY_KEYS.stream()
.filter(key -> StringUtils.isNotBlank(bootstrapProperties.getProperty(key)))
.forEach(key ->
securityProperties.put(BOOTSTRAP_KEYS_TO_YML_KEYS.get(key), bootstrapProperties.getProperty(key))
);
if (!securityProperties.isEmpty()) {
// Determine if sensitive properties were provided
final Map<String, String> sensitiveProperties = new HashMap<>();
BOOTSTRAP_SENSITIVE_PROPERTY_KEYS.stream()
.filter(key -> StringUtils.isNotBlank(bootstrapProperties.getProperty(key)))
.forEach(key ->
sensitiveProperties.put(BOOTSTRAP_KEYS_TO_YML_KEYS.get(key), bootstrapProperties.getProperty(key))
);
if (!sensitiveProperties.isEmpty()) {
securityProperties.put(CommonPropertyKeys.SENSITIVE_PROPS_KEY, sensitiveProperties);
}
final SecurityPropertiesSchema securityPropertiesSchema = new SecurityPropertiesSchema(securityProperties);
securityPropsOptional = Optional.of(securityPropertiesSchema);
}
}
return securityPropsOptional;
}
public static Optional<ProvenanceReportingSchema> buildProvenanceReportingPropertiesFromBootstrap(final Properties bootstrapProperties) {
Optional<ProvenanceReportingSchema> provenanceReportingPropsOptional = Optional.empty();
final Map<String, Object> provenanceReportingProperties = new HashMap<>();
if (bootstrapProperties != null) {
BOOTSTRAP_PROVENANCE_REPORTING_KEYS.stream()
.filter(key -> StringUtils.isNotBlank(bootstrapProperties.getProperty(key)))
.forEach(key ->
provenanceReportingProperties.put(BOOTSTRAP_KEYS_TO_YML_KEYS.get(key), bootstrapProperties.getProperty(key))
);
if (!provenanceReportingProperties.isEmpty()) {
final ProvenanceReportingSchema provenanceReportingSchema = new ProvenanceReportingSchema(provenanceReportingProperties);
provenanceReportingPropsOptional = Optional.of(provenanceReportingSchema);
}
}
return provenanceReportingPropsOptional;
}
public static boolean processorSSLOverride(final Properties bootstrapProperties) {
boolean shouldOverride = false;
if (bootstrapProperties != null) {
shouldOverride = Boolean.parseBoolean(bootstrapProperties.getProperty(USE_PARENT_SSL));
}
return shouldOverride;
}
}

View File

@ -1,856 +0,0 @@
/*
* 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.minifi.bootstrap.util;
import static org.apache.nifi.minifi.bootstrap.configuration.ingestors.PullHttpChangeIngestor.OVERRIDE_SECURITY;
import static org.apache.nifi.minifi.bootstrap.configuration.ingestors.PullHttpChangeIngestor.PULL_HTTP_BASE_KEY;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.APP_LOG_FILE_EXTENSION;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.APP_LOG_FILE_NAME;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.BOOTSTRAP_LOG_FILE_EXTENSION;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.BOOTSTRAP_LOG_FILE_NAME;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.DEFAULT_APP_LOG_FILE_NAME;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.DEFAULT_BOOTSTRAP_LOG_FILE_NAME;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.DEFAULT_LOG_DIR;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.DEFAULT_LOG_FILE_EXTENSION;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiExecCommandProvider.LOG_DIR;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.io.input.TeeInputStream;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.minifi.bootstrap.RunMiNiFi;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeException;
import org.apache.nifi.minifi.bootstrap.exception.InvalidConfigurationException;
import org.apache.nifi.minifi.bootstrap.service.BootstrapFileProvider;
import org.apache.nifi.minifi.commons.schema.ComponentStatusRepositorySchema;
import org.apache.nifi.minifi.commons.schema.ConfigSchema;
import org.apache.nifi.minifi.commons.schema.ConnectionSchema;
import org.apache.nifi.minifi.commons.schema.ContentRepositorySchema;
import org.apache.nifi.minifi.commons.schema.ControllerServiceSchema;
import org.apache.nifi.minifi.commons.schema.CorePropertiesSchema;
import org.apache.nifi.minifi.commons.schema.FlowControllerSchema;
import org.apache.nifi.minifi.commons.schema.FlowFileRepositorySchema;
import org.apache.nifi.minifi.commons.schema.FunnelSchema;
import org.apache.nifi.minifi.commons.schema.PortSchema;
import org.apache.nifi.minifi.commons.schema.ProcessGroupSchema;
import org.apache.nifi.minifi.commons.schema.ProcessorSchema;
import org.apache.nifi.minifi.commons.schema.ProvenanceReportingSchema;
import org.apache.nifi.minifi.commons.schema.ProvenanceRepositorySchema;
import org.apache.nifi.minifi.commons.schema.RemotePortSchema;
import org.apache.nifi.minifi.commons.schema.RemoteProcessGroupSchema;
import org.apache.nifi.minifi.commons.schema.ReportingSchema;
import org.apache.nifi.minifi.commons.schema.SecurityPropertiesSchema;
import org.apache.nifi.minifi.commons.schema.SensitivePropsSchema;
import org.apache.nifi.minifi.commons.schema.SwapSchema;
import org.apache.nifi.minifi.commons.schema.common.ConvertableSchema;
import org.apache.nifi.minifi.commons.schema.common.Schema;
import org.apache.nifi.minifi.commons.schema.common.StringUtil;
import org.apache.nifi.minifi.commons.schema.serialization.SchemaLoader;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.xml.processing.ProcessingException;
import org.apache.nifi.xml.processing.parsers.StandardDocumentProvider;
import org.apache.nifi.xml.processing.transform.StandardTransformProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
public final class ConfigTransformer {
// Underlying version of NIFI will be using
public static final String ROOT_GROUP = "Root-Group";
static final String MINIFI_CONFIG_FILE_PATH = "nifi.minifi.config.file";
static final String MINIFI_BOOTSTRAP_FILE_PATH = "nifi.minifi.bootstrap.file";
static final String MINIFI_LOG_DIRECTORY = "nifi.minifi.log.directory";
static final String MINIFI_APP_LOG_FILE = "nifi.minifi.app.log.file";
static final String MINIFI_BOOTSTRAP_LOG_FILE = "nifi.minifi.bootstrap.log.file";
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigTransformer.class);
private static final String OVERRIDE_CORE_PROPERTIES_KEY = PULL_HTTP_BASE_KEY + ".override.core";
private static final Base64.Encoder KEY_ENCODER = Base64.getEncoder().withoutPadding();
private static final int SENSITIVE_PROPERTIES_KEY_LENGTH = 24;
// Final util classes should have private constructor
private ConfigTransformer() {
}
public static ByteBuffer generateConfigFiles(InputStream configIs, String configDestinationPath, Properties bootstrapProperties) throws ConfigurationChangeException, IOException {
try (java.io.ByteArrayOutputStream byteArrayOutputStream = new java.io.ByteArrayOutputStream();
TeeInputStream teeInputStream = new TeeInputStream(configIs, byteArrayOutputStream)) {
ConfigTransformer.transformConfigFile(
teeInputStream,
configDestinationPath,
bootstrapProperties
);
return ByteBuffer.wrap(byteArrayOutputStream.toByteArray());
} catch (ConfigurationChangeException e){
throw e;
} catch (Exception e) {
throw new IOException("Unable to successfully transform the provided configuration", e);
}
}
public static void transformConfigFile(InputStream sourceStream, String destPath, Properties bootstrapProperties) throws Exception {
ConvertableSchema<ConfigSchema> convertableSchemaNew = throwIfInvalid(SchemaLoader.loadConvertableSchemaFromYaml(sourceStream));
ConfigSchema configSchemaNew = throwIfInvalid(convertableSchemaNew.convert());
SecurityPropertiesSchema securityProperties = BootstrapTransformer.buildSecurityPropertiesFromBootstrap(bootstrapProperties).orElse(null);
ProvenanceReportingSchema provenanceReportingProperties = BootstrapTransformer.buildProvenanceReportingPropertiesFromBootstrap(bootstrapProperties).orElse(null);
// See if we are providing defined properties from the filesystem configurations and use those as the definitive values
if (securityProperties != null) {
configSchemaNew.setSecurityProperties(securityProperties);
LOGGER.info("Bootstrap flow override: Replaced security properties");
}
if (provenanceReportingProperties != null) {
configSchemaNew.setProvenanceReportingProperties(provenanceReportingProperties);
LOGGER.info("Bootstrap flow override: Replaced provenance reporting properties");
}
// Replace all processor SSL controller services with MiNiFi parent, if bootstrap boolean is set to true
if (BootstrapTransformer.processorSSLOverride(bootstrapProperties)) {
for (ProcessorSchema processorConfig : configSchemaNew.getProcessGroupSchema().getProcessors()) {
processorConfig.getProperties().replace("SSL Context Service", processorConfig.getProperties().get("SSL Context Service"), "SSL-Context-Service");
LOGGER.info("Bootstrap flow override: Replaced {} SSL Context Service with parent MiNiFi SSL", processorConfig.getName());
}
}
Optional.ofNullable(bootstrapProperties)
.map(Properties::entrySet)
.orElse(Collections.emptySet())
.stream()
.filter(entry -> ((String) entry.getKey()).startsWith("c2"))
.forEach(entry -> configSchemaNew.getNifiPropertiesOverrides().putIfAbsent((String) entry.getKey(), (String) entry.getValue()));
// Config files and log files
if (bootstrapProperties != null) {
configSchemaNew.getNifiPropertiesOverrides().putIfAbsent(MINIFI_CONFIG_FILE_PATH, bootstrapProperties.getProperty(RunMiNiFi.MINIFI_CONFIG_FILE_KEY));
}
configSchemaNew.getNifiPropertiesOverrides().putIfAbsent(MINIFI_BOOTSTRAP_FILE_PATH, BootstrapFileProvider.getBootstrapConfFile().getAbsolutePath());
configSchemaNew.getNifiPropertiesOverrides().putIfAbsent(MINIFI_LOG_DIRECTORY, System.getProperty(LOG_DIR, DEFAULT_LOG_DIR).trim());
configSchemaNew.getNifiPropertiesOverrides().putIfAbsent(MINIFI_APP_LOG_FILE,
System.getProperty(APP_LOG_FILE_NAME, DEFAULT_APP_LOG_FILE_NAME).trim() + "." + System.getProperty(APP_LOG_FILE_EXTENSION, DEFAULT_LOG_FILE_EXTENSION).trim());
configSchemaNew.getNifiPropertiesOverrides().putIfAbsent(MINIFI_BOOTSTRAP_LOG_FILE,
System.getProperty(BOOTSTRAP_LOG_FILE_NAME, DEFAULT_BOOTSTRAP_LOG_FILE_NAME).trim() + "." + System.getProperty(BOOTSTRAP_LOG_FILE_EXTENSION, DEFAULT_LOG_FILE_EXTENSION).trim());
// Create nifi.properties and flow.xml.gz in memory
ByteArrayOutputStream nifiPropertiesOutputStream = new ByteArrayOutputStream();
writeNiFiProperties(configSchemaNew, nifiPropertiesOutputStream);
writeFlowXmlFile(configSchemaNew, destPath);
// Write nifi.properties and flow.xml.gz
writeNiFiPropertiesFile(nifiPropertiesOutputStream, destPath);
}
public static <T extends Schema> T throwIfInvalid(T schema) throws InvalidConfigurationException {
if (!schema.isValid()) {
throw new InvalidConfigurationException("Failed to transform config file due to:["
+ schema.getValidationIssues().stream().sorted().collect(Collectors.joining("], [")) + "]");
}
return schema;
}
public static ByteArrayInputStream asByteArrayInputStream(ByteBuffer byteBuffer) {
byte[] config = new byte[byteBuffer.remaining()];
byteBuffer.get(config);
return new ByteArrayInputStream(config);
}
protected static void writeNiFiPropertiesFile(ByteArrayOutputStream nifiPropertiesOutputStream, String destPath) throws IOException {
final Path nifiPropertiesPath = Paths.get(destPath, "nifi.properties");
try (FileOutputStream nifiProperties = new FileOutputStream(nifiPropertiesPath.toString())) {
nifiPropertiesOutputStream.writeTo(nifiProperties);
} finally {
if (nifiPropertiesOutputStream != null) {
nifiPropertiesOutputStream.flush();
nifiPropertiesOutputStream.close();
}
}
}
protected static void writeFlowXmlFile(ConfigSchema configSchema, OutputStream outputStream) throws ConfigTransformerException {
final StreamResult streamResult = new StreamResult(outputStream);
// configure the transformer and convert the DOM
final StandardTransformProvider transformProvider = new StandardTransformProvider();
transformProvider.setIndent(true);
// transform the document to byte stream
transformProvider.transform(createFlowXml(configSchema), streamResult);
}
protected static void writeFlowXmlFile(ConfigSchema configSchema, String path) throws IOException, ConfigTransformerException {
try (OutputStream fileOut = Files.newOutputStream(Paths.get(path, "flow.xml.gz"))) {
try (OutputStream outStream = new GZIPOutputStream(fileOut)) {
writeFlowXmlFile(configSchema, outStream);
}
}
}
protected static void writeNiFiProperties(ConfigSchema configSchema, OutputStream outputStream) throws IOException, ConfigurationChangeException {
try {
CorePropertiesSchema coreProperties = configSchema.getCoreProperties();
FlowFileRepositorySchema flowfileRepoSchema = configSchema.getFlowfileRepositoryProperties();
SwapSchema swapProperties = flowfileRepoSchema.getSwapProperties();
ContentRepositorySchema contentRepoProperties = configSchema.getContentRepositoryProperties();
ComponentStatusRepositorySchema componentStatusRepoProperties = configSchema.getComponentStatusRepositoryProperties();
SecurityPropertiesSchema securityProperties = configSchema.getSecurityProperties();
SensitivePropsSchema sensitiveProperties = securityProperties.getSensitiveProps();
ProvenanceRepositorySchema provenanceRepositorySchema = configSchema.getProvenanceRepositorySchema();
OrderedProperties orderedProperties = new OrderedProperties();
orderedProperties.setProperty("nifi.flow.configuration.file", "./conf/flow.xml.gz", "# Core Properties #" + System.lineSeparator());
orderedProperties.setProperty("nifi.flow.configuration.archive.enabled", "false");
orderedProperties.setProperty("nifi.flow.configuration.archive.dir", "./conf/archive/");
orderedProperties.setProperty("nifi.flowcontroller.autoResumeState", "true");
orderedProperties.setProperty("nifi.flowcontroller.graceful.shutdown.period", coreProperties.getFlowControllerGracefulShutdownPeriod());
orderedProperties.setProperty("nifi.flowservice.writedelay.interval", coreProperties.getFlowServiceWriteDelayInterval());
orderedProperties.setProperty("nifi.administrative.yield.duration", coreProperties.getAdministrativeYieldDuration());
orderedProperties.setProperty("nifi.variable.registry.properties", coreProperties.getVariableRegistryProperties());
orderedProperties.setProperty("nifi.bored.yield.duration", coreProperties.getBoredYieldDuration(),
"# If a component has no work to do (is \"bored\"), how long should we wait before checking again for work?");
orderedProperties.setProperty("nifi.authority.provider.configuration.file", "./conf/authority-providers.xml", "");
orderedProperties.setProperty("nifi.login.identity.provider.configuration.file", "./conf/login-identity-providers.xml");
orderedProperties.setProperty("nifi.templates.directory", "./conf/templates");
orderedProperties.setProperty("nifi.ui.banner.text", "");
orderedProperties.setProperty("nifi.ui.autorefresh.interval", "30 sec");
orderedProperties.setProperty("nifi.nar.library.directory", "./lib");
orderedProperties.setProperty("nifi.nar.working.directory", "./work/nar/");
orderedProperties.setProperty("nifi.nar.library.autoload.directory", "./extensions");
orderedProperties.setProperty("nifi.documentation.working.directory", "./work/docs/components");
orderedProperties.setProperty("nifi.state.management.configuration.file", "./conf/state-management.xml", System.lineSeparator() +
"####################" +
"# State Management #" +
"####################");
orderedProperties.setProperty("nifi.state.management.provider.local", "local-provider", "# The ID of the local state provider");
orderedProperties.setProperty("nifi.database.directory", "./database_repository", System.lineSeparator() + "# H2 Settings");
orderedProperties.setProperty("nifi.h2.url.append", ";LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE");
orderedProperties.setProperty("nifi.flowfile.repository.implementation", String.valueOf(flowfileRepoSchema.getFlowFileRepository()),
System.lineSeparator() + "# FlowFile Repository");
orderedProperties.setProperty("nifi.flowfile.repository.directory", "./flowfile_repository");
orderedProperties.setProperty("nifi.flowfile.repository.partitions", String.valueOf(flowfileRepoSchema.getPartitions()));
orderedProperties.setProperty("nifi.flowfile.repository.checkpoint.interval", flowfileRepoSchema.getCheckpointInterval());
orderedProperties.setProperty("nifi.flowfile.repository.always.sync", Boolean.toString(flowfileRepoSchema.getAlwaysSync()));
orderedProperties.setProperty("nifi.swap.manager.implementation", "org.apache.nifi.controller.FileSystemSwapManager", "");
orderedProperties.setProperty("nifi.queue.swap.threshold", String.valueOf(swapProperties.getThreshold()));
orderedProperties.setProperty("nifi.swap.in.period", swapProperties.getInPeriod());
orderedProperties.setProperty("nifi.swap.in.threads", String.valueOf(swapProperties.getInThreads()));
orderedProperties.setProperty("nifi.swap.out.period", swapProperties.getOutPeriod());
orderedProperties.setProperty("nifi.swap.out.threads", String.valueOf(swapProperties.getOutThreads()));
orderedProperties.setProperty("nifi.content.repository.implementation", contentRepoProperties.getContentRepository(), System.lineSeparator() + "# Content Repository");
orderedProperties.setProperty("nifi.content.claim.max.appendable.size", contentRepoProperties.getContentClaimMaxAppendableSize());
orderedProperties.setProperty("nifi.content.claim.max.flow.files", String.valueOf(contentRepoProperties.getContentClaimMaxFlowFiles()));
orderedProperties.setProperty("nifi.content.repository.archive.max.retention.period", contentRepoProperties.getContentRepoArchiveMaxRetentionPeriod());
orderedProperties.setProperty("nifi.content.repository.archive.max.usage.percentage", contentRepoProperties.getContentRepoArchiveMaxUsagePercentage());
orderedProperties.setProperty("nifi.content.repository.archive.enabled", Boolean.toString(contentRepoProperties.getContentRepoArchiveEnabled()));
orderedProperties.setProperty("nifi.content.repository.directory.default", "./content_repository");
orderedProperties.setProperty("nifi.content.repository.always.sync", Boolean.toString(contentRepoProperties.getAlwaysSync()));
orderedProperties.setProperty("nifi.provenance.repository.implementation", provenanceRepositorySchema.getProvenanceRepository(),
System.lineSeparator() + "# Provenance Repository Properties");
orderedProperties.setProperty("nifi.provenance.repository.rollover.time", provenanceRepositorySchema.getProvenanceRepoRolloverTimeKey());
orderedProperties.setProperty("nifi.provenance.repository.index.shard.size", provenanceRepositorySchema.getProvenanceRepoIndexShardSize());
orderedProperties.setProperty("nifi.provenance.repository.max.storage.size", provenanceRepositorySchema.getProvenanceRepoMaxStorageSize());
orderedProperties.setProperty("nifi.provenance.repository.max.storage.time", provenanceRepositorySchema.getProvenanceRepoMaxStorageTime());
orderedProperties.setProperty("nifi.provenance.repository.buffer.size", String.valueOf(provenanceRepositorySchema.getProvenanceRepoBufferSize()),
System.lineSeparator() + "# Volatile Provenance Respository Properties");
orderedProperties.setProperty("nifi.components.status.repository.implementation", "org.apache.nifi.controller.status.history.VolatileComponentStatusRepository",
System.lineSeparator() + "# Component Status Repository");
orderedProperties.setProperty("nifi.components.status.repository.buffer.size", String.valueOf(componentStatusRepoProperties.getBufferSize()));
orderedProperties.setProperty("nifi.components.status.snapshot.frequency", componentStatusRepoProperties.getSnapshotFrequency());
orderedProperties.setProperty("nifi.web.war.directory", "./lib", System.lineSeparator() + "# web properties #");
orderedProperties.setProperty("nifi.web.http.host", "");
orderedProperties.setProperty("nifi.web.http.port", "8081");
orderedProperties.setProperty("nifi.web.https.host", "");
orderedProperties.setProperty("nifi.web.https.port", "");
orderedProperties.setProperty("nifi.web.jetty.working.directory", "./work/jetty");
orderedProperties.setProperty("nifi.web.jetty.threads", "200");
final String sensitivePropertiesKey = sensitiveProperties.getKey();
final String notnullSensitivePropertiesKey;
// Auto-generate the sensitive properties key if not provided, NiFi security libraries require it
if (StringUtil.isNullOrEmpty(sensitivePropertiesKey)) {
LOGGER.warn("Generating Random Sensitive Properties Key [{}]", NiFiProperties.SENSITIVE_PROPS_KEY);
final SecureRandom secureRandom = new SecureRandom();
final byte[] sensitivePropertiesKeyBinary = new byte[SENSITIVE_PROPERTIES_KEY_LENGTH];
secureRandom.nextBytes(sensitivePropertiesKeyBinary);
notnullSensitivePropertiesKey = KEY_ENCODER.encodeToString(sensitivePropertiesKeyBinary);
} else {
notnullSensitivePropertiesKey = sensitivePropertiesKey;
}
orderedProperties.setProperty("nifi.sensitive.props.key", notnullSensitivePropertiesKey, System.lineSeparator() + "# security properties #");
orderedProperties.setProperty("nifi.sensitive.props.algorithm", sensitiveProperties.getAlgorithm());
orderedProperties.setProperty("nifi.security.keystore", securityProperties.getKeystore(), "");
orderedProperties.setProperty("nifi.security.keystoreType", securityProperties.getKeystoreType());
orderedProperties.setProperty("nifi.security.keystorePasswd", securityProperties.getKeystorePassword());
orderedProperties.setProperty("nifi.security.keyPasswd", securityProperties.getKeyPassword());
orderedProperties.setProperty("nifi.security.truststore", securityProperties.getTruststore());
orderedProperties.setProperty("nifi.security.truststoreType", securityProperties.getTruststoreType());
orderedProperties.setProperty("nifi.security.truststorePasswd", securityProperties.getTruststorePassword());
orderedProperties.setProperty("nifi.security.needClientAuth", "");
orderedProperties.setProperty("nifi.security.user.credential.cache.duration", "24 hours");
orderedProperties.setProperty("nifi.security.user.authority.provider", "file-provider");
orderedProperties.setProperty("nifi.security.user.login.identity.provider", "");
orderedProperties.setProperty("nifi.security.support.new.account.requests", "");
orderedProperties.setProperty("nifi.security.anonymous.authorities", "", "# Valid Authorities include: ROLE_MONITOR,ROLE_DFM,ROLE_ADMIN,ROLE_PROVENANCE,ROLE_NIFI");
orderedProperties.setProperty("nifi.security.ocsp.responder.url", "");
orderedProperties.setProperty("nifi.security.ocsp.responder.certificate", "");
orderedProperties.setProperty("nifi.cluster.is.node", "false", System.lineSeparator() + System.lineSeparator() + "# cluster node properties (only configure for cluster nodes) #");
orderedProperties.setProperty("nifi.cluster.is.manager", "false", System.lineSeparator() + "# cluster manager properties (only configure for cluster manager) #");
for (Map.Entry<String, String> entry : configSchema.getNifiPropertiesOverrides().entrySet()) {
orderedProperties.setProperty(entry.getKey(), entry.getValue());
}
orderedProperties.store(outputStream, PROPERTIES_FILE_APACHE_2_0_LICENSE);
} catch (NullPointerException e) {
throw new ConfigurationChangeException("Failed to parse the config YAML while creating the nifi.properties", e);
} finally {
outputStream.close();
}
}
protected static DOMSource createFlowXml(ConfigSchema configSchema) throws ConfigTransformerException {
try {
// create a new, empty document
final StandardDocumentProvider documentProvider = new StandardDocumentProvider();
documentProvider.setNamespaceAware(true);
final Document doc = documentProvider.newDocument();
// populate document with controller state
final Element rootNode = doc.createElement("flowController");
doc.appendChild(rootNode);
CorePropertiesSchema coreProperties = configSchema.getCoreProperties();
addTextElement(rootNode, "maxTimerDrivenThreadCount", String.valueOf(coreProperties.getMaxConcurrentThreads()));
addTextElement(rootNode, "maxEventDrivenThreadCount", String.valueOf(coreProperties.getMaxConcurrentThreads()));
FlowControllerSchema flowControllerProperties = configSchema.getFlowControllerProperties();
final Element element = doc.createElement("rootGroup");
rootNode.appendChild(element);
ProcessGroupSchema processGroupSchema = configSchema.getProcessGroupSchema();
processGroupSchema.setId(ROOT_GROUP);
processGroupSchema.setName(flowControllerProperties.getName());
processGroupSchema.setComment(flowControllerProperties.getComment());
addProcessGroup(doc, element, processGroupSchema, new ParentGroupIdResolver(processGroupSchema));
SecurityPropertiesSchema securityProperties = configSchema.getSecurityProperties();
boolean useSSL = securityProperties.useSSL();
if (useSSL) {
Element controllerServicesNode = doc.getElementById("controllerServices");
if (controllerServicesNode == null) {
controllerServicesNode = doc.createElement("controllerServices");
}
rootNode.appendChild(controllerServicesNode);
addSSLControllerService(controllerServicesNode, securityProperties);
}
List<ReportingSchema> reportingTasks = configSchema.getReportingTasksSchema();
ProvenanceReportingSchema provenanceProperties = configSchema.getProvenanceReportingProperties();
if (provenanceProperties != null) {
provenanceProperties.setSSL(useSSL);
ReportingSchema provenance = provenanceProperties.convert();
provenance.setId("Provenance-Reporting");
provenance.setName("Site-To-Site-Provenance-Reporting");
reportingTasks.add(provenance);
}
if (reportingTasks != null) {
final Element reportingTasksNode = doc.createElement("reportingTasks");
rootNode.appendChild(reportingTasksNode);
for (ReportingSchema task : reportingTasks) {
addReportingTask(reportingTasksNode, task);
}
}
return new DOMSource(doc);
} catch (final ProcessingException | DOMException | IllegalArgumentException e) {
throw new ConfigTransformerException(e);
} catch (Exception e) {
throw new ConfigTransformerException("Failed to parse the config YAML while writing the top level of the flow xml", e);
}
}
protected static void addSSLControllerService(final Element element, SecurityPropertiesSchema securityProperties) throws ConfigurationChangeException {
try {
final Element serviceElement = element.getOwnerDocument().createElement("controllerService");
addTextElement(serviceElement, "id", "SSL-Context-Service");
addTextElement(serviceElement, "name", "SSL-Context-Service");
addTextElement(serviceElement, "comment", "");
addTextElement(serviceElement, "class", "org.apache.nifi.ssl.StandardSSLContextService");
addTextElement(serviceElement, "enabled", "true");
Map<String, Object> attributes = new HashMap<>();
attributes.put("Keystore Filename", securityProperties.getKeystore());
attributes.put("Keystore Type", securityProperties.getKeystoreType());
attributes.put("Keystore Password", securityProperties.getKeystorePassword());
attributes.put("Truststore Filename", securityProperties.getTruststore());
attributes.put("Truststore Type", securityProperties.getTruststoreType());
attributes.put("Truststore Password", securityProperties.getTruststorePassword());
attributes.put("SSL Protocol", securityProperties.getSslProtocol());
addConfiguration(serviceElement, attributes);
element.appendChild(serviceElement);
} catch (Exception e) {
throw new ConfigurationChangeException("Failed to parse the config YAML while trying to create an SSL Controller Service", e);
}
}
protected static void addControllerService(final Element element, ControllerServiceSchema controllerServiceSchema) throws ConfigurationChangeException {
try {
final Element serviceElement = element.getOwnerDocument().createElement("controllerService");
addTextElement(serviceElement, "id", controllerServiceSchema.getId());
addTextElement(serviceElement, "name", controllerServiceSchema.getName());
addTextElement(serviceElement, "comment", "");
addTextElement(serviceElement, "class", controllerServiceSchema.getServiceClass());
addTextElement(serviceElement, "enabled", "true");
Map<String, Object> attributes = controllerServiceSchema.getProperties();
addConfiguration(serviceElement, attributes);
String annotationData = controllerServiceSchema.getAnnotationData();
if (annotationData != null && !annotationData.isEmpty()) {
addTextElement(element, "annotationData", annotationData);
}
element.appendChild(serviceElement);
} catch (Exception e) {
throw new ConfigurationChangeException("Failed to parse the config YAML while trying to create an SSL Controller Service", e);
}
}
protected static void addProcessGroup(Document doc, Element element, ProcessGroupSchema processGroupSchema, ParentGroupIdResolver parentGroupIdResolver) throws ConfigurationChangeException {
try {
String processGroupId = processGroupSchema.getId();
addTextElement(element, "id", processGroupId);
addTextElement(element, "name", processGroupSchema.getName());
addPosition(element);
addTextElement(element, "comment", processGroupSchema.getComment());
for (ProcessorSchema processorConfig : processGroupSchema.getProcessors()) {
addProcessor(element, processorConfig);
}
for (PortSchema portSchema : processGroupSchema.getInputPortSchemas()) {
addPort(doc, element, portSchema, "inputPort");
}
for (PortSchema portSchema : processGroupSchema.getOutputPortSchemas()) {
addPort(doc, element, portSchema, "outputPort");
}
for (FunnelSchema funnelSchema : processGroupSchema.getFunnels()) {
addFunnel(element, funnelSchema);
}
for (ProcessGroupSchema child : processGroupSchema.getProcessGroupSchemas()) {
Element processGroups = doc.createElement("processGroup");
element.appendChild(processGroups);
addProcessGroup(doc, processGroups, child, parentGroupIdResolver);
}
for (RemoteProcessGroupSchema remoteProcessGroupSchema : processGroupSchema.getRemoteProcessGroups()) {
addRemoteProcessGroup(element, remoteProcessGroupSchema);
}
for (ConnectionSchema connectionConfig : processGroupSchema.getConnections()) {
addConnection(element, connectionConfig, parentGroupIdResolver);
}
for (ControllerServiceSchema controllerServiceSchema : processGroupSchema.getControllerServices()) {
addControllerService(element, controllerServiceSchema);
}
} catch (ConfigurationChangeException e) {
throw e;
} catch (Exception e) {
throw new ConfigurationChangeException("Failed to parse the config YAML while trying to creating the root Process Group", e);
}
}
protected static void addPort(Document doc, Element parentElement, PortSchema portSchema, String tag) {
Element element = doc.createElement(tag);
parentElement.appendChild(element);
addTextElement(element, "id", portSchema.getId());
addTextElement(element, "name", portSchema.getName());
addPosition(element);
addTextElement(element, "comments", null);
addTextElement(element, "scheduledState", "RUNNING");
}
protected static void addProcessor(final Element parentElement, ProcessorSchema processorConfig) throws ConfigurationChangeException {
try {
final Document doc = parentElement.getOwnerDocument();
final Element element = doc.createElement("processor");
parentElement.appendChild(element);
addTextElement(element, "id", processorConfig.getId());
addTextElement(element, "name", processorConfig.getName());
addPosition(element);
addStyle(element);
addTextElement(element, "comment", "");
addTextElement(element, "class", processorConfig.getProcessorClass());
addTextElement(element, "maxConcurrentTasks", String.valueOf(processorConfig.getMaxConcurrentTasks()));
addTextElement(element, "schedulingPeriod", processorConfig.getSchedulingPeriod());
addTextElement(element, "penalizationPeriod", processorConfig.getPenalizationPeriod());
addTextElement(element, "yieldPeriod", processorConfig.getYieldPeriod());
addTextElement(element, "bulletinLevel", "WARN");
addTextElement(element, "lossTolerant", "false");
addTextElement(element, "scheduledState", "RUNNING");
addTextElement(element, "schedulingStrategy", processorConfig.getSchedulingStrategy());
addTextElement(element, "runDurationNanos", String.valueOf(processorConfig.getRunDurationNanos()));
String annotationData = processorConfig.getAnnotationData();
if (annotationData != null && !annotationData.isEmpty()) {
addTextElement(element, "annotationData", annotationData);
}
addConfiguration(element, processorConfig.getProperties());
Collection<String> autoTerminatedRelationships = processorConfig.getAutoTerminatedRelationshipsList();
if (autoTerminatedRelationships != null) {
for (String rel : autoTerminatedRelationships) {
addTextElement(element, "autoTerminatedRelationship", rel);
}
}
} catch (Exception e) {
throw new ConfigurationChangeException("Failed to parse the config YAML while trying to add a Processor", e);
}
}
protected static void addFunnel(final Element parentElement, FunnelSchema funnelSchema) {
Document document = parentElement.getOwnerDocument();
Element element = document.createElement("funnel");
parentElement.appendChild(element);
addTextElement(element, "id", funnelSchema.getId());
addPosition(element);
}
protected static void addReportingTask(final Element parentElement, ReportingSchema reportingSchema) throws ConfigurationChangeException {
try {
final Document doc = parentElement.getOwnerDocument();
final Element element = doc.createElement("reportingTask");
parentElement.appendChild(element);
addTextElement(element, "id", reportingSchema.getId());
addTextElement(element, "name", reportingSchema.getName());
addTextElement(element, "comment", reportingSchema.getComment());
addTextElement(element, "class", reportingSchema.getReportingClass());
addTextElement(element, "schedulingPeriod", reportingSchema.getSchedulingPeriod());
addTextElement(element, "scheduledState", "RUNNING");
addTextElement(element, "schedulingStrategy", reportingSchema.getSchedulingStrategy());
addConfiguration(element, reportingSchema.getProperties());
parentElement.appendChild(element);
} catch (Exception e) {
throw new ConfigurationChangeException("Failed to parse the config YAML while trying to add the Provenance Reporting Task", e);
}
}
protected static void addConfiguration(final Element element, Map<String, Object> elementConfig) {
final Document doc = element.getOwnerDocument();
if (elementConfig == null) {
return;
}
for (final Map.Entry<String, Object> entry : elementConfig.entrySet()) {
final Element propElement = doc.createElement("property");
addTextElement(propElement, "name", entry.getKey());
if (entry.getValue() != null) {
addTextElement(propElement, "value", entry.getValue().toString());
}
element.appendChild(propElement);
}
}
protected static void addStyle(final Element parentElement) {
final Element element = parentElement.getOwnerDocument().createElement("styles");
parentElement.appendChild(element);
}
protected static void addRemoteProcessGroup(final Element parentElement, RemoteProcessGroupSchema remoteProcessGroupProperties) throws ConfigurationChangeException {
try {
final Document doc = parentElement.getOwnerDocument();
final Element element = doc.createElement("remoteProcessGroup");
parentElement.appendChild(element);
addTextElement(element, "id", remoteProcessGroupProperties.getId());
addTextElement(element, "name", remoteProcessGroupProperties.getName());
addPosition(element);
addTextElement(element, "comment", remoteProcessGroupProperties.getComment());
// In the case we have multiple urls, select the first
addTextElement(element, "url", Arrays.asList(remoteProcessGroupProperties.getUrls().split(",")).get(0));
addTextElement(element, "urls", remoteProcessGroupProperties.getUrls());
addTextElement(element, "timeout", remoteProcessGroupProperties.getTimeout());
addTextElement(element, "yieldPeriod", remoteProcessGroupProperties.getYieldPeriod());
addTextElement(element, "transmitting", "true");
addTextElement(element, "transportProtocol", remoteProcessGroupProperties.getTransportProtocol());
addTextElement(element, "proxyHost", remoteProcessGroupProperties.getProxyHost());
if (remoteProcessGroupProperties.getProxyPort() != null) {
addTextElement(element, "proxyPort", Integer.toString(remoteProcessGroupProperties.getProxyPort()));
}
addTextElement(element, "proxyUser", remoteProcessGroupProperties.getProxyUser());
if (!StringUtils.isEmpty(remoteProcessGroupProperties.getProxyPassword())) {
addTextElement(element, "proxyPassword", remoteProcessGroupProperties.getProxyPassword());
}
List<RemotePortSchema> remoteInputPorts = remoteProcessGroupProperties.getInputPorts();
for (RemotePortSchema remoteInputPortSchema : remoteInputPorts) {
addRemoteGroupPort(element, remoteInputPortSchema, "inputPort");
}
List<RemotePortSchema> remoteOutputPorts = remoteProcessGroupProperties.getOutputPorts();
for (RemotePortSchema remoteOutputPortSchema : remoteOutputPorts) {
addRemoteGroupPort(element, remoteOutputPortSchema, "outputPort");
}
addTextElement(element, "networkInterface", remoteProcessGroupProperties.getLocalNetworkInterface());
parentElement.appendChild(element);
} catch (Exception e) {
throw new ConfigurationChangeException("Failed to parse the config YAML while trying to add the Remote Process Group", e);
}
}
protected static void addRemoteGroupPort(final Element parentElement, RemotePortSchema inputPort, String tagName) throws ConfigurationChangeException {
try {
final Document doc = parentElement.getOwnerDocument();
final Element element = doc.createElement(tagName);
parentElement.appendChild(element);
addTextElement(element, "id", inputPort.getId());
addTextElement(element, "name", inputPort.getName());
addPosition(element);
addTextElement(element, "comments", inputPort.getComment());
addTextElement(element, "scheduledState", "RUNNING");
addTextElement(element, "maxConcurrentTasks", String.valueOf(inputPort.getMax_concurrent_tasks()));
addTextElement(element, "useCompression", String.valueOf(inputPort.getUseCompression()));
parentElement.appendChild(element);
} catch (Exception e) {
throw new ConfigurationChangeException("Failed to parse the config YAML while trying to add the input port of the Remote Process Group", e);
}
}
protected static void addConnection(final Element parentElement, ConnectionSchema connectionProperties, ParentGroupIdResolver parentGroupIdResolver) throws ConfigurationChangeException {
try {
final Document doc = parentElement.getOwnerDocument();
final Element element = doc.createElement("connection");
parentElement.appendChild(element);
addTextElement(element, "id", connectionProperties.getId());
addTextElement(element, "name", connectionProperties.getName());
final Element bendPointsElement = doc.createElement("bendPoints");
element.appendChild(bendPointsElement);
addTextElement(element, "labelIndex", "1");
addTextElement(element, "zIndex", "0");
addConnectionSourceOrDestination(element, "source", connectionProperties.getSourceId(), parentGroupIdResolver);
addConnectionSourceOrDestination(element, "destination", connectionProperties.getDestinationId(), parentGroupIdResolver);
List<String> sourceRelationshipNames = connectionProperties.getSourceRelationshipNames();
if (sourceRelationshipNames.isEmpty()) {
addTextElement(element, "relationship", null);
} else {
for (String relationshipName : sourceRelationshipNames) {
addTextElement(element, "relationship", relationshipName);
}
}
addTextElement(element, "maxWorkQueueSize", String.valueOf(connectionProperties.getMaxWorkQueueSize()));
addTextElement(element, "maxWorkQueueDataSize", connectionProperties.getMaxWorkQueueDataSize());
addTextElement(element, "flowFileExpiration", connectionProperties.getFlowfileExpiration());
addTextElementIfNotNullOrEmpty(element, "queuePrioritizerClass", connectionProperties.getQueuePrioritizerClass());
parentElement.appendChild(element);
} catch (Exception e) {
throw new ConfigurationChangeException("Failed to parse the config YAML while trying to add the connection from the Processor to the input port of the Remote Process Group", e);
}
}
protected static void addConnectionSourceOrDestination(Element element, String sourceOrDestination, String id, ParentGroupIdResolver parentGroupIdResolver) {
String idTag = sourceOrDestination + "Id";
String groupIdTag = sourceOrDestination + "GroupId";
String typeTag = sourceOrDestination + "Type";
String parentId;
String type;
if ((parentId = parentGroupIdResolver.getRemoteInputPortParentId(id)) != null) {
type = "REMOTE_INPUT_PORT";
} else if ((parentId = parentGroupIdResolver.getRemoteOutputPortParentId(id)) != null) {
type = "REMOTE_OUTPUT_PORT";
} else if ((parentId = parentGroupIdResolver.getInputPortParentId(id)) != null) {
type = "INPUT_PORT";
} else if ((parentId = parentGroupIdResolver.getOutputPortParentId(id)) != null) {
type = "OUTPUT_PORT";
} else if ((parentId = parentGroupIdResolver.getFunnelParentId(id)) != null) {
type = "FUNNEL";
} else {
parentId = parentGroupIdResolver.getProcessorParentId(id);
type = "PROCESSOR";
}
addTextElement(element, idTag, id);
if (parentId != null) {
addTextElement(element, groupIdTag, parentId);
}
addTextElement(element, typeTag, type);
}
protected static void addPosition(final Element parentElement) {
final Element element = parentElement.getOwnerDocument().createElement("position");
element.setAttribute("x", "0");
element.setAttribute("y", "0");
parentElement.appendChild(element);
}
protected static void addTextElementIfNotNullOrEmpty(final Element element, final String name, final String value) {
StringUtil.doIfNotNullOrEmpty(value, s -> addTextElement(element, name, value));
}
protected static void addTextElement(final Element element, final String name, final String value) {
final Document doc = element.getOwnerDocument();
final Element toAdd = doc.createElement(name);
toAdd.setTextContent(value);
element.appendChild(toAdd);
}
public static ByteBuffer overrideNonFlowSectionsFromOriginalSchema(byte[] newSchema, ByteBuffer currentConfigScheme, Properties bootstrapProperties)
throws InvalidConfigurationException {
try {
boolean overrideCoreProperties = ConfigTransformer.overrideCoreProperties(bootstrapProperties);
boolean overrideSecurityProperties = ConfigTransformer.overrideSecurityProperties(bootstrapProperties);
if (overrideCoreProperties && overrideSecurityProperties) {
return ByteBuffer.wrap(newSchema);
} else {
ConvertableSchema<ConfigSchema> schemaNew = ConfigTransformer
.throwIfInvalid(SchemaLoader.loadConvertableSchemaFromYaml(new ByteArrayInputStream(newSchema)));
ConfigSchema configSchemaNew = ConfigTransformer.throwIfInvalid(schemaNew.convert());
ConvertableSchema<ConfigSchema> schemaOld = ConfigTransformer
.throwIfInvalid(SchemaLoader.loadConvertableSchemaFromYaml(new ByteBufferInputStream(currentConfigScheme)));
ConfigSchema configSchemaOld = ConfigTransformer.throwIfInvalid(schemaOld.convert());
configSchemaNew.setNifiPropertiesOverrides(configSchemaOld.getNifiPropertiesOverrides());
if (!overrideCoreProperties) {
LOGGER.debug("Preserving previous core properties...");
configSchemaNew.setCoreProperties(configSchemaOld.getCoreProperties());
}
if (!overrideSecurityProperties) {
LOGGER.debug("Preserving previous security properties...");
configSchemaNew.setSecurityProperties(configSchemaOld.getSecurityProperties());
}
StringWriter writer = new StringWriter();
SchemaLoader.toYaml(configSchemaNew, writer);
return ByteBuffer.wrap(writer.toString().getBytes()).asReadOnlyBuffer();
}
} catch (Exception e) {
throw new InvalidConfigurationException("Loading the old and the new schema for merging was not successful", e);
}
}
private static boolean overrideSecurityProperties(Properties properties) {
String overrideSecurityProperties = (String) properties.getOrDefault(OVERRIDE_SECURITY, "false");
return Boolean.parseBoolean(overrideSecurityProperties);
}
private static boolean overrideCoreProperties(Properties properties) {
String overrideCoreProps = (String) properties.getOrDefault(OVERRIDE_CORE_PROPERTIES_KEY, "false");
return Boolean.parseBoolean(overrideCoreProps);
}
public static final String PROPERTIES_FILE_APACHE_2_0_LICENSE =
" Licensed to the Apache Software Foundation (ASF) under one or more\n" +
"# contributor license agreements. See the NOTICE file distributed with\n" +
"# this work for additional information regarding copyright ownership.\n" +
"# The ASF licenses this file to You under the Apache License, Version 2.0\n" +
"# (the \"License\"); you may not use this file except in compliance with\n" +
"# the License. You may obtain a copy of the License at\n" +
"#\n" +
"# http://www.apache.org/licenses/LICENSE-2.0\n" +
"#\n" +
"# Unless required by applicable law or agreed to in writing, software\n" +
"# distributed under the License is distributed on an \"AS IS\" BASIS,\n" +
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
"# See the License for the specific language governing permissions and\n" +
"# limitations under the License.\n" +
"\n";
}

View File

@ -1,32 +0,0 @@
/*
* 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.minifi.bootstrap.util;
public class ConfigTransformerException extends Exception {
public ConfigTransformerException(String message) {
super(message);
}
public ConfigTransformerException(Throwable cause) {
super(cause);
}
public ConfigTransformerException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -34,6 +34,7 @@ import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
public class OrderedProperties extends Properties { public class OrderedProperties extends Properties {
private final Map<String, String> textBeforeMap = new HashMap<>(); private final Map<String, String> textBeforeMap = new HashMap<>();
@ -74,7 +75,7 @@ public class OrderedProperties extends Properties {
int equalsIndex = line.indexOf('='); int equalsIndex = line.indexOf('=');
if (equalsIndex != -1) { if (equalsIndex != -1) {
String textBefore = textBeforeMap.get(line.substring(0, equalsIndex)); String textBefore = textBeforeMap.get(line.substring(0, equalsIndex));
if (textBefore != null) { if (StringUtils.isNotBlank(textBefore)) {
bufferedWriter.write(textBefore); bufferedWriter.write(textBefore);
bufferedWriter.newLine(); bufferedWriter.newLine();
} }

View File

@ -1,101 +0,0 @@
/*
*
* * 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.minifi.bootstrap.util;
import org.apache.nifi.minifi.commons.schema.ProcessGroupSchema;
import org.apache.nifi.minifi.commons.schema.RemotePortSchema;
import org.apache.nifi.minifi.commons.schema.RemoteProcessGroupSchema;
import org.apache.nifi.minifi.commons.schema.common.BaseSchemaWithId;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class ParentGroupIdResolver {
private final Map<String, String> processorIdToParentIdMap;
private final Map<String, String> inputPortIdToParentIdMap;
private final Map<String, String> outputPortIdToParentIdMap;
private final Map<String, String> funnelIdToParentIdMap;
private final Map<String, String> remoteInputPortIdToParentIdMap;
private final Map<String, String> remoteOutputPortIdToParentIdMap;
public ParentGroupIdResolver(ProcessGroupSchema processGroupSchema) {
this.processorIdToParentIdMap = getParentIdMap(processGroupSchema, ProcessGroupSchema::getProcessors);
this.inputPortIdToParentIdMap = getParentIdMap(processGroupSchema, ProcessGroupSchema::getInputPortSchemas);
this.outputPortIdToParentIdMap = getParentIdMap(processGroupSchema, ProcessGroupSchema::getOutputPortSchemas);
this.funnelIdToParentIdMap = getParentIdMap(processGroupSchema, ProcessGroupSchema::getFunnels);
this.remoteInputPortIdToParentIdMap = getRemotePortParentIdMap(processGroupSchema, RemoteProcessGroupSchema::getInputPorts);
this.remoteOutputPortIdToParentIdMap = getRemotePortParentIdMap(processGroupSchema, RemoteProcessGroupSchema::getOutputPorts);
}
protected static Map<String, String> getParentIdMap(ProcessGroupSchema processGroupSchema, Function<ProcessGroupSchema, Collection<? extends BaseSchemaWithId>> schemaAccessor) {
Map<String, String> map = new HashMap<>();
getParentIdMap(processGroupSchema, map, schemaAccessor);
return map;
}
protected static void getParentIdMap(ProcessGroupSchema processGroupSchema, Map<String, String> output, Function<ProcessGroupSchema,
Collection<? extends BaseSchemaWithId>> schemaAccessor) {
schemaAccessor.apply(processGroupSchema).forEach(p -> output.put(p.getId(), processGroupSchema.getId()));
processGroupSchema.getProcessGroupSchemas().forEach(p -> getParentIdMap(p, output, schemaAccessor));
}
protected static Map<String, String> getRemotePortParentIdMap(ProcessGroupSchema processGroupSchema, Function<RemoteProcessGroupSchema, List<RemotePortSchema>> getPortsFunction) {
Map<String, String> result = new HashMap<>();
getRemotePortParentIdMap(processGroupSchema, result, getPortsFunction);
return result;
}
protected static void getRemotePortParentIdMap(ProcessGroupSchema processGroupSchema, Map<String, String> output, Function<RemoteProcessGroupSchema,
List<RemotePortSchema>> getPortsFunction) {
for (RemoteProcessGroupSchema remoteProcessGroupSchema : processGroupSchema.getRemoteProcessGroups()) {
for (RemotePortSchema remotePortSchema : getPortsFunction.apply(remoteProcessGroupSchema)) {
output.put(remotePortSchema.getId(), remoteProcessGroupSchema.getId());
}
}
processGroupSchema.getProcessGroupSchemas().forEach(p -> getRemotePortParentIdMap(p, output, getPortsFunction));
}
public String getRemoteInputPortParentId(String id) {
return remoteInputPortIdToParentIdMap.get(id);
}
public String getRemoteOutputPortParentId(String id) {
return remoteOutputPortIdToParentIdMap.get(id);
}
public String getInputPortParentId(String id) {
return inputPortIdToParentIdMap.get(id);
}
public String getOutputPortParentId(String id) {
return outputPortIdToParentIdMap.get(id);
}
public String getProcessorParentId(String id) {
return processorIdToParentIdMap.get(id);
}
public String getFunnelParentId(String id) {
return funnelIdToParentIdMap.get(id);
}
}

View File

@ -1,129 +0,0 @@
/*
* 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.minifi.bootstrap;
import org.apache.nifi.minifi.bootstrap.util.BootstrapTransformer;
import org.apache.nifi.minifi.commons.schema.ProvenanceReportingSchema;
import org.apache.nifi.minifi.commons.schema.SecurityPropertiesSchema;
import org.apache.nifi.minifi.commons.schema.SensitivePropsSchema;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class RunMiNiFiTest {
@Test
public void buildSecurityPropertiesNotDefined() throws Exception {
final Properties bootstrapProperties = getTestBootstrapProperties("bootstrap-ssl-ctx/bootstrap.conf.default");
final Optional<SecurityPropertiesSchema> securityPropsOptional = BootstrapTransformer.buildSecurityPropertiesFromBootstrap(bootstrapProperties);
assertFalse(securityPropsOptional.isPresent());
}
@Test
public void buildSecurityPropertiesDefined() throws Exception {
final Properties bootstrapProperties = getTestBootstrapProperties("bootstrap-ssl-ctx/bootstrap.conf.configured");
final Optional<SecurityPropertiesSchema> securityPropsOptional = BootstrapTransformer.buildSecurityPropertiesFromBootstrap(bootstrapProperties);
assertTrue(securityPropsOptional.isPresent());
final SecurityPropertiesSchema securityPropertiesSchema = securityPropsOptional.get();
assertEquals("/my/test/keystore.jks", securityPropertiesSchema.getKeystore());
assertEquals("JKS", securityPropertiesSchema.getKeystoreType());
assertEquals("mykeystorepassword", securityPropertiesSchema.getKeystorePassword());
assertEquals("mykeypassword", securityPropertiesSchema.getKeyPassword());
assertEquals("/my/test/truststore.jks", securityPropertiesSchema.getTruststore());
assertEquals("JKS", securityPropertiesSchema.getTruststoreType());
assertEquals("mytruststorepassword", securityPropertiesSchema.getTruststorePassword());
assertEquals("TLS", securityPropertiesSchema.getSslProtocol());
final SensitivePropsSchema sensitiveProps = securityPropertiesSchema.getSensitiveProps();
assertNotNull(sensitiveProps);
assertEquals("sensitivepropskey", sensitiveProps.getKey());
assertEquals("algo", sensitiveProps.getAlgorithm());
assertTrue(securityPropertiesSchema.isValid());
}
@Test
public void buildSecurityPropertiesDefinedButInvalid() throws Exception {
final Properties bootstrapProperties = getTestBootstrapProperties("bootstrap-ssl-ctx/bootstrap.conf.configured.invalid");
final Optional<SecurityPropertiesSchema> securityPropsOptional = BootstrapTransformer.buildSecurityPropertiesFromBootstrap(bootstrapProperties);
assertTrue(securityPropsOptional.isPresent());
final SecurityPropertiesSchema securityPropertiesSchema = securityPropsOptional.get();
assertEquals("/my/test/keystore.jks", securityPropertiesSchema.getKeystore());
assertEquals("NOTAKEYSTORETYPE", securityPropertiesSchema.getKeystoreType());
assertEquals("mykeystorepassword", securityPropertiesSchema.getKeystorePassword());
assertEquals("mykeypassword", securityPropertiesSchema.getKeyPassword());
assertEquals("/my/test/truststore.jks", securityPropertiesSchema.getTruststore());
assertEquals("JKS", securityPropertiesSchema.getTruststoreType());
assertEquals("mytruststorepassword", securityPropertiesSchema.getTruststorePassword());
final SensitivePropsSchema sensitiveProps = securityPropertiesSchema.getSensitiveProps();
assertNotNull(sensitiveProps);
assertEquals("sensitivepropskey", sensitiveProps.getKey());
assertEquals("algo", sensitiveProps.getAlgorithm());
assertFalse(securityPropertiesSchema.isValid());
}
@Test
public void buildProvenanceReportingNotDefined() throws Exception {
final Properties bootstrapProperties = getTestBootstrapProperties("bootstrap-provenance-reporting/bootstrap.conf.default");
final Optional<ProvenanceReportingSchema> provenanceReportingPropsOptional = BootstrapTransformer.buildProvenanceReportingPropertiesFromBootstrap(bootstrapProperties);
assertFalse(provenanceReportingPropsOptional.isPresent());
}
@Test
public void buildProvenanceReportingDefined() throws Exception {
final Properties bootstrapProperties = getTestBootstrapProperties("bootstrap-provenance-reporting/bootstrap.conf.configured");
final Optional<ProvenanceReportingSchema> provenanceReportingPropsOptional = BootstrapTransformer.buildProvenanceReportingPropertiesFromBootstrap(bootstrapProperties);
assertTrue(provenanceReportingPropsOptional.isPresent());
final ProvenanceReportingSchema provenanceReportingSchema = provenanceReportingPropsOptional.get();
assertEquals("This is a comment!", provenanceReportingSchema.getComment());
assertEquals("TIMER_DRIVEN", provenanceReportingSchema.getSchedulingStrategy());
assertEquals("15 secs", provenanceReportingSchema.getSchedulingPeriod());
assertEquals("http://localhost:8080/", provenanceReportingSchema.getDestinationUrl());
assertEquals("provenance", provenanceReportingSchema.getPortName());
assertEquals("http://${hostname(true)}:8081/nifi", provenanceReportingSchema.getOriginatingUrl());
assertEquals("10 secs", provenanceReportingSchema.getTimeout());
}
public static Properties getTestBootstrapProperties(final String fileName) throws IOException {
final Properties bootstrapProperties = new Properties();
try (final InputStream fis = RunMiNiFiTest.class.getClassLoader().getResourceAsStream(fileName)) {
bootstrapProperties.load(fis);
}
return bootstrapProperties;
}
}

View File

@ -21,9 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@ -61,28 +59,6 @@ public class WholeConfigDifferentiatorTest {
when(configurationFileHolder.getConfigFileReference()).thenReturn(new AtomicReference<>(defaultConfigBuffer)); when(configurationFileHolder.getConfigFileReference()).thenReturn(new AtomicReference<>(defaultConfigBuffer));
} }
// InputStream differentiator methods
@Test
public void TestSameInputStream() throws IOException {
Differentiator<InputStream> differentiator = WholeConfigDifferentiator.getInputStreamDifferentiator();
differentiator.initialize(configurationFileHolder);
FileInputStream fileInputStream = new FileInputStream(defaultConfigPath.toFile());
assertFalse(differentiator.isNew(fileInputStream));
}
@Test
public void TestNewInputStream() throws IOException {
Differentiator<InputStream> differentiator = WholeConfigDifferentiator.getInputStreamDifferentiator();
differentiator.initialize(configurationFileHolder);
FileInputStream fileInputStream = new FileInputStream(newConfigPath.toFile());
assertTrue(differentiator.isNew(fileInputStream));
}
// Bytebuffer differentiator methods
@Test @Test
public void TestSameByteBuffer() throws IOException { public void TestSameByteBuffer() throws IOException {
Differentiator<ByteBuffer> differentiator = WholeConfigDifferentiator.getByteBufferDifferentiator(); Differentiator<ByteBuffer> differentiator = WholeConfigDifferentiator.getByteBufferDifferentiator();

View File

@ -5,20 +5,22 @@
* The ASF licenses this file to You under the Apache License, Version 2.0 * 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 not use this file except in compliance with
* the License. You may obtain a copy of the License at * the License. You may obtain a copy of the License at
* * <p>
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* * <p>
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.apache.nifi.minifi.bootstrap.configuration.ingestors; package org.apache.nifi.minifi.bootstrap.configuration.ingestors;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static org.apache.nifi.minifi.bootstrap.configuration.ingestors.PullHttpChangeIngestor.PULL_HTTP_BASE_KEY; import static org.apache.nifi.minifi.bootstrap.configuration.ingestors.PullHttpChangeIngestor.PULL_HTTP_BASE_KEY;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -34,6 +36,7 @@ import java.util.concurrent.atomic.AtomicReference;
import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder; import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier; import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier;
import org.apache.nifi.minifi.bootstrap.configuration.differentiators.Differentiator; import org.apache.nifi.minifi.bootstrap.configuration.differentiators.Differentiator;
import org.apache.nifi.minifi.commons.api.MiNiFiProperties;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -50,12 +53,13 @@ public class FileChangeIngestorTest {
private Differentiator<ByteBuffer> mockDifferentiator; private Differentiator<ByteBuffer> mockDifferentiator;
private ConfigurationChangeNotifier testNotifier; private ConfigurationChangeNotifier testNotifier;
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
mockWatchService = Mockito.mock(WatchService.class); mockWatchService = mock(WatchService.class);
notifierSpy = Mockito.spy(new FileChangeIngestor()); notifierSpy = Mockito.spy(new FileChangeIngestor());
mockDifferentiator = Mockito.mock(Differentiator.class); mockDifferentiator = mock(Differentiator.class);
testNotifier = Mockito.mock(ConfigurationChangeNotifier.class); testNotifier = mock(ConfigurationChangeNotifier.class);
setMocks(); setMocks();
@ -64,6 +68,7 @@ public class FileChangeIngestorTest {
testProperties.put(PullHttpChangeIngestor.OVERRIDE_SECURITY, "true"); testProperties.put(PullHttpChangeIngestor.OVERRIDE_SECURITY, "true");
testProperties.put(PULL_HTTP_BASE_KEY + ".override.core", "true"); testProperties.put(PULL_HTTP_BASE_KEY + ".override.core", "true");
testProperties.put(FileChangeIngestor.POLLING_PERIOD_INTERVAL_KEY, FileChangeIngestor.DEFAULT_POLLING_PERIOD_INTERVAL); testProperties.put(FileChangeIngestor.POLLING_PERIOD_INTERVAL_KEY, FileChangeIngestor.DEFAULT_POLLING_PERIOD_INTERVAL);
testProperties.put(MiNiFiProperties.NIFI_MINIFI_FLOW_CONFIG.getKey(), MiNiFiProperties.NIFI_MINIFI_FLOW_CONFIG.getDefaultValue());
} }
@AfterEach @AfterEach
@ -74,31 +79,31 @@ public class FileChangeIngestorTest {
@Test @Test
public void testInitializeInvalidFile() { public void testInitializeInvalidFile() {
testProperties.put(FileChangeIngestor.CONFIG_FILE_PATH_KEY, "/land/of/make/believe"); testProperties.put(FileChangeIngestor.CONFIG_FILE_PATH_KEY, "/land/of/make/believe");
assertThrows(IllegalStateException.class, () -> notifierSpy.initialize(testProperties, Mockito.mock(ConfigurationFileHolder.class), Mockito.mock(ConfigurationChangeNotifier.class))); assertThrows(IllegalStateException.class, () -> notifierSpy.initialize(testProperties, mock(ConfigurationFileHolder.class), mock(ConfigurationChangeNotifier.class)));
} }
@Test @Test
public void testInitializeValidFile() { public void testInitializeValidFile() {
notifierSpy.initialize(testProperties, Mockito.mock(ConfigurationFileHolder.class), Mockito.mock(ConfigurationChangeNotifier.class)); notifierSpy.initialize(testProperties, mock(ConfigurationFileHolder.class), mock(ConfigurationChangeNotifier.class));
} }
@Test @Test
public void testInitializeInvalidPollingPeriod() { public void testInitializeInvalidPollingPeriod() {
testProperties.put(FileChangeIngestor.POLLING_PERIOD_INTERVAL_KEY, "abc"); testProperties.put(FileChangeIngestor.POLLING_PERIOD_INTERVAL_KEY, "abc");
assertThrows(IllegalStateException.class, () -> notifierSpy.initialize(testProperties, Mockito.mock(ConfigurationFileHolder.class), Mockito.mock(ConfigurationChangeNotifier.class))); assertThrows(IllegalStateException.class, () -> notifierSpy.initialize(testProperties, mock(ConfigurationFileHolder.class), mock(ConfigurationChangeNotifier.class)));
} }
@Test @Test
public void testInitializeUseDefaultPolling() { public void testInitializeUseDefaultPolling() {
testProperties.remove(FileChangeIngestor.POLLING_PERIOD_INTERVAL_KEY); testProperties.remove(FileChangeIngestor.POLLING_PERIOD_INTERVAL_KEY);
notifierSpy.initialize(testProperties, Mockito.mock(ConfigurationFileHolder.class), Mockito.mock(ConfigurationChangeNotifier.class)); notifierSpy.initialize(testProperties, mock(ConfigurationFileHolder.class), mock(ConfigurationChangeNotifier.class));
} }
/* Verify handleChange events */ /* Verify handleChange events */
@Test @Test
public void testTargetChangedNoModification() throws Exception { public void testTargetChangedNoModification() throws Exception {
when(mockDifferentiator.isNew(Mockito.any(ByteBuffer.class))).thenReturn(false); when(mockDifferentiator.isNew(Mockito.any(ByteBuffer.class))).thenReturn(false);
final ConfigurationChangeNotifier testNotifier = Mockito.mock(ConfigurationChangeNotifier.class); final ConfigurationChangeNotifier testNotifier = mock(ConfigurationChangeNotifier.class);
// In this case the WatchKey is null because there were no events found // In this case the WatchKey is null because there were no events found
establishMockEnvironmentForChangeTests(null); establishMockEnvironmentForChangeTests(null);
@ -109,14 +114,14 @@ public class FileChangeIngestorTest {
@Test @Test
public void testTargetChangedWithModificationEventNonConfigFile() throws Exception { public void testTargetChangedWithModificationEventNonConfigFile() throws Exception {
when(mockDifferentiator.isNew(Mockito.any(ByteBuffer.class))).thenReturn(false); when(mockDifferentiator.isNew(Mockito.any(ByteBuffer.class))).thenReturn(false);
final ConfigurationChangeNotifier testNotifier = Mockito.mock(ConfigurationChangeNotifier.class); final ConfigurationChangeNotifier testNotifier = mock(ConfigurationChangeNotifier.class);
// In this case, we receive a trigger event for the directory monitored, but it was another file not being monitored // In this case, we receive a trigger event for the directory monitored, but it was another file not being monitored
final WatchKey mockWatchKey = createMockWatchKeyForPath("footage_not_found.yml"); final WatchKey mockWatchKey = createMockWatchKeyForPath("footage_not_found.yml");
establishMockEnvironmentForChangeTests(mockWatchKey); establishMockEnvironmentForChangeTests(mockWatchKey);
notifierSpy.targetChanged(); notifierSpy.targetFileChanged();
verify(testNotifier, Mockito.never()).notifyListeners(Mockito.any(ByteBuffer.class)); verify(testNotifier, Mockito.never()).notifyListeners(Mockito.any(ByteBuffer.class));
} }
@ -129,8 +134,9 @@ public class FileChangeIngestorTest {
// Provided as a spy to allow injection of mock objects for some tests when dealing with the finalized FileSystems class // Provided as a spy to allow injection of mock objects for some tests when dealing with the finalized FileSystems class
establishMockEnvironmentForChangeTests(mockWatchKey); establishMockEnvironmentForChangeTests(mockWatchKey);
ConfigurationFileHolder configurationFileHolder = Mockito.mock(ConfigurationFileHolder.class); ConfigurationFileHolder configurationFileHolder = mock(ConfigurationFileHolder.class);
when(configurationFileHolder.getConfigFileReference()).thenReturn(new AtomicReference<>(ByteBuffer.wrap(new byte[0]))); when(configurationFileHolder.getConfigFileReference()).thenReturn(new AtomicReference<>(ByteBuffer.wrap(new byte[0])));
notifierSpy.initialize(testProperties, configurationFileHolder, testNotifier); notifierSpy.initialize(testProperties, configurationFileHolder, testNotifier);
setMocks(); setMocks();
@ -143,8 +149,8 @@ public class FileChangeIngestorTest {
/* Helper methods to establish mock environment */ /* Helper methods to establish mock environment */
private WatchKey createMockWatchKeyForPath(String configFilePath) { private WatchKey createMockWatchKeyForPath(String configFilePath) {
WatchKey mockWatchKey = Mockito.mock(WatchKey.class); WatchKey mockWatchKey = mock(WatchKey.class);
WatchEvent mockWatchEvent = Mockito.mock(WatchEvent.class); WatchEvent mockWatchEvent = mock(WatchEvent.class);
// In this case, we receive a trigger event for the directory monitored, and it was the file monitored // In this case, we receive a trigger event for the directory monitored, and it was the file monitored
when(mockWatchEvent.context()).thenReturn(Paths.get(configFilePath)); when(mockWatchEvent.context()).thenReturn(Paths.get(configFilePath));
@ -157,8 +163,8 @@ public class FileChangeIngestorTest {
private void establishMockEnvironmentForChangeTests(final WatchKey watchKey) { private void establishMockEnvironmentForChangeTests(final WatchKey watchKey) {
// Establish the file mock and its parent directory // Establish the file mock and its parent directory
final Path mockConfigFilePath = Mockito.mock(Path.class); final Path mockConfigFilePath = mock(Path.class);
final Path mockConfigFileParentPath = Mockito.mock(Path.class); final Path mockConfigFileParentPath = mock(Path.class);
// When getting the parent of the file, get the directory // When getting the parent of the file, get the directory
when(mockConfigFilePath.getParent()).thenReturn(mockConfigFileParentPath); when(mockConfigFilePath.getParent()).thenReturn(mockConfigFileParentPath);

View File

@ -15,15 +15,12 @@
* limitations under the License. * limitations under the License.
*/ */
package org.apache.nifi.minifi.bootstrap.configuration.ingestors.common; package org.apache.nifi.minifi.bootstrap.configuration.ingestors;
import static org.apache.nifi.minifi.bootstrap.configuration.ingestors.PullHttpChangeIngestor.PATH_KEY; import static org.apache.nifi.minifi.bootstrap.configuration.ingestors.PullHttpChangeIngestor.PATH_KEY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -32,18 +29,10 @@ import java.util.Collections;
import java.util.Properties; import java.util.Properties;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.nifi.minifi.bootstrap.RunMiNiFi;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeListener; import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeListener;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier; import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier;
import org.apache.nifi.minifi.bootstrap.configuration.ListenerHandleResult; import org.apache.nifi.minifi.bootstrap.configuration.ListenerHandleResult;
import org.apache.nifi.minifi.bootstrap.configuration.differentiators.Differentiator; import org.apache.nifi.minifi.bootstrap.configuration.differentiators.Differentiator;
import org.apache.nifi.minifi.bootstrap.configuration.ingestors.PullHttpChangeIngestor;
import org.apache.nifi.minifi.bootstrap.util.ByteBufferInputStream;
import org.apache.nifi.minifi.commons.schema.ConfigSchema;
import org.apache.nifi.minifi.commons.schema.common.ConvertableSchema;
import org.apache.nifi.minifi.commons.schema.exception.SchemaLoaderException;
import org.apache.nifi.minifi.commons.schema.serialization.SchemaLoader;
import org.apache.nifi.util.file.FileUtils;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.AbstractHandler;
@ -52,11 +41,11 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito; import org.mockito.Mockito;
public abstract class PullHttpChangeIngestorCommonTest { public abstract class PullHttpChangeIngestorCommonTest {
public static final String FLOW_JSON = "/flow.json";
public static volatile Server jetty; public static volatile Server jetty;
public static volatile int port; public static volatile int port;
public static volatile PullHttpChangeIngestor pullHttpChangeIngestor; public static volatile PullHttpChangeIngestor pullHttpChangeIngestor;
@ -64,8 +53,8 @@ public abstract class PullHttpChangeIngestorCommonTest {
public static Differentiator<ByteBuffer> mockDifferentiator = Mockito.mock(Differentiator.class); public static Differentiator<ByteBuffer> mockDifferentiator = Mockito.mock(Differentiator.class);
public static final String RESPONSE_STRING = "test"; public static final String RESPONSE_STRING = "test";
public static final String PATH_RESPONSE_STRING = "path"; public static final String PATH_RESPONSE_STRING = "path";
public static ByteBuffer configBuffer= ByteBuffer.wrap(RESPONSE_STRING.getBytes()); public static ByteBuffer configBuffer = ByteBuffer.wrap(RESPONSE_STRING.getBytes());
public static ByteBuffer pathConfigBuffer= ByteBuffer.wrap(PATH_RESPONSE_STRING.getBytes()); public static ByteBuffer pathConfigBuffer = ByteBuffer.wrap(PATH_RESPONSE_STRING.getBytes());
public static final String ETAG = "testEtag"; public static final String ETAG = "testEtag";
public static final String QUOTED_ETAG = "\"testEtag\""; public static final String QUOTED_ETAG = "\"testEtag\"";
@ -79,7 +68,7 @@ public abstract class PullHttpChangeIngestorCommonTest {
jetty.setHandler(handlerCollection); jetty.setHandler(handlerCollection);
} }
public abstract void pullHttpChangeIngestorInit(Properties properties) throws IOException, SchemaLoaderException; public abstract void pullHttpChangeIngestorInit(Properties properties) throws IOException;
@BeforeEach @BeforeEach
public void setListeners() { public void setListeners() {
@ -95,7 +84,7 @@ public abstract class PullHttpChangeIngestorCommonTest {
} }
@Test @Test
public void testNewUpdate() throws IOException, SchemaLoaderException { public void testNewUpdate() throws IOException {
Properties properties = new Properties(); Properties properties = new Properties();
properties.put(PullHttpChangeIngestor.OVERRIDE_SECURITY, "true"); properties.put(PullHttpChangeIngestor.OVERRIDE_SECURITY, "true");
pullHttpChangeIngestorInit(properties); pullHttpChangeIngestorInit(properties);
@ -108,28 +97,7 @@ public abstract class PullHttpChangeIngestorCommonTest {
} }
@Test @Test
public void testSecurityOverride() throws IOException, SchemaLoaderException { public void testNoUpdate() throws IOException {
Properties properties = new Properties();
properties.put(PullHttpChangeIngestor.OVERRIDE_SECURITY, "false");
properties.put(RunMiNiFi.MINIFI_CONFIG_FILE_KEY, "src/test/resources/config.yml");
properties.put(PATH_KEY, "/config-minimal.yml");
pullHttpChangeIngestorInit(properties);
when(mockDifferentiator.isNew(Mockito.any(ByteBuffer.class))).thenReturn(true);
pullHttpChangeIngestor.run();
ArgumentCaptor<ByteBuffer> argument = ArgumentCaptor.forClass(ByteBuffer.class);
verify(testNotifier, Mockito.times(1)).notifyListeners(argument.capture());
ConvertableSchema<ConfigSchema> configSchema = SchemaLoader.loadConvertableSchemaFromYaml(new ByteBufferInputStream(argument.getValue()));
ConfigSchema newSchema = configSchema.convert();
assertNotNull(newSchema.getSecurityProperties().getKeystore());
assertEquals(newSchema.getProcessGroupSchema().getProcessors().size(), 2);
}
@Test
public void testNoUpdate() throws IOException, SchemaLoaderException {
Properties properties = new Properties(); Properties properties = new Properties();
properties.put(PullHttpChangeIngestor.OVERRIDE_SECURITY, "true"); properties.put(PullHttpChangeIngestor.OVERRIDE_SECURITY, "true");
pullHttpChangeIngestorInit(properties); pullHttpChangeIngestorInit(properties);
@ -142,7 +110,7 @@ public abstract class PullHttpChangeIngestorCommonTest {
} }
@Test @Test
public void testUseEtag() throws IOException, SchemaLoaderException { public void testUseEtag() throws IOException {
Properties properties = new Properties(); Properties properties = new Properties();
properties.put(PullHttpChangeIngestor.OVERRIDE_SECURITY, "true"); properties.put(PullHttpChangeIngestor.OVERRIDE_SECURITY, "true");
pullHttpChangeIngestorInit(properties); pullHttpChangeIngestorInit(properties);
@ -159,13 +127,12 @@ public abstract class PullHttpChangeIngestorCommonTest {
pullHttpChangeIngestor.run(); pullHttpChangeIngestor.run();
verify(testNotifier, Mockito.times(1)).notifyListeners(Mockito.any()); verify(testNotifier, Mockito.times(1)).notifyListeners(Mockito.any());
} }
@Test @Test
public void testNewUpdateWithPath() throws IOException, SchemaLoaderException { public void testNewUpdateWithPath() throws IOException {
Properties properties = new Properties(); Properties properties = new Properties();
properties.put(PATH_KEY, "/config.yml"); properties.put(PATH_KEY, FLOW_JSON);
properties.put(PullHttpChangeIngestor.OVERRIDE_SECURITY, "true"); properties.put(PullHttpChangeIngestor.OVERRIDE_SECURITY, "true");
pullHttpChangeIngestorInit(properties); pullHttpChangeIngestorInit(properties);
pullHttpChangeIngestor.setUseEtag(false); pullHttpChangeIngestor.setUseEtag(false);
@ -177,10 +144,10 @@ public abstract class PullHttpChangeIngestorCommonTest {
} }
@Test @Test
public void testNoUpdateWithPath() throws IOException, SchemaLoaderException { public void testNoUpdateWithPath() throws IOException {
Properties properties = new Properties(); Properties properties = new Properties();
properties.put(PullHttpChangeIngestor.OVERRIDE_SECURITY, "true"); properties.put(PullHttpChangeIngestor.OVERRIDE_SECURITY, "true");
properties.put(PATH_KEY, "/config.yml"); properties.put(PATH_KEY, FLOW_JSON);
pullHttpChangeIngestorInit(properties); pullHttpChangeIngestorInit(properties);
pullHttpChangeIngestor.setUseEtag(false); pullHttpChangeIngestor.setUseEtag(false);
when(mockDifferentiator.isNew(Mockito.any(ByteBuffer.class))).thenReturn(false); when(mockDifferentiator.isNew(Mockito.any(ByteBuffer.class))).thenReturn(false);
@ -191,10 +158,10 @@ public abstract class PullHttpChangeIngestorCommonTest {
} }
@Test @Test
public void testUseEtagWithPath() throws IOException, SchemaLoaderException { public void testUseEtagWithPath() throws IOException {
Properties properties = new Properties(); Properties properties = new Properties();
properties.put(PullHttpChangeIngestor.OVERRIDE_SECURITY, "true"); properties.put(PullHttpChangeIngestor.OVERRIDE_SECURITY, "true");
properties.put(PATH_KEY, "/config.yml"); properties.put(PATH_KEY, FLOW_JSON);
pullHttpChangeIngestorInit(properties); pullHttpChangeIngestorInit(properties);
pullHttpChangeIngestor.setLastEtag(""); pullHttpChangeIngestor.setLastEtag("");
@ -209,14 +176,13 @@ public abstract class PullHttpChangeIngestorCommonTest {
pullHttpChangeIngestor.run(); pullHttpChangeIngestor.run();
verify(testNotifier, Mockito.times(1)).notifyListeners(Mockito.any()); verify(testNotifier, Mockito.times(1)).notifyListeners(Mockito.any());
} }
static class JettyHandler extends AbstractHandler { static class JettyHandler extends AbstractHandler {
volatile String configResponse; volatile String configResponse;
volatile String pathResponse; volatile String pathResponse;
public JettyHandler(String configResponse, String pathResponse){ public JettyHandler(String configResponse, String pathResponse) {
this.configResponse = configResponse; this.configResponse = configResponse;
this.pathResponse = pathResponse; this.pathResponse = pathResponse;
} }
@ -229,13 +195,11 @@ public abstract class PullHttpChangeIngestorCommonTest {
if ("GET".equals(request.getMethod())) { if ("GET".equals(request.getMethod())) {
if (QUOTED_ETAG.equals(baseRequest.getHeader("If-None-Match"))){ if (QUOTED_ETAG.equals(baseRequest.getHeader("If-None-Match"))) {
writeOutput(response, null, 304); writeOutput(response, null, 304);
} else { } else {
if ("/config.yml".equals(baseRequest.getPathInfo())) { if (FLOW_JSON.equals(baseRequest.getPathInfo())) {
writeOutput(response, pathResponse, 200); writeOutput(response, pathResponse, 200);
} else if ("/config-minimal.yml".equals(baseRequest.getPathInfo())) {
writeFileOutput(response, new File("src/test/resources/config-minimal.yml"), 200);
} else { } else {
writeOutput(response, configResponse, 200); writeOutput(response, configResponse, 200);
} }
@ -260,17 +224,5 @@ public abstract class PullHttpChangeIngestorCommonTest {
} }
} }
} }
private void writeFileOutput(HttpServletResponse response, File file, int responseCode) throws IOException {
response.setStatus(responseCode);
response.setHeader("ETag", ETAG);
if (file != null) {
response.setContentType("text/plain");
response.setContentLength((int) file.length());
response.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
FileUtils.copyFile(file, response.getOutputStream(), true, true);
}
}
} }
} }

View File

@ -18,18 +18,10 @@
package org.apache.nifi.minifi.bootstrap.configuration.ingestors; package org.apache.nifi.minifi.bootstrap.configuration.ingestors;
import static org.apache.nifi.minifi.bootstrap.configuration.ingestors.PullHttpChangeIngestor.PULL_HTTP_BASE_KEY; import static org.apache.nifi.minifi.bootstrap.configuration.ingestors.PullHttpChangeIngestor.PULL_HTTP_BASE_KEY;
import static org.mockito.Mockito.when;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder; import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder;
import org.apache.nifi.minifi.bootstrap.configuration.ingestors.common.PullHttpChangeIngestorCommonTest;
import org.apache.nifi.minifi.commons.schema.ConfigSchema;
import org.apache.nifi.minifi.commons.schema.exception.SchemaLoaderException;
import org.apache.nifi.minifi.commons.schema.serialization.SchemaLoader;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
@ -74,7 +66,7 @@ public class PullHttpChangeIngestorSSLTest extends PullHttpChangeIngestorCommonT
} }
@Override @Override
public void pullHttpChangeIngestorInit(Properties properties) throws IOException, SchemaLoaderException { public void pullHttpChangeIngestorInit(Properties properties) throws IOException {
properties.setProperty(PullHttpChangeIngestor.TRUSTSTORE_LOCATION_KEY, "./src/test/resources/localhost-ts.jks"); properties.setProperty(PullHttpChangeIngestor.TRUSTSTORE_LOCATION_KEY, "./src/test/resources/localhost-ts.jks");
properties.setProperty(PullHttpChangeIngestor.TRUSTSTORE_PASSWORD_KEY, "localtest"); properties.setProperty(PullHttpChangeIngestor.TRUSTSTORE_PASSWORD_KEY, "localtest");
properties.setProperty(PullHttpChangeIngestor.TRUSTSTORE_TYPE_KEY, "JKS"); properties.setProperty(PullHttpChangeIngestor.TRUSTSTORE_TYPE_KEY, "JKS");
@ -88,12 +80,6 @@ public class PullHttpChangeIngestorSSLTest extends PullHttpChangeIngestorCommonT
properties.put(PULL_HTTP_BASE_KEY + ".override.core", "true"); properties.put(PULL_HTTP_BASE_KEY + ".override.core", "true");
ConfigurationFileHolder configurationFileHolder = Mockito.mock(ConfigurationFileHolder.class); ConfigurationFileHolder configurationFileHolder = Mockito.mock(ConfigurationFileHolder.class);
ConfigSchema configSchema =
SchemaLoader.loadConfigSchemaFromYaml(PullHttpChangeIngestorSSLTest.class.getClassLoader().getResourceAsStream("config.yml"));
StringWriter writer = new StringWriter();
SchemaLoader.toYaml(configSchema, writer);
when(configurationFileHolder.getConfigFileReference()).thenReturn(new AtomicReference<>(ByteBuffer.wrap(writer.toString().getBytes())));
pullHttpChangeIngestor = new PullHttpChangeIngestor(); pullHttpChangeIngestor = new PullHttpChangeIngestor();
pullHttpChangeIngestor.initialize(properties, configurationFileHolder, testNotifier); pullHttpChangeIngestor.initialize(properties, configurationFileHolder, testNotifier);

View File

@ -18,18 +18,10 @@
package org.apache.nifi.minifi.bootstrap.configuration.ingestors; package org.apache.nifi.minifi.bootstrap.configuration.ingestors;
import static org.apache.nifi.minifi.bootstrap.configuration.ingestors.PullHttpChangeIngestor.PULL_HTTP_BASE_KEY; import static org.apache.nifi.minifi.bootstrap.configuration.ingestors.PullHttpChangeIngestor.PULL_HTTP_BASE_KEY;
import static org.mockito.Mockito.when;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder; import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder;
import org.apache.nifi.minifi.bootstrap.configuration.ingestors.common.PullHttpChangeIngestorCommonTest;
import org.apache.nifi.minifi.commons.schema.ConfigSchema;
import org.apache.nifi.minifi.commons.schema.exception.SchemaLoaderException;
import org.apache.nifi.minifi.commons.schema.serialization.SchemaLoader;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.ServerConnector;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.mockito.Mockito; import org.mockito.Mockito;
@ -59,7 +51,7 @@ public class PullHttpChangeIngestorTest extends PullHttpChangeIngestorCommonTest
@Override @Override
public void pullHttpChangeIngestorInit(Properties properties) throws IOException, SchemaLoaderException { public void pullHttpChangeIngestorInit(Properties properties) throws IOException {
port = ((ServerConnector) jetty.getConnectors()[0]).getLocalPort(); port = ((ServerConnector) jetty.getConnectors()[0]).getLocalPort();
properties.put(PullHttpChangeIngestor.PORT_KEY, String.valueOf(port)); properties.put(PullHttpChangeIngestor.PORT_KEY, String.valueOf(port));
properties.put(PullHttpChangeIngestor.HOST_KEY, "localhost"); properties.put(PullHttpChangeIngestor.HOST_KEY, "localhost");
@ -67,12 +59,6 @@ public class PullHttpChangeIngestorTest extends PullHttpChangeIngestorCommonTest
properties.put(PULL_HTTP_BASE_KEY + ".override.core", "true"); properties.put(PULL_HTTP_BASE_KEY + ".override.core", "true");
ConfigurationFileHolder configurationFileHolder = Mockito.mock(ConfigurationFileHolder.class); ConfigurationFileHolder configurationFileHolder = Mockito.mock(ConfigurationFileHolder.class);
ConfigSchema configSchema =
SchemaLoader.loadConfigSchemaFromYaml(PullHttpChangeIngestorTest.class.getClassLoader().getResourceAsStream("config.yml"));
StringWriter writer = new StringWriter();
SchemaLoader.toYaml(configSchema, writer);
when(configurationFileHolder.getConfigFileReference()).thenReturn(new AtomicReference<>(ByteBuffer.wrap(writer.toString().getBytes())));
pullHttpChangeIngestor = new PullHttpChangeIngestor(); pullHttpChangeIngestor = new PullHttpChangeIngestor();
pullHttpChangeIngestor.initialize(properties, configurationFileHolder, testNotifier); pullHttpChangeIngestor.initialize(properties, configurationFileHolder, testNotifier);
pullHttpChangeIngestor.setDifferentiator(mockDifferentiator); pullHttpChangeIngestor.setDifferentiator(mockDifferentiator);

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.apache.nifi.minifi.bootstrap.configuration.ingestors.common; package org.apache.nifi.minifi.bootstrap.configuration.ingestors;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ -30,11 +30,10 @@ import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
import org.apache.nifi.minifi.bootstrap.configuration.differentiators.Differentiator;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeListener; import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeListener;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier; import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier;
import org.apache.nifi.minifi.bootstrap.configuration.ListenerHandleResult; import org.apache.nifi.minifi.bootstrap.configuration.ListenerHandleResult;
import org.apache.nifi.minifi.bootstrap.configuration.ingestors.RestChangeIngestor; import org.apache.nifi.minifi.bootstrap.configuration.differentiators.Differentiator;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
@ -65,7 +64,9 @@ public abstract class RestChangeIngestorCommonTest {
.build(); .build();
try (Response response = client.newCall(request).execute()) { try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
Headers responseHeaders = response.headers(); Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) { for (int i = 0; i < responseHeaders.size(); i++) {
@ -84,11 +85,13 @@ public abstract class RestChangeIngestorCommonTest {
Request request = new Request.Builder() Request request = new Request.Builder()
.url(url) .url(url)
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, testString)) .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, testString))
.addHeader("charset","UTF-8") .addHeader("charset", "UTF-8")
.build(); .build();
try (Response response = client.newCall(request).execute()) { try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
Headers responseHeaders = response.headers(); Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) { for (int i = 0; i < responseHeaders.size(); i++) {
@ -108,11 +111,13 @@ public abstract class RestChangeIngestorCommonTest {
Request request = new Request.Builder() Request request = new Request.Builder()
.url(url) .url(url)
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, testString)) .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, testString))
.addHeader("charset","UTF-8") .addHeader("charset", "UTF-8")
.build(); .build();
try (Response response = client.newCall(request).execute()) { try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) throw new IOException("Unexpected code " + response); if (response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
Headers responseHeaders = response.headers(); Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) { for (int i = 0; i < responseHeaders.size(); i++) {

View File

@ -17,14 +17,24 @@
package org.apache.nifi.minifi.bootstrap.configuration.ingestors; package org.apache.nifi.minifi.bootstrap.configuration.ingestors;
import static org.apache.nifi.minifi.bootstrap.configuration.ingestors.PullHttpChangeIngestor.PULL_HTTP_BASE_KEY;
import static org.mockito.Mockito.when;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.KeyStore;
import java.util.Collections;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder; import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeListener; import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeListener;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier; import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier;
import org.apache.nifi.minifi.bootstrap.configuration.ListenerHandleResult; import org.apache.nifi.minifi.bootstrap.configuration.ListenerHandleResult;
import org.apache.nifi.minifi.bootstrap.configuration.ingestors.common.RestChangeIngestorCommonTest;
import org.apache.nifi.security.ssl.StandardKeyStoreBuilder; import org.apache.nifi.security.ssl.StandardKeyStoreBuilder;
import org.apache.nifi.security.ssl.StandardSslContextBuilder; import org.apache.nifi.security.ssl.StandardSslContextBuilder;
import org.apache.nifi.security.ssl.StandardTrustManagerBuilder; import org.apache.nifi.security.ssl.StandardTrustManagerBuilder;
@ -34,18 +44,6 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.mockito.Mockito; import org.mockito.Mockito;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.util.Collections;
import java.util.Properties;
import static org.apache.nifi.minifi.bootstrap.configuration.ingestors.PullHttpChangeIngestor.PULL_HTTP_BASE_KEY;
import static org.mockito.Mockito.when;
public class RestChangeIngestorSSLTest extends RestChangeIngestorCommonTest { public class RestChangeIngestorSSLTest extends RestChangeIngestorCommonTest {
@BeforeAll @BeforeAll

View File

@ -20,19 +20,17 @@ package org.apache.nifi.minifi.bootstrap.configuration.ingestors;
import static org.apache.nifi.minifi.bootstrap.configuration.ingestors.PullHttpChangeIngestor.PULL_HTTP_BASE_KEY; import static org.apache.nifi.minifi.bootstrap.configuration.ingestors.PullHttpChangeIngestor.PULL_HTTP_BASE_KEY;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.net.MalformedURLException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder; import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier; import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier;
import org.apache.nifi.minifi.bootstrap.configuration.ingestors.common.RestChangeIngestorCommonTest;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.mockito.Mockito; import org.mockito.Mockito;
import java.net.MalformedURLException;
import java.util.Properties;
public class RestChangeIngestorTest extends RestChangeIngestorCommonTest { public class RestChangeIngestorTest extends RestChangeIngestorCommonTest {
@BeforeAll @BeforeAll

View File

@ -61,8 +61,6 @@ class BootstrapCodecTest {
@Mock @Mock
private ConfigurationChangeListener configurationChangeListener; private ConfigurationChangeListener configurationChangeListener;
@Mock @Mock
private UpdateConfigurationService updateConfigurationService;
@Mock
private UpdatePropertiesService updatePropertiesService; private UpdatePropertiesService updatePropertiesService;
@InjectMocks @InjectMocks
@ -70,7 +68,6 @@ class BootstrapCodecTest {
@BeforeEach @BeforeEach
void setup() throws IllegalAccessException, NoSuchFieldException { void setup() throws IllegalAccessException, NoSuchFieldException {
mockFinal("updateConfigurationService", updateConfigurationService);
mockFinal("updatePropertiesService", updatePropertiesService); mockFinal("updatePropertiesService", updatePropertiesService);
} }
@ -193,19 +190,6 @@ class BootstrapCodecTest {
assertEquals(OK, outputStream.toString().trim()); assertEquals(OK, outputStream.toString().trim());
} }
@Test
void testUpdateConfigurationCommandShouldHandleUpdateConfiguration() throws IOException {
InputStream inputStream = new ByteArrayInputStream("UPDATE_CONFIGURATION".getBytes(StandardCharsets.UTF_8));
C2Operation c2Operation = new C2Operation();
c2Operation.setIdentifier("id");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
bootstrapCodec.communicate(inputStream, outputStream);
assertEquals(OK, outputStream.toString().trim());
verify(updateConfigurationService).handleUpdate();
}
@Test @Test
void testUpdatePropertiesCommandShouldHandleUpdateProperties() throws IOException { void testUpdatePropertiesCommandShouldHandleUpdateProperties() throws IOException {
InputStream inputStream = new ByteArrayInputStream("UPDATE_PROPERTIES".getBytes(StandardCharsets.UTF_8)); InputStream inputStream = new ByteArrayInputStream("UPDATE_PROPERTIES".getBytes(StandardCharsets.UTF_8));

View File

@ -0,0 +1,267 @@
/*
* 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.minifi.bootstrap.service;
import static java.lang.Boolean.TRUE;
import static java.nio.file.Files.newInputStream;
import static java.nio.file.Files.newOutputStream;
import static java.util.UUID.randomUUID;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Stream.concat;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.StringUtils.SPACE;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiPropertiesGenerator.DEFAULT_SENSITIVE_PROPERTIES_ENCODING_ALGORITHM;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiPropertiesGenerator.MINIFI_TO_NIFI_PROPERTY_MAPPING;
import static org.apache.nifi.minifi.bootstrap.service.MiNiFiPropertiesGenerator.NIFI_PROPERTIES_WITH_DEFAULT_VALUES_AND_COMMENTS;
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.MINIFI_APP_LOG_FILE;
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.MINIFI_BOOTSTRAP_FILE_PATH;
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.MINIFI_BOOTSTRAP_LOG_FILE;
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.MINIFI_LOG_DIRECTORY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.function.Function;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeException;
import org.apache.nifi.minifi.commons.api.MiNiFiProperties;
import org.apache.nifi.util.NiFiProperties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
public class MiNiFiPropertiesGeneratorTest {
@TempDir
private Path tempDir;
private Path configDirectory;
private Path bootstrapPropertiesFile;
private Path minifiPropertiesFile;
private MiNiFiPropertiesGenerator testPropertiesGenerator;
@BeforeEach
public void setup() throws IOException {
configDirectory = tempDir.toAbsolutePath().resolve("conf");
Files.createDirectories(configDirectory);
bootstrapPropertiesFile = configDirectory.resolve("bootstrap.conf");
minifiPropertiesFile = configDirectory.resolve("minifi.properties");
testPropertiesGenerator = new MiNiFiPropertiesGenerator();
}
@Test
public void testGenerateDefaultNiFiProperties() throws ConfigurationChangeException {
// given
Properties bootstrapProperties = createBootstrapProperties(Map.of());
// when
testPropertiesGenerator.generateMinifiProperties(configDirectory.toString(), bootstrapProperties);
// then
List<String> expectedMiNiFiProperties = NIFI_PROPERTIES_WITH_DEFAULT_VALUES_AND_COMMENTS.stream()
.map(triplet -> triplet.getLeft() + "=" + triplet.getMiddle())
.collect(toList());
List<String> resultMiNiFiProperties = loadMiNiFiProperties().entrySet()
.stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(toList());
assertTrue(resultMiNiFiProperties.containsAll(expectedMiNiFiProperties));
}
@Test
public void testMiNiFiPropertiesMappedToAppropriateNiFiProperties() throws ConfigurationChangeException {
// given
Properties bootstrapProperties = createBootstrapProperties(Stream.of(
MiNiFiProperties.NIFI_MINIFI_FLOW_CONFIG.getKey(),
MiNiFiProperties.NIFI_MINIFI_SECURITY_KEYSTORE.getKey(),
MiNiFiProperties.NIFI_MINIFI_SECURITY_KEYSTORE_TYPE.getKey(),
MiNiFiProperties.NIFI_MINIFI_SECURITY_KEYSTORE_PASSWD.getKey(),
MiNiFiProperties.NIFI_MINIFI_SECURITY_KEY_PASSWD.getKey(),
MiNiFiProperties.NIFI_MINIFI_SECURITY_TRUSTSTORE.getKey(),
MiNiFiProperties.NIFI_MINIFI_SECURITY_TRUSTSTORE_TYPE.getKey(),
MiNiFiProperties.NIFI_MINIFI_SECURITY_TRUSTSTORE_PASSWD.getKey())
.collect(toMap(Function.identity(), __ -> randomUUID().toString()))
);
// when
testPropertiesGenerator.generateMinifiProperties(configDirectory.toString(), bootstrapProperties);
// then
Properties miNiFiProperties = loadMiNiFiProperties();
assertTrue(
MINIFI_TO_NIFI_PROPERTY_MAPPING.entrySet().stream()
.allMatch(entry -> Objects.equals(bootstrapProperties.getProperty(entry.getKey()), miNiFiProperties.getProperty(entry.getValue()))));
}
@Test
public void testSensitivePropertiesAreGeneratedWhenNotProvidedInBootstrap() throws ConfigurationChangeException {
// given
Properties bootstrapProperties = createBootstrapProperties(Map.of());
// when
testPropertiesGenerator.generateMinifiProperties(configDirectory.toString(), bootstrapProperties);
// then
Properties miNiFiProperties = loadMiNiFiProperties();
assertTrue(isNotBlank(miNiFiProperties.getProperty(NiFiProperties.SENSITIVE_PROPS_KEY)));
assertEquals(DEFAULT_SENSITIVE_PROPERTIES_ENCODING_ALGORITHM, miNiFiProperties.getProperty(NiFiProperties.SENSITIVE_PROPS_ALGORITHM));
}
@Test
public void testSensitivePropertiesAreUsedWhenProvidedInBootstrap() throws ConfigurationChangeException {
// given
String sensitivePropertiesKey = "sensitive_properties_key";
String sensitivePropertiesAlgorithm = "sensitive_properties_algorithm";
Properties bootstrapProperties = createBootstrapProperties(Map.of(
MiNiFiProperties.NIFI_MINIFI_SENSITIVE_PROPS_KEY.getKey(), sensitivePropertiesKey,
MiNiFiProperties.NIFI_MINIFI_SENSITIVE_PROPS_ALGORITHM.getKey(), sensitivePropertiesAlgorithm
));
// when
testPropertiesGenerator.generateMinifiProperties(configDirectory.toString(), bootstrapProperties);
// then
Properties miNiFiProperties = loadMiNiFiProperties();
assertEquals(sensitivePropertiesKey, miNiFiProperties.getProperty(NiFiProperties.SENSITIVE_PROPS_KEY));
assertEquals(sensitivePropertiesAlgorithm, miNiFiProperties.getProperty(NiFiProperties.SENSITIVE_PROPS_ALGORITHM));
}
@Test
public void testNonBlankC2PropertiesAreCopiedToMiNiFiProperties() throws ConfigurationChangeException {
// given
Properties bootstrapProperties = createBootstrapProperties(Map.of(
MiNiFiProperties.C2_ENABLE.getKey(), TRUE.toString(),
MiNiFiProperties.C2_AGENT_CLASS.getKey(), EMPTY,
MiNiFiProperties.C2_AGENT_IDENTIFIER.getKey(), SPACE
));
// when
testPropertiesGenerator.generateMinifiProperties(configDirectory.toString(), bootstrapProperties);
// then
Properties miNiFiProperties = loadMiNiFiProperties();
assertEquals(TRUE.toString(), miNiFiProperties.getProperty(MiNiFiProperties.C2_ENABLE.getKey()));
assertNull(miNiFiProperties.getProperty(MiNiFiProperties.C2_AGENT_CLASS.getKey()));
assertNull(miNiFiProperties.getProperty(MiNiFiProperties.C2_AGENT_IDENTIFIER.getKey()));
assertNull(miNiFiProperties.getProperty(MiNiFiProperties.C2_REST_URL.getKey()));
}
@Test
public void testDefaultNiFiPropertiesAreOverridden() throws ConfigurationChangeException {
// given
String archiveDir = "/path/to";
Properties bootstrapProperties = createBootstrapProperties(Map.of(
NiFiProperties.FLOW_CONFIGURATION_ARCHIVE_ENABLED, TRUE.toString(),
NiFiProperties.FLOW_CONFIGURATION_ARCHIVE_DIR, archiveDir
));
// when
testPropertiesGenerator.generateMinifiProperties(configDirectory.toString(), bootstrapProperties);
// then
List<String> expectedMiNiFiProperties = concat(
NIFI_PROPERTIES_WITH_DEFAULT_VALUES_AND_COMMENTS.stream()
.filter(triplet ->
!triplet.getLeft().equals(NiFiProperties.FLOW_CONFIGURATION_ARCHIVE_ENABLED)
&& !triplet.getLeft().equals(NiFiProperties.FLOW_CONFIGURATION_ARCHIVE_DIR))
.map(triplet -> triplet.getLeft() + "=" + triplet.getMiddle()),
Stream.of(
NiFiProperties.FLOW_CONFIGURATION_ARCHIVE_ENABLED + "=" + TRUE,
NiFiProperties.FLOW_CONFIGURATION_ARCHIVE_DIR + "=" + archiveDir
))
.collect(toList());
List<String> resultMiNiFiProperties = loadMiNiFiProperties().entrySet()
.stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(toList());
assertTrue(resultMiNiFiProperties.containsAll(expectedMiNiFiProperties));
}
@Test
public void testArbitraryNiFiPropertyCanBePassedViaBootstrapConf() throws ConfigurationChangeException {
// given
Properties bootstrapProperties = createBootstrapProperties(Map.of(
"nifi.new.property", "new_property_value",
"nifi.other.new.property", "other_new_property_value"
));
// when
testPropertiesGenerator.generateMinifiProperties(configDirectory.toString(), bootstrapProperties);
// then
Properties miNiFiProperties = loadMiNiFiProperties();
assertEquals("new_property_value", miNiFiProperties.getProperty("nifi.new.property"));
assertEquals("other_new_property_value", miNiFiProperties.getProperty("nifi.other.new.property"));
}
@Test
public void bootstrapFileAndLogPropertiesAreGeneratedIntoMiNiFiProperties() throws ConfigurationChangeException {
// given
Properties bootstrapProperties = createBootstrapProperties(Map.of());
// when
testPropertiesGenerator.generateMinifiProperties(configDirectory.toString(), bootstrapProperties);
// then
Properties miNiFiProperties = loadMiNiFiProperties();
assertTrue(List.of(MINIFI_BOOTSTRAP_FILE_PATH, MINIFI_LOG_DIRECTORY, MINIFI_APP_LOG_FILE, MINIFI_BOOTSTRAP_LOG_FILE)
.stream()
.map(miNiFiProperties::getProperty)
.allMatch(StringUtils::isNotBlank)
);
}
private Properties createBootstrapProperties(Map<String, String> keyValues) {
try (OutputStream outputStream = newOutputStream(bootstrapPropertiesFile)) {
Properties properties = new Properties();
properties.putAll(keyValues);
properties.store(outputStream, EMPTY);
return properties;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private Properties loadMiNiFiProperties() {
try (InputStream inputStream = newInputStream(minifiPropertiesFile)) {
Properties properties = new Properties();
properties.load(inputStream);
return properties;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}

View File

@ -17,6 +17,18 @@
package org.apache.nifi.minifi.bootstrap.status.reporters; package org.apache.nifi.minifi.bootstrap.status.reporters;
import static org.apache.nifi.minifi.bootstrap.status.reporters.StatusLogger.ENCOUNTERED_IO_EXCEPTION;
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.NIFI_MINIFI_STATUS_REPORTER_LOG_LEVEL;
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.NIFI_MINIFI_STATUS_REPORTER_LOG_PERIOD;
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.NIFI_MINIFI_STATUS_REPORTER_LOG_QUERY;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.verify;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.nifi.logging.LogLevel; import org.apache.nifi.logging.LogLevel;
import org.apache.nifi.minifi.bootstrap.QueryableStatusAggregator; import org.apache.nifi.minifi.bootstrap.QueryableStatusAggregator;
import org.apache.nifi.minifi.commons.status.FlowStatusReport; import org.apache.nifi.minifi.commons.status.FlowStatusReport;
@ -25,22 +37,10 @@ import org.junit.jupiter.api.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.slf4j.Logger; import org.slf4j.Logger;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static org.apache.nifi.minifi.bootstrap.status.reporters.StatusLogger.ENCOUNTERED_IO_EXCEPTION;
import static org.apache.nifi.minifi.bootstrap.status.reporters.StatusLogger.LOGGING_LEVEL_KEY;
import static org.apache.nifi.minifi.bootstrap.status.reporters.StatusLogger.QUERY_KEY;
import static org.apache.nifi.minifi.bootstrap.status.reporters.StatusLogger.REPORT_PERIOD_KEY;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.verify;
public class StatusLoggerTest { public class StatusLoggerTest {
private static final String MOCK_STATUS = "FlowStatusReport{controllerServiceStatusList=null, processorStatusList=[{name='TailFile', processorHealth={runStatus='Running', hasBulletins=false, " + private static final String MOCK_STATUS =
"FlowStatusReport{controllerServiceStatusList=null, processorStatusList=[{name='TailFile', processorHealth={runStatus='Running', hasBulletins=false, " +
"validationErrorList=[]}, processorStats=null, bulletinList=null}], connectionStatusList=null, remoteProcessingGroupStatusList=null, instanceStatus=null, systemDiagnosticsStatus=null," + "validationErrorList=[]}, processorStats=null, bulletinList=null}], connectionStatusList=null, remoteProcessingGroupStatusList=null, instanceStatus=null, systemDiagnosticsStatus=null," +
" reportingTaskStatusList=null, errorsGeneratingReport=[]}"; " reportingTaskStatusList=null, errorsGeneratingReport=[]}";
@ -69,9 +69,9 @@ public class StatusLoggerTest {
@Test @Test
public void testFailedInitDueToFatalLogLevel() { public void testFailedInitDueToFatalLogLevel() {
Properties properties = new Properties(); Properties properties = new Properties();
properties.setProperty(REPORT_PERIOD_KEY, "1"); properties.setProperty(NIFI_MINIFI_STATUS_REPORTER_LOG_PERIOD.getKey(), "1");
properties.setProperty(LOGGING_LEVEL_KEY, LogLevel.FATAL.name()); properties.setProperty(NIFI_MINIFI_STATUS_REPORTER_LOG_LEVEL.getKey(), LogLevel.FATAL.name());
properties.setProperty(QUERY_KEY, MOCK_QUERY); properties.setProperty(NIFI_MINIFI_STATUS_REPORTER_LOG_QUERY.getKey(), MOCK_QUERY);
assertThrows(IllegalStateException.class, () -> statusLogger.initialize(properties, queryableStatusAggregator)); assertThrows(IllegalStateException.class, () -> statusLogger.initialize(properties, queryableStatusAggregator));
} }
@ -79,8 +79,8 @@ public class StatusLoggerTest {
@Test @Test
public void testFailedInitDueToNoPeriod() { public void testFailedInitDueToNoPeriod() {
Properties properties = new Properties(); Properties properties = new Properties();
properties.setProperty(LOGGING_LEVEL_KEY, LogLevel.INFO.name()); properties.setProperty(NIFI_MINIFI_STATUS_REPORTER_LOG_LEVEL.getKey(), LogLevel.INFO.name());
properties.setProperty(QUERY_KEY, MOCK_QUERY); properties.setProperty(NIFI_MINIFI_STATUS_REPORTER_LOG_QUERY.getKey(), MOCK_QUERY);
assertThrows(IllegalStateException.class, () -> statusLogger.initialize(properties, queryableStatusAggregator)); assertThrows(IllegalStateException.class, () -> statusLogger.initialize(properties, queryableStatusAggregator));
} }
@ -88,8 +88,8 @@ public class StatusLoggerTest {
@Test @Test
public void testFailedInitDueToNoQuery() { public void testFailedInitDueToNoQuery() {
Properties properties = new Properties(); Properties properties = new Properties();
properties.setProperty(REPORT_PERIOD_KEY, "1"); properties.setProperty(NIFI_MINIFI_STATUS_REPORTER_LOG_PERIOD.getKey(), "1");
properties.setProperty(LOGGING_LEVEL_KEY, LogLevel.INFO.name()); properties.setProperty(NIFI_MINIFI_STATUS_REPORTER_LOG_LEVEL.getKey(), LogLevel.INFO.name());
assertThrows(IllegalStateException.class, () -> statusLogger.initialize(properties, queryableStatusAggregator)); assertThrows(IllegalStateException.class, () -> statusLogger.initialize(properties, queryableStatusAggregator));
} }
@ -143,9 +143,9 @@ public class StatusLoggerTest {
@Test @Test
public void testTraceException() throws IOException { public void testTraceException() throws IOException {
Properties properties = new Properties(); Properties properties = new Properties();
properties.setProperty(REPORT_PERIOD_KEY, "1"); properties.setProperty(NIFI_MINIFI_STATUS_REPORTER_LOG_PERIOD.getKey(), "1");
properties.setProperty(LOGGING_LEVEL_KEY, LogLevel.TRACE.name()); properties.setProperty(NIFI_MINIFI_STATUS_REPORTER_LOG_LEVEL.getKey(), LogLevel.TRACE.name());
properties.setProperty(QUERY_KEY, MOCK_QUERY); properties.setProperty(NIFI_MINIFI_STATUS_REPORTER_LOG_QUERY.getKey(), MOCK_QUERY);
IOException ioException = new IOException("This is an expected test exception"); IOException ioException = new IOException("This is an expected test exception");
Mockito.when(queryableStatusAggregator.statusReport(MOCK_QUERY)).thenThrow(ioException); Mockito.when(queryableStatusAggregator.statusReport(MOCK_QUERY)).thenThrow(ioException);
@ -207,9 +207,9 @@ public class StatusLoggerTest {
private static Properties getProperties(LogLevel logLevel) { private static Properties getProperties(LogLevel logLevel) {
Properties properties = new Properties(); Properties properties = new Properties();
properties.setProperty(REPORT_PERIOD_KEY, "1"); properties.setProperty(NIFI_MINIFI_STATUS_REPORTER_LOG_PERIOD.getKey(), "1");
properties.setProperty(LOGGING_LEVEL_KEY, logLevel.name()); properties.setProperty(NIFI_MINIFI_STATUS_REPORTER_LOG_LEVEL.getKey(), logLevel.name());
properties.setProperty(QUERY_KEY, MOCK_QUERY); properties.setProperty(NIFI_MINIFI_STATUS_REPORTER_LOG_QUERY.getKey(), MOCK_QUERY);
return properties; return properties;
} }

View File

@ -1,809 +0,0 @@
/*
* 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.minifi.bootstrap.util;
import org.apache.commons.io.FileUtils;
import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeException;
import org.apache.nifi.minifi.bootstrap.exception.InvalidConfigurationException;
import org.apache.nifi.minifi.commons.schema.ConfigSchema;
import org.apache.nifi.minifi.commons.schema.ConnectionSchema;
import org.apache.nifi.minifi.commons.schema.ControllerServiceSchema;
import org.apache.nifi.minifi.commons.schema.FunnelSchema;
import org.apache.nifi.minifi.commons.schema.PortSchema;
import org.apache.nifi.minifi.commons.schema.ProcessGroupSchema;
import org.apache.nifi.minifi.commons.schema.ProcessorSchema;
import org.apache.nifi.minifi.commons.schema.RemotePortSchema;
import org.apache.nifi.minifi.commons.schema.RemoteProcessGroupSchema;
import org.apache.nifi.minifi.commons.schema.ReportingSchema;
import org.apache.nifi.minifi.commons.schema.common.StringUtil;
import org.apache.nifi.minifi.commons.schema.exception.SchemaLoaderException;
import org.apache.nifi.minifi.commons.schema.serialization.SchemaLoader;
import org.apache.nifi.util.StringUtils;
import org.apache.nifi.xml.processing.parsers.DocumentProvider;
import org.apache.nifi.xml.processing.parsers.StandardDocumentProvider;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import static org.apache.nifi.minifi.bootstrap.util.ConfigTransformer.MINIFI_APP_LOG_FILE;
import static org.apache.nifi.minifi.bootstrap.util.ConfigTransformer.MINIFI_BOOTSTRAP_FILE_PATH;
import static org.apache.nifi.minifi.bootstrap.util.ConfigTransformer.MINIFI_BOOTSTRAP_LOG_FILE;
import static org.apache.nifi.minifi.bootstrap.util.ConfigTransformer.MINIFI_CONFIG_FILE_PATH;
import static org.apache.nifi.minifi.bootstrap.util.ConfigTransformer.MINIFI_LOG_DIRECTORY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class ConfigTransformerTest {
public static final Map<String, Integer> PG_ELEMENT_ORDER_MAP = generateOrderMap(
Arrays.asList("processor", "inputPort", "outputPort", "funnel", "processGroup", "remoteProcessGroup", "connection"));
private XPathFactory xPathFactory;
private Element config;
@BeforeEach
public void setup() {
final DocumentProvider documentProvider = new StandardDocumentProvider();
final Document document = documentProvider.newDocument();
config = document.createElement("config");
xPathFactory = XPathFactory.newInstance();
}
@Test
public void testNullQueuePrioritizerNotWritten() throws ConfigurationChangeException, XPathExpressionException {
ConfigTransformer.addConnection(config, new ConnectionSchema(Collections.emptyMap()), new ParentGroupIdResolver(new ProcessGroupSchema(Collections.emptyMap(), ConfigSchema.TOP_LEVEL_NAME)));
XPath xpath = xPathFactory.newXPath();
String expression = "connection/queuePrioritizerClass";
assertNull(xpath.evaluate(expression, config, XPathConstants.NODE));
}
@Test
public void testEmptyQueuePrioritizerNotWritten() throws ConfigurationChangeException, XPathExpressionException {
Map<String, Object> map = new HashMap<>();
map.put(ConnectionSchema.QUEUE_PRIORITIZER_CLASS_KEY, "");
ConfigTransformer.addConnection(config, new ConnectionSchema(map), new ParentGroupIdResolver(new ProcessGroupSchema(Collections.emptyMap(), ConfigSchema.TOP_LEVEL_NAME)));
XPath xpath = xPathFactory.newXPath();
String expression = "connection/queuePrioritizerClass";
assertNull(xpath.evaluate(expression, config, XPathConstants.NODE));
}
@Test
public void testQueuePrioritizerWritten() throws ConfigurationChangeException, XPathExpressionException {
Map<String, Object> map = new HashMap<>();
map.put(ConnectionSchema.QUEUE_PRIORITIZER_CLASS_KEY, "org.apache.nifi.prioritizer.FirstInFirstOutPrioritizer");
ConfigTransformer.addConnection(config, new ConnectionSchema(map), new ParentGroupIdResolver(new ProcessGroupSchema(Collections.emptyMap(), ConfigSchema.TOP_LEVEL_NAME)));
XPath xpath = xPathFactory.newXPath();
String expression = "connection/queuePrioritizerClass/text()";
assertEquals("org.apache.nifi.prioritizer.FirstInFirstOutPrioritizer", xpath.evaluate(expression, config, XPathConstants.STRING));
}
@Test
public void testReportingTasksTransform() throws Exception {
testConfigFileTransform("config-reporting-task.yml");
}
@Test
public void testProcessGroupsTransform() throws Exception {
testConfigFileTransform("config-process-groups.yml");
}
@Test
public void testFunnelsTransform() throws Exception {
testConfigFileTransform("stress-test-framework-funnel.yml");
}
@Test
public void testFunnelAndRpgTransform() throws Exception {
testConfigFileTransform("config-funnel-and-rpg.yml");
}
@Test
public void testRpgTransform() throws Exception {
testConfigFileTransform("config-multiple-RPGs.yml");
}
@Test
public void testRpgProxyNoPassTransform() throws Exception {
testConfigFileTransform("InvokeHttpMiNiFiProxyNoPasswordTemplateTest.yml");
}
@Test
public void testRpgProxyPassTransform() throws Exception {
testConfigFileTransform("InvokeHttpMiNiFiProxyPasswordTemplateTest.yml");
}
@Test
public void testRpgOutputPort() throws Exception {
testConfigFileTransform("SimpleRPGToLogAttributes.yml");
}
@Test
public void testNifiPropertiesNoOverrides() throws IOException, ConfigurationChangeException, SchemaLoaderException {
Properties pre216Properties = new Properties();
try (InputStream pre216PropertiesStream = ConfigTransformerTest.class.getClassLoader().getResourceAsStream("MINIFI-216/nifi.properties.before")) {
pre216Properties.load(pre216PropertiesStream);
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (InputStream configStream = ConfigTransformerTest.class.getClassLoader().getResourceAsStream("MINIFI-216/config.yml")) {
ConfigTransformer.writeNiFiProperties(SchemaLoader.loadConfigSchemaFromYaml(configStream), outputStream);
}
Properties properties = new Properties();
properties.load(new ByteArrayInputStream(outputStream.toByteArray()));
for (String name : pre216Properties.stringPropertyNames()) {
assertEquals(pre216Properties.getProperty(name), properties.getProperty(name), "Property key " + name + " doesn't match.");
}
}
@Test
public void testNifiPropertiesOverrides() throws IOException, ConfigurationChangeException, SchemaLoaderException {
Properties pre216Properties = new Properties();
try (InputStream pre216PropertiesStream = ConfigTransformerTest.class.getClassLoader().getResourceAsStream("MINIFI-216/nifi.properties.before")) {
pre216Properties.load(pre216PropertiesStream);
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (InputStream configStream = ConfigTransformerTest.class.getClassLoader().getResourceAsStream("MINIFI-216/configOverrides.yml")) {
ConfigSchema configSchema = SchemaLoader.loadConfigSchemaFromYaml(configStream);
assertTrue(configSchema.getNifiPropertiesOverrides().size() > 0);
for (Map.Entry<String, String> entry : configSchema.getNifiPropertiesOverrides().entrySet()) {
pre216Properties.setProperty(entry.getKey(), entry.getValue());
}
ConfigTransformer.writeNiFiProperties(configSchema, outputStream);
}
Properties properties = new Properties();
properties.load(new ByteArrayInputStream(outputStream.toByteArray()));
for (String name : pre216Properties.stringPropertyNames()) {
assertEquals(pre216Properties.getProperty(name), properties.getProperty(name), "Property key " + name + " doesn't match.");
}
}
@Test
public void testNifiPropertiesVariableRegistry() throws IOException, ConfigurationChangeException, SchemaLoaderException {
Properties initialProperties = new Properties();
try (InputStream pre216PropertiesStream = ConfigTransformerTest.class.getClassLoader().getResourceAsStream("MINIFI-277/nifi.properties")) {
initialProperties.load(pre216PropertiesStream);
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (InputStream configStream = ConfigTransformerTest.class.getClassLoader().getResourceAsStream("MINIFI-277/config.yml")) {
ConfigSchema configSchema = SchemaLoader.loadConfigSchemaFromYaml(configStream);
ConfigTransformer.writeNiFiProperties(configSchema, outputStream);
}
Properties properties = new Properties();
properties.load(new ByteArrayInputStream(outputStream.toByteArray()));
for (String name : initialProperties.stringPropertyNames()) {
assertEquals(initialProperties.getProperty(name), properties.getProperty(name), "Property key " + name + " doesn't match.");
}
}
@Test
public void doesTransformFile() throws Exception {
File inputFile = new File("./src/test/resources/config.yml");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", null);
File nifiPropertiesFile = new File("./target/nifi.properties");
assertTrue(nifiPropertiesFile.exists());
assertTrue(nifiPropertiesFile.canRead());
nifiPropertiesFile.deleteOnExit();
File flowXml = new File("./target/flow.xml.gz");
assertTrue(flowXml.exists());
assertTrue(flowXml.canRead());
flowXml.deleteOnExit();
}
@Test
public void doesTransformV1File() throws Exception {
File inputFile = new File("./src/test/resources/config-v1.yml");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", null);
File nifiPropertiesFile = new File("./target/nifi.properties");
assertTrue(nifiPropertiesFile.exists());
assertTrue(nifiPropertiesFile.canRead());
nifiPropertiesFile.deleteOnExit();
File flowXml = new File("./target/flow.xml.gz");
assertTrue(flowXml.exists());
assertTrue(flowXml.canRead());
flowXml.deleteOnExit();
}
@Test
public void doesTransformInputStream() throws Exception {
File inputFile = new File("./src/test/resources/config.yml");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", null);
File nifiPropertiesFile = new File("./target/nifi.properties");
assertTrue(nifiPropertiesFile.exists());
assertTrue(nifiPropertiesFile.canRead());
nifiPropertiesFile.deleteOnExit();
File flowXml = new File("./target/flow.xml.gz");
assertTrue(flowXml.exists());
assertTrue(flowXml.canRead());
flowXml.deleteOnExit();
}
@Test
public void doesTransformOnDefaultFile() throws Exception {
File inputFile = new File("./src/test/resources/default.yml");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", null);
File nifiPropertiesFile = new File("./target/nifi.properties");
assertTrue(nifiPropertiesFile.exists());
assertTrue(nifiPropertiesFile.canRead());
nifiPropertiesFile.deleteOnExit();
File flowXml = new File("./target/flow.xml.gz");
assertTrue(flowXml.exists());
assertTrue(flowXml.canRead());
flowXml.deleteOnExit();
}
@Test
public void doesTransformOnMultipleProcessors() throws Exception {
File inputFile = new File("./src/test/resources/config-multiple-processors.yml");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", null);
File nifiPropertiesFile = new File("./target/nifi.properties");
assertTrue(nifiPropertiesFile.exists());
assertTrue(nifiPropertiesFile.canRead());
nifiPropertiesFile.deleteOnExit();
File flowXml = new File("./target/flow.xml.gz");
assertTrue(flowXml.exists());
assertTrue(flowXml.canRead());
flowXml.deleteOnExit();
}
@Test
public void doesTransformOnMultipleRemoteProcessGroups() throws Exception {
File inputFile = new File("./src/test/resources/config-multiple-RPGs.yml");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", null);
File nifiPropertiesFile = new File("./target/nifi.properties");
assertTrue(nifiPropertiesFile.exists());
assertTrue(nifiPropertiesFile.canRead());
nifiPropertiesFile.deleteOnExit();
File flowXml = new File("./target/flow.xml.gz");
assertTrue(flowXml.exists());
assertTrue(flowXml.canRead());
flowXml.deleteOnExit();
}
@Test
public void doesTransformOnMultipleInputPorts() throws Exception {
File inputFile = new File("./src/test/resources/config-multiple-input-ports.yml");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", null);
File nifiPropertiesFile = new File("./target/nifi.properties");
assertTrue(nifiPropertiesFile.exists());
assertTrue(nifiPropertiesFile.canRead());
nifiPropertiesFile.deleteOnExit();
File flowXml = new File("./target/flow.xml.gz");
assertTrue(flowXml.exists());
assertTrue(flowXml.canRead());
flowXml.deleteOnExit();
}
@Test
public void doesTransformOnMinimal() throws Exception {
File inputFile = new File("./src/test/resources/config-minimal.yml");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", null);
File nifiPropertiesFile = new File("./target/nifi.properties");
assertTrue(nifiPropertiesFile.exists());
assertTrue(nifiPropertiesFile.canRead());
nifiPropertiesFile.deleteOnExit();
File flowXml = new File("./target/flow.xml.gz");
assertTrue(flowXml.exists());
assertTrue(flowXml.canRead());
flowXml.deleteOnExit();
}
@Test
public void doesTransformOnProvenanceRepository() throws Exception {
File inputFile = new File("./src/test/resources/config-provenance-repository.yml");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", null);
File nifiPropertiesFile = new File("./target/nifi.properties");
assertTrue(nifiPropertiesFile.exists());
assertTrue(nifiPropertiesFile.canRead());
String nifi = FileUtils.readFileToString(nifiPropertiesFile, Charset.defaultCharset());
assertTrue(nifi.contains("nifi.provenance.repository.implementation=org.apache.nifi.provenance.WriteAheadProvenanceRepository"));
nifiPropertiesFile.deleteOnExit();
File flowXml = new File("./target/flow.xml.gz");
assertTrue(flowXml.exists());
assertTrue(flowXml.canRead());
flowXml.deleteOnExit();
}
@Test
public void doesTransformOnCustomProvenanceRepository() throws Exception {
File inputFile = new File("./src/test/resources/config-provenance-custom-repository.yml");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", null);
File nifiPropertiesFile = new File("./target/nifi.properties");
assertTrue(nifiPropertiesFile.exists());
assertTrue(nifiPropertiesFile.canRead());
String nifi = FileUtils.readFileToString(nifiPropertiesFile, Charset.defaultCharset());
assertTrue(nifi.contains("nifi.provenance.repository.implementation=org.apache.nifi.provenance.CustomProvenanceRepository"));
nifiPropertiesFile.deleteOnExit();
File flowXml = new File("./target/flow.xml.gz");
assertTrue(flowXml.exists());
assertTrue(flowXml.canRead());
flowXml.deleteOnExit();
}
@Test
public void handleTransformInvalidFile() throws Exception {
try {
File inputFile = new File("./src/test/resources/config-invalid.yml");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", null);
fail("Invalid configuration file was not detected.");
} catch (SchemaLoaderException e) {
assertEquals("Provided YAML configuration is not a Map", e.getMessage());
}
}
@Test
public void handleTransformMalformedField() throws Exception {
try {
File inputFile = new File("./src/test/resources/config-malformed-field.yml");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", null);
fail("Invalid configuration file was not detected.");
} catch (InvalidConfigurationException e) {
assertEquals("Failed to transform config file due to:['threshold' in section 'Swap' because it is found but could not be parsed as a Number]", e.getMessage());
}
}
@Test
public void handleTransformEmptyFile() throws Exception {
try {
File inputFile = new File("./src/test/resources/config-empty.yml");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", null);
fail("Invalid configuration file was not detected.");
} catch (SchemaLoaderException e) {
assertEquals("Provided YAML configuration is not a Map", e.getMessage());
}
}
@Test
public void handleTransformFileMissingRequiredField() throws Exception {
try {
File inputFile = new File("./src/test/resources/config-missing-required-field.yml");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", null);
fail("Invalid configuration file was not detected.");
} catch (InvalidConfigurationException e) {
assertEquals("Failed to transform config file due to:['class' in section 'Processors' because it was not found and it is required]", e.getMessage());
}
}
@Test
public void handleTransformFileMultipleProblems() throws Exception {
try {
File inputFile = new File("./src/test/resources/config-multiple-problems.yml");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", null);
fail("Invalid configuration file was not detected.");
} catch (InvalidConfigurationException e) {
assertEquals("Failed to transform config file due to:['class' in section 'Processors' because it was not found and it is required], " +
"['scheduling strategy' in section 'Provenance Reporting' because it is not a valid scheduling strategy], " +
"['source name' in section 'Connections' because it was not found and it is required]", e.getMessage());
}
}
@Test
public void checkSSLOverrides() throws Exception {
File inputFile = new File("./src/test/resources/MINIFI-516/config.yml");
final Properties bootstrapProperties = getTestBootstrapProperties("MINIFI-516/bootstrap.conf");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", bootstrapProperties);
// nifi.properties testing
File nifiPropertiesFile = new File("./target/nifi.properties");
assertTrue(nifiPropertiesFile.exists());
assertTrue(nifiPropertiesFile.canRead());
nifiPropertiesFile.deleteOnExit();
// flow.xml.gz testing
File flowXml = new File("./target/flow.xml.gz");
assertTrue(flowXml.exists());
assertTrue(flowXml.canRead());
final DocumentProvider documentProvider = new StandardDocumentProvider();
final Document xml;
try (final InputStream inputStream = new GZIPInputStream(new FileInputStream(flowXml))) {
xml = documentProvider.parse(inputStream);
}
XPath xPath = XPathFactory.newInstance().newXPath();
String result = xPath.evaluate("/flowController/rootGroup/processor/property[name = \"SSL Context Service\"]/value/text()", xml);
assertEquals(result, "SSL-Context-Service");
flowXml.deleteOnExit();
}
@Test
public void checkLogAndConfigFilePathOverrides() throws Exception {
File inputFile = new File("./src/test/resources/config-minimal.yml");
Properties bootstrapProperties = getTestBootstrapProperties("bootstrap.conf");
ConfigTransformer.transformConfigFile(new FileInputStream(inputFile), "./target/", bootstrapProperties);
File nifiPropertiesFile = new File("./target/nifi.properties");
assertTrue(nifiPropertiesFile.exists());
assertTrue(nifiPropertiesFile.canRead());
Properties nifiProperties = new Properties();
nifiProperties.load(new FileInputStream(nifiPropertiesFile));
// an absolute path is returned for bootstrap which is different per every environment. only testing that path ends with expected relative path
assertTrue(nifiProperties.getProperty(MINIFI_BOOTSTRAP_FILE_PATH).endsWith("bootstrap.conf"));
assertEquals("./conf/config.yml", nifiProperties.getProperty(MINIFI_CONFIG_FILE_PATH));
assertEquals("./logs", nifiProperties.getProperty(MINIFI_LOG_DIRECTORY));
assertEquals("minifi-app.log", nifiProperties.getProperty(MINIFI_APP_LOG_FILE));
assertEquals("minifi-bootstrap.log", nifiProperties.getProperty(MINIFI_BOOTSTRAP_LOG_FILE));
}
public void testConfigFileTransform(String configFile) throws Exception {
ConfigSchema configSchema = SchemaLoader.loadConfigSchemaFromYaml(ConfigTransformerTest.class.getClassLoader().getResourceAsStream(configFile));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ConfigTransformer.writeFlowXmlFile(configSchema, outputStream);
final DocumentProvider documentProvider = new StandardDocumentProvider();
Document document = documentProvider.parse(new ByteArrayInputStream(outputStream.toByteArray()));
testProcessGroup((Element) xPathFactory.newXPath().evaluate("flowController/rootGroup", document, XPathConstants.NODE), configSchema.getProcessGroupSchema());
testReportingTasks((Element) xPathFactory.newXPath().evaluate("flowController/reportingTasks", document, XPathConstants.NODE), configSchema.getReportingTasksSchema());
}
private void testProcessGroup(Element element, ProcessGroupSchema processGroupSchema) throws XPathExpressionException {
assertEquals(processGroupSchema.getId(), getText(element, "id"));
assertEquals(processGroupSchema.getName(), getText(element, "name"));
assertEquals(nullToEmpty(processGroupSchema.getComment()), nullToEmpty(getText(element, "comment")));
checkOrderOfChildren(element, PG_ELEMENT_ORDER_MAP);
NodeList processorElements = (NodeList) xPathFactory.newXPath().evaluate("processor", element, XPathConstants.NODESET);
assertEquals(processGroupSchema.getProcessors().size(), processorElements.getLength());
for (int i = 0; i < processorElements.getLength(); i++) {
testProcessor((Element) processorElements.item(i), processGroupSchema.getProcessors().get(i));
}
NodeList controllerServiceElements = (NodeList) xPathFactory.newXPath().evaluate("controllerService", element, XPathConstants.NODESET);
assertEquals(processGroupSchema.getControllerServices().size(), controllerServiceElements.getLength());
for (int i = 0; i < controllerServiceElements.getLength(); i++) {
testControllerService((Element) controllerServiceElements.item(i), processGroupSchema.getControllerServices().get(i));
}
NodeList remoteProcessGroupElements = (NodeList) xPathFactory.newXPath().evaluate("remoteProcessGroup", element, XPathConstants.NODESET);
assertEquals(processGroupSchema.getRemoteProcessGroups().size(), remoteProcessGroupElements.getLength());
for (int i = 0; i < remoteProcessGroupElements.getLength(); i++) {
testRemoteProcessGroups((Element) remoteProcessGroupElements.item(i), processGroupSchema.getRemoteProcessGroups().get(i));
}
NodeList funnelElements = (NodeList) xPathFactory.newXPath().evaluate("funnel", element, XPathConstants.NODESET);
assertEquals(processGroupSchema.getFunnels().size(), funnelElements.getLength());
for (int i = 0; i < funnelElements.getLength(); i++) {
testFunnel((Element) funnelElements.item(i), processGroupSchema.getFunnels().get(i));
}
NodeList inputPortElements = (NodeList) xPathFactory.newXPath().evaluate("inputPort", element, XPathConstants.NODESET);
assertEquals(processGroupSchema.getInputPortSchemas().size(), inputPortElements.getLength());
for (int i = 0; i < inputPortElements.getLength(); i++) {
testPort((Element) inputPortElements.item(i), processGroupSchema.getInputPortSchemas().get(i));
}
NodeList outputPortElements = (NodeList) xPathFactory.newXPath().evaluate("outputPort", element, XPathConstants.NODESET);
assertEquals(processGroupSchema.getOutputPortSchemas().size(), outputPortElements.getLength());
for (int i = 0; i < outputPortElements.getLength(); i++) {
testPort((Element) outputPortElements.item(i), processGroupSchema.getOutputPortSchemas().get(i));
}
NodeList processGroupElements = (NodeList) xPathFactory.newXPath().evaluate("processGroup", element, XPathConstants.NODESET);
assertEquals(processGroupSchema.getProcessGroupSchemas().size(), processGroupElements.getLength());
for (int i = 0; i < processGroupElements.getLength(); i++) {
testProcessGroup((Element) processGroupElements.item(i), processGroupSchema.getProcessGroupSchemas().get(i));
}
NodeList connectionElements = (NodeList) xPathFactory.newXPath().evaluate("connection", element, XPathConstants.NODESET);
assertEquals(processGroupSchema.getConnections().size(), connectionElements.getLength());
for (int i = 0; i < connectionElements.getLength(); i++) {
testConnection((Element) connectionElements.item(i), processGroupSchema.getConnections().get(i));
}
}
private void testProcessor(Element element, ProcessorSchema processorSchema) throws XPathExpressionException {
assertEquals(processorSchema.getId(), getText(element, "id"));
assertEquals(processorSchema.getName(), getText(element, "name"));
assertEquals(processorSchema.getProcessorClass(), getText(element, "class"));
assertEquals(processorSchema.getMaxConcurrentTasks().toString(), getText(element, "maxConcurrentTasks"));
assertEquals(processorSchema.getSchedulingPeriod(), getText(element, "schedulingPeriod"));
assertEquals(processorSchema.getPenalizationPeriod(), getText(element, "penalizationPeriod"));
assertEquals(processorSchema.getYieldPeriod(), getText(element, "yieldPeriod"));
assertEquals(processorSchema.getSchedulingStrategy(), getText(element, "schedulingStrategy"));
assertEquals(processorSchema.getRunDurationNanos().toString(), getText(element, "runDurationNanos"));
assertEquals(processorSchema.getAnnotationData(), getText(element, "annotationData"));
testProperties(element, processorSchema.getProperties());
}
private void testReportingTasks(Element element, List<ReportingSchema> reportingSchemas) throws XPathExpressionException {
NodeList taskElements = (NodeList) xPathFactory.newXPath().evaluate("reportingTask", element, XPathConstants.NODESET);
assertEquals(reportingSchemas.size(), taskElements.getLength());
for (int i = 0; i < taskElements.getLength(); i++) {
testReportingTask((Element) taskElements.item(i), reportingSchemas.get(i));
}
}
private void testReportingTask(Element element, ReportingSchema reportingSchema) throws XPathExpressionException {
assertEquals(reportingSchema.getId(), getText(element, "id"));
assertEquals(reportingSchema.getName(), getText(element, "name"));
assertEquals(reportingSchema.getComment(), getText(element, "comment"));
assertEquals(reportingSchema.getReportingClass(), getText(element, "class"));
assertEquals(reportingSchema.getSchedulingPeriod(), getText(element, "schedulingPeriod"));
assertEquals(reportingSchema.getSchedulingStrategy(), getText(element, "schedulingStrategy"));
testProperties(element, reportingSchema.getProperties());
}
private void testControllerService(Element element, ControllerServiceSchema controllerServiceSchema) throws XPathExpressionException {
assertEquals(controllerServiceSchema.getId(), getText(element, "id"));
assertEquals(controllerServiceSchema.getName(), getText(element, "name"));
assertEquals(controllerServiceSchema.getServiceClass(), getText(element, "class"));
assertEquals(controllerServiceSchema.getAnnotationData(), getText(element, "annotationData"));
testProperties(element, controllerServiceSchema.getProperties());
}
private void testRemoteProcessGroups(Element element, RemoteProcessGroupSchema remoteProcessingGroupSchema) throws XPathExpressionException {
assertEquals(remoteProcessingGroupSchema.getId(), getText(element, "id"));
assertEquals(remoteProcessingGroupSchema.getName(), getText(element, "name"));
assertEquals(remoteProcessingGroupSchema.getComment(), getText(element, "comment"));
assertEquals(remoteProcessingGroupSchema.getUrls(), getText(element, "url"));
assertEquals(remoteProcessingGroupSchema.getTimeout(), getText(element, "timeout"));
assertEquals(remoteProcessingGroupSchema.getYieldPeriod(), getText(element, "yieldPeriod"));
assertEquals(remoteProcessingGroupSchema.getTransportProtocol(), getText(element, "transportProtocol"));
assertEquals(remoteProcessingGroupSchema.getProxyHost(), getText(element, "proxyHost"));
String proxyPortText = getText(element, "proxyPort");
assertEquals(remoteProcessingGroupSchema.getProxyPort(), StringUtil.isNullOrEmpty(proxyPortText) ? null : Integer.parseInt(proxyPortText));
assertEquals(remoteProcessingGroupSchema.getProxyUser(), getText(element, "proxyUser"));
assertEquals(remoteProcessingGroupSchema.getProxyPassword(), getText(element, "proxyPassword"));
NodeList inputPortElements = (NodeList) xPathFactory.newXPath().evaluate("inputPort", element, XPathConstants.NODESET);
assertEquals(remoteProcessingGroupSchema.getInputPorts().size(), inputPortElements.getLength());
for (int i = 0; i < inputPortElements.getLength(); i++) {
testRemotePort((Element) inputPortElements.item(i), remoteProcessingGroupSchema.getInputPorts().get(i));
}
NodeList outputPortElements = (NodeList) xPathFactory.newXPath().evaluate("outputPort", element, XPathConstants.NODESET);
assertEquals(remoteProcessingGroupSchema.getOutputPorts().size(), outputPortElements.getLength());
for (int i = 0; i < outputPortElements.getLength(); i++) {
testRemotePort((Element) outputPortElements.item(i), remoteProcessingGroupSchema.getOutputPorts().get(i));
}
}
private void testRemotePort(Element element, RemotePortSchema remoteInputPortSchema) throws XPathExpressionException {
assertEquals(remoteInputPortSchema.getId(), getText(element, "id"));
assertEquals(remoteInputPortSchema.getName(), getText(element, "name"));
assertEquals(remoteInputPortSchema.getComment(), getText(element, "comment"));
assertEquals(remoteInputPortSchema.getMax_concurrent_tasks().toString(), getText(element, "maxConcurrentTasks"));
assertEquals(remoteInputPortSchema.getUseCompression(), Boolean.parseBoolean(getText(element, "useCompression")));
}
private void testPort(Element element, PortSchema portSchema) throws XPathExpressionException {
assertEquals(portSchema.getId(), getText(element, "id"));
assertEquals(portSchema.getName(), getText(element, "name"));
assertEquals("RUNNING", getText(element, "scheduledState"));
}
private void testFunnel(Element element, FunnelSchema funnelSchema) throws XPathExpressionException {
assertEquals(funnelSchema.getId(), getText(element, "id"));
}
private void testConnection(Element element, ConnectionSchema connectionSchema) throws XPathExpressionException {
assertEquals(connectionSchema.getId(), getText(element, "id"));
assertEquals(connectionSchema.getName(), getText(element, "name"));
assertEquals(connectionSchema.getSourceId(), getText(element, "sourceId"));
assertEquals(connectionSchema.getDestinationId(), getText(element, "destinationId"));
NodeList relationshipNodes = (NodeList) xPathFactory.newXPath().evaluate("relationship", element, XPathConstants.NODESET);
Set<String> sourceRelationships = new HashSet<>();
for (int i = 0; i < relationshipNodes.getLength(); i++) {
String textContent = relationshipNodes.item(i).getTextContent();
if (!StringUtil.isNullOrEmpty(textContent)) {
sourceRelationships.add(textContent);
}
}
assertEquals(new HashSet<>(connectionSchema.getSourceRelationshipNames()), sourceRelationships);
assertEquals(connectionSchema.getMaxWorkQueueSize().toString(), getText(element, "maxWorkQueueSize"));
assertEquals(connectionSchema.getMaxWorkQueueDataSize(), getText(element, "maxWorkQueueDataSize"));
assertEquals(connectionSchema.getFlowfileExpiration(), getText(element, "flowFileExpiration"));
assertEquals(connectionSchema.getQueuePrioritizerClass(), getText(element, "queuePrioritizerClass"));
}
private void testProperties(Element element, Map<String, Object> expected) throws XPathExpressionException {
NodeList propertyElements = (NodeList) xPathFactory.newXPath().evaluate("property", element, XPathConstants.NODESET);
Map<String, String> properties = new HashMap<>();
for (int i = 0; i < propertyElements.getLength(); i++) {
Element item = (Element) propertyElements.item(i);
properties.put(getText(item, "name"), getText(item, "value"));
}
assertEquals(expected.entrySet().stream().collect(Collectors.toMap(Map.Entry<String, Object>::getKey, e -> nullToEmpty(e.getValue()))), properties);
}
@Test
public void testContentRepoOverride() throws IOException, ConfigurationChangeException, SchemaLoaderException {
Properties pre216Properties = new Properties();
try (InputStream pre216PropertiesStream = ConfigTransformerTest.class.getClassLoader().getResourceAsStream("MINIFI-245/nifi.properties.before")) {
pre216Properties.load(pre216PropertiesStream);
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (InputStream configStream = ConfigTransformerTest.class.getClassLoader().getResourceAsStream("MINIFI-245/config.yml")) {
ConfigTransformer.writeNiFiProperties(SchemaLoader.loadConfigSchemaFromYaml(configStream), outputStream);
}
Properties properties = new Properties();
properties.load(new ByteArrayInputStream(outputStream.toByteArray()));
for (String name : pre216Properties.stringPropertyNames()) {
// Verify the Content Repo property was overridden
if ("nifi.content.repository.implementation".equals(name)) {
assertNotEquals(pre216Properties.getProperty(name), properties.getProperty(name), "Property key " + name + " was not overridden.");
} else {
assertEquals(pre216Properties.getProperty(name), properties.getProperty(name), "Property key " + name + " doesn't match.");
}
}
}
@Test
public void testFlowFileRepoOverride() throws IOException, ConfigurationChangeException, SchemaLoaderException {
Properties pre216Properties = new Properties();
try (InputStream pre216PropertiesStream = ConfigTransformerTest.class.getClassLoader().getResourceAsStream("NIFI-8753/nifi.properties.before")) {
pre216Properties.load(pre216PropertiesStream);
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (InputStream configStream = ConfigTransformerTest.class.getClassLoader().getResourceAsStream("NIFI-8753/config.yml")) {
ConfigTransformer.writeNiFiProperties(SchemaLoader.loadConfigSchemaFromYaml(configStream), outputStream);
}
Properties properties = new Properties();
properties.load(new ByteArrayInputStream(outputStream.toByteArray()));
for (String name : pre216Properties.stringPropertyNames()) {
// Verify the Content Repo property was overridden
if ("nifi.flowfile.repository.implementation".equals(name)) {
assertNotEquals(pre216Properties.getProperty(name), properties.getProperty(name), "Property key " + name + " was not overridden.");
} else {
assertEquals(pre216Properties.getProperty(name), properties.getProperty(name), "Property key " + name + " doesn't match.");
}
}
}
@Test
public void testNullSensitiveKey() throws IOException, ConfigurationChangeException, SchemaLoaderException {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (final InputStream configStream = ConfigTransformerTest.class.getClassLoader().getResourceAsStream("MINIFI-537/config.yml")) {
ConfigTransformer.writeNiFiProperties(SchemaLoader.loadConfigSchemaFromYaml(configStream), outputStream);
}
final Properties properties = new Properties();
properties.load(new ByteArrayInputStream(outputStream.toByteArray()));
// The property should not be empty/null as it is auto-generated when missing
assertTrue(StringUtils.isNotEmpty(properties.getProperty("nifi.sensitive.props.key")));
}
private String getText(Element element, String path) throws XPathExpressionException {
return (String) xPathFactory.newXPath().evaluate(path + "/text()", element, XPathConstants.STRING);
}
private String nullToEmpty(Object val) {
return val == null ? "" : val.toString();
}
private static Map<String, Integer> generateOrderMap(List<String> elements) {
Map<String, Integer> map = new HashMap<>();
for (int i = 0; i < elements.size(); i++) {
map.put(elements.get(i), i);
}
return Collections.unmodifiableMap(map);
}
private static void checkOrderOfChildren(Element element, Map<String, Integer> orderMap) {
int elementOrderList = 0;
NodeList childNodes = element.getChildNodes();
String lastOrderedElementName = null;
for (int i = 0; i < childNodes.getLength(); i++) {
String nodeName = childNodes.item(i).getNodeName();
Integer index = orderMap.get(nodeName);
if (index != null) {
if (elementOrderList > index) {
fail("Found " + nodeName + " after " + lastOrderedElementName + "; expected all " + nodeName + " elements to come before the following elements: " + orderMap.entrySet().stream()
.filter(e -> e.getValue() > index).sorted(Comparator.comparingInt(Map.Entry::getValue)).map(Map.Entry::getKey).collect(Collectors.joining(", ")));
}
lastOrderedElementName = nodeName;
elementOrderList = index;
}
}
}
public static Properties getTestBootstrapProperties(final String fileName) throws IOException {
final Properties bootstrapProperties = new Properties();
try (final InputStream fis = ConfigTransformerTest.class.getClassLoader().getResourceAsStream(fileName)) {
bootstrapProperties.load(fis);
}
return bootstrapProperties;
}
}

View File

@ -17,6 +17,7 @@
package org.apache.nifi.minifi.bootstrap.util; package org.apache.nifi.minifi.bootstrap.util;
import org.apache.nifi.minifi.bootstrap.service.MiNiFiPropertiesGenerator;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.io.BufferedReader; import java.io.BufferedReader;
@ -25,7 +26,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import static org.apache.nifi.minifi.bootstrap.util.ConfigTransformer.PROPERTIES_FILE_APACHE_2_0_LICENSE;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
@ -41,7 +41,7 @@ public class OrderedPropertiesTest {
orderedProperties.setProperty("prop3", "newVal3"); orderedProperties.setProperty("prop3", "newVal3");
orderedProperties.setProperty("prop1", "newVal1"); orderedProperties.setProperty("prop1", "newVal1");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
orderedProperties.store(byteArrayOutputStream, PROPERTIES_FILE_APACHE_2_0_LICENSE); orderedProperties.store(byteArrayOutputStream, MiNiFiPropertiesGenerator.PROPERTIES_FILE_APACHE_2_0_LICENSE);
try (BufferedReader actualReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()))); try (BufferedReader actualReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())));
BufferedReader expectedReader = new BufferedReader(new InputStreamReader(OrderedPropertiesTest.class.getClassLoader().getResourceAsStream("orderedPropertiesExpected.properties")))) { BufferedReader expectedReader = new BufferedReader(new InputStreamReader(OrderedPropertiesTest.class.getClassLoader().getResourceAsStream("orderedPropertiesExpected.properties")))) {

View File

@ -1,200 +0,0 @@
/*
*
* * 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.minifi.bootstrap.util;
import org.apache.nifi.minifi.commons.schema.exception.SchemaLoaderException;
import org.apache.nifi.minifi.commons.schema.serialization.SchemaLoader;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
public class ParentGroupIdResolverTest {
@Test
public void testRemoteInputPortParentId() throws IOException, SchemaLoaderException {
List<String> configLines = new ArrayList<>();
configLines.add("MiNiFi Config Version: 2");
configLines.add("Remote Process Groups:");
configLines.add("- name: rpgOne");
configLines.add(" id: rpgOneId");
configLines.add(" Input Ports:");
configLines.add(" - id: one");
configLines.add("Process Groups:");
configLines.add("- Remote Process Groups:");
configLines.add(" - name: rpgTwo");
configLines.add(" id: rpgTwoId");
configLines.add(" Input Ports:");
configLines.add(" - id: two");
ParentGroupIdResolver parentGroupIdResolver = createParentGroupIdResolver(configLines);
assertEquals("rpgOneId", parentGroupIdResolver.getRemoteInputPortParentId("one"));
assertEquals("rpgTwoId", parentGroupIdResolver.getRemoteInputPortParentId("two"));
assertNull(parentGroupIdResolver.getRemoteOutputPortParentId("one"));
assertNull(parentGroupIdResolver.getRemoteOutputPortParentId("two"));
assertNull(parentGroupIdResolver.getInputPortParentId("one"));
assertNull(parentGroupIdResolver.getInputPortParentId("two"));
assertNull(parentGroupIdResolver.getOutputPortParentId("one"));
assertNull(parentGroupIdResolver.getOutputPortParentId("two"));
assertNull(parentGroupIdResolver.getProcessorParentId("one"));
assertNull(parentGroupIdResolver.getProcessorParentId("two"));
assertNull(parentGroupIdResolver.getFunnelParentId("one"));
assertNull(parentGroupIdResolver.getFunnelParentId("two"));
}
@Test
public void testRemoteOutputPortParentId() throws IOException, SchemaLoaderException {
List<String> configLines = new ArrayList<>();
configLines.add("MiNiFi Config Version: 3");
configLines.add("Remote Process Groups:");
configLines.add("- name: rpgOne");
configLines.add(" id: rpgOneId");
configLines.add(" Output Ports:");
configLines.add(" - id: one");
configLines.add("Process Groups:");
configLines.add("- Remote Process Groups:");
configLines.add(" - name: rpgTwo");
configLines.add(" id: rpgTwoId");
configLines.add(" Output Ports:");
configLines.add(" - id: two");
ParentGroupIdResolver parentGroupIdResolver = createParentGroupIdResolver(configLines);
assertNull(parentGroupIdResolver.getRemoteInputPortParentId("one"));
assertNull(parentGroupIdResolver.getRemoteInputPortParentId("two"));
assertEquals("rpgOneId", parentGroupIdResolver.getRemoteOutputPortParentId("one"));
assertEquals("rpgTwoId", parentGroupIdResolver.getRemoteOutputPortParentId("two"));
assertNull(parentGroupIdResolver.getInputPortParentId("one"));
assertNull(parentGroupIdResolver.getInputPortParentId("two"));
assertNull(parentGroupIdResolver.getOutputPortParentId("one"));
assertNull(parentGroupIdResolver.getOutputPortParentId("two"));
assertNull(parentGroupIdResolver.getProcessorParentId("one"));
assertNull(parentGroupIdResolver.getProcessorParentId("two"));
assertNull(parentGroupIdResolver.getFunnelParentId("one"));
assertNull(parentGroupIdResolver.getFunnelParentId("two"));
}
@Test
public void testInputPortParentId() throws IOException, SchemaLoaderException {
List<String> configLines = new ArrayList<>();
configLines.add("MiNiFi Config Version: 2");
configLines.add("Input Ports:");
configLines.add("- id: one");
configLines.add("Process Groups:");
configLines.add("- id: pgTwo");
configLines.add(" Input Ports:");
configLines.add(" - id: two");
ParentGroupIdResolver parentGroupIdResolver = createParentGroupIdResolver(configLines);
assertNull(parentGroupIdResolver.getRemoteInputPortParentId("one"));
assertNull(parentGroupIdResolver.getRemoteInputPortParentId("two"));
assertNull(parentGroupIdResolver.getRemoteOutputPortParentId("one"));
assertNull(parentGroupIdResolver.getRemoteOutputPortParentId("two"));
assertEquals(ConfigTransformer.ROOT_GROUP, parentGroupIdResolver.getInputPortParentId("one"));
assertEquals("pgTwo", parentGroupIdResolver.getInputPortParentId("two"));
assertNull(parentGroupIdResolver.getOutputPortParentId("one"));
assertNull(parentGroupIdResolver.getOutputPortParentId("two"));
assertNull(parentGroupIdResolver.getProcessorParentId("one"));
assertNull(parentGroupIdResolver.getProcessorParentId("two"));
assertNull(parentGroupIdResolver.getFunnelParentId("one"));
assertNull(parentGroupIdResolver.getFunnelParentId("two"));
}
@Test
public void testOutputPortParentId() throws IOException, SchemaLoaderException {
List<String> configLines = new ArrayList<>();
configLines.add("MiNiFi Config Version: 2");
configLines.add("Output Ports:");
configLines.add("- id: one");
configLines.add("Process Groups:");
configLines.add("- id: pgTwo");
configLines.add(" Output Ports:");
configLines.add(" - id: two");
ParentGroupIdResolver parentGroupIdResolver = createParentGroupIdResolver(configLines);
assertNull(parentGroupIdResolver.getRemoteInputPortParentId("one"));
assertNull(parentGroupIdResolver.getRemoteInputPortParentId("two"));
assertNull(parentGroupIdResolver.getRemoteOutputPortParentId("one"));
assertNull(parentGroupIdResolver.getRemoteOutputPortParentId("two"));
assertNull(parentGroupIdResolver.getInputPortParentId("one"));
assertNull(parentGroupIdResolver.getInputPortParentId("two"));
assertEquals(ConfigTransformer.ROOT_GROUP, parentGroupIdResolver.getOutputPortParentId("one"));
assertEquals("pgTwo", parentGroupIdResolver.getOutputPortParentId("two"));
assertNull(parentGroupIdResolver.getProcessorParentId("one"));
assertNull(parentGroupIdResolver.getProcessorParentId("two"));
assertNull(parentGroupIdResolver.getFunnelParentId("one"));
assertNull(parentGroupIdResolver.getFunnelParentId("two"));
}
@Test
public void testProcessorParentId() throws IOException, SchemaLoaderException {
List<String> configLines = new ArrayList<>();
configLines.add("MiNiFi Config Version: 2");
configLines.add("Processors:");
configLines.add("- id: one");
configLines.add("Process Groups:");
configLines.add("- id: pgTwo");
configLines.add(" Processors:");
configLines.add(" - id: two");
ParentGroupIdResolver parentGroupIdResolver = createParentGroupIdResolver(configLines);
assertNull(parentGroupIdResolver.getRemoteInputPortParentId("one"));
assertNull(parentGroupIdResolver.getRemoteInputPortParentId("two"));
assertNull(parentGroupIdResolver.getRemoteOutputPortParentId("one"));
assertNull(parentGroupIdResolver.getRemoteOutputPortParentId("two"));
assertNull(parentGroupIdResolver.getInputPortParentId("one"));
assertNull(parentGroupIdResolver.getInputPortParentId("two"));
assertNull(parentGroupIdResolver.getOutputPortParentId("one"));
assertNull(parentGroupIdResolver.getOutputPortParentId("two"));
assertEquals(ConfigTransformer.ROOT_GROUP, parentGroupIdResolver.getProcessorParentId("one"));
assertEquals("pgTwo", parentGroupIdResolver.getProcessorParentId("two"));
assertNull(parentGroupIdResolver.getFunnelParentId("one"));
assertNull(parentGroupIdResolver.getFunnelParentId("two"));
}
@Test
public void testFunnelParentId() throws IOException, SchemaLoaderException {
List<String> configLines = new ArrayList<>();
configLines.add("MiNiFi Config Version: 2");
configLines.add("Funnels:");
configLines.add("- id: one");
configLines.add("Process Groups:");
configLines.add("- id: pgTwo");
configLines.add(" Funnels:");
configLines.add(" - id: two");
ParentGroupIdResolver parentGroupIdResolver = createParentGroupIdResolver(configLines);
assertNull(parentGroupIdResolver.getRemoteInputPortParentId("one"));
assertNull(parentGroupIdResolver.getRemoteInputPortParentId("two"));
assertNull(parentGroupIdResolver.getRemoteOutputPortParentId("one"));
assertNull(parentGroupIdResolver.getRemoteOutputPortParentId("two"));
assertNull(parentGroupIdResolver.getInputPortParentId("one"));
assertNull(parentGroupIdResolver.getInputPortParentId("two"));
assertNull(parentGroupIdResolver.getOutputPortParentId("one"));
assertNull(parentGroupIdResolver.getOutputPortParentId("two"));
assertNull(parentGroupIdResolver.getProcessorParentId("one"));
assertNull(parentGroupIdResolver.getProcessorParentId("two"));
assertEquals(ConfigTransformer.ROOT_GROUP, parentGroupIdResolver.getFunnelParentId("one"));
assertEquals("pgTwo", parentGroupIdResolver.getFunnelParentId("two"));
}
private ParentGroupIdResolver createParentGroupIdResolver(List<String> configLines) throws IOException, SchemaLoaderException {
return new ParentGroupIdResolver(SchemaLoader.loadConfigSchemaFromYaml(new ByteArrayInputStream(String.join("\n", configLines)
.getBytes(StandardCharsets.UTF_8))).getProcessGroupSchema());
}
}

View File

@ -38,17 +38,16 @@ When using the `CacheConfigurationProvider`, by default, the server will look fo
The pattern can be configured in [./conf/minifi-c2-context.xml](./minifi-c2-assembly/src/main/resources/conf/minifi-c2-context.xml) and the default value (${class}/config) will replace ${class} with the class query parameter and then look for CLASS/config.CONTENT_TYPE.vVERSION in the directory structure. The pattern can be configured in [./conf/minifi-c2-context.xml](./minifi-c2-assembly/src/main/resources/conf/minifi-c2-context.xml) and the default value (${class}/config) will replace ${class} with the class query parameter and then look for CLASS/config.CONTENT_TYPE.vVERSION in the directory structure.
Ex: http://localhost:10090/c2/config?class=raspi&version=1 with an Accept header that matches text/yml would result in [./files/raspi3/config.text.yml.v1](./minifi-c2-assembly/src/main/resources/files/raspi3/config.text.yml.v1) and omitting the version parameter would look for the highest version number in the directory. Ex: http://localhost:10090/c2/config?class=raspi&version=1 with an Accept header that matches application/json would result in [./files/raspi3/config.application.json.v1](./minifi-c2-assembly/src/main/resources/files/raspi3/config.application.json.v1) and omitting the version parameter would look for the highest version number in the directory.
The version resolution is cached in memory to accommodate many devices polling periodically. The cache duration can be configured with additional arguments in [./conf/minifi-c2-context.xml](../minifi-integration-tests/src/test/resources/c2/hierarchical/c2-edge2/conf/minifi-c2-context.xml#L55) that call the [overloaded constructor.](./minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/service/ConfigService.java#L81) The version resolution is cached in memory to accommodate many devices polling periodically. The cache duration can be configured with additional arguments in [./conf/minifi-c2-context.xml](../minifi-integration-tests/src/test/resources/c2/hierarchical/c2-edge2/conf/minifi-c2-context.xml#L55) that call the [overloaded constructor.](./minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/service/ConfigService.java#L81)
MiNiFi Java agents can be configured to poll the C2 server by [configuring the PullHttpChangeIngestor in their bootstrap.conf.](../minifi-integration-tests/src/test/resources/c2/hierarchical/minifi-edge1/bootstrap.conf#L37) MiNiFi Java agents can be configured to poll the C2 server by [configuring the PullHttpChangeIngestor in their bootstrap.conf.](../minifi-integration-tests/src/test/resources/c2/hierarchical/minifi-edge1/bootstrap.conf#L37)
### Configuration Providers: ### Configuration Providers:
There are three `ConfigurationProvider` implementations provided out of the box. There are two `ConfigurationProvider` implementations provided out of the box.
1. The [CacheConfigurationProvider](./minifi-c2-assembly/src/main/resources/conf/minifi-c2-context.xml) looks at a directory on the filesystem or in an Amazon S3 bucket. Which backend storage is used can be selected in `minifi-c2-context.xml` via the constructors to `CacheConfigurationProvider`. 1. The [CacheConfigurationProvider](./minifi-c2-assembly/src/main/resources/conf/minifi-c2-context.xml) looks at a directory on the filesystem or in an Amazon S3 bucket. Which backend storage is used can be selected in `minifi-c2-context.xml` via the constructors to `CacheConfigurationProvider`.
2. The [DelegatingConfigurationProvider](./minifi-c2-integration-tests/src/test/resources/c2-unsecure-delegating/conf/minifi-c2-context.xml) delegates to another C2 server to allow for hierarchical C2 structures to help with scaling and/or bridging networks. 2. The [DelegatingConfigurationProvider](./minifi-c2-integration-tests/src/test/resources/c2-unsecure-delegating/conf/minifi-c2-context.xml) delegates to another C2 server to allow for hierarchical C2 structures to help with scaling and/or bridging networks.
3. The [NiFiRestConfigurationProvider](./minifi-c2-integration-tests/src/test/resources/c2-unsecure-rest/conf/minifi-c2-context.xml) pulls templates from a NiFi instance over its REST API. (Note: sensitive values are NOT included in templates so this is unsuitable for flows with sensitive configuration currently)
### Example network diagram: ### Example network diagram:
Below is a network diagram showing the different configurations tested by [our hierarchical integration test docker-compose file.](../minifi-integration-tests/src/test/resources/docker-compose-c2-hierarchical.yml) It consists of a "cluster" network where real processing might occur as well as 3 "edge" networks that can get configuration from the cluster network a few different ways. The edge1 instance can directly access the authoritative C2 server via HTTPS. The edge2 instance is representative of a segmented network where the MiNiFi agents can talk to a local delegating C2 server over HTTP which asks the authoritative C2 server over HTTPS. The edge 3 instance can talk to the authoritative C2 server through a Squid proxy over HTTPS. Below is a network diagram showing the different configurations tested by [our hierarchical integration test docker-compose file.](../minifi-integration-tests/src/test/resources/docker-compose-c2-hierarchical.yml) It consists of a "cluster" network where real processing might occur as well as 3 "edge" networks that can get configuration from the cluster network a few different ways. The edge1 instance can directly access the authoritative C2 server via HTTPS. The edge2 instance is representative of a segmented network where the MiNiFi agents can talk to a local delegating C2 server over HTTP which asks the authoritative C2 server over HTTPS. The edge 3 instance can talk to the authoritative C2 server through a Squid proxy over HTTPS.

View File

@ -26,7 +26,7 @@ MiNiFi is a child project effort of Apache NiFi. The MiNiFi C2 Server aids in t
- [Export Control](#export-control) - [Export Control](#export-control)
## Requirements ## Requirements
* JRE 1.8 * JRE 17
* Apache Maven 3 * Apache Maven 3
## Getting Started ## Getting Started

View File

@ -100,13 +100,23 @@ limitations under the License.
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.nifi.minifi</groupId> <groupId>org.apache.nifi.minifi</groupId>
<artifactId>minifi-c2-provider-nifi-rest</artifactId> <artifactId>minifi-c2-jetty</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.nifi.minifi</groupId> <groupId>org.apache.nifi.minifi</groupId>
<artifactId>minifi-c2-jetty</artifactId> <artifactId>minifi-toolkit-configuration</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>

View File

@ -26,7 +26,7 @@
<bean class="org.apache.nifi.minifi.c2.provider.cache.CacheConfigurationProvider"> <bean class="org.apache.nifi.minifi.c2.provider.cache.CacheConfigurationProvider">
<constructor-arg> <constructor-arg>
<list> <list>
<value>text/yml</value> <value>application/json</value>
</list> </list>
</constructor-arg> </constructor-arg>
<constructor-arg> <constructor-arg>
@ -62,24 +62,6 @@
</bean> </bean>
</constructor-arg>--> </constructor-arg>-->
</bean> </bean>
<!--<bean class="org.apache.nifi.minifi.c2.provider.nifi.rest.NiFiRestConfigurationProvider">
<constructor-arg>
<bean class="org.apache.nifi.minifi.c2.cache.filesystem.FileSystemConfigurationCache">
<constructor-arg>
<value>./cache</value>
</constructor-arg>
<constructor-arg>
<value>\${class}/\${class}</value>
</constructor-arg>
</bean>
</constructor-arg>
<constructor-arg>
<value>${minifi.c2.server.provider.nifi.rest.api.url}</value>
</constructor-arg>
<constructor-arg>
<value>\${class}.v\${version}</value>
</constructor-arg>
</bean>-->
</list> </list>
</constructor-arg> </constructor-arg>
<constructor-arg> <constructor-arg>

View File

@ -0,0 +1,55 @@
# 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.
{
"encodingVersion": {
"majorVersion": 2,
"minorVersion": 0
},
"maxTimerDrivenThreadCount": 10,
"maxEventDrivenThreadCount": 1,
"registries": [],
"parameterContexts": [],
"parameterProviders": [],
"controllerServices": [],
"reportingTasks": [],
"templates": [],
"rootGroup": {
"identifier": "c1b4e586-2011-3f81-a11e-8d669f084d1c",
"instanceIdentifier": "29db3dbc-0188-1000-7025-4cab8b52d278",
"name": "MiNiFi Flow",
"comments": "",
"position": {
"x": 0.0,
"y": 0.0
},
"processGroups": [],
"remoteProcessGroups": [],
"processors": [],
"inputPorts": [],
"outputPorts": [],
"connections": [],
"labels": [],
"funnels": [],
"controllerServices": [],
"variables": {},
"defaultFlowFileExpiration": "0 sec",
"defaultBackPressureObjectThreshold": 10000,
"defaultBackPressureDataSizeThreshold": "1 GB",
"componentType": "PROCESS_GROUP",
"flowFileConcurrency": "UNBOUNDED",
"flowFileOutboundPolicy": "STREAM_WHEN_AVAILABLE"
}
}

View File

@ -1,63 +0,0 @@
# 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.
MiNiFi Config Version: 3
Flow Controller:
name: MiNiFi Flow
comment: ''
Core Properties:
flow controller graceful shutdown period: 10 sec
flow service write delay interval: 500 ms
administrative yield duration: 30 sec
bored yield duration: 10 millis
max concurrent threads: 1
FlowFile Repository:
partitions: 256
checkpoint interval: 2 mins
always sync: false
Swap:
threshold: 20000
in period: 5 sec
in threads: 1
out period: 5 sec
out threads: 4
Content Repository:
content claim max appendable size: 10 MB
content claim max flow files: 100
always sync: false
Provenance Repository:
provenance rollover time: 1 min
Component Status Repository:
buffer size: 1440
snapshot frequency: 1 min
Security Properties:
keystore: ''
keystore type: ''
keystore password: ''
key password: ''
truststore: ''
truststore type: ''
truststore password: ''
ssl protocol: ''
Sensitive Props:
key: ''
algorithm: NIFI_PBKDF2_AES_GCM_256
provider: BC
Processors: []
Process Groups: []
Funnels: []
Connections: []
Remote Process Groups: []
NiFi Properties Overrides: {}

View File

@ -36,7 +36,10 @@ import org.apache.nifi.minifi.c2.cache.filesystem.FileSystemConfigurationCache;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
public class FileSystemConfigurationCacheTest { public class FileSystemConfigurationCacheTest {
private static final String PATH_ROOT = "src/test/resources/files"; private static final String PATH_ROOT = "src/test/resources/files";
private static final String FLOW_CONTENT_TYPE = "test/json";
public static final String NOT_REGISTERED_CONTENT_TYPE = "test/contenttype";
@Test @Test
public void getConfigurationTest() throws IOException, ConfigurationProviderException { public void getConfigurationTest() throws IOException, ConfigurationProviderException {
@ -46,11 +49,11 @@ public class FileSystemConfigurationCacheTest {
Map<String, List<String>> parameters = new HashMap<>(); Map<String, List<String>> parameters = new HashMap<>();
ConfigurationCacheFileInfo info = cache.getCacheFileInfo("text/yaml", parameters); ConfigurationCacheFileInfo info = cache.getCacheFileInfo(FLOW_CONTENT_TYPE, parameters);
WriteableConfiguration configuration = info.getConfiguration(1); WriteableConfiguration configuration = info.getConfiguration(1);
assertEquals("config.text.yaml.v1", configuration.getName()); assertEquals("config.test.json.v1", configuration.getName());
assertEquals("1", configuration.getVersion()); assertEquals("1", configuration.getVersion());
assertTrue(configuration.exists()); assertTrue(configuration.exists());
} }
@ -63,7 +66,7 @@ public class FileSystemConfigurationCacheTest {
Map<String, List<String>> parameters = new HashMap<>(); Map<String, List<String>> parameters = new HashMap<>();
ConfigurationCacheFileInfo info = cache.getCacheFileInfo("test/contenttype", parameters); ConfigurationCacheFileInfo info = cache.getCacheFileInfo(NOT_REGISTERED_CONTENT_TYPE, parameters);
WriteableConfiguration configuration = info.getConfiguration(1); WriteableConfiguration configuration = info.getConfiguration(1);
@ -80,7 +83,7 @@ public class FileSystemConfigurationCacheTest {
Map<String, List<String>> parameters = new HashMap<>(); Map<String, List<String>> parameters = new HashMap<>();
ConfigurationCacheFileInfo info = cache.getCacheFileInfo("text/yaml", parameters); ConfigurationCacheFileInfo info = cache.getCacheFileInfo(FLOW_CONTENT_TYPE, parameters);
Stream<WriteableConfiguration> configs = info.getCachedConfigurations(); Stream<WriteableConfiguration> configs = info.getCachedConfigurations();
@ -96,6 +99,6 @@ public class FileSystemConfigurationCacheTest {
Map<String, List<String>> parameters = new HashMap<>(); Map<String, List<String>> parameters = new HashMap<>();
assertThrows(InvalidParameterException.class, () -> cache.getCacheFileInfo("test/contenttype", parameters)); assertThrows(InvalidParameterException.class, () -> cache.getCacheFileInfo(NOT_REGISTERED_CONTENT_TYPE, parameters));
} }
} }

View File

@ -0,0 +1,55 @@
# 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.
{
"encodingVersion": {
"majorVersion": 2,
"minorVersion": 0
},
"maxTimerDrivenThreadCount": 10,
"maxEventDrivenThreadCount": 1,
"registries": [],
"parameterContexts": [],
"parameterProviders": [],
"controllerServices": [],
"reportingTasks": [],
"templates": [],
"rootGroup": {
"identifier": "c1b4e586-2011-3f81-a11e-8d669f084d1c",
"instanceIdentifier": "29db3dbc-0188-1000-7025-4cab8b52d278",
"name": "NiFi Flow",
"comments": "",
"position": {
"x": 0.0,
"y": 0.0
},
"processGroups": [],
"remoteProcessGroups": [],
"processors": [],
"inputPorts": [],
"outputPorts": [],
"connections": [],
"labels": [],
"funnels": [],
"controllerServices": [],
"variables": {},
"defaultFlowFileExpiration": "0 sec",
"defaultBackPressureObjectThreshold": 10000,
"defaultBackPressureDataSizeThreshold": "1 GB",
"componentType": "PROCESS_GROUP",
"flowFileConcurrency": "UNBOUNDED",
"flowFileOutboundPolicy": "STREAM_WHEN_AVAILABLE"
}
}

View File

@ -1,63 +0,0 @@
# 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.
MiNiFi Config Version: 3
Flow Controller:
name: MiNiFi Flow
comment: ''
Core Properties:
flow controller graceful shutdown period: 10 sec
flow service write delay interval: 500 ms
administrative yield duration: 30 sec
bored yield duration: 10 millis
max concurrent threads: 1
FlowFile Repository:
partitions: 256
checkpoint interval: 2 mins
always sync: false
Swap:
threshold: 20000
in period: 5 sec
in threads: 1
out period: 5 sec
out threads: 4
Content Repository:
content claim max appendable size: 10 MB
content claim max flow files: 100
always sync: false
Provenance Repository:
provenance rollover time: 1 min
Component Status Repository:
buffer size: 1440
snapshot frequency: 1 min
Security Properties:
keystore: ''
keystore type: ''
keystore password: ''
key password: ''
truststore: ''
truststore type: ''
truststore password: ''
ssl protocol: ''
Sensitive Props:
key: ''
algorithm: NIFI_PBKDF2_AES_GCM_256
provider: BC
Processors: []
Process Groups: []
Funnels: []
Connections: []
Remote Process Groups: []
NiFi Properties Overrides: {}

View File

@ -16,7 +16,7 @@
# under the License. # under the License.
# #
FROM eclipse-temurin:11-jre FROM eclipse-temurin:17-jre
LABEL maintainer="Apache NiFi <dev@nifi.apache.org>" LABEL maintainer="Apache NiFi <dev@nifi.apache.org>"
LABEL site="https://nifi.apache.org" LABEL site="https://nifi.apache.org"

View File

@ -17,7 +17,7 @@
# #
ARG IMAGE_NAME=eclipse-temurin ARG IMAGE_NAME=eclipse-temurin
ARG IMAGE_TAG=11-jre ARG IMAGE_TAG=17-jre
FROM ${IMAGE_NAME}:${IMAGE_TAG} as builder FROM ${IMAGE_NAME}:${IMAGE_TAG} as builder
LABEL stage=builder LABEL stage=builder

View File

@ -30,12 +30,6 @@ limitations under the License.
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>org.apache.nifi.minifi</groupId>
<artifactId>minifi-commons-schema</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.apache.nifi.minifi</groupId> <groupId>org.apache.nifi.minifi</groupId>
<artifactId>minifi-c2-api</artifactId> <artifactId>minifi-c2-api</artifactId>
@ -59,6 +53,15 @@ limitations under the License.
<version>2.0.0</version> <version>2.0.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-core-api</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
@ -91,7 +94,7 @@ limitations under the License.
<artifactId>apache-rat-plugin</artifactId> <artifactId>apache-rat-plugin</artifactId>
<configuration> <configuration>
<excludes combine.children="append"> <excludes combine.children="append">
<exclude>src/test/resources/mocknifi/www/nifi-api/flow/templates</exclude> <exclude>**/config.application.json.*</exclude>
</excludes> </excludes>
</configuration> </configuration>
</plugin> </plugin>

View File

@ -17,23 +17,11 @@
package org.apache.nifi.minifi.c2.integration.test; package org.apache.nifi.minifi.c2.integration.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.palantir.docker.compose.DockerComposeExtension; import com.palantir.docker.compose.DockerComposeExtension;
import com.palantir.docker.compose.connection.DockerPort; import com.palantir.docker.compose.connection.DockerPort;
import org.apache.commons.io.IOUtils;
import org.apache.nifi.minifi.commons.schema.ConfigSchema;
import org.apache.nifi.minifi.commons.schema.serialization.SchemaLoader;
import org.apache.nifi.security.util.KeyStoreUtils;
import org.apache.nifi.security.util.KeystoreType;
import org.apache.nifi.security.util.SslContextFactory;
import org.apache.nifi.security.util.StandardTlsConfiguration;
import org.apache.nifi.security.util.TlsConfiguration;
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandalone;
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandaloneCommandLine;
import org.junit.jupiter.api.Test;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -49,9 +37,19 @@ import java.security.KeyStore;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.net.ssl.HttpsURLConnection;
import static org.junit.jupiter.api.Assertions.assertEquals; import javax.net.ssl.SSLContext;
import static org.junit.jupiter.api.Assertions.assertThrows; import javax.net.ssl.TrustManagerFactory;
import org.apache.commons.io.IOUtils;
import org.apache.nifi.controller.flow.VersionedDataflow;
import org.apache.nifi.security.util.KeyStoreUtils;
import org.apache.nifi.security.util.KeystoreType;
import org.apache.nifi.security.util.SslContextFactory;
import org.apache.nifi.security.util.StandardTlsConfiguration;
import org.apache.nifi.security.util.TlsConfiguration;
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandalone;
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandaloneCommandLine;
import org.junit.jupiter.api.Test;
public abstract class AbstractTestSecure extends AbstractTestUnsecure { public abstract class AbstractTestSecure extends AbstractTestUnsecure {
public static final String C2_URL = "https://c2:10443/c2/config"; public static final String C2_URL = "https://c2:10443/c2/config";
@ -115,8 +113,8 @@ public abstract class AbstractTestSecure extends AbstractTestUnsecure {
assertReturnCode("", sslContext, 403); assertReturnCode("", sslContext, 403);
ConfigSchema configSchema = assertReturnCode("?class=raspi2", sslContext, 200); VersionedDataflow versionedDataflow = assertReturnCode("?class=raspi2", sslContext, 200);
assertEquals("raspi2.v1", configSchema.getFlowControllerProperties().getName()); assertEquals("raspi2.v1", versionedDataflow.getRootGroup().getName());
assertReturnCode("?class=raspi3", sslContext, 403); assertReturnCode("?class=raspi3", sslContext, 403);
} }
@ -128,8 +126,8 @@ public abstract class AbstractTestSecure extends AbstractTestUnsecure {
assertReturnCode("", sslContext, 403); assertReturnCode("", sslContext, 403);
assertReturnCode("?class=raspi2", sslContext, 403); assertReturnCode("?class=raspi2", sslContext, 403);
ConfigSchema configSchema = assertReturnCode("?class=raspi3", sslContext, 200); VersionedDataflow versionedDataflow = assertReturnCode("?class=raspi3", sslContext, 200);
assertEquals("raspi3.v2", configSchema.getFlowControllerProperties().getName()); assertEquals("raspi3.v2", versionedDataflow.getRootGroup().getName());
} }
@Test @Test
@ -138,11 +136,11 @@ public abstract class AbstractTestSecure extends AbstractTestUnsecure {
assertReturnCode("", sslContext, 400); assertReturnCode("", sslContext, 400);
ConfigSchema configSchema = assertReturnCode("?class=raspi2", sslContext, 200); VersionedDataflow dataflowRaspi2 = assertReturnCode("?class=raspi2", sslContext, 200);
assertEquals("raspi2.v1", configSchema.getFlowControllerProperties().getName()); assertEquals("raspi2.v1", dataflowRaspi2.getRootGroup().getName());
configSchema = assertReturnCode("?class=raspi3", sslContext, 200); VersionedDataflow dataflowRaspi3 = assertReturnCode("?class=raspi3", sslContext, 200);
assertEquals("raspi3.v2", configSchema.getFlowControllerProperties().getName()); assertEquals("raspi3.v2", dataflowRaspi3.getRootGroup().getName());
} }
@Test @Test
@ -179,12 +177,15 @@ public abstract class AbstractTestSecure extends AbstractTestUnsecure {
return SslContextFactory.createSslContext(tlsConfiguration); return SslContextFactory.createSslContext(tlsConfiguration);
} }
protected ConfigSchema assertReturnCode(String query, SSLContext sslContext, int expectedReturnCode) throws Exception { protected VersionedDataflow assertReturnCode(String query, SSLContext sslContext, int expectedReturnCode) throws Exception {
HttpsURLConnection httpsURLConnection = openUrlConnection(C2_URL + query, sslContext); HttpsURLConnection httpsURLConnection = openUrlConnection(C2_URL + query, sslContext);
httpsURLConnection.setRequestProperty("Accept", "application/json");
try { try {
assertEquals(expectedReturnCode, httpsURLConnection.getResponseCode()); assertEquals(expectedReturnCode, httpsURLConnection.getResponseCode());
if (expectedReturnCode == 200) { if (expectedReturnCode == 200) {
return SchemaLoader.loadConfigSchemaFromYaml(httpsURLConnection.getInputStream()); try (InputStream inputStream = httpsURLConnection.getInputStream()) {
return toVersionedDataFlow(inputStream);
}
} }
} finally { } finally {
httpsURLConnection.disconnect(); httpsURLConnection.disconnect();

View File

@ -17,20 +17,21 @@
package org.apache.nifi.minifi.c2.integration.test; package org.apache.nifi.minifi.c2.integration.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
import com.palantir.docker.compose.DockerComposeExtension; import com.palantir.docker.compose.DockerComposeExtension;
import com.palantir.docker.compose.connection.Container; import com.palantir.docker.compose.connection.Container;
import com.palantir.docker.compose.connection.DockerPort; import com.palantir.docker.compose.connection.DockerPort;
import org.apache.nifi.minifi.commons.schema.ConfigSchema;
import org.apache.nifi.minifi.commons.schema.exception.SchemaLoaderException;
import org.apache.nifi.minifi.commons.schema.serialization.SchemaLoader;
import org.junit.jupiter.api.Test;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import org.apache.commons.io.IOUtils;
import static org.junit.jupiter.api.Assertions.assertEquals; import org.apache.nifi.controller.flow.VersionedDataflow;
import org.junit.jupiter.api.Test;
public abstract class AbstractTestUnsecure { public abstract class AbstractTestUnsecure {
protected String c2Url; protected String c2Url;
@ -49,31 +50,28 @@ public abstract class AbstractTestUnsecure {
} }
@Test @Test
public void testCurrentVersion() throws IOException, SchemaLoaderException { public void testCurrentVersion() throws IOException {
ConfigSchema configSchema = getConfigSchema(c2Url + "?class=raspi3"); VersionedDataflow versionedDataflow = getFlowDefinition(c2Url + "?class=raspi3");
assertEquals(3, configSchema.getVersion()); assertEquals("raspi3.v2", versionedDataflow.getRootGroup().getName());
assertEquals("raspi3.v2", configSchema.getFlowControllerProperties().getName());
} }
@Test @Test
public void testVersion1() throws IOException, SchemaLoaderException { public void testVersion1() throws IOException {
ConfigSchema configSchema = getConfigSchema(c2Url + "?class=raspi3&version=1"); VersionedDataflow versionedDataflow = getFlowDefinition(c2Url + "?class=raspi3&version=1");
assertEquals(3, configSchema.getVersion()); assertEquals("raspi3.v1", versionedDataflow.getRootGroup().getName());
assertEquals("raspi3.v1", configSchema.getFlowControllerProperties().getName());
} }
@Test @Test
public void testVersion2() throws IOException, SchemaLoaderException { public void testVersion2() throws IOException {
ConfigSchema configSchema = getConfigSchema(c2Url + "?class=raspi3&version=2"); VersionedDataflow versionedDataflow = getFlowDefinition(c2Url + "?class=raspi3&version=2");
assertEquals(3, configSchema.getVersion()); assertEquals("raspi3.v2", versionedDataflow.getRootGroup().getName());
assertEquals("raspi3.v2", configSchema.getFlowControllerProperties().getName());
} }
@Test @Test
public void testUnacceptable() throws IOException { public void testUnacceptable() throws IOException {
HttpURLConnection urlConnection = openSuperUserUrlConnection(c2Url + "?class=raspi3"); HttpURLConnection urlConnection = openSuperUserUrlConnection(c2Url + "?class=raspi3");
try { try {
urlConnection.setRequestProperty("Accept", "text/xml"); urlConnection.setRequestProperty("Accept", "text/invalid");
assertEquals(406, urlConnection.getResponseCode()); assertEquals(406, urlConnection.getResponseCode());
} finally { } finally {
urlConnection.disconnect(); urlConnection.disconnect();
@ -90,18 +88,24 @@ public abstract class AbstractTestUnsecure {
} }
} }
public ConfigSchema getConfigSchema(String urlString) throws IOException, SchemaLoaderException { public VersionedDataflow getFlowDefinition(String urlString) throws IOException {
HttpURLConnection urlConnection = openSuperUserUrlConnection(urlString); HttpURLConnection urlConnection = openSuperUserUrlConnection(urlString);
ConfigSchema configSchema; urlConnection.setRequestProperty("Accept", "application/json");
try (InputStream inputStream = urlConnection.getInputStream()) { try (InputStream inputStream = urlConnection.getInputStream()) {
configSchema = SchemaLoader.loadConfigSchemaFromYaml(inputStream); return toVersionedDataFlow(inputStream);
} finally { } finally {
urlConnection.disconnect(); urlConnection.disconnect();
} }
return configSchema;
} }
protected HttpURLConnection openSuperUserUrlConnection(String url) throws IOException { protected HttpURLConnection openSuperUserUrlConnection(String url) throws IOException {
return (HttpURLConnection) new URL(url).openConnection(); return (HttpURLConnection) new URL(url).openConnection();
} }
protected VersionedDataflow toVersionedDataFlow(InputStream inputStream) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setAnnotationIntrospector(new JaxbAnnotationIntrospector(objectMapper.getTypeFactory()));
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper.readValue(IOUtils.toByteArray(inputStream), VersionedDataflow.class);
}
} }

View File

@ -1,118 +0,0 @@
/*
* 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.minifi.c2.integration.test;
import com.palantir.docker.compose.DockerComposeExtension;
import org.apache.nifi.minifi.c2.integration.test.health.HttpsStatusCodeHealthCheck;
import org.apache.nifi.security.util.KeystoreType;
import org.apache.nifi.security.util.SslContextFactory;
import org.apache.nifi.security.util.StandardTlsConfiguration;
import org.apache.nifi.security.util.TlsConfiguration;
import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandalone;
import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
import org.bouncycastle.util.io.pem.PemWriter;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.BeforeAll;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.util.Arrays;
public class NiFiRestConfigurationProviderSecureTest extends AbstractTestSecure {
private static SSLSocketFactory healthCheckSocketFactory;
private static Path certificatesDirectory;
private static SSLContext trustSslContext;
// Not annotated as rule because we need to generate certificatesDirectory first
public static DockerComposeExtension docker = DockerComposeExtension.builder()
.file("target/test-classes/docker-compose-NiFiRestConfigurationProviderSecureTest.yml")
.waitingForServices(Arrays.asList("squid", "mocknifi"),
new HttpsStatusCodeHealthCheck(container -> "https://mocknifi:8443/", containers -> containers.get(0), containers -> containers.get(1), () -> {
Path c2 = certificatesDirectory.resolve("c2");
try {
TlsConfiguration tlsConfiguration = new StandardTlsConfiguration(
c2.resolve("keystore.jks").toFile().getAbsolutePath(),
"badKeystorePass",
"badKeyPass", KeystoreType.JKS,
c2.resolve("truststore.jks").toFile().getAbsolutePath(),
"badTrustPass",
KeystoreType.JKS);
return SslContextFactory.createSslContext(tlsConfiguration).getSocketFactory();
} catch (Exception e) {
throw new RuntimeException(e);
}
}, 200))
.waitingForServices(Arrays.asList("squid", "c2"),
new HttpsStatusCodeHealthCheck(container -> C2_URL, containers -> containers.get(0), containers -> containers.get(1), () -> healthCheckSocketFactory, 403))
.build();
public NiFiRestConfigurationProviderSecureTest() {
super(docker, certificatesDirectory, trustSslContext);
}
/**
* Generates certificates with the tls-toolkit and then starts up the docker compose file
*/
@BeforeAll
public static void initCertificates() throws Exception {
certificatesDirectory = Paths.get(NiFiRestConfigurationProviderSecureTest.class.getClassLoader()
.getResource("docker-compose-NiFiRestConfigurationProviderSecureTest.yml").getFile()).getParent().toAbsolutePath().resolve("certificates-NiFiRestConfigurationProviderSecureTest");
trustSslContext = initCertificates(certificatesDirectory, Arrays.asList("c2", "mocknifi"));
healthCheckSocketFactory = trustSslContext.getSocketFactory();
KeyStore mockNiFiKeyStore = KeyStore.getInstance("JKS");
try (InputStream inputStream = Files.newInputStream(certificatesDirectory.resolve("mocknifi").resolve("keystore.jks"))) {
mockNiFiKeyStore.load(inputStream, "badKeystorePass".toCharArray());
}
try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(Files.newOutputStream(certificatesDirectory.resolve("mocknifi").resolve("cert.pem"))))) {
pemWriter.writeObject(new JcaMiscPEMGenerator(mockNiFiKeyStore.getKey(TlsToolkitStandalone.NIFI_KEY, "badKeyPass".toCharArray())));
for (Certificate certificate : mockNiFiKeyStore.getCertificateChain(TlsToolkitStandalone.NIFI_KEY)) {
pemWriter.writeObject(new JcaMiscPEMGenerator(certificate));
}
}
KeyStore mockNiFiTrustStore = KeyStore.getInstance("JKS");
try (InputStream inputStream = Files.newInputStream(certificatesDirectory.resolve("mocknifi").resolve("truststore.jks"))) {
mockNiFiTrustStore.load(inputStream, "badTrustPass".toCharArray());
}
try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(Files.newOutputStream(certificatesDirectory.resolve("mocknifi").resolve("ca.pem"))))) {
pemWriter.writeObject(new JcaMiscPEMGenerator(mockNiFiTrustStore.getCertificate(TlsToolkitStandalone.NIFI_CERT)));
}
docker.before();
}
@AfterAll
public static void cleanup() {
docker.after();
}
@BeforeEach
public void setup() {
super.setup(docker);
}
}

View File

@ -1,39 +0,0 @@
/*
* 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.minifi.c2.integration.test;
import com.palantir.docker.compose.DockerComposeExtension;
import com.palantir.docker.compose.connection.waiting.HealthChecks;
import org.apache.nifi.minifi.c2.integration.test.health.HttpStatusCodeHealthCheck;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
public class NiFiRestConfigurationProviderUnsecureTest extends AbstractTestUnsecure {
@RegisterExtension
public static DockerComposeExtension docker = DockerComposeExtension.builder()
.file("target/test-classes/docker-compose-NiFiRestConfigurationProviderUnsecureTest.yml")
.waitingForService("mocknifi", HealthChecks.toRespond2xxOverHttp(8080,
dockerPort -> "http://" + dockerPort.getIp() + ":" + dockerPort.getExternalPort() + "/"))
.waitingForService("c2", new HttpStatusCodeHealthCheck(FileSystemCacheProviderUnsecureTest::getUnsecureConfigUrl, 400))
.build();
@BeforeEach
public void setup() {
super.setup(docker);
}
}

View File

@ -1,60 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<beans default-lazy-init="true"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util-3.1.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
<bean id="configService" class="org.apache.nifi.minifi.c2.service.ConfigService" scope="singleton">
<constructor-arg>
<list>
<bean class="org.apache.nifi.minifi.c2.provider.nifi.rest.NiFiRestConfigurationProvider">
<constructor-arg>
<bean class="org.apache.nifi.minifi.c2.cache.filesystem.FileSystemConfigurationCache">
<constructor-arg>
<value>./cache</value>
</constructor-arg>
<constructor-arg>
<value>${class}/${class}</value>
</constructor-arg>
</bean>
</constructor-arg>
<constructor-arg>
<value>https://mocknifi:8443/nifi-api</value>
</constructor-arg>
<constructor-arg>
<value>${class}.v${version}</value>
</constructor-arg>
</bean>
</list>
</constructor-arg>
<constructor-arg>
<bean class="org.apache.nifi.minifi.c2.security.authorization.GrantedAuthorityAuthorizer">
<constructor-arg value="classpath:authorizations.yaml"/>
</bean>
</constructor-arg>
</bean>
</beans>

View File

@ -1,59 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<beans default-lazy-init="true"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util-3.1.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
<bean id="configService" class="org.apache.nifi.minifi.c2.service.ConfigService" scope="singleton">
<constructor-arg>
<list>
<bean class="org.apache.nifi.minifi.c2.provider.nifi.rest.NiFiRestConfigurationProvider">
<constructor-arg>
<bean class="org.apache.nifi.minifi.c2.cache.filesystem.FileSystemConfigurationCache">
<constructor-arg>
<value>./cache</value>
</constructor-arg>
<constructor-arg>
<value>${class}/${class}</value>
</constructor-arg>
</bean>
</constructor-arg>
<constructor-arg>
<value>http://mocknifi:8080/nifi-api</value>
</constructor-arg>
<constructor-arg>
<value>${class}.v${version}</value>
</constructor-arg>
</bean>
</list>
</constructor-arg>
<constructor-arg>
<bean class="org.apache.nifi.minifi.c2.security.authorization.GrantedAuthorityAuthorizer">
<constructor-arg value="classpath:authorizations.yaml"/>
</bean>
</constructor-arg>
</bean>
</beans>

View File

@ -0,0 +1,40 @@
{
"encodingVersion": {
"majorVersion": 2,
"minorVersion": 0
},
"maxTimerDrivenThreadCount": 10,
"maxEventDrivenThreadCount": 1,
"registries": [],
"parameterContexts": [],
"parameterProviders": [],
"controllerServices": [],
"reportingTasks": [],
"templates": [],
"rootGroup": {
"identifier": "c1b4e586-2011-3f81-a11e-8d669f084d1c",
"instanceIdentifier": "29db3dbc-0188-1000-7025-4cab8b52d278",
"name": "raspi2.v1",
"comments": "",
"position": {
"x": 0.0,
"y": 0.0
},
"processGroups": [],
"remoteProcessGroups": [],
"processors": [],
"inputPorts": [],
"outputPorts": [],
"connections": [],
"labels": [],
"funnels": [],
"controllerServices": [],
"variables": {},
"defaultFlowFileExpiration": "0 sec",
"defaultBackPressureObjectThreshold": 10000,
"defaultBackPressureDataSizeThreshold": "1 GB",
"componentType": "PROCESS_GROUP",
"flowFileConcurrency": "UNBOUNDED",
"flowFileOutboundPolicy": "STREAM_WHEN_AVAILABLE"
}
}

View File

@ -1,63 +0,0 @@
# 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.
MiNiFi Config Version: 3
Flow Controller:
name: raspi2.v1
comment: ''
Core Properties:
flow controller graceful shutdown period: 10 sec
flow service write delay interval: 500 ms
administrative yield duration: 30 sec
bored yield duration: 10 millis
max concurrent threads: 1
FlowFile Repository:
partitions: 256
checkpoint interval: 2 mins
always sync: false
Swap:
threshold: 20000
in period: 5 sec
in threads: 1
out period: 5 sec
out threads: 4
Content Repository:
content claim max appendable size: 10 MB
content claim max flow files: 100
always sync: false
Provenance Repository:
provenance rollover time: 1 min
Component Status Repository:
buffer size: 1440
snapshot frequency: 1 min
Security Properties:
keystore: ''
keystore type: ''
keystore password: ''
key password: ''
truststore: ''
truststore type: ''
truststore password: ''
ssl protocol: ''
Sensitive Props:
key: ''
algorithm: NIFI_PBKDF2_AES_GCM_256
provider: BC
Processors: []
Process Groups: []
Funnels: []
Connections: []
Remote Process Groups: []
NiFi Properties Overrides: {}

View File

@ -0,0 +1,40 @@
{
"encodingVersion": {
"majorVersion": 2,
"minorVersion": 0
},
"maxTimerDrivenThreadCount": 10,
"maxEventDrivenThreadCount": 1,
"registries": [],
"parameterContexts": [],
"parameterProviders": [],
"controllerServices": [],
"reportingTasks": [],
"templates": [],
"rootGroup": {
"identifier": "c1b4e586-2011-3f81-a11e-8d669f084d1c",
"instanceIdentifier": "29db3dbc-0188-1000-7025-4cab8b52d278",
"name": "raspi3.v1",
"comments": "",
"position": {
"x": 0.0,
"y": 0.0
},
"processGroups": [],
"remoteProcessGroups": [],
"processors": [],
"inputPorts": [],
"outputPorts": [],
"connections": [],
"labels": [],
"funnels": [],
"controllerServices": [],
"variables": {},
"defaultFlowFileExpiration": "0 sec",
"defaultBackPressureObjectThreshold": 10000,
"defaultBackPressureDataSizeThreshold": "1 GB",
"componentType": "PROCESS_GROUP",
"flowFileConcurrency": "UNBOUNDED",
"flowFileOutboundPolicy": "STREAM_WHEN_AVAILABLE"
}
}

View File

@ -0,0 +1,40 @@
{
"encodingVersion": {
"majorVersion": 2,
"minorVersion": 0
},
"maxTimerDrivenThreadCount": 10,
"maxEventDrivenThreadCount": 1,
"registries": [],
"parameterContexts": [],
"parameterProviders": [],
"controllerServices": [],
"reportingTasks": [],
"templates": [],
"rootGroup": {
"identifier": "c1b4e586-2011-3f81-a11e-8d669f084d1c",
"instanceIdentifier": "29db3dbc-0188-1000-7025-4cab8b52d278",
"name": "raspi3.v2",
"comments": "",
"position": {
"x": 0.0,
"y": 0.0
},
"processGroups": [],
"remoteProcessGroups": [],
"processors": [],
"inputPorts": [],
"outputPorts": [],
"connections": [],
"labels": [],
"funnels": [],
"controllerServices": [],
"variables": {},
"defaultFlowFileExpiration": "0 sec",
"defaultBackPressureObjectThreshold": 10000,
"defaultBackPressureDataSizeThreshold": "1 GB",
"componentType": "PROCESS_GROUP",
"flowFileConcurrency": "UNBOUNDED",
"flowFileOutboundPolicy": "STREAM_WHEN_AVAILABLE"
}
}

View File

@ -1,63 +0,0 @@
# 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.
MiNiFi Config Version: 3
Flow Controller:
name: raspi3.v1
comment: ''
Core Properties:
flow controller graceful shutdown period: 10 sec
flow service write delay interval: 500 ms
administrative yield duration: 30 sec
bored yield duration: 10 millis
max concurrent threads: 1
FlowFile Repository:
partitions: 256
checkpoint interval: 2 mins
always sync: false
Swap:
threshold: 20000
in period: 5 sec
in threads: 1
out period: 5 sec
out threads: 4
Content Repository:
content claim max appendable size: 10 MB
content claim max flow files: 100
always sync: false
Provenance Repository:
provenance rollover time: 1 min
Component Status Repository:
buffer size: 1440
snapshot frequency: 1 min
Security Properties:
keystore: ''
keystore type: ''
keystore password: ''
key password: ''
truststore: ''
truststore type: ''
truststore password: ''
ssl protocol: ''
Sensitive Props:
key: ''
algorithm: NIFI_PBKDF2_AES_GCM_256
provider: BC
Processors: []
Process Groups: []
Funnels: []
Connections: []
Remote Process Groups: []
NiFi Properties Overrides: {}

View File

@ -1,63 +0,0 @@
# 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.
MiNiFi Config Version: 3
Flow Controller:
name: raspi3.v2
comment: ''
Core Properties:
flow controller graceful shutdown period: 10 sec
flow service write delay interval: 500 ms
administrative yield duration: 30 sec
bored yield duration: 10 millis
max concurrent threads: 1
FlowFile Repository:
partitions: 256
checkpoint interval: 2 mins
always sync: false
Swap:
threshold: 20000
in period: 5 sec
in threads: 1
out period: 5 sec
out threads: 4
Content Repository:
content claim max appendable size: 10 MB
content claim max flow files: 100
always sync: false
Provenance Repository:
provenance rollover time: 1 min
Component Status Repository:
buffer size: 1440
snapshot frequency: 1 min
Security Properties:
keystore: ''
keystore type: ''
keystore password: ''
key password: ''
truststore: ''
truststore type: ''
truststore password: ''
ssl protocol: ''
Sensitive Props:
key: ''
algorithm: NIFI_PBKDF2_AES_GCM_256
provider: BC
Processors: []
Process Groups: []
Funnels: []
Connections: []
Remote Process Groups: []
NiFi Properties Overrides: {}

View File

@ -1,55 +0,0 @@
# 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.
version: "2"
services:
c2:
image: apache/nifi-minifi-c2:${minifi.c2.version}-maven
ports:
- "10443"
hostname: c2
volumes:
- ./c2-secure/conf/c2.properties:/opt/minifi-c2/minifi-c2-${minifi.c2.version}/conf/c2.properties
- ./c2-secure/conf/authorities.yaml:/opt/minifi-c2/minifi-c2-${minifi.c2.version}/conf/authorities.yaml
- ./c2-secure/conf/authorizations.yaml:/opt/minifi-c2/minifi-c2-${minifi.c2.version}/conf/authorizations.yaml
- ./c2-secure-rest/conf/minifi-c2-context.xml:/opt/minifi-c2/minifi-c2-${minifi.c2.version}/conf/minifi-c2-context.xml
- ./certificates-NiFiRestConfigurationProviderSecureTest/c2/keystore.jks:/opt/minifi-c2/minifi-c2-${minifi.c2.version}/conf/keystore.jks
- ./certificates-NiFiRestConfigurationProviderSecureTest/c2/truststore.jks:/opt/minifi-c2/minifi-c2-${minifi.c2.version}/conf/truststore.jks
squid:
image: chrisdaish/squid
ports:
- "3128"
hostname: squid
volumes:
- ./squid/squid.conf:/etc/squid/squid.conf
mocknifi:
image: python:2
ports:
- "8443"
hostname: mocknifi
volumes:
- ./mocknifi-secure/server.py:/root/server.py
- ./mocknifi/www:/var/www
- ./certificates-NiFiRestConfigurationProviderSecureTest/mocknifi/cert.pem:/root/cert.pem
- ./certificates-NiFiRestConfigurationProviderSecureTest/mocknifi/ca.pem:/root/ca.pem
working_dir: /var/www
entrypoint:
- python
- /root/server.py

View File

@ -1,37 +0,0 @@
# 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.
version: "2"
services:
c2:
image: apache/nifi-minifi-c2:${minifi.c2.version}-maven
ports:
- "10090"
hostname: c2
volumes:
- ./c2-unsecure-rest/conf/minifi-c2-context.xml:/opt/minifi-c2/minifi-c2-${minifi.c2.version}/conf/minifi-c2-context.xml
mocknifi:
image: python:2
ports:
- "8080"
hostname: mocknifi
volumes:
- ./mocknifi-unsecure/server.py:/root/server.py
- ./mocknifi/www:/var/www
working_dir: /var/www
entrypoint:
- python
- /root/server.py

View File

@ -1,48 +0,0 @@
# 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.
#!/usr/bin/env python
import logging
from argparse import ArgumentParser
from BaseHTTPServer import HTTPServer
from os import chdir
from SimpleHTTPServer import SimpleHTTPRequestHandler
from SocketServer import ThreadingMixIn
from ssl import CERT_REQUIRED, wrap_socket
# Needs to be threaded or health check hangs the server
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
pass
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
parser = ArgumentParser(description='Serve up directory with ssl')
parser.add_argument('--dir', default='/var/www')
parser.add_argument('--port', type=int, default=8443)
parser.add_argument('--cert', default='/root/cert.pem')
parser.add_argument('--ca', default='/root/ca.pem')
logging.debug('About to parse arguments')
args = parser.parse_args()
logging.debug('Serving directory ' + args.dir + ' via HTTPS at port ' + str(args.port))
chdir(args.dir)
server = ThreadedHTTPServer(('', args.port), SimpleHTTPRequestHandler)
server.socket = wrap_socket(server.socket, certfile=args.cert, ca_certs=args.ca, server_side=True, cert_reqs=CERT_REQUIRED)
server.serve_forever()

View File

@ -1,44 +0,0 @@
# 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.
#!/usr/bin/env python
import logging
from argparse import ArgumentParser
from BaseHTTPServer import HTTPServer
from os import chdir
from SimpleHTTPServer import SimpleHTTPRequestHandler
from SocketServer import ThreadingMixIn
# Needs to be threaded or health check hangs the server
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
pass
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
parser = ArgumentParser(description='Serve up directory over http')
parser.add_argument('--dir', default='/var/www')
parser.add_argument('--port', type=int, default=8080)
logging.debug('About to parse arguments')
args = parser.parse_args()
logging.debug('Serving directory ' + args.dir + ' via HTTP at port ' + str(args.port))
chdir(args.dir)
server = ThreadedHTTPServer(('', args.port), SimpleHTTPRequestHandler)
server.serve_forever()

View File

@ -1,53 +0,0 @@
{
"generated": "15:41:45 EDT",
"templates": [
{
"id": "f080ec50-ca32-4b36-8453-5a7145bec4c5",
"permissions": {
"canRead": true,
"canWrite": true
},
"template": {
"description": "",
"encoding-version": "1.0",
"groupId": "8f8eda5e-015a-1000-a9c1-b7e4fe10ae83",
"id": "f080ec50-ca32-4b36-8453-5a7145bec4c5",
"name": "raspi3.v1",
"timestamp": "03/07/2017 11:13:03 EST",
"uri": "http://localhost:8080/nifi-api/templates/f080ec50-ca32-4b36-8453-5a7145bec4c5"
}
},
{
"id": "87048385-a6ca-42fe-b2d8-6a563cedd036",
"permissions": {
"canRead": true,
"canWrite": true
},
"template": {
"description": "",
"encoding-version": "1.0",
"groupId": "8f8eda5e-015a-1000-a9c1-b7e4fe10ae83",
"id": "87048385-a6ca-42fe-b2d8-6a563cedd036",
"name": "raspi2.v1",
"timestamp": "03/17/2017 15:41:33 EDT",
"uri": "http://localhost:8080/nifi-api/templates/87048385-a6ca-42fe-b2d8-6a563cedd036"
}
},
{
"id": "dd737a3e-333e-40df-a0bc-d7e28c8e6843",
"permissions": {
"canRead": true,
"canWrite": true
},
"template": {
"description": "",
"encoding-version": "1.0",
"groupId": "8f8eda5e-015a-1000-a9c1-b7e4fe10ae83",
"id": "dd737a3e-333e-40df-a0bc-d7e28c8e6843",
"name": "raspi3.v2",
"timestamp": "03/17/2017 13:22:58 EDT",
"uri": "http://localhost:8080/nifi-api/templates/dd737a3e-333e-40df-a0bc-d7e28c8e6843"
}
}
]
}

View File

@ -1,203 +0,0 @@
<?xml version="1.0" ?>
<!--
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.
-->
<template encoding-version="1.0">
<description></description>
<groupId>8f8eda5e-015a-1000-a9c1-b7e4fe10ae83</groupId>
<name>raspi2.v1</name>
<snippet>
<connections>
<id>8f96f2a9-015a-1000-0000-000000000000</id>
<parentGroupId>8f8eda5e-015a-1000-0000-000000000000</parentGroupId>
<backPressureDataSizeThreshold>1 GB</backPressureDataSizeThreshold>
<backPressureObjectThreshold>10000</backPressureObjectThreshold>
<destination>
<groupId>8f8eda5e-015a-1000-0000-000000000000</groupId>
<id>8f96bf68-015a-1000-0000-000000000000</id>
<type>PROCESSOR</type>
</destination>
<flowFileExpiration>0 sec</flowFileExpiration>
<labelIndex>1</labelIndex>
<name></name>
<selectedRelationships>success</selectedRelationships>
<source>
<groupId>8f8eda5e-015a-1000-0000-000000000000</groupId>
<id>8f96e313-015a-1000-0000-000000000000</id>
<type>PROCESSOR</type>
</source>
<zIndex>0</zIndex>
</connections>
<processors>
<id>8f96bf68-015a-1000-0000-000000000000</id>
<parentGroupId>8f8eda5e-015a-1000-0000-000000000000</parentGroupId>
<position>
<x>14.0</x>
<y>253.0</y>
</position>
<config>
<bulletinLevel>WARN</bulletinLevel>
<comments></comments>
<concurrentlySchedulableTaskCount>1</concurrentlySchedulableTaskCount>
<descriptors>
<entry>
<key>Log Level</key>
<value>
<name>Log Level</name>
</value>
</entry>
<entry>
<key>Log Payload</key>
<value>
<name>Log Payload</name>
</value>
</entry>
<entry>
<key>Attributes to Log</key>
<value>
<name>Attributes to Log</name>
</value>
</entry>
<entry>
<key>Attributes to Ignore</key>
<value>
<name>Attributes to Ignore</name>
</value>
</entry>
<entry>
<key>Log prefix</key>
<value>
<name>Log prefix</name>
</value>
</entry>
</descriptors>
<executionNode>ALL</executionNode>
<lossTolerant>false</lossTolerant>
<penaltyDuration>30 sec</penaltyDuration>
<properties>
<entry>
<key>Log Level</key>
<value>info</value>
</entry>
<entry>
<key>Log Payload</key>
<value>true</value>
</entry>
<entry>
<key>Attributes to Log</key>
</entry>
<entry>
<key>Attributes to Ignore</key>
</entry>
<entry>
<key>Log prefix</key>
</entry>
</properties>
<runDurationMillis>0</runDurationMillis>
<schedulingPeriod>0 sec</schedulingPeriod>
<schedulingStrategy>TIMER_DRIVEN</schedulingStrategy>
<yieldDuration>1 sec</yieldDuration>
</config>
<name>LogAttribute</name>
<relationships>
<autoTerminate>true</autoTerminate>
<name>success</name>
</relationships>
<style></style>
<type>org.apache.nifi.processors.standard.LogAttribute</type>
</processors>
<processors>
<id>8f96e313-015a-1000-0000-000000000000</id>
<parentGroupId>8f8eda5e-015a-1000-0000-000000000000</parentGroupId>
<position>
<x>0.0</x>
<y>0.0</y>
</position>
<config>
<bulletinLevel>WARN</bulletinLevel>
<comments></comments>
<concurrentlySchedulableTaskCount>1</concurrentlySchedulableTaskCount>
<descriptors>
<entry>
<key>File Size</key>
<value>
<name>File Size</name>
</value>
</entry>
<entry>
<key>Batch Size</key>
<value>
<name>Batch Size</name>
</value>
</entry>
<entry>
<key>Data Format</key>
<value>
<name>Data Format</name>
</value>
</entry>
<entry>
<key>Unique FlowFiles</key>
<value>
<name>Unique FlowFiles</name>
</value>
</entry>
<entry>
<key>generate-ff-custom-text</key>
<value>
<name>generate-ff-custom-text</name>
</value>
</entry>
</descriptors>
<executionNode>ALL</executionNode>
<lossTolerant>false</lossTolerant>
<penaltyDuration>30 sec</penaltyDuration>
<properties>
<entry>
<key>File Size</key>
<value>0B</value>
</entry>
<entry>
<key>Batch Size</key>
<value>1</value>
</entry>
<entry>
<key>Data Format</key>
<value>Text</value>
</entry>
<entry>
<key>Unique FlowFiles</key>
<value>false</value>
</entry>
<entry>
<key>generate-ff-custom-text</key>
<value>abcdefg</value>
</entry>
</properties>
<runDurationMillis>0</runDurationMillis>
<schedulingPeriod>0 sec</schedulingPeriod>
<schedulingStrategy>TIMER_DRIVEN</schedulingStrategy>
<yieldDuration>1 sec</yieldDuration>
</config>
<name>GenerateFlowFile</name>
<relationships>
<autoTerminate>false</autoTerminate>
<name>success</name>
</relationships>
<style></style>
<type>org.apache.nifi.processors.standard.GenerateFlowFile</type>
</processors>
</snippet>
<timestamp>03/17/2017 15:41:33 EDT</timestamp>
</template>

View File

@ -1,203 +0,0 @@
<?xml version="1.0" ?>
<!--
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.
-->
<template encoding-version="1.0">
<description></description>
<groupId>8f8eda5e-015a-1000-a9c1-b7e4fe10ae83</groupId>
<name>raspi3.v2</name>
<snippet>
<connections>
<id>8f96f2a9-015a-1000-0000-000000000000</id>
<parentGroupId>8f8eda5e-015a-1000-0000-000000000000</parentGroupId>
<backPressureDataSizeThreshold>1 GB</backPressureDataSizeThreshold>
<backPressureObjectThreshold>10000</backPressureObjectThreshold>
<destination>
<groupId>8f8eda5e-015a-1000-0000-000000000000</groupId>
<id>8f96bf68-015a-1000-0000-000000000000</id>
<type>PROCESSOR</type>
</destination>
<flowFileExpiration>0 sec</flowFileExpiration>
<labelIndex>1</labelIndex>
<name></name>
<selectedRelationships>success</selectedRelationships>
<source>
<groupId>8f8eda5e-015a-1000-0000-000000000000</groupId>
<id>8f96e313-015a-1000-0000-000000000000</id>
<type>PROCESSOR</type>
</source>
<zIndex>0</zIndex>
</connections>
<processors>
<id>8f96bf68-015a-1000-0000-000000000000</id>
<parentGroupId>8f8eda5e-015a-1000-0000-000000000000</parentGroupId>
<position>
<x>14.0</x>
<y>253.0</y>
</position>
<config>
<bulletinLevel>WARN</bulletinLevel>
<comments></comments>
<concurrentlySchedulableTaskCount>1</concurrentlySchedulableTaskCount>
<descriptors>
<entry>
<key>Log Level</key>
<value>
<name>Log Level</name>
</value>
</entry>
<entry>
<key>Log Payload</key>
<value>
<name>Log Payload</name>
</value>
</entry>
<entry>
<key>Attributes to Log</key>
<value>
<name>Attributes to Log</name>
</value>
</entry>
<entry>
<key>Attributes to Ignore</key>
<value>
<name>Attributes to Ignore</name>
</value>
</entry>
<entry>
<key>Log prefix</key>
<value>
<name>Log prefix</name>
</value>
</entry>
</descriptors>
<executionNode>ALL</executionNode>
<lossTolerant>false</lossTolerant>
<penaltyDuration>30 sec</penaltyDuration>
<properties>
<entry>
<key>Log Level</key>
<value>info</value>
</entry>
<entry>
<key>Log Payload</key>
<value>true</value>
</entry>
<entry>
<key>Attributes to Log</key>
</entry>
<entry>
<key>Attributes to Ignore</key>
</entry>
<entry>
<key>Log prefix</key>
</entry>
</properties>
<runDurationMillis>0</runDurationMillis>
<schedulingPeriod>0 sec</schedulingPeriod>
<schedulingStrategy>TIMER_DRIVEN</schedulingStrategy>
<yieldDuration>1 sec</yieldDuration>
</config>
<name>LogAttribute</name>
<relationships>
<autoTerminate>true</autoTerminate>
<name>success</name>
</relationships>
<style></style>
<type>org.apache.nifi.processors.standard.LogAttribute</type>
</processors>
<processors>
<id>8f96e313-015a-1000-0000-000000000000</id>
<parentGroupId>8f8eda5e-015a-1000-0000-000000000000</parentGroupId>
<position>
<x>0.0</x>
<y>0.0</y>
</position>
<config>
<bulletinLevel>WARN</bulletinLevel>
<comments></comments>
<concurrentlySchedulableTaskCount>1</concurrentlySchedulableTaskCount>
<descriptors>
<entry>
<key>File Size</key>
<value>
<name>File Size</name>
</value>
</entry>
<entry>
<key>Batch Size</key>
<value>
<name>Batch Size</name>
</value>
</entry>
<entry>
<key>Data Format</key>
<value>
<name>Data Format</name>
</value>
</entry>
<entry>
<key>Unique FlowFiles</key>
<value>
<name>Unique FlowFiles</name>
</value>
</entry>
<entry>
<key>generate-ff-custom-text</key>
<value>
<name>generate-ff-custom-text</name>
</value>
</entry>
</descriptors>
<executionNode>ALL</executionNode>
<lossTolerant>false</lossTolerant>
<penaltyDuration>30 sec</penaltyDuration>
<properties>
<entry>
<key>File Size</key>
<value>0B</value>
</entry>
<entry>
<key>Batch Size</key>
<value>1</value>
</entry>
<entry>
<key>Data Format</key>
<value>Text</value>
</entry>
<entry>
<key>Unique FlowFiles</key>
<value>false</value>
</entry>
<entry>
<key>generate-ff-custom-text</key>
<value>abcdefg</value>
</entry>
</properties>
<runDurationMillis>0</runDurationMillis>
<schedulingPeriod>0 sec</schedulingPeriod>
<schedulingStrategy>TIMER_DRIVEN</schedulingStrategy>
<yieldDuration>1 sec</yieldDuration>
</config>
<name>GenerateFlowFile</name>
<relationships>
<autoTerminate>false</autoTerminate>
<name>success</name>
</relationships>
<style></style>
<type>org.apache.nifi.processors.standard.GenerateFlowFile</type>
</processors>
</snippet>
<timestamp>03/17/2017 13:22:58 EDT</timestamp>
</template>

View File

@ -1,202 +0,0 @@
<?xml version="1.0" ?>
<!--
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.
-->
<template encoding-version="1.0">
<description></description>
<groupId>8f8eda5e-015a-1000-a9c1-b7e4fe10ae83</groupId>
<name>raspi3.v1</name>
<snippet>
<connections>
<id>8f96f2a9-015a-1000-0000-000000000000</id>
<parentGroupId>8f8eda5e-015a-1000-0000-000000000000</parentGroupId>
<backPressureDataSizeThreshold>1 GB</backPressureDataSizeThreshold>
<backPressureObjectThreshold>10000</backPressureObjectThreshold>
<destination>
<groupId>8f8eda5e-015a-1000-0000-000000000000</groupId>
<id>8f96bf68-015a-1000-0000-000000000000</id>
<type>PROCESSOR</type>
</destination>
<flowFileExpiration>0 sec</flowFileExpiration>
<labelIndex>1</labelIndex>
<name></name>
<selectedRelationships>success</selectedRelationships>
<source>
<groupId>8f8eda5e-015a-1000-0000-000000000000</groupId>
<id>8f96e313-015a-1000-0000-000000000000</id>
<type>PROCESSOR</type>
</source>
<zIndex>0</zIndex>
</connections>
<processors>
<id>8f96bf68-015a-1000-0000-000000000000</id>
<parentGroupId>8f8eda5e-015a-1000-0000-000000000000</parentGroupId>
<position>
<x>14.0</x>
<y>253.0</y>
</position>
<config>
<bulletinLevel>WARN</bulletinLevel>
<comments></comments>
<concurrentlySchedulableTaskCount>1</concurrentlySchedulableTaskCount>
<descriptors>
<entry>
<key>Log prefix</key>
<value>
<name>Log prefix</name>
</value>
</entry>
<entry>
<key>Log Level</key>
<value>
<name>Log Level</name>
</value>
</entry>
<entry>
<key>Attributes to Ignore</key>
<value>
<name>Attributes to Ignore</name>
</value>
</entry>
<entry>
<key>Attributes to Log</key>
<value>
<name>Attributes to Log</name>
</value>
</entry>
<entry>
<key>Log Payload</key>
<value>
<name>Log Payload</name>
</value>
</entry>
</descriptors>
<executionNode>ALL</executionNode>
<lossTolerant>false</lossTolerant>
<penaltyDuration>30 sec</penaltyDuration>
<properties>
<entry>
<key>Log prefix</key>
</entry>
<entry>
<key>Log Level</key>
<value>info</value>
</entry>
<entry>
<key>Attributes to Ignore</key>
</entry>
<entry>
<key>Attributes to Log</key>
</entry>
<entry>
<key>Log Payload</key>
<value>true</value>
</entry>
</properties>
<runDurationMillis>0</runDurationMillis>
<schedulingPeriod>0 sec</schedulingPeriod>
<schedulingStrategy>TIMER_DRIVEN</schedulingStrategy>
<yieldDuration>1 sec</yieldDuration>
</config>
<name>LogAttribute</name>
<relationships>
<autoTerminate>true</autoTerminate>
<name>success</name>
</relationships>
<style></style>
<type>org.apache.nifi.processors.standard.LogAttribute</type>
</processors>
<processors>
<id>8f96e313-015a-1000-0000-000000000000</id>
<parentGroupId>8f8eda5e-015a-1000-0000-000000000000</parentGroupId>
<position>
<x>0.0</x>
<y>0.0</y>
</position>
<config>
<bulletinLevel>WARN</bulletinLevel>
<comments></comments>
<concurrentlySchedulableTaskCount>1</concurrentlySchedulableTaskCount>
<descriptors>
<entry>
<key>File Size</key>
<value>
<name>File Size</name>
</value>
</entry>
<entry>
<key>generate-ff-custom-text</key>
<value>
<name>generate-ff-custom-text</name>
</value>
</entry>
<entry>
<key>Batch Size</key>
<value>
<name>Batch Size</name>
</value>
</entry>
<entry>
<key>Unique FlowFiles</key>
<value>
<name>Unique FlowFiles</name>
</value>
</entry>
<entry>
<key>Data Format</key>
<value>
<name>Data Format</name>
</value>
</entry>
</descriptors>
<executionNode>ALL</executionNode>
<lossTolerant>false</lossTolerant>
<penaltyDuration>30 sec</penaltyDuration>
<properties>
<entry>
<key>File Size</key>
<value>0B</value>
</entry>
<entry>
<key>generate-ff-custom-text</key>
</entry>
<entry>
<key>Batch Size</key>
<value>1</value>
</entry>
<entry>
<key>Unique FlowFiles</key>
<value>false</value>
</entry>
<entry>
<key>Data Format</key>
<value>Text</value>
</entry>
</properties>
<runDurationMillis>0</runDurationMillis>
<schedulingPeriod>0 sec</schedulingPeriod>
<schedulingStrategy>TIMER_DRIVEN</schedulingStrategy>
<yieldDuration>1 sec</yieldDuration>
</config>
<name>GenerateFlowFile</name>
<relationships>
<autoTerminate>false</autoTerminate>
<name>success</name>
</relationships>
<style></style>
<type>org.apache.nifi.processors.standard.GenerateFlowFile</type>
</processors>
</snippet>
<timestamp>03/07/2017 11:13:03 EST</timestamp>
</template>

View File

@ -38,7 +38,6 @@ import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -52,10 +51,6 @@ public class DelegatingConfigurationProvider implements ConfigurationProvider {
private final HttpConnector httpConnector; private final HttpConnector httpConnector;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
public DelegatingConfigurationProvider(ConfigurationCache configurationCache, String delegateUrl) throws InvalidParameterException, GeneralSecurityException, IOException {
this(configurationCache, new HttpConnector(delegateUrl));
}
public DelegatingConfigurationProvider(ConfigurationCache configurationCache, HttpConnector httpConnector) { public DelegatingConfigurationProvider(ConfigurationCache configurationCache, HttpConnector httpConnector) {
this.configurationCache = configurationCache; this.configurationCache = configurationCache;
this.httpConnector = httpConnector; this.httpConnector = httpConnector;

View File

@ -57,7 +57,7 @@ public class DelegatingConfigurationProviderTest {
@BeforeEach @BeforeEach
public void setup() throws ConfigurationProviderException { public void setup() throws ConfigurationProviderException {
contentType = "text/yml"; contentType = "test/json";
version = 2; version = 2;
parameters = new HashMap<>(); parameters = new HashMap<>();
parameters.put("net", Collections.singletonList("edge")); parameters.put("net", Collections.singletonList("edge"));

View File

@ -1,188 +0,0 @@
/*
* 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.minifi.c2.provider.nifi.rest;
import com.fasterxml.jackson.core.JsonFactory;
import org.apache.nifi.minifi.c2.api.Configuration;
import org.apache.nifi.minifi.c2.api.ConfigurationProvider;
import org.apache.nifi.minifi.c2.api.ConfigurationProviderException;
import org.apache.nifi.minifi.c2.api.InvalidParameterException;
import org.apache.nifi.minifi.c2.api.cache.ConfigurationCache;
import org.apache.nifi.minifi.c2.api.cache.WriteableConfiguration;
import org.apache.nifi.minifi.c2.api.util.Pair;
import org.apache.nifi.minifi.c2.provider.util.HttpConnector;
import org.apache.nifi.minifi.commons.schema.ConfigSchema;
import org.apache.nifi.minifi.commons.schema.serialization.SchemaSaver;
import org.apache.nifi.minifi.toolkit.configuration.ConfigMain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.bind.JAXBException;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class NiFiRestConfigurationProvider implements ConfigurationProvider {
public static final String CONTENT_TYPE = "text/yml";
private static final Logger logger = LoggerFactory.getLogger(NiFiRestConfigurationProvider.class);
private final JsonFactory jsonFactory = new JsonFactory();
private final ConfigurationCache configurationCache;
private final HttpConnector httpConnector;
private final String templateNamePattern;
public NiFiRestConfigurationProvider(ConfigurationCache configurationCache, String nifiUrl, String templateNamePattern) throws InvalidParameterException, GeneralSecurityException, IOException {
this(configurationCache, new HttpConnector(nifiUrl), templateNamePattern);
}
public NiFiRestConfigurationProvider(ConfigurationCache configurationCache, HttpConnector httpConnector, String templateNamePattern) {
this.configurationCache = configurationCache;
this.httpConnector = httpConnector;
this.templateNamePattern = templateNamePattern;
}
@Override
public List<String> getContentTypes() {
return Collections.singletonList(CONTENT_TYPE);
}
@Override
public Configuration getConfiguration(String contentType, Integer version, Map<String, List<String>> parameters) throws ConfigurationProviderException {
if (!CONTENT_TYPE.equals(contentType)) {
throw new ConfigurationProviderException("Unsupported content type: " + contentType + " supported value is " + CONTENT_TYPE);
}
String filename = templateNamePattern;
for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
if (entry.getValue().size() != 1) {
throw new InvalidParameterException("Multiple values for same parameter not supported in this provider.");
}
filename = filename.replaceAll(Pattern.quote("${" + entry.getKey() + "}"), entry.getValue().get(0));
}
int index = filename.indexOf("${");
while (index != -1) {
int endIndex = filename.indexOf("}", index);
if (endIndex == -1) {
break;
}
String variable = filename.substring(index + 2, endIndex);
if (!"version".equals(variable)) {
throw new InvalidParameterException("Found unsubstituted parameter " + variable);
}
index = endIndex + 1;
}
String id = null;
if (version == null) {
String filenamePattern = Arrays.stream(filename.split(Pattern.quote("${version}"), -1)).map(Pattern::quote).collect(Collectors.joining("([0-9]+)"));
Pair<String, Integer> maxIdAndVersion = getMaxIdAndVersion(filenamePattern);
id = maxIdAndVersion.getFirst();
version = maxIdAndVersion.getSecond();
}
filename = filename.replaceAll(Pattern.quote("${version}"), Integer.toString(version));
WriteableConfiguration configuration = configurationCache.getCacheFileInfo(contentType, parameters).getConfiguration(version);
if (configuration.exists()) {
if (logger.isDebugEnabled()) {
logger.debug("Configuration " + configuration + " exists and can be served from configurationCache.");
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Configuration " + configuration + " doesn't exist, will need to download and convert template.");
}
if (id == null) {
try {
String tmpFilename = templateNamePattern;
for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
if (entry.getValue().size() != 1) {
throw new InvalidParameterException("Multiple values for same parameter not supported in this provider.");
}
tmpFilename = tmpFilename.replaceAll(Pattern.quote("${" + entry.getKey() + "}"), entry.getValue().get(0));
}
Pair<Stream<Pair<String, String>>, Closeable> streamCloseablePair = getIdAndFilenameStream();
try {
String finalFilename = filename;
id = streamCloseablePair.getFirst().filter(p -> finalFilename.equals(p.getSecond())).map(Pair::getFirst).findFirst()
.orElseThrow(() -> new InvalidParameterException("Unable to find template named " + finalFilename));
} finally {
streamCloseablePair.getSecond().close();
}
} catch (IOException|TemplatesIteratorException e) {
throw new ConfigurationProviderException("Unable to retrieve template list", e);
}
}
HttpURLConnection urlConnection = httpConnector.get("/templates/" + id + "/download");
try (InputStream inputStream = urlConnection.getInputStream()){
ConfigSchema configSchema = ConfigMain.transformTemplateToSchema(inputStream);
SchemaSaver.saveConfigSchema(configSchema, configuration.getOutputStream());
} catch (IOException e) {
throw new ConfigurationProviderException("Unable to download template from url " + urlConnection.getURL(), e);
} catch (JAXBException e) {
throw new ConfigurationProviderException("Unable to convert template to yaml", e);
} finally {
urlConnection.disconnect();
}
}
return configuration;
}
private Pair<Stream<Pair<String, String>>, Closeable> getIdAndFilenameStream() throws ConfigurationProviderException, IOException {
TemplatesIterator templatesIterator = new TemplatesIterator(httpConnector, jsonFactory);
return new Pair<>(StreamSupport.stream(Spliterators.spliteratorUnknownSize(templatesIterator, Spliterator.ORDERED), false), templatesIterator);
}
private Pair<Stream<Pair<String, Integer>>, Closeable> getIdAndVersionStream(String filenamePattern) throws ConfigurationProviderException, IOException {
Pattern filename = Pattern.compile(filenamePattern);
Pair<Stream<Pair<String, String>>, Closeable> streamCloseablePair = getIdAndFilenameStream();
return new Pair<>(streamCloseablePair.getFirst().map(p -> {
Matcher matcher = filename.matcher(p.getSecond());
if (!matcher.matches()) {
return null;
}
return new Pair<>(p.getFirst(), Integer.parseInt(matcher.group(1)));
}).filter(Objects::nonNull), streamCloseablePair.getSecond());
}
private Pair<String, Integer> getMaxIdAndVersion(String filenamePattern) throws ConfigurationProviderException {
try {
Pair<Stream<Pair<String, Integer>>, Closeable> streamCloseablePair = getIdAndVersionStream(filenamePattern);
try {
return streamCloseablePair.getFirst().sorted(Comparator.comparing(p -> ((Pair<String, Integer>) p).getSecond()).reversed()).findFirst()
.orElseThrow(() -> new ConfigurationProviderException("Didn't find any templates that matched " + filenamePattern));
} finally {
streamCloseablePair.getSecond().close();
}
} catch (IOException|TemplatesIteratorException e) {
throw new ConfigurationProviderException("Unable to retrieve template list", e);
}
}
}

View File

@ -1,116 +0,0 @@
/*
* 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.minifi.c2.provider.nifi.rest;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import org.apache.nifi.minifi.c2.api.ConfigurationProviderException;
import org.apache.nifi.minifi.c2.api.util.Pair;
import org.apache.nifi.minifi.c2.provider.util.HttpConnector;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class TemplatesIterator implements Iterator<Pair<String, String>>, Closeable {
public static final String FLOW_TEMPLATES = "/flow/templates";
private final HttpURLConnection urlConnection;
private final InputStream inputStream;
private final JsonParser parser;
private Pair<String, String> next;
public TemplatesIterator(HttpConnector httpConnector, JsonFactory jsonFactory) throws ConfigurationProviderException, IOException {
urlConnection = httpConnector.get(FLOW_TEMPLATES);
inputStream = urlConnection.getInputStream();
parser = jsonFactory.createParser(inputStream);
while (parser.nextToken() != JsonToken.END_OBJECT) {
if ("templates".equals(parser.getCurrentName())) {
break;
}
}
next = getNext();
}
private Pair<String, String> getNext() throws IOException {
while (parser.nextToken() != JsonToken.END_ARRAY) {
if ("template".equals(parser.getCurrentName())) {
String id = null;
String name = null;
while (parser.nextToken() != JsonToken.END_OBJECT) {
String currentName = parser.getCurrentName();
if ("id".equals(currentName)) {
parser.nextToken();
id = parser.getText();
} else if ("name".equals(currentName)) {
parser.nextToken();
name = parser.getText();
}
}
return new Pair<>(id, name);
}
}
return null;
}
@Override
public boolean hasNext() {
return next != null;
}
@Override
public Pair<String, String> next() {
if (next == null) {
throw new NoSuchElementException();
}
try {
return next;
} finally {
try {
next = getNext();
} catch (IOException e) {
throw new TemplatesIteratorException(e);
}
}
}
@Override
public void close() throws IOException {
if (parser != null) {
try {
parser.close();
} catch (IOException e) {
//Ignore
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
//Ignore
}
}
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}

View File

@ -1,66 +0,0 @@
/*
* 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.minifi.c2.provider.nifi.rest;
import org.apache.nifi.minifi.c2.api.cache.ConfigurationCache;
import org.apache.nifi.minifi.c2.provider.util.HttpConnector;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Comparator;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
public class NiFiRestConfigurationProviderTest {
private ConfigurationCache configConfigurationCache;
private HttpConnector httpConnector;
private NiFiRestConfigurationProvider niFiRestConfigurationProvider;
private Path cachePath;
@BeforeEach
public void setup() throws IOException {
configConfigurationCache = mock(ConfigurationCache.class);
httpConnector = mock(HttpConnector.class);
niFiRestConfigurationProvider = new NiFiRestConfigurationProvider(configConfigurationCache, httpConnector, "${class}.v${version}");
cachePath = Files.createTempDirectory(NiFiRestConfigurationProviderTest.class.getCanonicalName());
}
@AfterEach
public void teardown() throws IOException {
Files.walk(cachePath)
.sorted(Comparator.reverseOrder())
.forEach(p -> {
try {
Files.deleteIfExists(p);
} catch (IOException e) {
p.toFile().deleteOnExit();
}
});
}
@Test
public void testContentType() {
assertEquals(Collections.singletonList(NiFiRestConfigurationProvider.CONTENT_TYPE), niFiRestConfigurationProvider.getContentTypes());
}
}

View File

@ -1,110 +0,0 @@
/*
* 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.minifi.c2.provider.nifi.rest;
import com.fasterxml.jackson.core.JsonFactory;
import org.apache.nifi.minifi.c2.api.ConfigurationProviderException;
import org.apache.nifi.minifi.c2.api.util.Pair;
import org.apache.nifi.minifi.c2.provider.util.HttpConnector;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class TemplatesIteratorTest {
private JsonFactory jsonFactory;
private HttpURLConnection httpURLConnection;
private HttpConnector httpConnector;
@BeforeEach
public void setup() throws ConfigurationProviderException {
jsonFactory = new JsonFactory();
httpURLConnection = mock(HttpURLConnection.class);
httpConnector = mock(HttpConnector.class);
when(httpConnector.get(TemplatesIterator.FLOW_TEMPLATES)).thenReturn(httpURLConnection);
}
@Test
public void testIteratorNoSuchElementException() throws ConfigurationProviderException, IOException {
when(httpURLConnection.getInputStream()).thenReturn(TemplatesIteratorTest.class.getClassLoader().getResourceAsStream("noTemplates.json"));
try (TemplatesIterator templatesIterator = new TemplatesIterator(httpConnector, jsonFactory)) {
assertFalse(templatesIterator.hasNext());
assertThrows(NoSuchElementException.class, templatesIterator::next);
} finally {
verify(httpURLConnection).disconnect();
}
}
@Test
public void testIteratorNoTemplates() throws ConfigurationProviderException, IOException {
when(httpURLConnection.getInputStream()).thenReturn(TemplatesIteratorTest.class.getClassLoader().getResourceAsStream("noTemplates.json"));
List<Pair<String, String>> idToNameList = new ArrayList<>();
try (TemplatesIterator templatesIterator = new TemplatesIterator(httpConnector, jsonFactory)) {
templatesIterator.forEachRemaining(idToNameList::add);
}
assertEquals(0, idToNameList.size());
verify(httpURLConnection).disconnect();
}
@Test
public void testIteratorSingleTemplate() throws ConfigurationProviderException, IOException {
when(httpURLConnection.getInputStream()).thenReturn(TemplatesIteratorTest.class.getClassLoader().getResourceAsStream("oneTemplate.json"));
List<Pair<String, String>> idToNameList = new ArrayList<>();
try (TemplatesIterator templatesIterator = new TemplatesIterator(httpConnector, jsonFactory)) {
templatesIterator.forEachRemaining(idToNameList::add);
}
assertEquals(1, idToNameList.size());
Pair<String, String> idNamePair = idToNameList.get(0);
assertEquals("d05845ae-ceda-4c50-b7c2-037e42ddf1d3", idNamePair.getFirst());
assertEquals("raspi3.v1", idNamePair.getSecond());
verify(httpURLConnection).disconnect();
}
@Test
public void testIteratorTwoTemplates() throws ConfigurationProviderException, IOException {
when(httpURLConnection.getInputStream()).thenReturn(TemplatesIteratorTest.class.getClassLoader().getResourceAsStream("twoTemplates.json"));
List<Pair<String, String>> idToNameList = new ArrayList<>();
try (TemplatesIterator templatesIterator = new TemplatesIterator(httpConnector, jsonFactory)) {
templatesIterator.forEachRemaining(idToNameList::add);
}
assertEquals(2, idToNameList.size());
Pair<String, String> idNamePair = idToNameList.get(0);
assertEquals("d05845ae-ceda-4c50-b7c2-037e42ddf1d3", idNamePair.getFirst());
assertEquals("raspi3.v1", idNamePair.getSecond());
idNamePair = idToNameList.get(1);
assertEquals("9384b48d-85b4-478a-bf3e-64d113f8fbc5", idNamePair.getFirst());
assertEquals("raspi3.v2", idNamePair.getSecond());
verify(httpURLConnection).disconnect();
}
}

View File

@ -1 +0,0 @@
{"templates":[],"generated":"14:55:13 EST"}

View File

@ -1,21 +0,0 @@
{
"generated": "10:26:04 EST",
"templates": [
{
"id": "d05845ae-ceda-4c50-b7c2-037e42ddf1d3",
"permissions": {
"canRead": true,
"canWrite": true
},
"template": {
"description": "",
"encoding-version": "1.0",
"groupId": "8f8eda5e-015a-1000-a9c1-b7e4fe10ae83",
"id": "d05845ae-ceda-4c50-b7c2-037e42ddf1d3",
"name": "raspi3.v1",
"timestamp": "03/02/2017 10:26:01 EST",
"uri": "http://localhost:8081/nifi-api/templates/d05845ae-ceda-4c50-b7c2-037e42ddf1d3"
}
}
]
}

View File

@ -1,37 +0,0 @@
{
"generated": "14:53:15 EST",
"templates": [
{
"id": "d05845ae-ceda-4c50-b7c2-037e42ddf1d3",
"permissions": {
"canRead": true,
"canWrite": true
},
"template": {
"description": "",
"encoding-version": "1.0",
"groupId": "8f8eda5e-015a-1000-a9c1-b7e4fe10ae83",
"id": "d05845ae-ceda-4c50-b7c2-037e42ddf1d3",
"name": "raspi3.v1",
"timestamp": "03/02/2017 10:26:01 EST",
"uri": "http://localhost:8081/nifi-api/templates/d05845ae-ceda-4c50-b7c2-037e42ddf1d3"
}
},
{
"id": "9384b48d-85b4-478a-bf3e-64d113f8fbc5",
"permissions": {
"canRead": true,
"canWrite": true
},
"template": {
"description": "",
"encoding-version": "1.0",
"groupId": "8f8eda5e-015a-1000-a9c1-b7e4fe10ae83",
"id": "9384b48d-85b4-478a-bf3e-64d113f8fbc5",
"name": "raspi3.v2",
"timestamp": "03/02/2017 13:08:14 EST",
"uri": "http://localhost:8081/nifi-api/templates/9384b48d-85b4-478a-bf3e-64d113f8fbc5"
}
}
]
}

View File

@ -29,6 +29,5 @@ limitations under the License.
<module>minifi-c2-provider-util</module> <module>minifi-c2-provider-util</module>
<module>minifi-c2-provider-cache</module> <module>minifi-c2-provider-cache</module>
<module>minifi-c2-provider-delegating</module> <module>minifi-c2-provider-delegating</module>
<module>minifi-c2-provider-nifi-rest</module>
</modules> </modules>
</project> </project>

View File

@ -16,7 +16,8 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.apache.nifi.minifi</groupId> <groupId>org.apache.nifi.minifi</groupId>

View File

@ -19,5 +19,7 @@ package org.apache.nifi.minifi.commons.api;
public interface MiNiFiConstants { public interface MiNiFiConstants {
String BOOTSTRAP_UPDATED_FILE_NAME = "bootstrap-updated.conf"; String BOOTSTRAP_UPDATED_FILE_NAME = "bootstrap-updated.conf";
String CONFIG_UPDATED_FILE_NAME = "config-updated.yml";
String BACKUP_EXTENSION = ".backup";
String RAW_EXTENSION = ".raw";
} }

View File

@ -15,17 +15,19 @@
* limitations under the License. * limitations under the License.
*/ */
package org.apache.nifi.minifi; package org.apache.nifi.minifi.commons.api;
import static org.apache.nifi.minifi.MiNiFiProperties.ValidatorNames.BOOLEAN_VALIDATOR; import static java.util.stream.Collectors.toSet;
import static org.apache.nifi.minifi.MiNiFiProperties.ValidatorNames.LONG_VALIDATOR; import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.ValidatorNames.BOOLEAN_VALIDATOR;
import static org.apache.nifi.minifi.MiNiFiProperties.ValidatorNames.NON_NEGATIVE_INTEGER_VALIDATOR; import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.ValidatorNames.LONG_VALIDATOR;
import static org.apache.nifi.minifi.MiNiFiProperties.ValidatorNames.PORT_VALIDATOR; import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.ValidatorNames.NON_NEGATIVE_INTEGER_VALIDATOR;
import static org.apache.nifi.minifi.MiNiFiProperties.ValidatorNames.TIME_PERIOD_VALIDATOR; import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.ValidatorNames.PORT_VALIDATOR;
import static org.apache.nifi.minifi.MiNiFiProperties.ValidatorNames.VALID; import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.ValidatorNames.TIME_PERIOD_VALIDATOR;
import static org.apache.nifi.minifi.commons.api.MiNiFiProperties.ValidatorNames.VALID;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -35,39 +37,6 @@ public enum MiNiFiProperties {
LIB_DIR("lib.dir", "./lib", false, false, VALID), LIB_DIR("lib.dir", "./lib", false, false, VALID),
CONF_DIR("conf.dir", "./conf", false, false, VALID), CONF_DIR("conf.dir", "./conf", false, false, VALID),
GRACEFUL_SHUTDOWN_SECOND("graceful.shutdown.seconds", "20", false, true, NON_NEGATIVE_INTEGER_VALIDATOR), GRACEFUL_SHUTDOWN_SECOND("graceful.shutdown.seconds", "20", false, true, NON_NEGATIVE_INTEGER_VALIDATOR),
NIFI_MINIFI_CONFIG("nifi.minifi.config", "./conf/config.yml", false, true, VALID),
NIFI_MINIFI_SECURITY_KEYSTORE("nifi.minifi.security.keystore", null, false, false, VALID),
NIFI_MINIFI_SECURITY_KEYSTORE_TYPE("nifi.minifi.security.keystoreType", null, false, false, VALID),
NIFI_MINIFI_SECURITY_KEYSTORE_PASSWD("nifi.minifi.security.keystorePasswd", null, true, false, VALID),
NIFI_MINIFI_SECURITY_KEY_PASSWD("nifi.minifi.security.keyPasswd", null, true, false, VALID),
NIFI_MINIFI_SECURITY_TRUSTSTORE("nifi.minifi.security.truststore", null, false, false, VALID),
NIFI_MINIFI_SECURITY_TRUSTSTORE_TYPE("nifi.minifi.security.truststoreType", null, false, false, VALID),
NIFI_MINIFI_SECURITY_TRUSTSTORE_PASSWD("nifi.minifi.security.truststorePasswd", null, true, false, VALID),
NIFI_MINIFI_SECURITY_SSL_PROTOCOL("nifi.minifi.security.ssl.protocol", null, false, false, VALID),
NIFI_MINIFI_SENSITIVE_PROPS_KEY("nifi.minifi.sensitive.props.key", null, true, false, VALID),
NIFI_MINIFI_SENSITIVE_PROPS_ALGORITHM("nifi.minifi.sensitive.props.algorithm", null, true, false, VALID),
NIFI_MINIFI_PROVENANCE_REPORTING_COMMENT("nifi.minifi.provenance.reporting.comment", null, false, true, VALID),
NIFI_MINIFI_PROVENANCE_REPORTING_SCHEDULING_STRATEGY("nifi.minifi.provenance.reporting.scheduling.strategy", null, false, true, VALID),
NIFI_MINIFI_PROVENANCE_REPORTING_SCHEDULING_PERIOD("nifi.minifi.provenance.reporting.scheduling.period", null, false, true, TIME_PERIOD_VALIDATOR),
NIFI_MINIFI_PROVENANCE_REPORTING_DESTINATION_URL("nifi.minifi.provenance.reporting.destination.url", null, false, true, VALID),
NIFI_MINIFI_PROVENANCE_REPORTING_INPUT_PORT_NAME("nifi.minifi.provenance.reporting.input.port.name", null, false, true, VALID),
NIFI_MINIFI_PROVENANCE_REPORTING_INSTANCE_URL("nifi.minifi.provenance.reporting.instance.url", null, false, true, VALID),
NIFI_MINIFI_PROVENANCE_REPORTING_BATCH_SIZE("nifi.minifi.provenance.reporting.batch.size", null, false, true, NON_NEGATIVE_INTEGER_VALIDATOR),
NIFI_MINIFI_PROVENANCE_REPORTING_COMMUNICATIONS_TIMEOUT("nifi.minifi.provenance.reporting.communications.timeout", null, false, true, TIME_PERIOD_VALIDATOR),
NIFI_MINIFI_FLOW_USE_PARENT_SSL("nifi.minifi.flow.use.parent.ssl", null, false, true, VALID),
NIFI_MINIFI_NOTIFIER_INGESTORS("nifi.minifi.notifier.ingestors", null, false, true, VALID),
NIFI_MINIFI_NOTIFIER_INGESTORS_FILE_CONFIG_PATH("nifi.minifi.notifier.ingestors.file.config.path", null, false, true, VALID),
NIFI_MINIFI_NOTIFIER_INGESTORS_FILE_POLLING_PERIOD_SECONDS("nifi.minifi.notifier.ingestors.file.polling.period.seconds", null, false, true, NON_NEGATIVE_INTEGER_VALIDATOR),
NIFI_MINIFI_NOTIFIER_INGESTORS_RECEIVE_HTTP_PORT("nifi.minifi.notifier.ingestors.receive.http.port", null, false, true, PORT_VALIDATOR),
NIFI_MINIFI_NOTIFIER_INGESTORS_PULL_HTTP_HOSTNAME("nifi.minifi.notifier.ingestors.pull.http.hostname", null, false, true, VALID),
NIFI_MINIFI_NOTIFIER_INGESTORS_PULL_HTTP_PORT("nifi.minifi.notifier.ingestors.pull.http.port", null, false, true, PORT_VALIDATOR),
NIFI_MINIFI_NOTIFIER_INGESTORS_PULL_HTTP_PATH("nifi.minifi.notifier.ingestors.pull.http.path", null, false, true, VALID),
NIFI_MINIFI_NOTIFIER_INGESTORS_PULL_HTTP_QUERY("nifi.minifi.notifier.ingestors.pull.http.query", null, false, true, VALID),
NIFI_MINIFI_NOTIFIER_INGESTORS_PULL_HTTP_PERIOD_MS("nifi.minifi.notifier.ingestors.pull.http.period.ms", null, false, true, NON_NEGATIVE_INTEGER_VALIDATOR),
NIFI_MINIFI_STATUS_REPORTER_COMPONENTS("nifi.minifi.status.reporter.components", null, false, true, VALID),
NIFI_MINIFI_STATUS_REPORTER_LOG_QUERY("nifi.minifi.status.reporter.log.query", null, false, true, VALID),
NIFI_MINIFI_STATUS_REPORTER_LOG_LEVEL("nifi.minifi.status.reporter.log.level", null, false, true, VALID),
NIFI_MINIFI_STATUS_REPORTER_LOG_PERIOD("nifi.minifi.status.reporter.log.period", null, false, true, VALID),
JAVA_ARG_1("java.arg.1", null, false, true, VALID), JAVA_ARG_1("java.arg.1", null, false, true, VALID),
JAVA_ARG_2("java.arg.2", null, false, true, VALID), JAVA_ARG_2("java.arg.2", null, false, true, VALID),
JAVA_ARG_3("java.arg.3", null, false, true, VALID), JAVA_ARG_3("java.arg.3", null, false, true, VALID),
@ -82,7 +51,24 @@ public enum MiNiFiProperties {
JAVA_ARG_12("java.arg.12", null, false, true, VALID), JAVA_ARG_12("java.arg.12", null, false, true, VALID),
JAVA_ARG_13("java.arg.13", null, false, true, VALID), JAVA_ARG_13("java.arg.13", null, false, true, VALID),
JAVA_ARG_14("java.arg.14", null, false, true, VALID), JAVA_ARG_14("java.arg.14", null, false, true, VALID),
NIFI_MINIFI_FLOW_CONFIG("nifi.minifi.flow.config", "./conf/flow.json.gz", false, false, VALID),
NIFI_MINIFI_FLOW_MAX_CONCURRENT_THREADS("nifi.minifi.flow.max.concurrent.threads", null, false, true, VALID),
NIFI_MINIFI_SECURITY_KEYSTORE("nifi.minifi.security.keystore", null, false, false, VALID),
NIFI_MINIFI_SECURITY_KEYSTORE_TYPE("nifi.minifi.security.keystoreType", null, false, false, VALID),
NIFI_MINIFI_SECURITY_KEYSTORE_PASSWD("nifi.minifi.security.keystorePasswd", null, true, false, VALID),
NIFI_MINIFI_SECURITY_KEY_PASSWD("nifi.minifi.security.keyPasswd", null, true, false, VALID),
NIFI_MINIFI_SECURITY_TRUSTSTORE("nifi.minifi.security.truststore", null, false, false, VALID),
NIFI_MINIFI_SECURITY_TRUSTSTORE_TYPE("nifi.minifi.security.truststoreType", null, false, false, VALID),
NIFI_MINIFI_SECURITY_TRUSTSTORE_PASSWD("nifi.minifi.security.truststorePasswd", null, true, false, VALID),
NIFI_MINIFI_SECURITY_SSL_PROTOCOL("nifi.minifi.security.ssl.protocol", null, false, false, VALID),
NIFI_MINIFI_FLOW_USE_PARENT_SSL("nifi.minifi.flow.use.parent.ssl", null, false, true, VALID),
NIFI_MINIFI_SENSITIVE_PROPS_KEY("nifi.minifi.sensitive.props.key", null, true, false, VALID),
NIFI_MINIFI_SENSITIVE_PROPS_ALGORITHM("nifi.minifi.sensitive.props.algorithm", null, true, false, VALID),
C2_ENABLE("c2.enable", "false", false, true, BOOLEAN_VALIDATOR), C2_ENABLE("c2.enable", "false", false, true, BOOLEAN_VALIDATOR),
C2_AGENT_HEARTBEAT_PERIOD("c2.agent.heartbeat.period", "1000", false, true, LONG_VALIDATOR),
C2_AGENT_CLASS("c2.agent.class", "", false, true, VALID),
C2_AGENT_IDENTIFIER("c2.agent.identifier", null, false, true, VALID),
C2_FULL_HEARTBEAT("c2.full.heartbeat", "true", false, true, BOOLEAN_VALIDATOR),
C2_REST_URL("c2.rest.url", "", false, true, VALID), C2_REST_URL("c2.rest.url", "", false, true, VALID),
C2_REST_URL_ACK("c2.rest.url.ack", "", false, true, VALID), C2_REST_URL_ACK("c2.rest.url.ack", "", false, true, VALID),
C2_REST_PATH_BASE("c2.rest.path.base", "", false, true, VALID), C2_REST_PATH_BASE("c2.rest.path.base", "", false, true, VALID),
@ -93,13 +79,11 @@ public enum MiNiFiProperties {
C2_REST_CALL_TIMEOUT("c2.rest.callTimeout", "10 sec", false, true, TIME_PERIOD_VALIDATOR), C2_REST_CALL_TIMEOUT("c2.rest.callTimeout", "10 sec", false, true, TIME_PERIOD_VALIDATOR),
C2_MAX_IDLE_CONNECTIONS("c2.rest.maxIdleConnections", "5", false, true, NON_NEGATIVE_INTEGER_VALIDATOR), C2_MAX_IDLE_CONNECTIONS("c2.rest.maxIdleConnections", "5", false, true, NON_NEGATIVE_INTEGER_VALIDATOR),
C2_KEEP_ALIVE_DURATION("c2.rest.keepAliveDuration", "5 min", false, true, TIME_PERIOD_VALIDATOR), C2_KEEP_ALIVE_DURATION("c2.rest.keepAliveDuration", "5 min", false, true, TIME_PERIOD_VALIDATOR),
C2_AGENT_HEARTBEAT_PERIOD("c2.agent.heartbeat.period", "1000", false, true, LONG_VALIDATOR), C2_REST_HTTP_HEADERS("c2.rest.http.headers", "Accept:application/json", false, true, VALID),
C2_AGENT_CLASS("c2.agent.class", "", false, true, VALID),
C2_CONFIG_DIRECTORY("c2.config.directory", "./conf", false, true, VALID), C2_CONFIG_DIRECTORY("c2.config.directory", "./conf", false, true, VALID),
C2_RUNTIME_MANIFEST_IDENTIFIER("c2.runtime.manifest.identifier", "", false, true, VALID), C2_RUNTIME_MANIFEST_IDENTIFIER("c2.runtime.manifest.identifier", "", false, true, VALID),
C2_RUNTIME_TYPE("c2.runtime.type", "", false, true, VALID), C2_RUNTIME_TYPE("c2.runtime.type", "", false, true, VALID),
C2_AGENT_IDENTIFIER("c2.agent.identifier", null, false, true, VALID), C2_ASSET_DIRECTORY("c2.asset.directory", "./asset", false, true, VALID),
C2_FULL_HEARTBEAT("c2.full.heartbeat", "true", false, true, BOOLEAN_VALIDATOR),
C2_SECURITY_TRUSTSTORE_LOCATION("c2.security.truststore.location", "", false, false, VALID), C2_SECURITY_TRUSTSTORE_LOCATION("c2.security.truststore.location", "", false, false, VALID),
C2_SECURITY_TRUSTSTORE_PASSWORD("c2.security.truststore.password", "", true, false, VALID), C2_SECURITY_TRUSTSTORE_PASSWORD("c2.security.truststore.password", "", true, false, VALID),
C2_SECURITY_TRUSTSTORE_TYPE("c2.security.truststore.type", "JKS", false, false, VALID), C2_SECURITY_TRUSTSTORE_TYPE("c2.security.truststore.type", "JKS", false, false, VALID),
@ -107,11 +91,35 @@ public enum MiNiFiProperties {
C2_SECURITY_KEYSTORE_PASSWORD("c2.security.keystore.password", "", true, false, VALID), C2_SECURITY_KEYSTORE_PASSWORD("c2.security.keystore.password", "", true, false, VALID),
C2_SECURITY_KEYSTORE_TYPE("c2.security.keystore.type", "JKS", false, false, VALID), C2_SECURITY_KEYSTORE_TYPE("c2.security.keystore.type", "JKS", false, false, VALID),
C2_REQUEST_COMPRESSION("c2.request.compression", "none", false, true, VALID), C2_REQUEST_COMPRESSION("c2.request.compression", "none", false, true, VALID),
C2_ASSET_DIRECTORY("c2.asset.directory", "./asset", false, true, VALID); NIFI_MINIFI_NOTIFIER_INGESTORS("nifi.minifi.notifier.ingestors", null, false, true, VALID),
NIFI_MINIFI_NOTIFIER_INGESTORS_FILE_CONFIG_PATH("nifi.minifi.notifier.ingestors.file.config.path", null, false, true, VALID),
NIFI_MINIFI_NOTIFIER_INGESTORS_FILE_POLLING_PERIOD_SECONDS("nifi.minifi.notifier.ingestors.file.polling.period.seconds", null, false, true, NON_NEGATIVE_INTEGER_VALIDATOR),
NIFI_MINIFI_NOTIFIER_INGESTORS_RECEIVE_HTTP_PORT("nifi.minifi.notifier.ingestors.receive.http.port", null, false, true, PORT_VALIDATOR),
NIFI_MINIFI_NOTIFIER_INGESTORS_PULL_HTTP_HOSTNAME("nifi.minifi.notifier.ingestors.pull.http.hostname", null, false, true, VALID),
NIFI_MINIFI_NOTIFIER_INGESTORS_PULL_HTTP_PORT("nifi.minifi.notifier.ingestors.pull.http.port", null, false, true, PORT_VALIDATOR),
NIFI_MINIFI_NOTIFIER_INGESTORS_PULL_HTTP_PATH("nifi.minifi.notifier.ingestors.pull.http.path", null, false, true, VALID),
NIFI_MINIFI_NOTIFIER_INGESTORS_PULL_HTTP_QUERY("nifi.minifi.notifier.ingestors.pull.http.query", null, false, true, VALID),
NIFI_MINIFI_NOTIFIER_INGESTORS_PULL_HTTP_PERIOD_MS("nifi.minifi.notifier.ingestors.pull.http.period.ms", null, false, true, NON_NEGATIVE_INTEGER_VALIDATOR),
NIFI_MINIFI_NOTIFIER_INGESTORS_PULL_HTTP_HEADERS("nifi.minifi.notifier.ingestors.pull.http.headers", null, false, true, NON_NEGATIVE_INTEGER_VALIDATOR),
NIFI_MINIFI_STATUS_REPORTER_COMPONENTS("nifi.minifi.status.reporter.components", null, false, true, VALID),
NIFI_MINIFI_STATUS_REPORTER_LOG_QUERY("nifi.minifi.status.reporter.log.query", null, false, true, VALID),
NIFI_MINIFI_STATUS_REPORTER_LOG_LEVEL("nifi.minifi.status.reporter.log.level", null, false, true, VALID),
NIFI_MINIFI_STATUS_REPORTER_LOG_PERIOD("nifi.minifi.status.reporter.log.period", null, false, true, VALID),
NIFI_MINIFI_PROVENANCE_REPORTING_COMMENT("nifi.minifi.provenance.reporting.comment", null, false, true, VALID),
NIFI_MINIFI_PROVENANCE_REPORTING_SCHEDULING_STRATEGY("nifi.minifi.provenance.reporting.scheduling.strategy", null, false, true, VALID),
NIFI_MINIFI_PROVENANCE_REPORTING_SCHEDULING_PERIOD("nifi.minifi.provenance.reporting.scheduling.period", null, false, true, TIME_PERIOD_VALIDATOR),
NIFI_MINIFI_PROVENANCE_REPORTING_DESTINATION_URL("nifi.minifi.provenance.reporting.destination.url", null, false, true, VALID),
NIFI_MINIFI_PROVENANCE_REPORTING_INPUT_PORT_NAME("nifi.minifi.provenance.reporting.input.port.name", null, false, true, VALID),
NIFI_MINIFI_PROVENANCE_REPORTING_INSTANCE_URL("nifi.minifi.provenance.reporting.instance.url", null, false, true, VALID),
NIFI_MINIFI_PROVENANCE_REPORTING_COMPRESS_EVENTS("nifi.minifi.provenance.reporting.compress.events", null, false, true, VALID),
NIFI_MINIFI_PROVENANCE_REPORTING_BATCH_SIZE("nifi.minifi.provenance.reporting.batch.size", null, false, true, NON_NEGATIVE_INTEGER_VALIDATOR),
NIFI_MINIFI_PROVENANCE_REPORTING_COMMUNICATIONS_TIMEOUT("nifi.minifi.provenance.reporting.communications.timeout", null, false, true, TIME_PERIOD_VALIDATOR);
public static final LinkedHashMap<String, MiNiFiProperties> PROPERTIES_BY_KEY = Arrays.stream(MiNiFiProperties.values()) // These are not "real" bootstrap properties. They are generated runtime in bootstrap and populated into also generated minifi.properties
.sorted() public static final String MINIFI_BOOTSTRAP_FILE_PATH = "nifi.minifi.bootstrap.file";
.collect(Collectors.toMap(MiNiFiProperties::getKey, Function.identity(), (x, y) -> y, LinkedHashMap::new)); public static final String MINIFI_LOG_DIRECTORY = "nifi.minifi.log.directory";
public static final String MINIFI_APP_LOG_FILE = "nifi.minifi.app.log.file";
public static final String MINIFI_BOOTSTRAP_LOG_FILE = "nifi.minifi.bootstrap.log.file";
private final String key; private final String key;
private final String defaultValue; private final String defaultValue;
@ -127,6 +135,43 @@ public enum MiNiFiProperties {
this.validator = validator; this.validator = validator;
} }
public static LinkedHashMap<String, MiNiFiProperties> sortedPropertiesByKey() {
return Arrays.stream(MiNiFiProperties.values())
.sorted()
.collect(Collectors.toMap(MiNiFiProperties::getKey, Function.identity(), (x, y) -> y, LinkedHashMap::new));
}
public static Set<String> securityPropertyKeys() {
return Set.of(
NIFI_MINIFI_SECURITY_KEYSTORE,
NIFI_MINIFI_SECURITY_KEYSTORE_TYPE,
NIFI_MINIFI_SECURITY_KEYSTORE_PASSWD,
NIFI_MINIFI_SECURITY_KEY_PASSWD,
NIFI_MINIFI_SECURITY_TRUSTSTORE,
NIFI_MINIFI_SECURITY_TRUSTSTORE_TYPE,
NIFI_MINIFI_SECURITY_TRUSTSTORE_PASSWD,
NIFI_MINIFI_SECURITY_SSL_PROTOCOL)
.stream()
.map(MiNiFiProperties::getKey)
.collect(toSet());
}
public static Set<String> provenanceReportingPropertyKeys() {
return Set.of(
NIFI_MINIFI_PROVENANCE_REPORTING_COMMENT,
NIFI_MINIFI_PROVENANCE_REPORTING_SCHEDULING_STRATEGY,
NIFI_MINIFI_PROVENANCE_REPORTING_SCHEDULING_PERIOD,
NIFI_MINIFI_PROVENANCE_REPORTING_DESTINATION_URL,
NIFI_MINIFI_PROVENANCE_REPORTING_INPUT_PORT_NAME,
NIFI_MINIFI_PROVENANCE_REPORTING_INSTANCE_URL,
NIFI_MINIFI_PROVENANCE_REPORTING_COMPRESS_EVENTS,
NIFI_MINIFI_PROVENANCE_REPORTING_BATCH_SIZE,
NIFI_MINIFI_PROVENANCE_REPORTING_COMMUNICATIONS_TIMEOUT)
.stream()
.map(MiNiFiProperties::getKey)
.collect(toSet());
}
public String getKey() { public String getKey() {
return key; return key;
} }

View File

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

View File

@ -15,45 +15,39 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<artifactId>minifi-c2-provider</artifactId>
<groupId>org.apache.nifi.minifi</groupId> <groupId>org.apache.nifi.minifi</groupId>
<artifactId>minifi-commons</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>minifi-c2-provider-nifi-rest</artifactId>
<packaging>jar</packaging> <artifactId>minifi-commons-framework</artifactId>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.apache.nifi.minifi</groupId> <groupId>org.apache.nifi.minifi</groupId>
<artifactId>minifi-c2-api</artifactId> <artifactId>minifi-commons-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-core-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-property-utils</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.nifi.minifi</groupId> <groupId>com.fasterxml.jackson.module</groupId>
<artifactId>minifi-c2-provider-util</artifactId> <artifactId>jackson-module-jaxb-annotations</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi.minifi</groupId>
<artifactId>minifi-toolkit-configuration</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
</dependencies> </dependencies>
@ -64,9 +58,8 @@ limitations under the License.
<artifactId>apache-rat-plugin</artifactId> <artifactId>apache-rat-plugin</artifactId>
<configuration> <configuration>
<excludes combine.children="append"> <excludes combine.children="append">
<exclude>src/test/resources/noTemplates.json</exclude> <exclude>**/org.mockito.plugins.MockMaker</exclude>
<exclude>src/test/resources/oneTemplate.json</exclude> <exclude>**/default_flow.json</exclude>
<exclude>src/test/resources/twoTemplates.json</exclude>
</excludes> </excludes>
</configuration> </configuration>
</plugin> </plugin>

View File

@ -0,0 +1,290 @@
/*
* 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.minifi.commons.service;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.parseBoolean;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Map.entry;
import static java.util.Optional.empty;
import static java.util.Optional.ofNullable;
import static java.util.UUID.randomUUID;
import static java.util.stream.Collectors.toMap;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.controller.flow.VersionedDataflow;
import org.apache.nifi.controller.serialization.FlowSerializationException;
import org.apache.nifi.flow.Bundle;
import org.apache.nifi.flow.ComponentType;
import org.apache.nifi.flow.ControllerServiceAPI;
import org.apache.nifi.flow.ScheduledState;
import org.apache.nifi.flow.VersionedComponent;
import org.apache.nifi.flow.VersionedControllerService;
import org.apache.nifi.flow.VersionedProcessGroup;
import org.apache.nifi.flow.VersionedReportingTask;
import org.apache.nifi.logging.LogLevel;
import org.apache.nifi.minifi.commons.api.MiNiFiProperties;
import org.apache.nifi.properties.ReadableProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FlowEnrichService {
static final String DEFAULT_SSL_CONTEXT_SERVICE_NAME = "SSL Context Service";
static final String COMMON_SSL_CONTEXT_SERVICE_NAME = "SSL-Context-Service";
static final String COMMON_SSL_CONTEXT_SERVICE_ID = "generated-common-ssl-context";
static final String SITE_TO_SITE_PROVENANCE_REPORTING_TASK_NAME = "Site-To-Site-Provenance-Reporting";
static final String SITE_TO_SITE_PROVENANCE_REPORTING_TASK_ID = "generated-s2s-provenance-reporting-task";
private static final Logger LOG = LoggerFactory.getLogger(FlowEnrichService.class);
private static final String NIFI_BUNDLE_GROUP = "org.apache.nifi";
private static final String STANDARD_RESTRICTED_SSL_CONTEXT_SERVICE = "org.apache.nifi.ssl.StandardRestrictedSSLContextService";
private static final String RESTRICTED_SSL_CONTEXT_SERVICE_API = "org.apache.nifi.ssl.RestrictedSSLContextService";
private static final String SSL_CONTEXT_SERVICE_API = "org.apache.nifi.ssl.SSLContextService";
private static final String SSL_CONTEXT_SERVICE_NAR = "nifi-ssl-context-service-nar";
private static final String STANDARD_SERVICES_API_NAR_ARTIFACT = "nifi-standard-services-api-nar";
private static final String SITE_TO_SITE_PROVENANCE_REPORTING_TASK = "org.apache.nifi.reporting.SiteToSiteProvenanceReportingTask";
private static final String SITE_TO_SITE_REPORTING_NAR_ARTIFACT = "nifi-site-to-site-reporting-nar";
private static final String PROVENANCE_REPORTING_TASK_PROTOCOL = "HTTP";
private static final String PROVENANCE_REPORTING_TASK_BEGINNING_OF_STREAM = "beginning-of-stream";
private final ReadableProperties minifiProperties;
public FlowEnrichService(ReadableProperties minifiProperties) {
this.minifiProperties = minifiProperties;
}
public byte[] enrichFlow(byte[] flowCandidate) {
if (LOG.isDebugEnabled()) {
LOG.debug("Enriching flow with content: \n{}", new String(flowCandidate, UTF_8));
}
VersionedDataflow versionedDataflow = parseVersionedDataflow(flowCandidate);
Optional<Integer> maxConcurrentThreads = ofNullable(minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_FLOW_MAX_CONCURRENT_THREADS.getKey()))
.map(Integer::parseInt);
maxConcurrentThreads.ifPresent(versionedDataflow::setMaxTimerDrivenThreadCount);
VersionedProcessGroup rootGroup = versionedDataflow.getRootGroup();
if (rootGroup.getIdentifier() == null) {
rootGroup.setIdentifier(randomUUID().toString());
}
if (rootGroup.getInstanceIdentifier() == null) {
rootGroup.setInstanceIdentifier(randomUUID().toString());
}
Optional<VersionedControllerService> commonSslControllerService = createCommonSslControllerService();
commonSslControllerService
.ifPresent(sslControllerService -> {
List<VersionedControllerService> currentControllerServices = ofNullable(versionedDataflow.getControllerServices()).orElseGet(ArrayList::new);
currentControllerServices.add(sslControllerService);
versionedDataflow.setControllerServices(currentControllerServices);
});
commonSslControllerService
.filter(__ -> parseBoolean(minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_FLOW_USE_PARENT_SSL.getKey())))
.map(VersionedComponent::getInstanceIdentifier)
.ifPresent(commonSslControllerServiceInstanceId -> overrideProcessorsSslControllerService(rootGroup, commonSslControllerServiceInstanceId));
createProvenanceReportingTask(commonSslControllerService.map(VersionedComponent::getInstanceIdentifier).orElse(EMPTY))
.ifPresent(provenanceReportingTask -> {
List<VersionedReportingTask> currentReportingTasks = ofNullable(versionedDataflow.getReportingTasks()).orElseGet(ArrayList::new);
currentReportingTasks.add(provenanceReportingTask);
versionedDataflow.setReportingTasks(currentReportingTasks);
});
byte[] enrichedFlow = toByteArray(versionedDataflow);
if (LOG.isDebugEnabled()) {
LOG.debug("Enriched flow with content: \n{}", new String(enrichedFlow, UTF_8));
}
return enrichedFlow;
}
private VersionedDataflow parseVersionedDataflow(byte[] flow) {
try {
ObjectMapper objectMapper = deserializationObjectMapper();
return objectMapper.readValue(flow, VersionedDataflow.class);
} catch (final Exception e) {
throw new FlowSerializationException("Could not parse flow as a VersionedDataflow", e);
}
}
private ObjectMapper deserializationObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setAnnotationIntrospector(new JaxbAnnotationIntrospector(objectMapper.getTypeFactory()));
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
private Optional<VersionedControllerService> createCommonSslControllerService() {
if (!parentSslEnabled()) {
LOG.debug("Parent SSL is disabled, skip creating parent SSL Controller Service");
return empty();
}
LOG.debug("Parent SSL is enabled, creating parent SSL Controller Service");
VersionedControllerService sslControllerService = new VersionedControllerService();
sslControllerService.setIdentifier(randomUUID().toString());
sslControllerService.setInstanceIdentifier(COMMON_SSL_CONTEXT_SERVICE_ID);
sslControllerService.setName(COMMON_SSL_CONTEXT_SERVICE_NAME);
sslControllerService.setComments(EMPTY);
sslControllerService.setType(STANDARD_RESTRICTED_SSL_CONTEXT_SERVICE);
sslControllerService.setScheduledState(ScheduledState.ENABLED);
sslControllerService.setBulletinLevel(LogLevel.WARN.name());
sslControllerService.setComponentType(ComponentType.CONTROLLER_SERVICE);
sslControllerService.setBundle(createBundle(SSL_CONTEXT_SERVICE_NAR));
sslControllerService.setProperties(sslControllerServiceProperties());
sslControllerService.setControllerServiceApis(List.of(
controllerServiceAPI(SSL_CONTEXT_SERVICE_API, createBundle(STANDARD_SERVICES_API_NAR_ARTIFACT)),
controllerServiceAPI(RESTRICTED_SSL_CONTEXT_SERVICE_API, createBundle(STANDARD_SERVICES_API_NAR_ARTIFACT))
));
sslControllerService.setPropertyDescriptors(Map.of());
return Optional.of(sslControllerService);
}
private boolean parentSslEnabled() {
return MiNiFiProperties.securityPropertyKeys().stream()
.map(minifiProperties::getProperty)
.allMatch(StringUtils::isNotBlank);
}
private Map<String, String> sslControllerServiceProperties() {
return Map.of(
"Keystore Filename", minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_SECURITY_KEYSTORE.getKey()),
"Keystore Password", minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_SECURITY_KEYSTORE_PASSWD.getKey()),
"key-password", minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_SECURITY_KEY_PASSWD.getKey()),
"Keystore Type", minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_SECURITY_KEYSTORE_TYPE.getKey()),
"Truststore Filename", minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_SECURITY_TRUSTSTORE.getKey()),
"Truststore Password", minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_SECURITY_TRUSTSTORE_PASSWD.getKey()),
"Truststore Type", minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_SECURITY_TRUSTSTORE_TYPE.getKey()),
"SSL Protocol", minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_SECURITY_SSL_PROTOCOL.getKey())
);
}
private ControllerServiceAPI controllerServiceAPI(String type, Bundle bundle) {
ControllerServiceAPI controllerServiceAPI = new ControllerServiceAPI();
controllerServiceAPI.setType(type);
controllerServiceAPI.setBundle(bundle);
return controllerServiceAPI;
}
private void overrideProcessorsSslControllerService(VersionedProcessGroup processGroup, String commonSslControllerServiceInstanceId) {
LOG.debug("Use parent SSL is enabled, overriding processors' SSL Controller service to {}", commonSslControllerServiceInstanceId);
processGroup.getProcessors()
.forEach(processor -> processor.getProperties()
.replace(
DEFAULT_SSL_CONTEXT_SERVICE_NAME,
processor.getProperties().get(DEFAULT_SSL_CONTEXT_SERVICE_NAME),
commonSslControllerServiceInstanceId));
processGroup.getProcessGroups()
.forEach(childProcessGroup -> overrideProcessorsSslControllerService(childProcessGroup, commonSslControllerServiceInstanceId));
}
private Optional<VersionedReportingTask> createProvenanceReportingTask(String sslControllerServiceIdentifier) {
if (!provenanceReportingEnabled()) {
LOG.debug("Provenance reporting task is disabled, skip creating provenance reporting task");
return empty();
}
LOG.debug("Provenance reporting task is enabled, creating provenance reporting task");
VersionedReportingTask reportingTask = new VersionedReportingTask();
reportingTask.setIdentifier(randomUUID().toString());
reportingTask.setInstanceIdentifier(SITE_TO_SITE_PROVENANCE_REPORTING_TASK_ID);
reportingTask.setName(SITE_TO_SITE_PROVENANCE_REPORTING_TASK_NAME);
reportingTask.setComments(minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_PROVENANCE_REPORTING_COMMENT.getKey()));
reportingTask.setType(SITE_TO_SITE_PROVENANCE_REPORTING_TASK);
reportingTask.setBundle(createBundle(SITE_TO_SITE_REPORTING_NAR_ARTIFACT));
reportingTask.setScheduledState(ScheduledState.RUNNING);
reportingTask.setSchedulingStrategy(minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_PROVENANCE_REPORTING_SCHEDULING_STRATEGY.getKey()));
reportingTask.setSchedulingPeriod(minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_PROVENANCE_REPORTING_SCHEDULING_PERIOD.getKey()));
reportingTask.setComponentType(ComponentType.REPORTING_TASK);
reportingTask.setProperties(provenanceReportingTaskProperties(sslControllerServiceIdentifier));
reportingTask.setPropertyDescriptors(Map.of());
return Optional.of(reportingTask);
}
private boolean provenanceReportingEnabled() {
return MiNiFiProperties.provenanceReportingPropertyKeys().stream()
.map(minifiProperties::getProperty)
.allMatch(StringUtils::isNotBlank);
}
private Bundle createBundle(String artifact) {
Bundle bundle = new Bundle();
bundle.setGroup(NIFI_BUNDLE_GROUP);
bundle.setArtifact(artifact);
bundle.setVersion(EMPTY);
return bundle;
}
private Map<String, String> provenanceReportingTaskProperties(String sslControllerServiceIdentifier) {
return List.of(
entry("Input Port Name", minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_PROVENANCE_REPORTING_INPUT_PORT_NAME.getKey())),
entry("s2s-transport-protocol", PROVENANCE_REPORTING_TASK_PROTOCOL),
entry("Platform", "nifi"),
entry("Destination URL", minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_PROVENANCE_REPORTING_DESTINATION_URL.getKey())),
entry("include-null-values", FALSE.toString()),
entry("Compress Events", minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_PROVENANCE_REPORTING_COMPRESS_EVENTS.getKey())),
entry("Batch Size", minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_PROVENANCE_REPORTING_BATCH_SIZE.getKey())),
entry("Communications Timeout", minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_PROVENANCE_REPORTING_COMMUNICATIONS_TIMEOUT.getKey())),
entry("start-position", PROVENANCE_REPORTING_TASK_BEGINNING_OF_STREAM),
entry("Instance URL", minifiProperties.getProperty(MiNiFiProperties.NIFI_MINIFI_PROVENANCE_REPORTING_INSTANCE_URL.getKey())),
entry("SSL Context Service", sslControllerServiceIdentifier))
.stream()
.filter(entry -> StringUtils.isNotBlank(entry.getValue()))
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
}
private byte[] toByteArray(VersionedDataflow versionedDataflow) {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
JsonFactory factory = new JsonFactory();
JsonGenerator generator = factory.createGenerator(byteArrayOutputStream);
generator.setCodec(serializationObjectMapper());
generator.writeObject(versionedDataflow);
generator.flush();
byteArrayOutputStream.flush();
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Unable to convert flow to byte array", e);
}
}
private ObjectMapper serializationObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.setDefaultPropertyInclusion(JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL));
objectMapper.setAnnotationIntrospector(new JaxbAnnotationIntrospector(objectMapper.getTypeFactory()));
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
}

View File

@ -0,0 +1,88 @@
/*
* 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.minifi.commons.util;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.copy;
import static java.nio.file.Files.deleteIfExists;
import static java.nio.file.Files.exists;
import static java.nio.file.Files.newOutputStream;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.zip.GZIPOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class FlowUpdateUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(FlowUpdateUtils.class);
private FlowUpdateUtils() {
}
public static void backup(Path current, Path backup) throws IOException {
if (current != null && exists(current)) {
copy(current, backup, REPLACE_EXISTING);
} else {
LOGGER.warn("Flow configuration files does not exist. No backup copy will be created");
}
}
public static void persist(byte[] flowConfig, Path flowPath, boolean compress) throws IOException {
LOGGER.debug("Persisting flow to path {} with content\n{}\n and compress {}", flowPath, new String(flowConfig, UTF_8), compress);
try (ByteArrayInputStream flowInputStream = new ByteArrayInputStream(flowConfig);
OutputStream fileOut = compress
? new GZIPOutputStream(newOutputStream(flowPath, WRITE, CREATE, TRUNCATE_EXISTING))
: newOutputStream(flowPath, WRITE, CREATE, TRUNCATE_EXISTING)) {
flowInputStream.transferTo(fileOut);
}
LOGGER.info("Updated configuration was written to: {}", flowPath);
}
public static void revert(Path backupFlowConfigFile, Path currentFlowConfigFile) {
if (backupFlowConfigFile != null && exists(backupFlowConfigFile)) {
try {
copy(backupFlowConfigFile, currentFlowConfigFile, REPLACE_EXISTING);
} catch (IOException e) {
LOGGER.error("Revert to previous flow failed. Please stop MiNiFi and revert the flow manually");
throw new UncheckedIOException("Failed to revert flow", e);
}
} else {
LOGGER.error("Backup flow configuration file does not exist");
throw new RuntimeException("Backup flow configuration file does not exist");
}
}
public static void removeIfExists(Path flowConfigFile) {
if (flowConfigFile != null) {
try {
deleteIfExists(flowConfigFile);
} catch (IOException e) {
LOGGER.warn("Unable to remove flow configuration backup file", e);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More