Search in sources :

Example 1 with NodeConnectionStatus

use of org.apache.nifi.cluster.coordination.node.NodeConnectionStatus in project nifi by apache.

the class StandardNiFiServiceFacade method deleteNode.

@Override
public void deleteNode(final String nodeId) {
    final NiFiUser user = NiFiUserUtils.getNiFiUser();
    if (user == null) {
        throw new WebApplicationException(new Throwable("Unable to access details for current user."));
    }
    final String userDn = user.getIdentity();
    final NodeIdentifier nodeIdentifier = clusterCoordinator.getNodeIdentifier(nodeId);
    if (nodeIdentifier == null) {
        throw new UnknownNodeException("Cannot remove Node with ID " + nodeId + " because it is not part of the cluster");
    }
    final NodeConnectionStatus nodeConnectionStatus = clusterCoordinator.getConnectionStatus(nodeIdentifier);
    if (!nodeConnectionStatus.getState().equals(NodeConnectionState.DISCONNECTED)) {
        throw new IllegalNodeDeletionException("Cannot remove Node with ID " + nodeId + " because it is not disconnected, current state = " + nodeConnectionStatus.getState());
    }
    clusterCoordinator.removeNode(nodeIdentifier, userDn);
    heartbeatMonitor.removeHeartbeat(nodeIdentifier);
}
Also used : NiFiUser(org.apache.nifi.authorization.user.NiFiUser) WebApplicationException(javax.ws.rs.WebApplicationException) UnknownNodeException(org.apache.nifi.cluster.manager.exception.UnknownNodeException) NodeIdentifier(org.apache.nifi.cluster.protocol.NodeIdentifier) NodeConnectionStatus(org.apache.nifi.cluster.coordination.node.NodeConnectionStatus) IllegalNodeDeletionException(org.apache.nifi.cluster.manager.exception.IllegalNodeDeletionException)

Example 2 with NodeConnectionStatus

use of org.apache.nifi.cluster.coordination.node.NodeConnectionStatus in project nifi by apache.

the class ThreadPoolRequestReplicator method replicate.

/**
 * Replicates the request to all nodes in the given set of node identifiers
 *
 * @param nodeIds             the NodeIdentifiers that identify which nodes to send the request to
 * @param method              the HTTP method to use
 * @param uri                 the URI to send the request to
 * @param entity              the entity to use
 * @param headers             the HTTP Headers
 * @param performVerification whether or not to verify that all nodes in the cluster are connected and that all nodes can perform request. Ignored if request is not mutable.
 * @param response            the response to update with the results
 * @param executionPhase      <code>true</code> if this is the execution phase, <code>false</code> otherwise
 * @param monitor             a monitor that will be notified when the request completes (successfully or otherwise)
 * @return an AsyncClusterResponse that can be used to obtain the result
 */
