Skip to content
This repository has been archived by the owner on May 26, 2020. It is now read-only.

Commit

Permalink
Allow JMX client to re-attach to a running previous command
Browse files Browse the repository at this point in the history
  • Loading branch information
amckenzie committed Feb 12, 2020
1 parent d51a209 commit 854b1ee
Show file tree
Hide file tree
Showing 21 changed files with 664 additions and 31 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ on [Keep a CHANGELOG](http://keepachangelog.com/). This project adheres to
[Semantic Versioning](http://semver.org/).

## [Unreleased]
### Added
JMX client can now attach to a running command by using the command line switch '--attach <command id>'

## [2.4.1] - 2019-11-20
### Fixed
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ From Framework 6.0.0 and above

- **-cn,--context-name** <arg> _The name of the context on which to run the command. Required_
- **-c,--command** <arg> _The name of the System Command to run_
- **-a,--attach** <arg> _Attach to as running system command by specifying its command id_
- **-h,--host** <arg> _Hostname or IP address of the Wildfly server. Defaults to localhost_
- **-p,--port** <arg> _Wildfly management port. Defaults to 9990 (the default for Wildfly)_
- **-u, --username** _Optional username for Wildfly management security_
Expand All @@ -23,6 +24,17 @@ From Framework 6.0.0 and above
_java -jar framework-jmx-command-client.jar --context-name example-single --command PING_

Would run the command 'PING' against a local wildfly instance with no authentication with a example-single.war deployed

### Attaching to a running command
Sometimes the JMX client may fail (or be accidentally closed). As the command running on Wildfly
is asynchronous, then it would still be running on the server even if the client failed. In that
case you may want to re-attach to the running command by supplying the command id:

_java -jar framework-jmx-command-client.jar --attach <command id>_

The command id is always logged by the client when a command is first sent. Failing that
it can be seen in the system_command_status table (which lists the status of all commands
sent to wildfly)

###### Note:
If you are running wildfly on your local machine and running as the same user as the one you are
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package uk.gov.justice.framework.command.client;

import static java.util.UUID.fromString;

import uk.gov.justice.framework.command.client.io.CommandPrinter;
import uk.gov.justice.framework.command.client.io.ToConsolePrinter;
import uk.gov.justice.framework.command.client.jmx.SystemCommandAttacher;
import uk.gov.justice.framework.command.client.jmx.SystemCommandInvoker;
import uk.gov.justice.services.jmx.api.command.SystemCommandDetails;
import uk.gov.justice.services.jmx.system.command.client.connection.JmxParameters;

import java.util.List;
import java.util.UUID;

import javax.inject.Inject;

Expand All @@ -17,6 +20,9 @@ public class CommandExecutor {
@Inject
private SystemCommandInvoker systemCommandInvoker;

@Inject
private SystemCommandAttacher systemCommandAttacher;

@Inject
private CommandPrinter commandPrinter;

Expand All @@ -27,9 +33,14 @@ public void executeCommand(

if (commandLine.hasOption("list")) {
commandPrinter.printSystemCommands(systemCommandDetails);
} else if (commandLine.hasOption("attach")) {
final UUID commandId = fromString(commandLine.getOptionValue("attach"));
systemCommandAttacher.attachToRunningCommand(commandId, jmxParameters);
} else {
final String commandName = commandLine.getOptionValue("command");
systemCommandInvoker.runSystemCommand(commandName, jmxParameters);
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package uk.gov.justice.framework.command.client;

import static java.util.Optional.empty;
import static java.util.Optional.of;
import static java.util.UUID.fromString;

import uk.gov.justice.framework.command.client.io.ToConsolePrinter;

import java.util.Optional;
import java.util.UUID;

import javax.inject.Inject;

import org.apache.commons.cli.CommandLine;

public class CommandIdProvider {

@Inject
private ToConsolePrinter toConsolePrinter;

public Optional<UUID> getCommandId(final CommandLine commandLine) {
final String commandId = commandLine.getOptionValue("attach");

if (commandId == null) {
toConsolePrinter.println("Please specify a command id to attach to");
} else {
try {
return of(fromString(commandId));
} catch (final IllegalArgumentException e) {
toConsolePrinter.printf("Command id '%s' is not a UUID", commandId);
}
}

return empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import uk.gov.justice.framework.command.client.cdi.producers.OptionsFactory;
import uk.gov.justice.framework.command.client.jmx.ListCommandsInvoker;
import uk.gov.justice.framework.command.client.startup.CommandLineArgumentParser;
import uk.gov.justice.services.jmx.api.command.SystemCommand;
import uk.gov.justice.services.jmx.api.command.SystemCommandDetails;
import uk.gov.justice.services.jmx.system.command.client.connection.JmxParameters;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import static java.lang.String.format;
import static java.time.Duration.between;
import static org.apache.commons.lang3.time.DurationFormatUtils.formatDuration;

import uk.gov.justice.framework.command.client.io.ToConsolePrinter;
import uk.gov.justice.framework.command.client.util.Sleeper;
import uk.gov.justice.framework.command.client.util.UtcClock;
import uk.gov.justice.services.jmx.api.command.SystemCommand;
import uk.gov.justice.services.jmx.api.mbean.SystemCommanderMBean;

import java.time.ZonedDateTime;
Expand All @@ -28,20 +28,24 @@ public class CommandPoller {
@Inject
private ToConsolePrinter toConsolePrinter;

public void runUntilComplete(final SystemCommanderMBean systemCommanderMBean, final UUID commandId, final String commandName) {
public void runUntilComplete(final SystemCommanderMBean systemCommanderMBean, final RunContext runContext) {

final ZonedDateTime startTime = clock.now();
final UUID commandId = runContext.getCommandId();
final String commandName = runContext.getCommandName();
final ZonedDateTime startTime = runContext.getStartTime();

int count = 0;
while (! commandChecker.commandComplete(systemCommanderMBean, commandId, startTime)) {
sleeper.sleepFor(1_000);
count++;

if (count % 10 == 0) {
final long seconds = between(startTime, clock.now()).getSeconds();
toConsolePrinter.println(format("%s running for %d seconds", commandName, seconds));
final long durationMillis = between(startTime, clock.now()).toMillis();

final String duration = formatDuration(durationMillis, "HH:mm:ss");

toConsolePrinter.println(format("%s running for %s (hh:mm:ss)", commandName, duration));
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import static java.util.Optional.of;

import uk.gov.justice.framework.command.client.io.ToConsolePrinter;
import uk.gov.justice.services.jmx.api.command.SystemCommand;
import uk.gov.justice.services.jmx.api.command.SystemCommandDetails;
import uk.gov.justice.services.jmx.api.mbean.SystemCommanderMBean;
import uk.gov.justice.services.jmx.system.command.client.SystemCommanderClient;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package uk.gov.justice.framework.command.client.jmx;

import java.time.ZonedDateTime;
import java.util.Objects;
import java.util.UUID;

public class RunContext {

private final UUID commandId;
private final String commandName;
private final ZonedDateTime startTime;

public RunContext(final UUID commandId, final String commandName, final ZonedDateTime startTime) {
this.commandId = commandId;
this.commandName = commandName;
this.startTime = startTime;
}

public UUID getCommandId() {
return commandId;
}

public String getCommandName() {
return commandName;
}

public ZonedDateTime getStartTime() {
return startTime;
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof RunContext)) return false;
final RunContext that = (RunContext) o;
return Objects.equals(commandId, that.commandId) &&
Objects.equals(commandName, that.commandName) &&
Objects.equals(startTime, that.startTime);
}

@Override
public int hashCode() {
return Objects.hash(commandId, commandName, startTime);
}

@Override
public String toString() {
return "RunContext{" +
"commandId=" + commandId +
", commandName='" + commandName + '\'' +
", startTime=" + startTime +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package uk.gov.justice.framework.command.client.jmx;

import static uk.gov.justice.services.jmx.api.domain.CommandState.COMMAND_IN_PROGRESS;
import static uk.gov.justice.services.jmx.api.domain.CommandState.COMMAND_RECEIVED;

import uk.gov.justice.framework.command.client.io.ToConsolePrinter;
import uk.gov.justice.services.jmx.api.CommandNotFoundException;
import uk.gov.justice.services.jmx.api.domain.CommandState;
import uk.gov.justice.services.jmx.api.domain.SystemCommandStatus;
import uk.gov.justice.services.jmx.api.mbean.SystemCommanderMBean;

import java.time.ZonedDateTime;
import java.util.UUID;

import javax.inject.Inject;

public class SystemCommandAttachRunner {

@Inject
private CommandPoller commandPoller;

@Inject
private ToConsolePrinter toConsolePrinter;

public void attach(final UUID commandId, final SystemCommanderMBean systemCommanderMBean) {
try {
final SystemCommandStatus commandStatus = systemCommanderMBean.getCommandStatus(commandId);
final String commandName = commandStatus.getSystemCommandName();
final CommandState commandState = commandStatus.getCommandState();

if (commandState == COMMAND_IN_PROGRESS || commandState == COMMAND_RECEIVED) {
final ZonedDateTime startTime = commandStatus.getStatusChangedAt();
final RunContext runContext = new RunContext(
commandId,
commandName,
startTime
);

commandPoller.runUntilComplete(systemCommanderMBean, runContext);
} else {
toConsolePrinter.printf("Cannot attach to Command with id '%s'. Command is not running. Current command status: %s", commandId, commandState);
}
} catch (final CommandNotFoundException e) {
toConsolePrinter.printf("No system command exists with id '%s", commandId);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package uk.gov.justice.framework.command.client.jmx;

import uk.gov.justice.framework.command.client.io.ToConsolePrinter;
import uk.gov.justice.services.jmx.api.SystemCommandInvocationFailedException;
import uk.gov.justice.services.jmx.api.mbean.SystemCommanderMBean;
import uk.gov.justice.services.jmx.system.command.client.SystemCommanderClient;
import uk.gov.justice.services.jmx.system.command.client.SystemCommanderClientFactory;
import uk.gov.justice.services.jmx.system.command.client.connection.JmxParameters;

import java.util.UUID;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

@ApplicationScoped
public class SystemCommandAttacher {

@Inject
private SystemCommanderClientFactory systemCommanderClientFactory;

@Inject
private SystemCommandAttachRunner systemCommandAttachRunner;

@Inject
private ToConsolePrinter toConsolePrinter;

public void attachToRunningCommand(final UUID commandId, final JmxParameters jmxParameters) {

final String contextName = jmxParameters.getContextName();
toConsolePrinter.printf("Attaching to system command with id'%s'", commandId);
toConsolePrinter.printf("Connecting to %s context at '%s' on port %d", contextName, jmxParameters.getHost(), jmxParameters.getPort());

jmxParameters.getCredentials().ifPresent(credentials -> toConsolePrinter.printf("Connecting with credentials for user '%s'", credentials.getUsername()));

try (final SystemCommanderClient systemCommanderClient = systemCommanderClientFactory.create(jmxParameters)) {
final SystemCommanderMBean systemCommanderMBean = systemCommanderClient.getRemote(contextName);
systemCommandAttachRunner.attach(commandId, systemCommanderMBean);
} catch (final SystemCommandInvocationFailedException e) {
toConsolePrinter.printf("The command with id '%s' failed: %s", commandId, e.getMessage());
toConsolePrinter.println(e.getServerStackTrace());
throw e;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package uk.gov.justice.framework.command.client.jmx;

import uk.gov.justice.framework.command.client.io.ToConsolePrinter;
import uk.gov.justice.framework.command.client.util.UtcClock;
import uk.gov.justice.services.jmx.api.SystemCommandInvocationFailedException;
import uk.gov.justice.services.jmx.api.UnrunnableSystemCommandException;
import uk.gov.justice.services.jmx.api.command.SystemCommand;
import uk.gov.justice.services.jmx.api.mbean.SystemCommanderMBean;
import uk.gov.justice.services.jmx.system.command.client.SystemCommanderClient;
import uk.gov.justice.services.jmx.system.command.client.SystemCommanderClientFactory;
Expand All @@ -26,6 +26,10 @@ public class SystemCommandInvoker {
@Inject
private ToConsolePrinter toConsolePrinter;

@Inject
private UtcClock clock;


public void runSystemCommand(final String commandName, final JmxParameters jmxParameters) {

final String contextName = jmxParameters.getContextName();
Expand All @@ -42,7 +46,14 @@ public void runSystemCommand(final String commandName, final JmxParameters jmxPa
final SystemCommanderMBean systemCommanderMBean = systemCommanderClient.getRemote(contextName);
final UUID commandId = systemCommanderMBean.call(commandName);
toConsolePrinter.printf("System command '%s' with id '%s' successfully sent to %s", commandName, commandId, contextName);
commandPoller.runUntilComplete(systemCommanderMBean, commandId, commandName);

final RunContext runContext = new RunContext(
commandId,
commandName,
clock.now()
);

commandPoller.runUntilComplete(systemCommanderMBean, runContext);

} catch (final UnrunnableSystemCommandException e) {
toConsolePrinter.printf("The command '%s' is not supported on this %s context", commandName, contextName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ public Optional<CommandLine> parse(final String[] args) {
try {
final CommandLine commandLine = basicParser.parse(optionsFactory.createOptions(), args);

if (commandLine.hasOption("command") || commandLine.hasOption("list")) {
if (commandLine.hasOption("command") || commandLine.hasOption("attach") || commandLine.hasOption("list")) {
return of(commandLine);
}

toConsolePrinter.println("No system command specifed.");
toConsolePrinter.println("No system command specified.");

return empty();

Expand Down
Loading

0 comments on commit 854b1ee

Please sign in to comment.