Repository: nifi
Updated Branches:
  refs/heads/master 1913b1e2a -> 7abb02fff


NIFI-5027 Adding commands pg-get-services, pg-enable-services, and 
pg-disable-services
- Improving response when service is stuck enabling, and improving response 
when some services couldn't be enabled
- Throwing exception when a service is stuck enabling or can't be enabled so 
that standalone mode gets a non-zero status code, also allowing use of -verbose 
so stand-alone can decide if output is desired
- Improving information provided by pg-disable-services

Signed-off-by: Pierre Villard <[email protected]>

This closes #2604.


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/7abb02ff
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/7abb02ff
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/7abb02ff

Branch: refs/heads/master
Commit: 7abb02fff000e70523c8b7afb6d3b9a7df5c929f
Parents: 1913b1e
Author: Bryan Bende <[email protected]>
Authored: Tue Apr 3 09:30:30 2018 -0400
Committer: Pierre Villard <[email protected]>
Committed: Wed Apr 4 23:26:24 2018 +0200

----------------------------------------------------------------------
 .../cli/impl/client/nifi/FlowClient.java        |  19 ++
 .../impl/client/nifi/impl/JerseyFlowClient.java |  40 ++++
 .../cli/impl/command/AbstractCommand.java       |   8 +
 .../cli/impl/command/nifi/NiFiCommandGroup.java |   6 +
 .../nifi/pg/PGDisableControllerServices.java    | 176 +++++++++++++++
 .../nifi/pg/PGEnableControllerServices.java     | 221 +++++++++++++++++++
 .../nifi/pg/PGGetControllerServices.java        |  60 +++++
 .../pg/cs/ControllerServiceStateCounts.java     |  58 +++++
 .../nifi/pg/cs/ControllerServiceStates.java     |  28 +++
 .../nifi/pg/cs/ControllerServiceUtil.java       |  68 ++++++
 .../impl/result/ControllerServicesResult.java   |  81 +++++++
 11 files changed, 765 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/7abb02ff/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/FlowClient.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/FlowClient.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/FlowClient.java
index 5e56cd3..bc56ece 100644
--- 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/FlowClient.java
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/FlowClient.java
@@ -16,6 +16,8 @@
  */
 package org.apache.nifi.toolkit.cli.impl.client.nifi;
 
+import org.apache.nifi.web.api.entity.ActivateControllerServicesEntity;
+import org.apache.nifi.web.api.entity.ControllerServicesEntity;
 import org.apache.nifi.web.api.entity.CurrentUserEntity;
 import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
 import org.apache.nifi.web.api.entity.ScheduleComponentsEntity;
@@ -78,4 +80,21 @@ public interface FlowClient {
     VersionedFlowSnapshotMetadataSetEntity getVersions(String registryId, 
String bucketId, String flowId)
             throws NiFiClientException, IOException;
 
+
+    /**
+     * Retrieves the controller services for the given group.
+     *
+     * @param groupId the process group id
+     * @return tje controller services entity
+     */
+    ControllerServicesEntity getControllerServices(String groupId) throws 
NiFiClientException, IOException;
+
+    /**
+     * Enable or disable controller services in the given process group.
+     *
+     * @param activateControllerServicesEntity the entity indicating the 
process group to enable/disable services for
+     * @return the activate response
+     */
+    ActivateControllerServicesEntity 
activateControllerServices(ActivateControllerServicesEntity 
activateControllerServicesEntity) throws NiFiClientException, IOException;
+
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/7abb02ff/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyFlowClient.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyFlowClient.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyFlowClient.java
index 36a74bf..4489998 100644
--- 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyFlowClient.java
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyFlowClient.java
@@ -23,7 +23,9 @@ import 
org.apache.nifi.toolkit.cli.impl.client.nifi.ProcessGroupBox;
 import org.apache.nifi.web.api.dto.PositionDTO;
 import org.apache.nifi.web.api.dto.flow.FlowDTO;
 import org.apache.nifi.web.api.dto.flow.ProcessGroupFlowDTO;
+import org.apache.nifi.web.api.entity.ActivateControllerServicesEntity;
 import org.apache.nifi.web.api.entity.ComponentEntity;
+import org.apache.nifi.web.api.entity.ControllerServicesEntity;
 import org.apache.nifi.web.api.entity.CurrentUserEntity;
 import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
 import org.apache.nifi.web.api.entity.ScheduleComponentsEntity;
@@ -177,4 +179,42 @@ public class JerseyFlowClient extends AbstractJerseyClient 
implements FlowClient
             return 
getRequestBuilder(target).get(VersionedFlowSnapshotMetadataSetEntity.class);
         });
     }
