You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ma...@apache.org on 2018/10/11 13:52:53 UTC

[2/6] nifi git commit: NIFI-5585 Added capability to offload a node that is disconnected from the cluster. Updated NodeClusterCoordinator to allow idempotent requests to offload a cluster Added capability to connect/delete/disconnect/offload a node from

http://git-wip-us.apache.org/repos/asf/nifi/blob/04d8da8f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster-table.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster-table.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster-table.js
index 095f714..0dc74d7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster-table.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster-table.js
@@ -528,6 +528,8 @@
                 promptForConnect(item);
             } else if (target.hasClass('prompt-for-removal')) {
                 promptForRemoval(item);
+            } else if (target.hasClass('prompt-for-offload')) {
+                promptForOffload(item);
             } else if (target.hasClass('prompt-for-disconnect')) {
                 promptForDisconnect(item);
             }
@@ -630,19 +632,29 @@
             var actionFormatter = function (row, cell, value, columnDef, dataContext) {
                 var canDisconnect = false;
                 var canConnect = false;
+                var isOffloaded = false;
 
                 // determine the current status
                 if (dataContext.status === 'CONNECTED' || dataContext.status === 'CONNECTING') {
                     canDisconnect = true;
-                } else if (dataContext.status === 'DISCONNECTED') {
+                }
+                if (dataContext.status === 'DISCONNECTED') {
                     canConnect = true;
                 }
+                if (dataContext.status === 'OFFLOADED') {
+                    isOffloaded = true;
+                }
 
                 // return the appropriate markup
                 if (canConnect) {
-                    return '<div title="Connect" class="pointer prompt-for-connect fa fa-plug"></div><div title="Delete" class="pointer prompt-for-removal fa fa-trash"></div>';
+                    return '<div title="Connect" class="pointer prompt-for-connect fa fa-plug"></div>' +
+                        '<div title="Delete" class="pointer prompt-for-removal fa fa-trash"></div>' +
+                        '<div title="Offload" class="pointer prompt-for-offload fa fa-rotate-90 fa-upload"></div>';
                 } else if (canDisconnect) {
                     return '<div title="Disconnect" class="pointer prompt-for-disconnect fa fa-power-off"></div>';
+                } else if (isOffloaded) {
+                    return '<div title="Connect" class="pointer prompt-for-connect fa fa-plug"></div>' +
+                        '<div title="Delete" class="pointer prompt-for-removal fa fa-trash"></div>';
                 } else {
                     return '<div style="width: 16px; height: 16px;">&nbsp;</div>';
                 }
@@ -947,6 +959,50 @@
     };
 
     /**
+     * Prompts to verify node offload.
+     *
+     * @argument {object} node     The node
+     */
+    var promptForOffload = function (node) {
+        nfDialog.showYesNoDialog({
+            headerText: 'Offload Node',
+            dialogContent: 'Offload \'' + formatNodeAddress(node) + '\'?',
+            yesHandler: function () {
+                offload(node.nodeId);
+            }
+        });
+    };
+
+    /**
+     * Offloads the node in the specified row.
+     *
+     * @argument {string} nodeId     The node id
+     */
+    var offload = function (nodeId) {
+        var entity = {
+            'node': {
+                'nodeId': nodeId,
+                'status': 'OFFLOADING'
+            }
+        };
+
+        $.ajax({
+            type: 'PUT',
+            url: config.urls.nodes + '/' + encodeURIComponent(nodeId),
+            data: JSON.stringify(entity),
+            dataType: 'json',
+            contentType: 'application/json'
+        }).done(function (response) {
+            var node = response.node;
+
+            // update the node in the table
+            var clusterGrid = $('#cluster-nodes-table').data('gridInstance');
+            var clusterData = clusterGrid.getData();
+            clusterData.updateItem(node.nodeId, node);
+        }).fail(nfErrorHandler.handleAjaxError);
+    };
+
+    /**
      * Prompts to verify node disconnection.
      *
      * @argument {object} node     The node

http://git-wip-us.apache.org/repos/asf/nifi/blob/04d8da8f/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ControllerClient.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ControllerClient.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ControllerClient.java
index 22821ee..6cb3226 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ControllerClient.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ControllerClient.java
@@ -16,6 +16,8 @@
  */
 package org.apache.nifi.toolkit.cli.impl.client.nifi;
 