AsyncClusterResponse replicate(final Set<NodeIdentifier> nodeIds, final String method, final URI uri, final Object entity, final Map<String, String> headers, final boolean performVerification, StandardAsyncClusterResponse response, final boolean executionPhase, final boolean merge, final Object monitor) {
    try {
        // state validation
        Objects.requireNonNull(nodeIds);
        Objects.requireNonNull(method);
        Objects.requireNonNull(uri);
        Objects.requireNonNull(entity);
        Objects.requireNonNull(headers);
        if (nodeIds.isEmpty()) {
            throw new IllegalArgumentException("Cannot replicate request to 0 nodes");
        }
        // verify all of the nodes exist and are in the proper state
        for (final NodeIdentifier nodeId : nodeIds) {
            final NodeConnectionStatus status = clusterCoordinator.getConnectionStatus(nodeId);
            if (status == null) {
                throw new UnknownNodeException("Node " + nodeId + " does not exist in this cluster");
            }
            if (status.getState() != NodeConnectionState.CONNECTED) {
                throw new IllegalClusterStateException("Cannot replicate request to Node " + nodeId + " because the node is not connected");
            }
        }
        logger.debug("Replicating request {} {} with entity {} to {}; response is {}", method, uri, entity, nodeIds, response);
        // Update headers to indicate the current revision so that we can
        // prevent multiple users changing the flow at the same time
        final Map<String, String> updatedHeaders = new HashMap<>(headers);
        final String requestId = updatedHeaders.computeIfAbsent(REQUEST_TRANSACTION_ID_HEADER, key -> UUID.randomUUID().toString());
        long verifyClusterStateNanos = -1;
        if (performVerification) {
            final long start = System.nanoTime();
            verifyClusterState(method, uri.getPath());
            verifyClusterStateNanos = System.nanoTime() - start;
        }
        int numRequests = responseMap.size();
        if (numRequests >= maxConcurrentRequests) {
            numRequests = purgeExpiredRequests();
        }
        if (numRequests >= maxConcurrentRequests) {
            final Map<String, Long> countsByUri = responseMap.values().stream().collect(Collectors.groupingBy(StandardAsyncClusterResponse::getURIPath, Collectors.counting()));
            logger.error("Cannot replicate request {} {} because there are {} outstanding HTTP Requests already. Request Counts Per URI = {}", method, uri.getPath(), numRequests, countsByUri);
            throw new IllegalStateException("There are too many outstanding HTTP requests with a total " + numRequests + " outstanding requests");
        }
        // create a response object if one was not already passed to us
        if (response == null) {
            // create the request objects and replicate to all nodes.
            // When the request has completed, we need to ensure that we notify the monitor, if there is one.
            final CompletionCallback completionCallback = clusterResponse -> {
                try {
                    onCompletedResponse(requestId);
                } finally {
                    if (monitor != null) {
                        synchronized (monitor) {
                            monitor.notify();
                        }
                        logger.debug("Notified monitor {} because request {} {} has completed", monitor, method, uri);
                    }
                }
            };
            final Runnable responseConsumedCallback = () -> onResponseConsumed(requestId);
            response = new StandardAsyncClusterResponse(requestId, uri, method, nodeIds, responseMapper, completionCallback, responseConsumedCallback, merge);
            responseMap.put(requestId, response);
        }
        if (verifyClusterStateNanos > -1) {
            response.addTiming("Verify Cluster State", "All Nodes", verifyClusterStateNanos);
        }
        logger.debug("For Request ID {}, response object is {}", requestId, response);
        // if mutable request, we have to do a two-phase commit where we ask each node to verify
        // that the request can take place and then, if all nodes agree that it can, we can actually
        // issue the request. This is all handled by calling performVerification, which will replicate
        // the 'vote' request to all nodes and then if successful will call back into this method to
        // replicate the actual request.
        final boolean mutableRequest = isMutableRequest(method, uri.getPath());
        if (mutableRequest && performVerification) {
            logger.debug("Performing verification (first phase of two-phase commit) for Request ID {}", requestId);
            performVerification(nodeIds, method, uri, entity, updatedHeaders, response, merge, monitor);
            return response;
        } else if (mutableRequest) {
            response.setPhase(StandardAsyncClusterResponse.COMMIT_PHASE);
        }
        // Callback function for generating a NodeHttpRequestCallable that can be used to perform the work
        final StandardAsyncClusterResponse finalResponse = response;
        NodeRequestCompletionCallback nodeCompletionCallback = nodeResponse -> {
            logger.debug("Received response from {} for {} {}", nodeResponse.getNodeId(), method, uri.getPath());
            finalResponse.add(nodeResponse);
        };
        // instruct the node to actually perform the underlying action
        if (mutableRequest && executionPhase) {
            updatedHeaders.put(REQUEST_EXECUTION_HTTP_HEADER, "true");
        }
        // replicate the request to all nodes
        final Function<NodeIdentifier, NodeHttpRequest> requestFactory = nodeId -> new NodeHttpRequest(nodeId, method, createURI(uri, nodeId), entity, updatedHeaders, nodeCompletionCallback, finalResponse);
        submitAsyncRequest(nodeIds, uri.getScheme(), uri.getPath(), requestFactory, updatedHeaders);
        return response;
    } catch (final Throwable t) {
        if (monitor != null) {
            synchronized (monitor) {
                monitor.notify();
            }
            logger.debug("Notified monitor {} because request {} {} has failed with Throwable {}", monitor, method, uri, t);
        }
        if (response != null) {
            final RuntimeException failure = (t instanceof RuntimeException) ? (RuntimeException) t : new RuntimeException("Failed to submit Replication Request to background thread", t);
            response.setFailure(failure, new NodeIdentifier());
        }
        throw t;
    }
}
Also used : NodeIdentifier(org.apache.nifi.cluster.protocol.NodeIdentifier) DisconnectedNodeMutableRequestException(org.apache.nifi.cluster.manager.exception.DisconnectedNodeMutableRequestException) URISyntaxException(java.net.URISyntaxException) LoggerFactory(org.slf4j.LoggerFactory) UriConstructionException(org.apache.nifi.cluster.manager.exception.UriConstructionException) StringUtils(org.apache.commons.lang3.StringUtils) MediaType(javax.ws.rs.core.MediaType) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) Map(java.util.Map) URI(java.net.URI) ThreadFactory(java.util.concurrent.ThreadFactory) NodeResponse(org.apache.nifi.cluster.manager.NodeResponse) ReadWriteLock(java.util.concurrent.locks.ReadWriteLock) ComponentIdGenerator(org.apache.nifi.util.ComponentIdGenerator) HttpHeaders(org.apache.nifi.remote.protocol.http.HttpHeaders) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) HttpResponseMapper(org.apache.nifi.cluster.coordination.http.HttpResponseMapper) Set(java.util.Set) ClientProperties(org.glassfish.jersey.client.ClientProperties) Invocation(javax.ws.rs.client.Invocation) UUID(java.util.UUID) EncodingFilter(org.glassfish.jersey.client.filter.EncodingFilter) StandardHttpResponseMapper(org.apache.nifi.cluster.coordination.http.StandardHttpResponseMapper) ProxiedEntitiesUtils(org.apache.nifi.web.security.ProxiedEntitiesUtils) Entity(javax.ws.rs.client.Entity) LinkedBlockingQueue(java.util.concurrent.LinkedBlockingQueue) Collectors(java.util.stream.Collectors) Executors(java.util.concurrent.Executors) Objects(java.util.Objects) List(java.util.List) GZipEncoder(org.glassfish.jersey.message.GZipEncoder) Stream(java.util.stream.Stream) Response(javax.ws.rs.core.Response) IllegalClusterStateException(org.apache.nifi.cluster.manager.exception.IllegalClusterStateException) Entry(java.util.Map.Entry) ThreadPoolExecutor(java.util.concurrent.ThreadPoolExecutor) NoConnectedNodesException(org.apache.nifi.cluster.manager.exception.NoConnectedNodesException) Client(javax.ws.rs.client.Client) AccessDeniedException(org.apache.nifi.authorization.AccessDeniedException) HashMap(java.util.HashMap) ConnectingNodeMutableRequestException(org.apache.nifi.cluster.manager.exception.ConnectingNodeMutableRequestException) ReentrantReadWriteLock(java.util.concurrent.locks.ReentrantReadWriteLock) Function(java.util.function.Function) HttpMethod(javax.ws.rs.HttpMethod) ConcurrentMap(java.util.concurrent.ConcurrentMap) HashSet(java.util.HashSet) NiFiUser(org.apache.nifi.authorization.user.NiFiUser) ClusterCoordinator(org.apache.nifi.cluster.coordination.ClusterCoordinator) ScheduledExecutorService(java.util.concurrent.ScheduledExecutorService) NodeConnectionState(org.apache.nifi.cluster.coordination.node.NodeConnectionState) Status(javax.ws.rs.core.Response.Status) NodeConnectionStatus(org.apache.nifi.cluster.coordination.node.NodeConnectionStatus) LongSummaryStatistics(java.util.LongSummaryStatistics) JwtAuthenticationFilter(org.apache.nifi.web.security.jwt.JwtAuthenticationFilter) Logger(org.slf4j.Logger) MultivaluedHashMap(javax.ws.rs.core.MultivaluedHashMap) MultivaluedMap(javax.ws.rs.core.MultivaluedMap) TimeUnit(java.util.concurrent.TimeUnit) Lock(java.util.concurrent.locks.Lock) EventReporter(org.apache.nifi.events.EventReporter) FormatUtils(org.apache.nifi.util.FormatUtils) NiFiProperties(org.apache.nifi.util.NiFiProperties) NiFiUserUtils(org.apache.nifi.authorization.user.NiFiUserUtils) Severity(org.apache.nifi.reporting.Severity) UnknownNodeException(org.apache.nifi.cluster.manager.exception.UnknownNodeException) WebTarget(javax.ws.rs.client.WebTarget) Collections(java.util.Collections) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) HashMap(java.util.HashMap) MultivaluedHashMap(javax.ws.rs.core.MultivaluedHashMap) IllegalClusterStateException(org.apache.nifi.cluster.manager.exception.IllegalClusterStateException) NodeConnectionStatus(org.apache.nifi.cluster.coordination.node.NodeConnectionStatus) UnknownNodeException(org.apache.nifi.cluster.manager.exception.UnknownNodeException) NodeIdentifier(org.apache.nifi.cluster.protocol.NodeIdentifier)