+
+    @Override
+    public ControllerServicesEntity getControllerServices(final String 
groupId) throws NiFiClientException, IOException {
+        if (StringUtils.isBlank(groupId)) {
+            throw new IllegalArgumentException("Group Id cannot be null or 
blank");
+        }
+
+        return executeAction("Error retrieving controller services", () -> {
+            final WebTarget target = flowTarget
+                    .path("process-groups/{id}/controller-services")
+                    .resolveTemplate("id", groupId);
+
+            return 
getRequestBuilder(target).get(ControllerServicesEntity.class);
+        });
+    }
+
+    @Override
+    public ActivateControllerServicesEntity activateControllerServices(final 
ActivateControllerServicesEntity activateControllerServicesEntity)
+            throws NiFiClientException, IOException {
+
+        if (activateControllerServicesEntity == null) {
+            throw new IllegalArgumentException("Entity cannot be null");
+        }
+
+        if (StringUtils.isBlank(activateControllerServicesEntity.getId())) {
+            throw new IllegalArgumentException("Entity must contain a process 
group id");
+        }
+
+        return executeAction("Error enabling or disabling controlling 
services", () -> {
+            final WebTarget target = flowTarget
+                    .path("process-groups/{id}/controller-services")
+                    .resolveTemplate("id", 
activateControllerServicesEntity.getId());
+
+            return getRequestBuilder(target).put(
+                    Entity.entity(activateControllerServicesEntity, 
MediaType.APPLICATION_JSON_TYPE),
+                    ActivateControllerServicesEntity.class);
+        });
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/7abb02ff/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractCommand.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractCommand.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractCommand.java
index 1742243..b692bb0 100644
--- 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractCommand.java
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/AbstractCommand.java
@@ -205,4 +205,12 @@ public abstract class AbstractCommand<R extends Result> 
implements Command<R> {
         }
     }
 
+    protected boolean isVerbose(final Properties properties) {
+        return properties.containsKey(CommandOption.VERBOSE.getLongName());
+    }
+
+    protected boolean isInteractive() {
+        return getContext().isInteractive();
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/7abb02ff/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 8f2206a..ac5ec62 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,7 +21,10 @@ import 
org.apache.nifi.toolkit.cli.impl.command.AbstractCommandGroup;
 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.pg.PGChangeVersion;
+import 
org.apache.nifi.toolkit.cli.impl.command.nifi.pg.PGDisableControllerServices;
+import 
org.apache.nifi.toolkit.cli.impl.command.nifi.pg.PGEnableControllerServices;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.pg.PGGetAllVersions;
+import 
org.apache.nifi.toolkit.cli.impl.command.nifi.pg.PGGetControllerServices;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.pg.PGGetVars;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.pg.PGGetVersion;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.pg.PGImport;
@@ -68,6 +71,9 @@ public class NiFiCommandGroup extends AbstractCommandGroup {
         commands.add(new PGGetAllVersions());
         commands.add(new PGList());
         commands.add(new PGStatus());
+        commands.add(new PGGetControllerServices());
+        commands.add(new PGEnableControllerServices());
+        commands.add(new PGDisableControllerServices());
         return new ArrayList<>(commands);
     }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/7abb02ff/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGDisableControllerServices.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGDisableControllerServices.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGDisableControllerServices.java
new file mode 100644
index 0000000..26552db
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGDisableControllerServices.java
@@ -0,0 +1,176 @@
+/*
+ * 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.pg;
+
+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.FlowClient;
+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.command.nifi.pg.cs.ControllerServiceStateCounts;
+import 
org.apache.nifi.toolkit.cli.impl.command.nifi.pg.cs.ControllerServiceStates;
+import 
org.apache.nifi.toolkit.cli.impl.command.nifi.pg.cs.ControllerServiceUtil;
+import org.apache.nifi.toolkit.cli.impl.result.VoidResult;
+import org.apache.nifi.web.api.entity.ActivateControllerServicesEntity;
+import org.apache.nifi.web.api.entity.BulletinEntity;
+import org.apache.nifi.web.api.entity.ControllerServiceEntity;
+import org.apache.nifi.web.api.entity.ControllerServicesEntity;
+
+import java.io.IOException;
+import java.util.Properties;
+
+public class PGDisableControllerServices extends 
AbstractNiFiCommand<VoidResult> {
+
+    public static final int MAX_DISABLING_ITERATIONS = 20;
+    public static final long DELAY_MS = 2000;
+
+    public PGDisableControllerServices() {
+        super("pg-disable-services", VoidResult.class);
+    }
+
+    @Override
+    public String getDescription() {
+        return "Disables the controller services in the given process group. 
Any services that are in use by a running component " +
+                "will fail to be disabled and will need to be stopped first 
using pg-stop.";
+    }
+
+    @Override
+    protected void doInitialize(final Context context) {
+        addOption(CommandOption.PG_ID.createOption());
+    }
+
+    @Override
+    public VoidResult doExecute(NiFiClient client, Properties properties)
+            throws NiFiClientException, IOException, MissingOptionException, 
CommandException {
+
+        final FlowClient flowClient = client.getFlowClient();
+        final String pgId = getRequiredArg(properties, CommandOption.PG_ID);
+
+        final ControllerServiceStateCounts initialServiceStates = 
getControllerServiceStates(flowClient, pgId);
+
+        // if we have no enabled or enabling services then there is nothing to 
do so return
+        if (initialServiceStates.getEnabled() == 0 && 
initialServiceStates.getEnabling() == 0) {
+            if (shouldPrint(properties)) {
+                println();
+                println("No services are currently enabled/enabling, nothing 
to do...");
+                println();
+            }
+            return VoidResult.getInstance();
+        }
+
+        if (shouldPrint(properties)) {
+            println();
+            println("Starting states:");
+            printControllerServiceStates(initialServiceStates);
+            println();
+            println("Attempting to disable services...");
+        }
+
+        // send the request to disable services and wait a second
+        final ActivateControllerServicesEntity disableEntity = new 
ActivateControllerServicesEntity();
+        disableEntity.setId(pgId);
+        
disableEntity.setState(ActivateControllerServicesEntity.STATE_DISABLED);
+
+        flowClient.activateControllerServices(disableEntity);
+        sleep(1000);
+
+        // wait for disabling services to become disabled, or until we've 
waited up to max number of waits
+        int disablingWaitCount = 1;
+        ControllerServiceStateCounts serviceStates = 
getControllerServiceStates(flowClient, pgId);
+        while (serviceStates.getDisabling() > 0 && disablingWaitCount < 
MAX_DISABLING_ITERATIONS) {
+            if (shouldPrint(properties)) {
+                println("Currently " + serviceStates.getDisabling() + " 
services are disabling, waiting to finish before proceeding ("
+                        + disablingWaitCount + " of " + 
MAX_DISABLING_ITERATIONS + ")...");
+            }
+            sleep(DELAY_MS);
+            disablingWaitCount++;
+            serviceStates = getControllerServiceStates(flowClient, pgId);
+        }
+
+        // if we have still have disabling services then we may have a stuck 
service so throw an exception
+        if (serviceStates.getDisabling() > 0) {
+            if (shouldPrint(properties)) {
+                printServicesStillDisabling(flowClient, pgId);
+            }
+            throw new CommandException("One or more services may be stuck 
disabling, run command with -verbose to obtain more details");
+        }
+
+        // otherwise the command was successful so print the final states and 
return
+        if (shouldPrint(properties)) {
+            println();
+            println("Finished States:");
+            printControllerServiceStates(serviceStates);
+            println();
+        }
+
+        return VoidResult.getInstance();
+    }
+
+    private void printControllerServiceStates(final 
ControllerServiceStateCounts serviceStates) {
+        println(" - " + serviceStates.getEnabled() + " enabled");
+        println(" - " + serviceStates.getEnabling() + " enabling");
+        println(" - " + serviceStates.getDisabled() + " disabled");
+        println(" - " + serviceStates.getDisabling() + " disabling");
+    }
+
+    private void printServicesStillDisabling(final FlowClient flowClient, 
final String pgId)
+            throws NiFiClientException, IOException {
+
+        final ControllerServicesEntity servicesEntity = 
flowClient.getControllerServices(pgId);
+        if (servicesEntity == null || servicesEntity.getControllerServices() 
== null) {
+            return;
+        }
+
+        println();
+        println("One or more services appear to be stuck disabling: ");
+
+        for (final ControllerServiceEntity serviceEntity : 
servicesEntity.getControllerServices()) {
+            if 
(ControllerServiceStates.STATE_DISABLING.equals(serviceEntity.getComponent().getState()))
 {
+                println();
+                println("Service: " + serviceEntity.getId() + " - " + 
serviceEntity.getComponent().getName());
+
+                if (serviceEntity.getBulletins() != null) {
+                    println();
+                    println("Reasons: ");
+                    for (final BulletinEntity bulletinEntity : 
serviceEntity.getBulletins()) {
+                        println("- " + 
bulletinEntity.getBulletin().getMessage());
+                    }
+                }
+            }
+        }
+    }
+
+    private void sleep(long millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException e) {
+            Thread.interrupted();
+        }
+    }
+
+    private boolean shouldPrint(final Properties properties) {
+        return isInteractive() || isVerbose(properties);
+    }
+
+    private ControllerServiceStateCounts getControllerServiceStates(final 
FlowClient flowClient, final String pgId)
+            throws NiFiClientException, IOException {
+        return ControllerServiceUtil.getControllerServiceStates(flowClient, 
pgId);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/7abb02ff/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGEnableControllerServices.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGEnableControllerServices.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGEnableControllerServices.java
new file mode 100644
index 0000000..98689c5
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGEnableControllerServices.java
@@ -0,0 +1,221 @@
+/*
+ * 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.pg;
+
+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.FlowClient;
+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.command.nifi.pg.cs.ControllerServiceStateCounts;
+import 
org.apache.nifi.toolkit.cli.impl.command.nifi.pg.cs.ControllerServiceStates;
+import 
org.apache.nifi.toolkit.cli.impl.command.nifi.pg.cs.ControllerServiceUtil;
+import org.apache.nifi.toolkit.cli.impl.result.VoidResult;
+import org.apache.nifi.web.api.dto.ControllerServiceDTO;
+import org.apache.nifi.web.api.entity.ActivateControllerServicesEntity;
+import org.apache.nifi.web.api.entity.BulletinEntity;
+import org.apache.nifi.web.api.entity.ControllerServiceEntity;
+import org.apache.nifi.web.api.entity.ControllerServicesEntity;
+
+import java.io.IOException;
+import java.util.Properties;
+
+public class PGEnableControllerServices extends 
AbstractNiFiCommand<VoidResult> {
+
+    public static final int MAX_ATTEMPTS = 180;
+    public static final int MAX_ENABLING_ITERATIONS = 20;
+    public static final long ENABLING_DELAY_MS = 2000;
+
+    public PGEnableControllerServices() {
+        super("pg-enable-services", VoidResult.class);
+    }
+
+    @Override
+    public String getDescription() {
+        return "Attempts to enable all controller services in the given PG. In 
stand-alone mode this command will not " +
+                "produce all of the output seen in interactive mode unless the 
--verbose argument is specified.";
+    }
+
+    @Override
+    protected void doInitialize(final Context context) {
+        addOption(CommandOption.PG_ID.createOption());
+    }
+
+    @Override
+    public VoidResult doExecute(final NiFiClient client, final Properties 
properties)
+            throws NiFiClientException, IOException, MissingOptionException, 
CommandException {
+
+        final String pgId = getRequiredArg(properties, CommandOption.PG_ID);
+        final FlowClient flowClient = client.getFlowClient();
+
+        if (shouldPrint(properties)) {
+            println();
+        }
+
+        int count = 0;
+        int prevNumEnabled = -1;
+        int enablingIterations = 1;
+
+        // request to enable services until the number of enabled services is 
no longer changing, which means either all
+        // services have been enabled, or the rest of the services are invalid 
and can't be enabled
+        while (count < MAX_ATTEMPTS) {
+
+            // retrieve the current states of the services in the given pg
+            final ControllerServiceStateCounts states = 
getControllerServiceStates(flowClient, pgId);
+
+            // if any services are currently enabling then sleep and loop again
+            if (states.getEnabling() > 0) {
+                if (enablingIterations < MAX_ENABLING_ITERATIONS) {
+                    if (shouldPrint(properties)) {
+                        println("Currently " + states.getEnabling() + " 
services are enabling, waiting to finish before " +
+                                "proceeding (" + enablingIterations + " of " + 
MAX_ENABLING_ITERATIONS + ")");
+                    }
+                    try {
+                        Thread.sleep(ENABLING_DELAY_MS);
+                    } catch (InterruptedException e) {
+                        Thread.interrupted();
+                    }
+
+                    enablingIterations++;
+                    continue;
+                } else {
+                    if (shouldPrint(properties)) {
+                        printServicesStillEnabling(flowClient, pgId);
+                    }
+
+                    // throw an exception so stand-alone mode will exit with a 
non-zero status code
+                    throw new CommandException("One or more services are stuck 
enabling, run command with -verbose to obtain more details");
+                }
+            }
+
+            // reset the enabling iteration count since we got past the above 
block without breaking
+            enablingIterations = 1;
+
+            // if no services are enabling and the number of enabled services 
equals the number of enabled services from
+            // last iteration, then we know there are no more that we can 
enable so break
+            if (states.getEnabled() == prevNumEnabled && states.getEnabling() 
== 0) {
+                if (shouldPrint(properties)) {
+                    println();
+                    println("Finished with " + states.getEnabled() + " enabled 
services and " + states.getDisabled() + " disabled services");
+                }
+
+                if (states.getDisabled() > 0 || states.getDisabling() > 0) {
+                    if (shouldPrint(properties)) {
+                        printServicesNotEnabled(flowClient, pgId);
+                        println();
+                    }
+
+                    // throw an exception so stand-alone mode will exit with a 
non-zero status code
+                    throw new CommandException("One or more services could not 
be enabled, run command with -verbose to obtain more details");
+                } else {
+                    if (shouldPrint(properties)) {
+                        println();
+                    }
+
+                    // just break here to proceed with normal completion (i.e. 
will have a zero status code)
+                    break;
+                }
+            }
+
+            // if we didn't break then store the number that were enabled to 
compare with next time
+            prevNumEnabled = states.getEnabled();
+
+            if (shouldPrint(properties)) {
+                println("Currently " + states.getEnabled() + " enabled 
services and " + states.getDisabled()
+                        + " disabled services, attempting to enable 
services...");
+            }
+
+            // send the request to enable services
+            final ActivateControllerServicesEntity enableEntity = new 
ActivateControllerServicesEntity();
+            enableEntity.setId(pgId);
+            
enableEntity.setState(ActivateControllerServicesEntity.STATE_ENABLED);
+
+            flowClient.activateControllerServices(enableEntity);
+            count++;
+        }
+
+        return VoidResult.getInstance();
+    }
+
+    private boolean shouldPrint(final Properties properties) {
+        return isInteractive() || isVerbose(properties);
+    }
+
+    private ControllerServiceStateCounts getControllerServiceStates(final 
FlowClient flowClient, final String pgId)
+            throws NiFiClientException, IOException {
+        return ControllerServiceUtil.getControllerServiceStates(flowClient, 
pgId);
+    }
+
+    private void printServicesStillEnabling(final FlowClient flowClient, final 
String pgId)
+            throws NiFiClientException, IOException {
+
+        final ControllerServicesEntity servicesEntity = 
flowClient.getControllerServices(pgId);
+        if (servicesEntity == null || servicesEntity.getControllerServices() 
== null) {
+            return;
+        }
+
+        println();
+        println("One or more services appear to be stuck enabling: ");
+
+        for (final ControllerServiceEntity serviceEntity : 
servicesEntity.getControllerServices()) {
+            if 
(ControllerServiceStates.STATE_ENABLING.equals(serviceEntity.getComponent().getState()))
 {
+                println();
+                println("Service: " + serviceEntity.getId() + " - " + 
serviceEntity.getComponent().getName());
+
+                if (serviceEntity.getBulletins() != null) {
+                    println();
+                    println("Reasons: ");
+                    for (final BulletinEntity bulletinEntity : 
serviceEntity.getBulletins()) {
+                        println("- " + 
bulletinEntity.getBulletin().getMessage());
+                    }
+                }
+            }
+        }
+    }
+
+    private void printServicesNotEnabled(final FlowClient flowClient, final 
String pgId)
+            throws NiFiClientException, IOException {
+
+        final ControllerServicesEntity servicesEntity = 
flowClient.getControllerServices(pgId);
+        if (servicesEntity == null || servicesEntity.getControllerServices() 
== null) {
+            return;
+        }
+
+        println();
+        println("The following services could not be enabled: ");
+
+        for (final ControllerServiceEntity serviceEntity : 
servicesEntity.getControllerServices()) {
+            if 
(!ControllerServiceStates.STATE_ENABLED.equals(serviceEntity.getComponent().getState()))
 {
+                println();
+                println("Service: " + serviceEntity.getId() + " - " + 
serviceEntity.getComponent().getName());
+
+                final ControllerServiceDTO serviceDTO = 
serviceEntity.getComponent();
+                if (serviceDTO.getValidationErrors() != null) {
+                    println();
+                    println("Validation Errors: ");
+                    for (final String validationError : 
serviceDTO.getValidationErrors()) {
+                        println("- " + validationError);
+                    }
+                }
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/7abb02ff/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGGetControllerServices.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGGetControllerServices.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGGetControllerServices.java
new file mode 100644
index 0000000..f4178aa
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGGetControllerServices.java
@@ -0,0 +1,60 @@
+/*
+ * 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.pg;
+
+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.FlowClient;
+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.ControllerServicesResult;
+import org.apache.nifi.web.api.entity.ControllerServicesEntity;
+
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Command to get the list of controller services for a given process group.
+ */
+public class PGGetControllerServices extends 
AbstractNiFiCommand<ControllerServicesResult> {
+
+    public PGGetControllerServices() {
+        super("pg-get-services", ControllerServicesResult.class);
+    }
+
+    @Override
+    public String getDescription() {
+        return "Retrieves the list of controller services for the given 
process group.";
+    }
+
+    @Override
+    protected void doInitialize(final Context context) {
+        addOption(CommandOption.PG_ID.createOption());
+    }
+
+    @Override
+    public ControllerServicesResult doExecute(NiFiClient client, Properties 
properties)
+            throws NiFiClientException, IOException, MissingOptionException, 
CommandException {
+        final String pgId = getRequiredArg(properties, CommandOption.PG_ID);
+        final FlowClient flowClient = client.getFlowClient();
+        final ControllerServicesEntity servicesEntity = 
flowClient.getControllerServices(pgId);
+        return new ControllerServicesResult(getResultType(properties), 
servicesEntity);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/7abb02ff/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/cs/ControllerServiceStateCounts.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/cs/ControllerServiceStateCounts.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/cs/ControllerServiceStateCounts.java
new file mode 100644
index 0000000..d6fb3f9
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/cs/ControllerServiceStateCounts.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.pg.cs;
+
+public class ControllerServiceStateCounts {
+
+    private int enabled;
+    private int enabling;
+    private int disabled;
+    private int disabling;
+
+    public int getEnabled() {
+        return enabled;
+    }
+
+    public void incrementEnabled() {
+        enabled++;
+    }
+
+    public int getEnabling() {
+        return enabling;
+    }
+
+    public void incrementEnabling() {
+        enabling++;
+    }
+
+    public int getDisabled() {
+        return disabled;
+    }
+
+    public void incrementDisabled() {
+        disabled++;
+    }
+
+    public int getDisabling() {
+        return disabling;
+    }
+
+    public void incrementDisabling() {
+        disabling++;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/7abb02ff/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/cs/ControllerServiceStates.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/cs/ControllerServiceStates.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/cs/ControllerServiceStates.java
new file mode 100644
index 0000000..f1b01c1
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/cs/ControllerServiceStates.java
@@ -0,0 +1,28 @@
+/*
+ * 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.pg.cs;
+
+/**
+ * Possible states for a controller service.
+ */
+public interface ControllerServiceStates {
+
+    String STATE_ENABLED = "ENABLED";
+    String STATE_ENABLING = "ENABLING";
+    String STATE_DISABLED = "DISABLED";
+    String STATE_DISABLING = "DISABLING";
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/7abb02ff/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/cs/ControllerServiceUtil.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/cs/ControllerServiceUtil.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/cs/ControllerServiceUtil.java
new file mode 100644
index 0000000..ccf6227
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/cs/ControllerServiceUtil.java
@@ -0,0 +1,68 @@
+/*
+ * 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.pg.cs;
+
+import org.apache.nifi.toolkit.cli.impl.client.nifi.FlowClient;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.web.api.entity.ControllerServiceEntity;
+import org.apache.nifi.web.api.entity.ControllerServicesEntity;
+
+import java.io.IOException;
+
+/**
+ * Utility methods for controller service commands.
+ */
+public class ControllerServiceUtil {
+
+    public static ControllerServiceStateCounts 
getControllerServiceStates(final FlowClient flowClient, final String pgId)
+            throws NiFiClientException, IOException {
+        final ControllerServicesEntity servicesEntity = 
flowClient.getControllerServices(pgId);
+        return getControllerServiceStates(servicesEntity);
+    }
+
+    public static ControllerServiceStateCounts 
getControllerServiceStates(final ControllerServicesEntity servicesEntity)
+            throws NiFiClientException {
+
+        final ControllerServiceStateCounts states = new 
ControllerServiceStateCounts();
+        if (servicesEntity == null || servicesEntity.getControllerServices() 
== null || servicesEntity.getControllerServices().isEmpty()) {
+            return states;
+        }
+
+        for (final ControllerServiceEntity serviceEntity : 
servicesEntity.getControllerServices()) {
+            final String state = serviceEntity.getComponent().getState();
+            switch(state) {
+                case ControllerServiceStates.STATE_ENABLED:
+                    states.incrementEnabled();
+                    break;
+                case ControllerServiceStates.STATE_ENABLING:
+                    states.incrementEnabling();
+                    break;
+                case ControllerServiceStates.STATE_DISABLED:
+                    states.incrementDisabled();
+                    break;
+                case ControllerServiceStates.STATE_DISABLING:
+                    states.incrementDisabling();
+                    break;
+                default:
+                    throw new NiFiClientException("Unexpected controller 
service state: " + state);
+            }
+        }
+
+        return states;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/7abb02ff/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/ControllerServicesResult.java
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/ControllerServicesResult.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/ControllerServicesResult.java
new file mode 100644
index 0000000..c25bc73
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/ControllerServicesResult.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.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.ControllerServiceDTO;
+import org.apache.nifi.web.api.entity.ControllerServiceEntity;
+import org.apache.nifi.web.api.entity.ControllerServicesEntity;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Result for ControllerServicesEntity.
+ */
+public class ControllerServicesResult extends 
AbstractWritableResult<ControllerServicesEntity> {
+
+    private final ControllerServicesEntity controllerServicesEntity;
+
+    public ControllerServicesResult(final ResultType resultType, final 
ControllerServicesEntity controllerServicesEntity) {
+        super(resultType);
+        this.controllerServicesEntity = controllerServicesEntity;
+        Validate.notNull(this.controllerServicesEntity);
+    }
+
+    @Override
+    protected void writeSimpleResult(final PrintStream output) throws 
IOException {
+        final Set<ControllerServiceEntity> serviceEntities = 
controllerServicesEntity.getControllerServices();
+        if (serviceEntities == null) {
+            return;
+        }
+
+        final List<ControllerServiceDTO> serviceDTOS = serviceEntities.stream()
+                .map(s -> s.getComponent())
+                .collect(Collectors.toList());
+
+        Collections.sort(serviceDTOS, 
Comparator.comparing(ControllerServiceDTO::getName));
+
+        final Table table = new Table.Builder()
+                .column("#", 3, 3, false)
+                .column("Name", 5, 40, false)
+                .column("State", 5, 40, false)
+                .build();
+
+        for (int i=0; i < serviceDTOS.size(); i++) {
+            final ControllerServiceDTO serviceDTO = serviceDTOS.get(i);
+            table.addRow(String.valueOf(i+1), serviceDTO.getName(), 
serviceDTO.getState());
+        }
+
+        final TableWriter tableWriter = new DynamicTableWriter();
+        tableWriter.write(table, output);
+    }
+
+    @Override
+    public ControllerServicesEntity getResult() {
+        return controllerServicesEntity;
+    }
+}

Reply via email to