+import org.apache.nifi.web.api.entity.ClusterEntity;
+import org.apache.nifi.web.api.entity.NodeEntity;
 import org.apache.nifi.web.api.entity.RegistryClientEntity;
 import org.apache.nifi.web.api.entity.RegistryClientsEntity;
 
@@ -34,4 +36,16 @@ public interface ControllerClient {
 
     RegistryClientEntity updateRegistryClient(RegistryClientEntity registryClientEntity) throws NiFiClientException, IOException;
 
+    NodeEntity connectNode(String nodeId, NodeEntity nodeEntity) throws NiFiClientException, IOException;
+
+    NodeEntity deleteNode(String nodeId) throws NiFiClientException, IOException;
+
+    NodeEntity disconnectNode(String nodeId, NodeEntity nodeEntity) throws NiFiClientException, IOException;
+
+    NodeEntity getNode(String nodeId) throws NiFiClientException, IOException;
+
+    ClusterEntity getNodes() throws NiFiClientException, IOException;
+
+    NodeEntity offloadNode(String nodeId, NodeEntity nodeEntity) throws NiFiClientException, IOException;
+
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/04d8da8f/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyControllerClient.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyControllerClient.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyControllerClient.java
index 9c9ffc4..a162790 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyControllerClient.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyControllerClient.java
@@ -19,6 +19,8 @@ package org.apache.nifi.toolkit.cli.impl.client.nifi.impl;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.ControllerClient;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.web.api.entity.ClusterEntity;
+import org.apache.nifi.web.api.entity.NodeEntity;
 import org.apache.nifi.web.api.entity.RegistryClientEntity;
 import org.apache.nifi.web.api.entity.RegistryClientsEntity;
 
@@ -104,4 +106,89 @@ public class JerseyControllerClient extends AbstractJerseyClient implements Cont
         });
     }
 