Example 3 with NodeConnectionStatus

use of org.apache.nifi.cluster.coordination.node.NodeConnectionStatus in project nifi by apache.

the class TestThreadPoolRequestReplicator method testMultipleRequestWithTwoPhaseCommit.

@Test(timeout = 15000)
public void testMultipleRequestWithTwoPhaseCommit() {
    final Set<NodeIdentifier> nodeIds = new HashSet<>();
    final NodeIdentifier nodeId = new NodeIdentifier("1", "localhost", 8100, "localhost", 8101, "localhost", 8102, 8103, false);
    nodeIds.add(nodeId);
    final ClusterCoordinator coordinator = mock(ClusterCoordinator.class);
    when(coordinator.getConnectionStatus(Mockito.any(NodeIdentifier.class))).thenReturn(new NodeConnectionStatus(nodeId, NodeConnectionState.CONNECTED));
    final AtomicInteger requestCount = new AtomicInteger(0);
    final NiFiProperties props = NiFiProperties.createBasicNiFiProperties(null, null);
    final ThreadPoolRequestReplicator replicator = new ThreadPoolRequestReplicator(2, 5, 100, ClientBuilder.newClient(), coordinator, "1 sec", "1 sec", null, null, props) {

        @Override
        protected NodeResponse replicateRequest(final Invocation invocation, final NodeIdentifier nodeId, final String method, final URI uri, final String requestId, Map<String, String> givenHeaders, final StandardAsyncClusterResponse response) {
            // the resource builder will not expose its headers to us, so we are using Mockito's Whitebox class to extract them.
            final ClientRequest requestContext = (ClientRequest) Whitebox.getInternalState(invocation, "requestContext");
            final Object expectsHeader = requestContext.getHeaders().getFirst(ThreadPoolRequestReplicator.REQUEST_VALIDATION_HTTP_HEADER);
            final int statusCode;
            if (requestCount.incrementAndGet() == 1) {
                assertEquals(ThreadPoolRequestReplicator.NODE_CONTINUE, expectsHeader);
                statusCode = 150;
            } else {
                assertNull(expectsHeader);
                statusCode = Status.OK.getStatusCode();
            }
            // Return given response from all nodes.
            final Response clientResponse = mock(Response.class);
            when(clientResponse.getStatus()).thenReturn(statusCode);
            return new NodeResponse(nodeId, method, uri, clientResponse, -1L, requestId);
        }
    };
    try {
        // set the user
        final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(StandardNiFiUser.ANONYMOUS));
        SecurityContextHolder.getContext().setAuthentication(authentication);
        final AsyncClusterResponse clusterResponse = replicator.replicate(nodeIds, HttpMethod.POST, new URI("http://localhost:80/processors/1"), new ProcessorEntity(), new HashMap<>(), true, true);
        clusterResponse.awaitMergedResponse();
        // Ensure that we received two requests - the first should contain the X-NcmExpects header; the second should not.
        // These assertions are validated above, in the overridden replicateRequest method.
        assertEquals(2, requestCount.get());
    } catch (final Exception e) {
        e.printStackTrace();
        Assert.fail(e.toString());
    } finally {
        replicator.shutdown();
    }
}
Also used : NiFiProperties(org.apache.nifi.util.NiFiProperties) Invocation(javax.ws.rs.client.Invocation) NodeResponse(org.apache.nifi.cluster.manager.NodeResponse) ProcessorEntity(org.apache.nifi.web.api.entity.ProcessorEntity) URI(java.net.URI) NodeConnectionStatus(org.apache.nifi.cluster.coordination.node.NodeConnectionStatus) ClientRequest(org.glassfish.jersey.client.ClientRequest) NiFiUserDetails(org.apache.nifi.authorization.user.NiFiUserDetails) HashSet(java.util.HashSet) ClusterCoordinator(org.apache.nifi.cluster.coordination.ClusterCoordinator) DisconnectedNodeMutableRequestException(org.apache.nifi.cluster.manager.exception.DisconnectedNodeMutableRequestException) URISyntaxException(java.net.URISyntaxException) IllegalClusterStateException(org.apache.nifi.cluster.manager.exception.IllegalClusterStateException) ProcessingException(javax.ws.rs.ProcessingException) ConnectingNodeMutableRequestException(org.apache.nifi.cluster.manager.exception.ConnectingNodeMutableRequestException) SocketTimeoutException(java.net.SocketTimeoutException) NiFiAuthenticationToken(org.apache.nifi.web.security.token.NiFiAuthenticationToken) NodeResponse(org.apache.nifi.cluster.manager.NodeResponse) Response(javax.ws.rs.core.Response) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) Authentication(org.springframework.security.core.Authentication) NodeIdentifier(org.apache.nifi.cluster.protocol.NodeIdentifier) Map(java.util.Map) HashMap(java.util.HashMap) MultiValueMap(org.apache.commons.collections4.map.MultiValueMap) Test(org.junit.Test)

