From 7abb02fff000e70523c8b7afb6d3b9a7df5c929f Mon Sep 17 00:00:00 2001 From: Bryan Bende Date: Tue, 3 Apr 2018 09:30:30 -0400 Subject: [PATCH] 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 This closes #2604. --- .../cli/impl/client/nifi/FlowClient.java | 19 ++ .../client/nifi/impl/JerseyFlowClient.java | 40 ++++ .../cli/impl/command/AbstractCommand.java | 8 + .../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(+) create mode 100644 nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGDisableControllerServices.java create mode 100644 nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGEnableControllerServices.java create mode 100644 nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/PGGetControllerServices.java create mode 100644 nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/cs/ControllerServiceStateCounts.java create mode 100644 nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/cs/ControllerServiceStates.java create mode 100644 nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/pg/cs/ControllerServiceUtil.java create mode 100644 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/client/nifi/FlowClient.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/FlowClient.java index 5e56cd3ab1..bc56eceeea 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; + } 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 36a74bf9f5..4489998c00 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); + }); + } } 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 1742243ecc..b692bb0e29 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 implements Command { } } + protected boolean isVerbose(final Properties properties) { + return properties.containsKey(CommandOption.VERBOSE.getLongName()); + } + + protected boolean isInteractive() { + return getContext().isInteractive(); + } + } 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 8f2206a90e..ac5ec62e1d 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); } } 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 0000000000..26552db44b --- /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 { + + 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); + } +} 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 0000000000..98689c54da --- /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 { + + 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); + } + } + } + } + } + +} 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 0000000000..f4178aaae0 --- /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 { + + 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); + } +} 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 0000000000..d6fb3f960b --- /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++; + } + +} 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 0000000000..f1b01c1b83 --- /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"; +} 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 0000000000..ccf62271a1 --- /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; + } + +} 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 0000000000..c25bc732ff --- /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 { + + 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 serviceEntities = controllerServicesEntity.getControllerServices(); + if (serviceEntities == null) { + return; + } + + final List 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; + } +}