+    @Override
+    public NodeEntity deleteNode(final String nodeId) throws NiFiClientException, IOException {
+        if (StringUtils.isBlank(nodeId)) {
+            throw new IllegalArgumentException("Node ID cannot be null or empty");
+        }
+
+        return executeAction("Error deleting node", () -> {
+            final WebTarget target = controllerTarget.path("cluster/nodes/" + nodeId);
+
+            return getRequestBuilder(target).delete(NodeEntity.class);
+        });
+    }
+
+    @Override
+    public NodeEntity connectNode(final String nodeId, final NodeEntity nodeEntity) throws NiFiClientException, IOException {
+        if (StringUtils.isBlank(nodeId)) {
+            throw new IllegalArgumentException("Node ID cannot be null or empty");
+        }
+
+        if (nodeEntity == null) {
+            throw new IllegalArgumentException("Node entity cannot be null");
+        }
+
+        return executeAction("Error connecting node", () -> {
+            final WebTarget target = controllerTarget.path("cluster/nodes/" + nodeId);
+
+            return getRequestBuilder(target).put(Entity.entity(nodeEntity, MediaType.APPLICATION_JSON), NodeEntity.class);
+        });
+    }
+
+    @Override
+    public NodeEntity offloadNode(final String nodeId, final NodeEntity nodeEntity) throws NiFiClientException, IOException {
+        if (StringUtils.isBlank(nodeId)) {
+            throw new IllegalArgumentException("Node ID cannot be null or empty");
+        }
+
+        if (nodeEntity == null) {
+            throw new IllegalArgumentException("Node entity cannot be null");
+        }
+
+        return executeAction("Error offloading node", () -> {
+            final WebTarget target = controllerTarget.path("cluster/nodes/" + nodeId);
+
+            return getRequestBuilder(target).put(Entity.entity(nodeEntity, MediaType.APPLICATION_JSON), NodeEntity.class);
+        });
+    }
+
+    @Override
+    public NodeEntity disconnectNode(final String nodeId, final NodeEntity nodeEntity) throws NiFiClientException, IOException {
+        if (StringUtils.isBlank(nodeId)) {
+            throw new IllegalArgumentException("Node ID cannot be null or empty");
+        }
+
+        if (nodeEntity == null) {
+            throw new IllegalArgumentException("Node entity cannot be null");
+        }
+
+        return executeAction("Error disconnecting node", () -> {
+            final WebTarget target = controllerTarget.path("cluster/nodes/" + nodeId);
+
+            return getRequestBuilder(target).put(Entity.entity(nodeEntity, MediaType.APPLICATION_JSON), NodeEntity.class);
+        });
+    }
+
+    @Override
+    public NodeEntity getNode(String nodeId) throws NiFiClientException, IOException {
+        if (StringUtils.isBlank(nodeId)) {
+            throw new IllegalArgumentException("Node ID cannot be null or empty");
+        }
+
+        return executeAction("Error retrieving node status", () -> {
+            final WebTarget target = controllerTarget.path("cluster/nodes/" + nodeId);
+
+            return getRequestBuilder(target).get(NodeEntity.class);
+        });
+    }
+
+    @Override
+    public ClusterEntity getNodes() throws NiFiClientException, IOException {
+        return executeAction("Error retrieving node status", () -> {
+            final WebTarget target = controllerTarget.path("cluster");
+
+            return getRequestBuilder(target).get(ClusterEntity.class);
+        });
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/04d8da8f/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandOption.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandOption.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandOption.java
index ad15036..171e6cf 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandOption.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandOption.java
@@ -49,6 +49,9 @@ public enum CommandOption {
     SRC_FLOW_ID("sf", "sourceFlowIdentifier", "A flow identifier from the source registry", true),
     SRC_FLOW_VERSION("sfv", "sourceFlowVersion", "A version of a flow from the source registry", true),
 
+    // NiFi - Nodes
+    NIFI_NODE_ID("nnid", "nifiNodeId", "The ID of a node in the NiFi cluster", true),
+
     // NiFi - Registries
     REGISTRY_CLIENT_ID("rcid", "registryClientId", "The id of a registry client", true),
     REGISTRY_CLIENT_NAME("rcn", "registryClientName", "The name of the registry client", true),

http://git-wip-us.apache.org/repos/asf/nifi/blob/04d8da8f/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/NiFiCommandGroup.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/NiFiCommandGroup.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/NiFiCommandGroup.java
index 00a38a2..298b709 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/NiFiCommandGroup.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/NiFiCommandGroup.java
@@ -21,6 +21,12 @@ import org.apache.nifi.toolkit.cli.impl.command.AbstractCommandGroup;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.flow.ClusterSummary;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.flow.CurrentUser;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.flow.GetRootId;
+import org.apache.nifi.toolkit.cli.impl.command.nifi.nodes.ConnectNode;
+import org.apache.nifi.toolkit.cli.impl.command.nifi.nodes.OffloadNode;
+import org.apache.nifi.toolkit.cli.impl.command.nifi.nodes.DeleteNode;
+import org.apache.nifi.toolkit.cli.impl.command.nifi.nodes.DisconnectNode;
+import org.apache.nifi.toolkit.cli.impl.command.nifi.nodes.GetNode;
+import org.apache.nifi.toolkit.cli.impl.command.nifi.nodes.GetNodes;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.pg.PGChangeVersion;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.pg.PGDisableControllerServices;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.pg.PGEnableControllerServices;
@@ -58,7 +64,13 @@ public class NiFiCommandGroup extends AbstractCommandGroup {
         final List<AbstractNiFiCommand> commands = new ArrayList<>();
         commands.add(new CurrentUser());
         commands.add(new ClusterSummary());
+        commands.add(new ConnectNode());
+        commands.add(new DeleteNode());
+        commands.add(new DisconnectNode());
         commands.add(new GetRootId());
+        commands.add(new GetNode());
+        commands.add(new GetNodes());
+        commands.add(new OffloadNode());
         commands.add(new ListRegistryClients());
         commands.add(new CreateRegistryClient());
         commands.add(new UpdateRegistryClient());

http://git-wip-us.apache.org/repos/asf/nifi/blob/04d8da8f/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/ConnectNode.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/ConnectNode.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/ConnectNode.java
new file mode 100644
index 0000000..8ec0066
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/ConnectNode.java
@@ -0,0 +1,67 @@
+/*
+ * 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.toolkit.cli.impl.command.nifi.nodes;
+
+import org.apache.commons.cli.MissingOptionException;
+import org.apache.nifi.toolkit.cli.api.CommandException;
+import org.apache.nifi.toolkit.cli.api.Context;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.ControllerClient;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.toolkit.cli.impl.command.CommandOption;
+import org.apache.nifi.toolkit.cli.impl.command.nifi.AbstractNiFiCommand;
+import org.apache.nifi.toolkit.cli.impl.result.NodeResult;
+import org.apache.nifi.web.api.dto.NodeDTO;
+import org.apache.nifi.web.api.entity.NodeEntity;
+
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Command for offloading a node of the NiFi cluster.
+ */
+public class ConnectNode extends AbstractNiFiCommand<NodeResult> {
+
+    public ConnectNode() {
+        super("connect-node", NodeResult.class);
+    }
+
+    @Override
+    public String getDescription() {
+        return "Connects a node to the NiFi cluster.";
+    }
+
+    @Override
+    protected void doInitialize(Context context) {
+        addOption(CommandOption.NIFI_NODE_ID.createOption());
+    }
+
+    @Override
+    public NodeResult doExecute(NiFiClient client, Properties properties) throws NiFiClientException, IOException, MissingOptionException, CommandException {
+        final String nodeId = getRequiredArg(properties, CommandOption.NIFI_NODE_ID);
+        final ControllerClient controllerClient = client.getControllerClient();
+
+        NodeDTO nodeDto = new NodeDTO();
+        nodeDto.setNodeId(nodeId);
+        // TODO There are no constants for the CONNECT node statuses
+        nodeDto.setStatus("CONNECTING");
+        NodeEntity nodeEntity = new NodeEntity();
+        nodeEntity.setNode(nodeDto);
+        NodeEntity nodeEntityResult = controllerClient.connectNode(nodeId, nodeEntity);
+        return new NodeResult(getResultType(properties), nodeEntityResult);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/04d8da8f/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/DeleteNode.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/DeleteNode.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/DeleteNode.java
new file mode 100644
index 0000000..280e625
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/DeleteNode.java
@@ -0,0 +1,58 @@
+/*
+ * 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.toolkit.cli.impl.command.nifi.nodes;
+
+import org.apache.commons.cli.MissingOptionException;
+import org.apache.nifi.toolkit.cli.api.Context;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.ControllerClient;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.toolkit.cli.impl.command.CommandOption;
+import org.apache.nifi.toolkit.cli.impl.command.nifi.AbstractNiFiCommand;
+import org.apache.nifi.toolkit.cli.impl.result.OkResult;
+
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Command for deleting a node from the NiFi cluster.
+ */
+public class DeleteNode extends AbstractNiFiCommand<OkResult> {
+
+    public DeleteNode() {
+        super("delete-node", OkResult.class);
+    }
+
+    @Override
+    public String getDescription() {
+        return "Deletes a node from the NiFi cluster.";
+    }
+
+    @Override
+    protected void doInitialize(Context context) {
+        addOption(CommandOption.NIFI_NODE_ID.createOption());
+    }
+
+    @Override
+    public OkResult doExecute(NiFiClient client, Properties properties) throws NiFiClientException, IOException, MissingOptionException {
+        final String nodeId = getRequiredArg(properties, CommandOption.NIFI_NODE_ID);
+        final ControllerClient controllerClient = client.getControllerClient();
+
+        controllerClient.deleteNode(nodeId);
+        return new OkResult(getContext().isInteractive());
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/04d8da8f/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/DisconnectNode.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/DisconnectNode.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/DisconnectNode.java
new file mode 100644
index 0000000..98fa03a
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/DisconnectNode.java
@@ -0,0 +1,67 @@
+/*
+ * 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.toolkit.cli.impl.command.nifi.nodes;
+
+import org.apache.commons.cli.MissingOptionException;
+import org.apache.nifi.toolkit.cli.api.CommandException;
+import org.apache.nifi.toolkit.cli.api.Context;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.ControllerClient;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.toolkit.cli.impl.command.CommandOption;
+import org.apache.nifi.toolkit.cli.impl.command.nifi.AbstractNiFiCommand;
+import org.apache.nifi.toolkit.cli.impl.result.NodeResult;
+import org.apache.nifi.web.api.dto.NodeDTO;
+import org.apache.nifi.web.api.entity.NodeEntity;
+
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Command for disconnecting a node from the NiFi cluster.
+ */
+public class DisconnectNode extends AbstractNiFiCommand<NodeResult> {
+
+    public DisconnectNode() {
+        super("disconnect-node", NodeResult.class);
+    }
+
+    @Override
+    public String getDescription() {
+        return "Disconnects a node from the NiFi cluster.";
+    }
+
+    @Override
+    protected void doInitialize(Context context) {
+        addOption(CommandOption.NIFI_NODE_ID.createOption());
+    }
+
+    @Override
+    public NodeResult doExecute(NiFiClient client, Properties properties) throws NiFiClientException, IOException, MissingOptionException, CommandException {
+        final String nodeId = getRequiredArg(properties, CommandOption.NIFI_NODE_ID);
+        final ControllerClient controllerClient = client.getControllerClient();
+
+        NodeDTO nodeDto = new NodeDTO();
+        nodeDto.setNodeId(nodeId);
+        // TODO There's no constant for node status in
+        nodeDto.setStatus("DISCONNECTING");
+        NodeEntity nodeEntity = new NodeEntity();
+        nodeEntity.setNode(nodeDto);
+        NodeEntity nodeEntityResult = controllerClient.disconnectNode(nodeId, nodeEntity);
+        return new NodeResult(getResultType(properties), nodeEntityResult);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/04d8da8f/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/GetNode.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/GetNode.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/GetNode.java
new file mode 100644
index 0000000..54687bd
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/GetNode.java
@@ -0,0 +1,59 @@
+/*
+ * 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.toolkit.cli.impl.command.nifi.nodes;
+
+import org.apache.commons.cli.MissingOptionException;
+import org.apache.nifi.toolkit.cli.api.Context;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.ControllerClient;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.toolkit.cli.impl.command.CommandOption;
+import org.apache.nifi.toolkit.cli.impl.command.nifi.AbstractNiFiCommand;
+import org.apache.nifi.toolkit.cli.impl.result.NodeResult;
+import org.apache.nifi.web.api.entity.NodeEntity;
+
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Command for retrieving the status of the nodes from the NiFi cluster.
+ */
+public class GetNode extends AbstractNiFiCommand<NodeResult> {
+
+    public GetNode() {
+        super("get-node", NodeResult.class);
+    }
+
+    @Override
+    public String getDescription() {
+        return "Retrieves the status for a node in the NiFi cluster.";
+    }
+
+    @Override
+    protected void doInitialize(Context context) {
+        addOption(CommandOption.NIFI_NODE_ID.createOption());
+    }
+
+    @Override
+    public NodeResult doExecute(NiFiClient client, Properties properties) throws NiFiClientException, IOException, MissingOptionException {
+        final String nodeId = getRequiredArg(properties, CommandOption.NIFI_NODE_ID);
+        final ControllerClient controllerClient = client.getControllerClient();
+
+        NodeEntity nodeEntityResult = controllerClient.getNode(nodeId);
+        return new NodeResult(getResultType(properties), nodeEntityResult);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/04d8da8f/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/GetNodes.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/GetNodes.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/GetNodes.java
new file mode 100644
index 0000000..368fb4d
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/GetNodes.java
@@ -0,0 +1,52 @@
+/*
+ * 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.toolkit.cli.impl.command.nifi.nodes;
+
+import org.apache.commons.cli.MissingOptionException;
+import org.apache.nifi.toolkit.cli.api.CommandException;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.ControllerClient;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.toolkit.cli.impl.command.nifi.AbstractNiFiCommand;
+import org.apache.nifi.toolkit.cli.impl.result.NodesResult;
+import org.apache.nifi.web.api.entity.ClusterEntity;
+
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Command for retrieving the status of the nodes from the NiFi cluster.
+ */
+public class GetNodes extends AbstractNiFiCommand<NodesResult> {
+
+    public GetNodes() {
+        super("get-nodes", NodesResult.class);
+    }
+
+    @Override
+    public String getDescription() {
+        return "Retrieves statuses for the nodes of the NiFi cluster.";
+    }
+
+    @Override
+    public NodesResult doExecute(NiFiClient client, Properties properties) throws NiFiClientException, IOException, MissingOptionException, CommandException {
+        final ControllerClient controllerClient = client.getControllerClient();
+
+        ClusterEntity clusterEntityResult = controllerClient.getNodes();
+        return new NodesResult(getResultType(properties), clusterEntityResult);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/04d8da8f/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/OffloadNode.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/OffloadNode.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/OffloadNode.java
new file mode 100644
index 0000000..aa759b1
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/nodes/OffloadNode.java
@@ -0,0 +1,67 @@
+/*
+ * 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.toolkit.cli.impl.command.nifi.nodes;
+
+import org.apache.commons.cli.MissingOptionException;
+import org.apache.nifi.toolkit.cli.api.CommandException;
+import org.apache.nifi.toolkit.cli.api.Context;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.ControllerClient;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.toolkit.cli.impl.command.CommandOption;
+import org.apache.nifi.toolkit.cli.impl.command.nifi.AbstractNiFiCommand;
+import org.apache.nifi.toolkit.cli.impl.result.NodeResult;
+import org.apache.nifi.web.api.dto.NodeDTO;
+import org.apache.nifi.web.api.entity.NodeEntity;
+
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Command for offloading a node of the NiFi cluster.
+ */
+public class OffloadNode extends AbstractNiFiCommand<NodeResult> {
+
+    public OffloadNode() {
+        super("offload-node", NodeResult.class);
+    }
+
+    @Override
+    public String getDescription() {
+        return "Offloads a node of the NiFi cluster.";
+    }
+
+    @Override
+    protected void doInitialize(Context context) {
+        addOption(CommandOption.NIFI_NODE_ID.createOption());
+    }
+
+    @Override
+    public NodeResult doExecute(NiFiClient client, Properties properties) throws NiFiClientException, IOException, MissingOptionException, CommandException {
+        final String nodeId = getRequiredArg(properties, CommandOption.NIFI_NODE_ID);
+        final ControllerClient controllerClient = client.getControllerClient();
+
+        NodeDTO nodeDto = new NodeDTO();
+        nodeDto.setNodeId(nodeId);
+        // TODO There are no constants for the OFFLOAD node statuses
+        nodeDto.setStatus("OFFLOADING");
+        NodeEntity nodeEntity = new NodeEntity();
+        nodeEntity.setNode(nodeDto);
+        NodeEntity nodeEntityResult = controllerClient.offloadNode(nodeId, nodeEntity);
+        return new NodeResult(getResultType(properties), nodeEntityResult);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/04d8da8f/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/NodeResult.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/NodeResult.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/NodeResult.java
new file mode 100644
index 0000000..3e1efdf
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/NodeResult.java
@@ -0,0 +1,48 @@
+/*
+ * 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.toolkit.cli.impl.result;
+
+import org.apache.commons.lang3.Validate;
+import org.apache.nifi.toolkit.cli.api.ResultType;
+import org.apache.nifi.web.api.dto.NodeDTO;
+import org.apache.nifi.web.api.entity.NodeEntity;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+public class NodeResult extends AbstractWritableResult<NodeEntity> {
+
+    private final NodeEntity nodeEntity;
+
+    public NodeResult(ResultType resultType, NodeEntity nodeEntity) {
+        super(resultType);
+        this.nodeEntity = nodeEntity;
+        Validate.notNull(nodeEntity);
+    }
+
+    @Override
+    public NodeEntity getResult() {
+        return nodeEntity;
+    }
+
+    @Override
+    protected void writeSimpleResult(PrintStream output) throws IOException {
+        NodeDTO nodeDTO = nodeEntity.getNode();
+        output.printf("Node ID: %s\nNode Address: %s\nAPI Port: %s\nNode Status:%s",
+                nodeDTO.getNodeId(), nodeDTO.getAddress(), nodeDTO.getApiPort(), nodeDTO.getStatus());
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/04d8da8f/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/NodesResult.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/NodesResult.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/NodesResult.java
new file mode 100644
index 0000000..daab27f
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/NodesResult.java
@@ -0,0 +1,66 @@
+/*
+ * 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.toolkit.cli.impl.result;
+
+import org.apache.commons.lang3.Validate;
+import org.apache.nifi.toolkit.cli.api.ResultType;
+import org.apache.nifi.toolkit.cli.impl.result.writer.DynamicTableWriter;
+import org.apache.nifi.toolkit.cli.impl.result.writer.Table;
+import org.apache.nifi.toolkit.cli.impl.result.writer.TableWriter;
+import org.apache.nifi.web.api.dto.NodeDTO;
+import org.apache.nifi.web.api.entity.ClusterEntity;
+import org.glassfish.jersey.internal.guava.Lists;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.List;
+
+public class NodesResult extends AbstractWritableResult<ClusterEntity> {
+
+    private final ClusterEntity clusterEntity;
+
+    public NodesResult(ResultType resultType, ClusterEntity clusterEntity) {
+        super(resultType);
+        this.clusterEntity = clusterEntity;
+        Validate.notNull(clusterEntity);
+    }
+
+    @Override
+    public ClusterEntity getResult() {
+        return clusterEntity;
+    }
+
+    @Override
+    protected void writeSimpleResult(PrintStream output) throws IOException {
+        final Table table = new Table.Builder()
+                .column("#", 3, 3, false)
+                .column("Node ID", 36, 36, false)
+                .column("Node Address", 36, 36, true)
+                .column("API Port", 8, 8, false)
+                .column("Node Status", 13, 13, false)
+                .build();
+
+        List<NodeDTO> nodes = Lists.newArrayList(clusterEntity.getCluster().getNodes());
+        for (int i = 0; i < nodes.size(); ++i) {
+            NodeDTO nodeDTO = nodes.get(i);
+            table.addRow(String.valueOf(i), nodeDTO.getNodeId(), nodeDTO.getAddress(), String.valueOf(nodeDTO.getApiPort()), nodeDTO.getStatus());
+        }
+
+        final TableWriter tableWriter = new DynamicTableWriter();
+        tableWriter.write(table, output);
+    }
+}