Example 4 with NodeConnectionStatus

use of org.apache.nifi.cluster.coordination.node.NodeConnectionStatus in project nifi by apache.

the class ClusterConnectionIT method testNodeInheritsClusterTopologyOnHeartbeat.

@Test(timeout = 60000)
public void testNodeInheritsClusterTopologyOnHeartbeat() throws InterruptedException {
    final Node node1 = cluster.createNode();
    final Node node2 = cluster.createNode();
    final Node node3 = cluster.createNode();
    cluster.waitUntilAllNodesConnected(10, TimeUnit.SECONDS);
    final Node coordinator = cluster.waitForClusterCoordinator(10, TimeUnit.SECONDS);
    final NodeIdentifier node4NotReallyInCluster = new NodeIdentifier(UUID.randomUUID().toString(), "localhost", 9283, "localhost", 9284, "localhost", 9285, null, false, null);
    final Map<NodeIdentifier, NodeConnectionStatus> replacementStatuses = new HashMap<>();
    replacementStatuses.put(node1.getIdentifier(), new NodeConnectionStatus(node1.getIdentifier(), DisconnectionCode.USER_DISCONNECTED));
    replacementStatuses.put(node4NotReallyInCluster, new NodeConnectionStatus(node4NotReallyInCluster, NodeConnectionState.CONNECTING));
    // reset coordinator status so that other nodes with get its now-fake view of the cluster
    coordinator.getClusterCoordinator().resetNodeStatuses(replacementStatuses);
    final List<NodeConnectionStatus> expectedStatuses = coordinator.getClusterCoordinator().getConnectionStatuses();
    // give nodes a bit to heartbeat in. We need to wait long enough that each node heartbeats.
    // But we need to not wait more than 8 seconds because that's when nodes start getting kicked out.
    Thread.sleep(6000L);
    for (final Node node : new Node[] { node1, node2, node3 }) {
        assertEquals(expectedStatuses, node.getClusterCoordinator().getConnectionStatuses());
    }
}
Also used : HashMap(java.util.HashMap) NodeIdentifier(org.apache.nifi.cluster.protocol.NodeIdentifier) NodeConnectionStatus(org.apache.nifi.cluster.coordination.node.NodeConnectionStatus) Test(org.junit.Test)

