This is an automated email from the ASF dual-hosted git repository.
ferdei pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new e26aa233d7 NIFI-10458 Add support for the DESCRIBE/MANIFEST Operation
e26aa233d7 is described below
commit e26aa233d71584071421142021d1d0d33204da1d
Author: Csaba Bejan <[email protected]>
AuthorDate: Thu Sep 8 14:42:16 2022 +0200
NIFI-10458 Add support for the DESCRIBE/MANIFEST Operation
Signed-off-by: Ferenc Erdei <[email protected]>
This closes #6375
---
.../org/apache/nifi/c2/client/C2ClientConfig.java | 12 ++++
.../nifi/c2/client/service/C2HeartbeatFactory.java | 34 ++++++++-
.../service/operation/C2OperationService.java | 9 ++-
.../DescribeManifestOperationHandler.java | 81 +++++++++++++++++++++
.../c2/client/service/C2HeartbeatFactoryTest.java | 76 ++++++++++++++++++--
.../DescribeManifestOperationHandlerTest.java | 84 ++++++++++++++++++++++
.../org/apache/nifi/c2/protocol/api/AgentInfo.java | 10 +++
.../src/main/resources/conf/bootstrap.conf | 2 +
.../java/org/apache/nifi/c2/C2NiFiProperties.java | 1 +
.../org/apache/nifi/c2/C2NifiClientService.java | 10 ++-
10 files changed, 311 insertions(+), 8 deletions(-)
diff --git
a/c2/c2-client-bundle/c2-client-base/src/main/java/org/apache/nifi/c2/client/C2ClientConfig.java
b/c2/c2-client-bundle/c2-client-base/src/main/java/org/apache/nifi/c2/client/C2ClientConfig.java
index 5d7481dd9d..ecf677c399 100644
---
a/c2/c2-client-bundle/c2-client-base/src/main/java/org/apache/nifi/c2/client/C2ClientConfig.java
+++
b/c2/c2-client-bundle/c2-client-base/src/main/java/org/apache/nifi/c2/client/C2ClientConfig.java
@@ -25,6 +25,7 @@ public class C2ClientConfig {
private final String c2AckUrl;
private final String agentClass;
private final String agentIdentifier;
+ private final boolean fullHeartbeat;
private final String confDirectory;
private final String runtimeManifestIdentifier;
private final String runtimeType;
@@ -46,6 +47,7 @@ public class C2ClientConfig {
this.c2AckUrl = builder.c2AckUrl;
this.agentClass = builder.agentClass;
this.agentIdentifier = builder.agentIdentifier;
+ this.fullHeartbeat = builder.fullHeartbeat;
this.confDirectory = builder.confDirectory;
this.runtimeManifestIdentifier = builder.runtimeManifestIdentifier;
this.runtimeType = builder.runtimeType;
@@ -78,6 +80,10 @@ public class C2ClientConfig {
return agentIdentifier;
}
+ public boolean isFullHeartbeat() {
+ return fullHeartbeat;
+ }
+
public String getConfDirectory() {
return confDirectory;
}
@@ -143,6 +149,7 @@ public class C2ClientConfig {
private String c2AckUrl;
private String agentClass;
private String agentIdentifier;
+ private boolean fullHeartbeat;
private String confDirectory;
private String runtimeManifestIdentifier;
private String runtimeType;
@@ -178,6 +185,11 @@ public class C2ClientConfig {
return this;
}
+ public Builder fullHeartbeat(final boolean fullHeartbeat) {
+ this.fullHeartbeat = fullHeartbeat;
+ return this;
+ }
+
public Builder confDirectory(final String confDirectory) {
this.confDirectory = confDirectory;
return this;
diff --git
a/c2/c2-client-bundle/c2-client-service/src/main/java/org/apache/nifi/c2/client/service/C2HeartbeatFactory.java
b/c2/c2-client-bundle/c2-client-service/src/main/java/org/apache/nifi/c2/client/service/C2HeartbeatFactory.java
index bac10e2d0b..063017ea5a 100644
---
a/c2/c2-client-bundle/c2-client-service/src/main/java/org/apache/nifi/c2/client/service/C2HeartbeatFactory.java
+++
b/c2/c2-client-bundle/c2-client-service/src/main/java/org/apache/nifi/c2/client/service/C2HeartbeatFactory.java
@@ -23,8 +23,12 @@ import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.net.InetAddress;
import java.net.NetworkInterface;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.Enumeration;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -41,6 +45,7 @@ import org.apache.nifi.c2.protocol.api.FlowInfo;
import org.apache.nifi.c2.protocol.api.FlowQueueStatus;
import org.apache.nifi.c2.protocol.api.NetworkInfo;
import org.apache.nifi.c2.protocol.api.SystemInfo;
+import org.apache.nifi.c2.protocol.component.api.Bundle;
import org.apache.nifi.c2.protocol.component.api.RuntimeManifest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -92,7 +97,11 @@ public class C2HeartbeatFactory {
agentStatus.setRepositories(repos);
agentInfo.setStatus(agentStatus);
- agentInfo.setAgentManifest(manifest);
+
agentInfo.setAgentManifestHash(calculateManifestHash(manifest.getBundles()));
+
+ if (clientConfig.isFullHeartbeat()) {
+ agentInfo.setAgentManifest(manifest);
+ }
return agentInfo;
}
@@ -225,4 +234,27 @@ public class C2HeartbeatFactory {
return confDirectory;
}
+
+ private String calculateManifestHash(List<Bundle> loadedBundles) {
+ byte[] bytes;
+ try {
+ bytes =
MessageDigest.getInstance("SHA-512").digest(loadedBundles.stream()
+ .map(bundle -> bundle.getGroup() + bundle.getArtifact() +
bundle.getVersion())
+ .sorted()
+ .collect(Collectors.joining(","))
+ .getBytes(StandardCharsets.UTF_8));
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("Unable to set up manifest hash
calculation due to not having support for the chosen digest algorithm", e);
+ }
+
+ return bytesToHex(bytes);
+ }
+
+ private String bytesToHex(byte[] in) {
+ final StringBuilder builder = new StringBuilder();
+ for (byte b : in) {
+ builder.append(String.format("%02x", b));
+ }
+ return builder.toString();
+ }
}
diff --git
a/c2/c2-client-bundle/c2-client-service/src/main/java/org/apache/nifi/c2/client/service/operation/C2OperationService.java
b/c2/c2-client-bundle/c2-client-service/src/main/java/org/apache/nifi/c2/client/service/operation/C2OperationService.java
index fadc8ad79b..5ce9c51ef4 100644
---
a/c2/c2-client-bundle/c2-client-service/src/main/java/org/apache/nifi/c2/client/service/operation/C2OperationService.java
+++
b/c2/c2-client-bundle/c2-client-service/src/main/java/org/apache/nifi/c2/client/service/operation/C2OperationService.java
@@ -24,9 +24,13 @@ import org.apache.nifi.c2.protocol.api.C2Operation;
import org.apache.nifi.c2.protocol.api.C2OperationAck;
import org.apache.nifi.c2.protocol.api.OperandType;
import org.apache.nifi.c2.protocol.api.OperationType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class C2OperationService {
+ private static final Logger logger =
LoggerFactory.getLogger(C2OperationService.class);
+
private final Map<OperationType, Map<OperandType, C2OperationHandler>>
handlerMap = new HashMap<>();
public C2OperationService(List<C2OperationHandler> handlers) {
@@ -37,7 +41,10 @@ public class C2OperationService {
public Optional<C2OperationAck> handleOperation(C2Operation operation) {
return getHandlerForOperation(operation)
- .map(handler -> handler.handle(operation));
+ .map(handler -> {
+ logger.info("Handling {} {} operation",
operation.getOperation(), operation.getOperand());
+ return handler.handle(operation);
+ });
}
private Optional<C2OperationHandler> getHandlerForOperation(C2Operation
operation) {
diff --git
a/c2/c2-client-bundle/c2-client-service/src/main/java/org/apache/nifi/c2/client/service/operation/DescribeManifestOperationHandler.java
b/c2/c2-client-bundle/c2-client-service/src/main/java/org/apache/nifi/c2/client/service/operation/DescribeManifestOperationHandler.java
new file mode 100644
index 0000000000..2f55b2510d
--- /dev/null
+++
b/c2/c2-client-bundle/c2-client-service/src/main/java/org/apache/nifi/c2/client/service/operation/DescribeManifestOperationHandler.java
@@ -0,0 +1,81 @@
+/*
+ * 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.c2.client.service.operation;
+
+import static org.apache.commons.lang3.StringUtils.EMPTY;
+import static org.apache.nifi.c2.protocol.api.OperandType.MANIFEST;
+import static org.apache.nifi.c2.protocol.api.OperationType.DESCRIBE;
+
+import java.util.Optional;
+import java.util.function.Supplier;
+import org.apache.nifi.c2.client.service.C2HeartbeatFactory;
+import org.apache.nifi.c2.client.service.model.RuntimeInfoWrapper;
+import org.apache.nifi.c2.protocol.api.AgentInfo;
+import org.apache.nifi.c2.protocol.api.C2Heartbeat;
+import org.apache.nifi.c2.protocol.api.C2Operation;
+import org.apache.nifi.c2.protocol.api.C2OperationAck;
+import org.apache.nifi.c2.protocol.api.C2OperationState;
+import org.apache.nifi.c2.protocol.api.OperandType;
+import org.apache.nifi.c2.protocol.api.OperationType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DescribeManifestOperationHandler implements C2OperationHandler {
+
+ private static final Logger logger =
LoggerFactory.getLogger(DescribeManifestOperationHandler.class);
+
+ private final C2HeartbeatFactory heartbeatFactory;
+ private final Supplier<RuntimeInfoWrapper> runtimeInfoSupplier;
+
+ public DescribeManifestOperationHandler(C2HeartbeatFactory
heartbeatFactory, Supplier<RuntimeInfoWrapper> runtimeInfoSupplier) {
+ this.heartbeatFactory = heartbeatFactory;
+ this.runtimeInfoSupplier = runtimeInfoSupplier;
+ }
+
+ @Override
+ public OperationType getOperationType() {
+ return DESCRIBE;
+ }
+
+ @Override
+ public OperandType getOperandType() {
+ return MANIFEST;
+ }
+
+ @Override
+ public C2OperationAck handle(C2Operation operation) {
+ String opIdentifier = Optional.ofNullable(operation.getIdentifier())
+ .orElse(EMPTY);
+ C2OperationAck operationAck = new C2OperationAck();
+ C2OperationState state = new C2OperationState();
+ operationAck.setOperationState(state);
+ operationAck.setOperationId(opIdentifier);
+
+ RuntimeInfoWrapper runtimeInfoWrapper = runtimeInfoSupplier.get();
+ C2Heartbeat heartbeat = heartbeatFactory.create(runtimeInfoWrapper);
+
+ AgentInfo agentInfo = heartbeat.getAgentInfo();
+ agentInfo.setAgentManifest(runtimeInfoWrapper.getManifest());
+ operationAck.setAgentInfo(agentInfo);
+ operationAck.setDeviceInfo(heartbeat.getDeviceInfo());
+ operationAck.setFlowInfo(heartbeat.getFlowInfo());
+
+ state.setState(C2OperationState.OperationState.FULLY_APPLIED);
+
+ return operationAck;
+ }
+}
diff --git
a/c2/c2-client-bundle/c2-client-service/src/test/java/org/apache/nifi/c2/client/service/C2HeartbeatFactoryTest.java
b/c2/c2-client-bundle/c2-client-service/src/test/java/org/apache/nifi/c2/client/service/C2HeartbeatFactoryTest.java
index 111d750db5..c1c8efb86d 100644
---
a/c2/c2-client-bundle/c2-client-service/src/test/java/org/apache/nifi/c2/client/service/C2HeartbeatFactoryTest.java
+++
b/c2/c2-client-bundle/c2-client-service/src/test/java/org/apache/nifi/c2/client/service/C2HeartbeatFactoryTest.java
@@ -17,12 +17,15 @@
package org.apache.nifi.c2.client.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.File;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.apache.nifi.c2.client.C2ClientConfig;
@@ -30,6 +33,7 @@ import
org.apache.nifi.c2.client.service.model.RuntimeInfoWrapper;
import org.apache.nifi.c2.protocol.api.AgentRepositories;
import org.apache.nifi.c2.protocol.api.C2Heartbeat;
import org.apache.nifi.c2.protocol.api.FlowQueueStatus;
+import org.apache.nifi.c2.protocol.component.api.Bundle;
import org.apache.nifi.c2.protocol.component.api.RuntimeManifest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -51,6 +55,9 @@ public class C2HeartbeatFactoryTest {
@Mock
private FlowIdHolder flowIdHolder;
+ @Mock
+ private RuntimeInfoWrapper runtimeInfoWrapper;
+
@InjectMocks
private C2HeartbeatFactory c2HeartbeatFactory;
@@ -66,8 +73,9 @@ public class C2HeartbeatFactoryTest {
void testCreateHeartbeat() {
when(flowIdHolder.getFlowId()).thenReturn(FLOW_ID);
when(clientConfig.getAgentClass()).thenReturn(AGENT_CLASS);
+ when(runtimeInfoWrapper.getManifest()).thenReturn(createManifest());
- C2Heartbeat heartbeat =
c2HeartbeatFactory.create(mock(RuntimeInfoWrapper.class));
+ C2Heartbeat heartbeat = c2HeartbeatFactory.create(runtimeInfoWrapper);
assertEquals(FLOW_ID, heartbeat.getFlowId());
assertEquals(AGENT_CLASS, heartbeat.getAgentClass());
@@ -75,16 +83,20 @@ public class C2HeartbeatFactoryTest {
@Test
void testCreateGeneratesAgentAndDeviceIdIfNotPresent() {
- C2Heartbeat heartbeat =
c2HeartbeatFactory.create(mock(RuntimeInfoWrapper.class));
+ when(runtimeInfoWrapper.getManifest()).thenReturn(createManifest());
+
+ C2Heartbeat heartbeat = c2HeartbeatFactory.create(runtimeInfoWrapper);
assertNotNull(heartbeat.getAgentId());
assertNotNull(heartbeat.getDeviceId());
}
@Test
- void testCreatePopulatesFromRuntimeInfoWrapper() {
+ void testCreatePopulatesFromRuntimeInfoWrapperForFullHeartbeat() {
+ when(clientConfig.isFullHeartbeat()).thenReturn(true);
+
AgentRepositories repos = new AgentRepositories();
- RuntimeManifest manifest = new RuntimeManifest();
+ RuntimeManifest manifest = createManifest();
Map<String, FlowQueueStatus> queueStatus = new HashMap<>();
C2Heartbeat heartbeat = c2HeartbeatFactory.create(new
RuntimeInfoWrapper(repos, manifest, queueStatus));
@@ -94,10 +106,66 @@ public class C2HeartbeatFactoryTest {
assertEquals(queueStatus, heartbeat.getFlowInfo().getQueues());
}
+ @Test
+ void testCreatePopulatesFromRuntimeInfoWrapperForLightHeartbeat() {
+ when(clientConfig.isFullHeartbeat()).thenReturn(false);
+
+ AgentRepositories repos = new AgentRepositories();
+ RuntimeManifest manifest = createManifest();
+ Map<String, FlowQueueStatus> queueStatus = new HashMap<>();
+
+ C2Heartbeat heartbeat = c2HeartbeatFactory.create(new
RuntimeInfoWrapper(repos, manifest, queueStatus));
+
+ assertEquals(repos,
heartbeat.getAgentInfo().getStatus().getRepositories());
+ assertNull(heartbeat.getAgentInfo().getAgentManifest());
+ assertEquals(queueStatus, heartbeat.getFlowInfo().getQueues());
+ }
+
@Test
void testCreateThrowsExceptionWhenConfDirNotSet() {
when(clientConfig.getConfDirectory()).thenReturn(String.class.getSimpleName());
assertThrows(IllegalStateException.class, () ->
c2HeartbeatFactory.create(mock(RuntimeInfoWrapper.class)));
}
+
+ @Test
+ void testManifestHashChangesWhenManifestBundleChanges() {
+ Bundle bundle1 = new Bundle("group1", "artifact1", "version1");
+ Bundle bundle2 = new Bundle("group2", "artifact2", "version2");
+ RuntimeManifest manifest1 = createManifest(bundle1);
+ RuntimeManifest manifest2 = createManifest(bundle2);
+ RuntimeManifest manifest3 = createManifest(bundle1, bundle2);
+
+ when(runtimeInfoWrapper.getManifest()).thenReturn(manifest1);
+ C2Heartbeat heartbeat1 = c2HeartbeatFactory.create(runtimeInfoWrapper);
+ String hash1 = heartbeat1.getAgentInfo().getAgentManifestHash();
+ assertNotNull(hash1);
+
+ // same manifest should result in the same hash
+ assertEquals(hash1,
c2HeartbeatFactory.create(runtimeInfoWrapper).getAgentInfo().getAgentManifestHash());
+
+ // different manifest should result in hash change
+ when(runtimeInfoWrapper.getManifest()).thenReturn(manifest2);
+ C2Heartbeat heartbeat2 = c2HeartbeatFactory.create(runtimeInfoWrapper);
+ String hash2 = heartbeat2.getAgentInfo().getAgentManifestHash();
+ assertNotEquals(hash2, hash1);
+
+ // different manifest with multiple bundles should result in hash
change compared to all previous
+ when(runtimeInfoWrapper.getManifest()).thenReturn(manifest3);
+ C2Heartbeat heartbeat3 = c2HeartbeatFactory.create(runtimeInfoWrapper);
+ String hash3 = heartbeat3.getAgentInfo().getAgentManifestHash();
+ assertNotEquals(hash3, hash1);
+ assertNotEquals(hash3, hash2);
+ }
+
+ private RuntimeManifest createManifest() {
+ return createManifest(new Bundle());
+ }
+
+ private RuntimeManifest createManifest(Bundle... bundles) {
+ RuntimeManifest manifest = new RuntimeManifest();
+ manifest.setBundles(Arrays.asList(bundles));
+
+ return manifest;
+ }
}
diff --git
a/c2/c2-client-bundle/c2-client-service/src/test/java/org/apache/nifi/c2/client/service/operation/DescribeManifestOperationHandlerTest.java
b/c2/c2-client-bundle/c2-client-service/src/test/java/org/apache/nifi/c2/client/service/operation/DescribeManifestOperationHandlerTest.java
new file mode 100644
index 0000000000..2017cc18a5
--- /dev/null
+++
b/c2/c2-client-bundle/c2-client-service/src/test/java/org/apache/nifi/c2/client/service/operation/DescribeManifestOperationHandlerTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.c2.client.service.operation;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.when;
+
+import org.apache.nifi.c2.client.service.C2HeartbeatFactory;
+import org.apache.nifi.c2.client.service.model.RuntimeInfoWrapper;
+import org.apache.nifi.c2.protocol.api.AgentInfo;
+import org.apache.nifi.c2.protocol.api.C2Heartbeat;
+import org.apache.nifi.c2.protocol.api.C2Operation;
+import org.apache.nifi.c2.protocol.api.C2OperationAck;
+import org.apache.nifi.c2.protocol.api.C2OperationState;
+import org.apache.nifi.c2.protocol.api.DeviceInfo;
+import org.apache.nifi.c2.protocol.api.FlowInfo;
+import org.apache.nifi.c2.protocol.api.OperandType;
+import org.apache.nifi.c2.protocol.api.OperationType;
+import org.apache.nifi.c2.protocol.component.api.RuntimeManifest;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public class DescribeManifestOperationHandlerTest {
+
+ private static final String OPERATION_ID = "operationId";
+
+ @Mock
+ private C2HeartbeatFactory heartbeatFactory;
+
+ @Test
+ void testDescribeManifestOperationHandlerCreateSuccess() {
+ DescribeManifestOperationHandler handler = new
DescribeManifestOperationHandler(null, null);
+
+ assertEquals(OperationType.DESCRIBE, handler.getOperationType());
+ assertEquals(OperandType.MANIFEST, handler.getOperandType());
+ }
+
+ @Test
+ void testDescribeManifestOperationHandlerPopulatesAckSuccessfully() {
+ RuntimeManifest manifest = new RuntimeManifest();
+ manifest.setIdentifier("manifestId");
+ RuntimeInfoWrapper runtimeInfoWrapper = new RuntimeInfoWrapper(null,
manifest, null);
+
+ C2Heartbeat heartbeat = new C2Heartbeat();
+ AgentInfo agentInfo = new AgentInfo();
+ DeviceInfo deviceInfo = new DeviceInfo();
+ FlowInfo flowInfo = new FlowInfo();
+ heartbeat.setAgentInfo(agentInfo);
+ heartbeat.setDeviceInfo(deviceInfo);
+ heartbeat.setFlowInfo(flowInfo);
+
+
when(heartbeatFactory.create(runtimeInfoWrapper)).thenReturn(heartbeat);
+ DescribeManifestOperationHandler handler = new
DescribeManifestOperationHandler(heartbeatFactory, () -> runtimeInfoWrapper);
+
+ C2Operation operation = new C2Operation();
+ operation.setIdentifier(OPERATION_ID);
+
+ C2OperationAck response = handler.handle(operation);
+
+ assertEquals(OPERATION_ID, response.getOperationId());
+ assertEquals(C2OperationState.OperationState.FULLY_APPLIED,
response.getOperationState().getState());
+ assertEquals(agentInfo, response.getAgentInfo());
+ assertEquals(manifest, response.getAgentInfo().getAgentManifest());
+ assertEquals(deviceInfo, response.getDeviceInfo());
+ assertEquals(flowInfo, response.getFlowInfo());
+ }
+}
diff --git
a/c2/c2-protocol/c2-protocol-api/src/main/java/org/apache/nifi/c2/protocol/api/AgentInfo.java
b/c2/c2-protocol/c2-protocol-api/src/main/java/org/apache/nifi/c2/protocol/api/AgentInfo.java
index ac06e265b8..5660b8c2eb 100644
---
a/c2/c2-protocol/c2-protocol-api/src/main/java/org/apache/nifi/c2/protocol/api/AgentInfo.java
+++
b/c2/c2-protocol/c2-protocol-api/src/main/java/org/apache/nifi/c2/protocol/api/AgentInfo.java
@@ -29,6 +29,7 @@ public class AgentInfo implements Serializable {
private String identifier;
private String agentClass;
+ private String agentManifestHash;
private RuntimeManifest agentManifest;
private AgentStatus status;
@@ -55,6 +56,15 @@ public class AgentInfo implements Serializable {
this.agentClass = agentClass;
}
+ @ApiModelProperty("The hash code of the manifest definition generated by
the agent.")
+ public String getAgentManifestHash() {
+ return this.agentManifestHash;
+ }
+
+ public void setAgentManifestHash(String agentManifestHash) {
+ this.agentManifestHash = agentManifestHash;
+ }
+
@ApiModelProperty("The specification of the agent's capabilities")
public RuntimeManifest getAgentManifest() {
return agentManifest;
diff --git
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-resources/src/main/resources/conf/bootstrap.conf
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-resources/src/main/resources/conf/bootstrap.conf
index ac05d01286..1233a05f1d 100644
---
a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-resources/src/main/resources/conf/bootstrap.conf
+++
b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-resources/src/main/resources/conf/bootstrap.conf
@@ -149,6 +149,8 @@ java.arg.14=-Djava.awt.headless=true
#c2.runtime.type=minifi-java
# Optional. Defaults to a hardware based unique identifier
#c2.agent.identifier=
+# If set to false heartbeat won't contain the manifest. Defaults to true.
+#c2.full.heartbeat=false
## Define TLS security properties for C2 communications
#c2.security.truststore.location=
#c2.security.truststore.password=
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/c2/C2NiFiProperties.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/c2/C2NiFiProperties.java
index 7983d9967a..40373436df 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/c2/C2NiFiProperties.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/c2/C2NiFiProperties.java
@@ -38,6 +38,7 @@ public class C2NiFiProperties {
public static final String C2_CALL_TIMEOUT = C2_PREFIX +
"rest.callTimeout";
public static final String C2_AGENT_CLASS_KEY = C2_PREFIX + "agent.class";
public static final String C2_AGENT_IDENTIFIER_KEY = C2_PREFIX +
"agent.identifier";
+ public static final String C2_FULL_HEARTBEAT_KEY = C2_PREFIX +
"full.heartbeat";
public static final String C2_ROOT_CLASS_DEFINITIONS_KEY = C2_PREFIX +
"root.class.definitions";
public static final String C2_METRICS_NAME_KEY =
C2_ROOT_CLASS_DEFINITIONS_KEY + ".metrics.name";
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/c2/C2NifiClientService.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/c2/C2NifiClientService.java
index 66dcd9967d..fe227b1f9e 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/c2/C2NifiClientService.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/c2/C2NifiClientService.java
@@ -30,6 +30,7 @@ import org.apache.nifi.c2.client.service.C2HeartbeatFactory;
import org.apache.nifi.c2.client.service.FlowIdHolder;
import org.apache.nifi.c2.client.service.model.RuntimeInfoWrapper;
import org.apache.nifi.c2.client.service.operation.C2OperationService;
+import
org.apache.nifi.c2.client.service.operation.DescribeManifestOperationHandler;
import
org.apache.nifi.c2.client.service.operation.UpdateConfigurationOperationHandler;
import org.apache.nifi.c2.protocol.api.AgentRepositories;
import org.apache.nifi.c2.protocol.api.AgentRepositoryStatus;
@@ -89,10 +90,14 @@ public class C2NifiClientService {
this.heartbeatPeriod = clientConfig.getHeartbeatPeriod();
this.flowController = flowController;
C2HttpClient client = new C2HttpClient(clientConfig, new
C2JacksonSerializer());
+ C2HeartbeatFactory heartbeatFactory = new
C2HeartbeatFactory(clientConfig, flowIdHolder);
this.c2ClientService = new C2ClientService(
client,
- new C2HeartbeatFactory(clientConfig, flowIdHolder),
- new C2OperationService(Arrays.asList(new
UpdateConfigurationOperationHandler(client, flowIdHolder,
this::updateFlowContent)))
+ heartbeatFactory,
+ new C2OperationService(Arrays.asList(
+ new UpdateConfigurationOperationHandler(client, flowIdHolder,
this::updateFlowContent),
+ new DescribeManifestOperationHandler(heartbeatFactory,
this::generateRuntimeInfo)
+ ))
);
}
@@ -100,6 +105,7 @@ public class C2NifiClientService {
return new C2ClientConfig.Builder()
.agentClass(properties.getProperty(C2NiFiProperties.C2_AGENT_CLASS_KEY, ""))
.agentIdentifier(properties.getProperty(C2NiFiProperties.C2_AGENT_IDENTIFIER_KEY))
+
.fullHeartbeat(Boolean.parseBoolean(properties.getProperty(C2NiFiProperties.C2_FULL_HEARTBEAT_KEY,
"true")))
.heartbeatPeriod(Long.parseLong(properties.getProperty(C2NiFiProperties.C2_AGENT_HEARTBEAT_PERIOD_KEY,
String.valueOf(C2NiFiProperties.C2_AGENT_DEFAULT_HEARTBEAT_PERIOD))))
.connectTimeout((long)
FormatUtils.getPreciseTimeDuration(properties.getProperty(C2NiFiProperties.C2_CONNECTION_TIMEOUT,