Example 5 with NodeConnectionStatus

use of org.apache.nifi.cluster.coordination.node.NodeConnectionStatus in project nifi by apache.

the class StandardFlowService method disconnect.

private void disconnect(final String explanation) {
    writeLock.lock();
    try {
        logger.info("Disconnecting node due to " + explanation);
        // mark node as not connected
        controller.setConnectionStatus(new NodeConnectionStatus(nodeId, DisconnectionCode.UNKNOWN, explanation));
        // turn off primary flag
        controller.setPrimary(false);
        // stop heartbeating
        controller.stopHeartbeating();
        // set node to not clustered
        controller.setClustered(false, null);
        clusterCoordinator.setConnected(false);
        logger.info("Node disconnected due to " + explanation);
    } finally {
        writeLock.unlock();
    }
}
Also used : NodeConnectionStatus(org.apache.nifi.cluster.coordination.node.NodeConnectionStatus)

Aggregations

NodeConnectionStatus (org.apache.nifi.cluster.coordination.node.NodeConnectionStatus)16 NodeIdentifier (org.apache.nifi.cluster.protocol.NodeIdentifier)12 ClusterCoordinator (org.apache.nifi.cluster.coordination.ClusterCoordinator)5 Test (org.junit.Test)5 HashMap (java.util.HashMap)4 Map (java.util.Map)4 ProtocolException (org.apache.nifi.cluster.protocol.ProtocolException)4 ByteArrayInputStream (java.io.ByteArrayInputStream)3 ByteArrayOutputStream (java.io.ByteArrayOutputStream)3 IOException (java.io.IOException)3 List (java.util.List)3 NodeConnectionState (org.apache.nifi.cluster.coordination.node.NodeConnectionState)3 DataFlow (org.apache.nifi.cluster.protocol.DataFlow)3 HeartbeatPayload (org.apache.nifi.cluster.protocol.HeartbeatPayload)3 StandardDataFlow (org.apache.nifi.cluster.protocol.StandardDataFlow)3 HeartbeatMessage (org.apache.nifi.cluster.protocol.message.HeartbeatMessage)3 URI (java.net.URI)2 URISyntaxException (java.net.URISyntaxException)2 Collections (java.util.Collections)2 HashSet (java.util.HashSet)2