diff --git a/src/main/java/org/cerberus/controllers/ApplicationController.java b/src/main/java/org/cerberus/controllers/ApplicationController.java index df3cea2..051afbd 100644 --- a/src/main/java/org/cerberus/controllers/ApplicationController.java +++ b/src/main/java/org/cerberus/controllers/ApplicationController.java @@ -14,6 +14,7 @@ import java.util.UUID; import static org.cerberus.core.constant.RoleSecurity.ADMIN; import static org.cerberus.core.constant.RoleSecurity.MAINTAINER; +import static org.cerberus.services.DaemonHandlingService.Action.*; @RestController @RequestMapping("/api/applications") @@ -54,4 +55,31 @@ public class ApplicationController { securityService.checkHasAnyRole(connectedUser, id, ADMIN, MAINTAINER); service.delete(id); } + + @PostMapping("/{id}/start") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void start(@PathVariable("id") UUID id, Principal connectedUser) { + securityService.checkHasAnyRole(connectedUser, id, ADMIN, MAINTAINER); + service.doServiceAction(id, START); + } + + @PostMapping("/{id}/stop") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void stop(@PathVariable("id") UUID id, Principal connectedUser) { + securityService.checkHasAnyRole(connectedUser, id, ADMIN, MAINTAINER); + service.doServiceAction(id, STOP); + } + + @PostMapping("/{id}/restart") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void restart(@PathVariable("id") UUID id, Principal connectedUser) { + securityService.checkHasAnyRole(connectedUser, id, ADMIN, MAINTAINER); + service.doServiceAction(id, RESTART); + } + + @GetMapping("/{id}/status") + public int status(@PathVariable("id") UUID id, Principal connectedUser) { + securityService.checkHasAnyRole(connectedUser, id, ADMIN, MAINTAINER); + return service.getStatus(id); + } } diff --git a/src/main/java/org/cerberus/core/constant/ResultCode.java b/src/main/java/org/cerberus/core/constant/ResultCode.java new file mode 100644 index 0000000..a79053b --- /dev/null +++ b/src/main/java/org/cerberus/core/constant/ResultCode.java @@ -0,0 +1,18 @@ +package org.cerberus.core.constant; + +public enum ResultCode { + SUCCESS(0), + FAILED(1), + STATE_UNCHANGED(2), + ILLEGAL_ARGUMENT(3); + + private int val; + + private ResultCode(final int pVal) { + val = pVal; + } + + public final int val() { + return val; + } +} diff --git a/src/main/java/org/cerberus/core/exceptions/ServiceUnavailableException.java b/src/main/java/org/cerberus/core/exceptions/ServiceUnavailableException.java new file mode 100644 index 0000000..a0cfa44 --- /dev/null +++ b/src/main/java/org/cerberus/core/exceptions/ServiceUnavailableException.java @@ -0,0 +1,15 @@ +package org.cerberus.core.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.SERVICE_UNAVAILABLE) +public class ServiceUnavailableException extends BusinessException { + public ServiceUnavailableException(String message) { + super(message); + } + + public ServiceUnavailableException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/cerberus/entities/business/SystemResult.java b/src/main/java/org/cerberus/entities/business/SystemResult.java new file mode 100644 index 0000000..0c1a1b1 --- /dev/null +++ b/src/main/java/org/cerberus/entities/business/SystemResult.java @@ -0,0 +1,99 @@ +package org.cerberus.entities.business; + +/** + * Object that contains the results of a system command. + * + * @author takiguchi + * + */ +public class SystemResult { + /** The result code of the command. */ + private int resultCode; + /** The standard output of the command. */ + private StringBuilder stdOut; + /** The standard errors output of the command. */ + private StringBuilder stdErr; + + public SystemResult() { + super(); + stdOut = new StringBuilder(); + stdErr = new StringBuilder(); + } + + /** + * Gets the result code. + * + * @return the result code + */ + public int getResultCode() { + return resultCode; + } + + /** + * Sets the result code. + * + * @param resultCode + * the new result code + */ + public void setResultCode(int resultCode) { + this.resultCode = resultCode; + } + + /** + * Gets the std out. + * + * @return the std out + */ + public String getStdOut() { + return stdOut.toString(); + } + + /** + * Add an output line to the {@code stdOut}. + * + * @param pLine + * The output line to append. + */ + public void addOutputLine(final String pLine) { + stdOut.append(pLine); + } + + /** + * Sets the std out. + * + * @param stdOut + * the new std out + */ + public void setStdOut(String stdOut) { + this.stdOut = new StringBuilder(stdOut); + } + + /** + * Gets the std err. + * + * @return the std err + */ + public String getStdErr() { + return stdErr.toString(); + } + + /** + * Add an error line to the {@code stdErr}. + * + * @param pLine + * The error line to append. + */ + public void addErrorLine(final String pLine) { + stdErr.append(pLine); + } + + /** + * Sets the std err. + * + * @param stdErr + * the new std err + */ + public void setStdErr(String stdErr) { + this.stdErr = new StringBuilder(stdErr); + } +} \ No newline at end of file diff --git a/src/main/java/org/cerberus/services/ApplicationService.java b/src/main/java/org/cerberus/services/ApplicationService.java index 9ee6b66..e402560 100644 --- a/src/main/java/org/cerberus/services/ApplicationService.java +++ b/src/main/java/org/cerberus/services/ApplicationService.java @@ -8,6 +8,8 @@ import org.cerberus.validators.ApplicationValidator; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.UUID; + import static org.cerberus.core.constant.Role.MAINTAINER; import static org.cerberus.core.utils.StringUtils.concat; @@ -16,14 +18,17 @@ public class ApplicationService extends AbstractService { private ApplicationRepository repository; private ApplicationRoleService applicationRoleService; private ApplicationValidator validator; + private DaemonHandlingService daemonHandlingService; ApplicationService(ApplicationRepository repository, ApplicationRoleService applicationRoleService, - ApplicationValidator validator) { + ApplicationValidator validator, + DaemonHandlingService daemonHandlingService) { super(repository); this.repository = repository; this.applicationRoleService = applicationRoleService; this.validator = validator; + this.daemonHandlingService = daemonHandlingService; } @Transactional @@ -63,4 +68,12 @@ public class ApplicationService extends AbstractService { return repository.save(application); } + + public void doServiceAction(UUID applicationId, DaemonHandlingService.Action action) { + daemonHandlingService.doServiceAction(findByIdOrElseThrow(applicationId), action); + } + + public int getStatus(UUID applicationId) { + return daemonHandlingService.getStatus(findByIdOrElseThrow(applicationId)); + } } diff --git a/src/main/java/org/cerberus/services/DaemonHandlingService.java b/src/main/java/org/cerberus/services/DaemonHandlingService.java new file mode 100644 index 0000000..c74e020 --- /dev/null +++ b/src/main/java/org/cerberus/services/DaemonHandlingService.java @@ -0,0 +1,47 @@ +package org.cerberus.services; + +import org.cerberus.core.exceptions.BadRequestException; +import org.cerberus.core.exceptions.ServiceUnavailableException; +import org.cerberus.entities.business.SystemResult; +import org.cerberus.entities.persistence.Application; +import org.springframework.stereotype.Service; + +import static org.cerberus.core.constant.ResultCode.SUCCESS; +import static org.cerberus.core.utils.StringUtils.concat; + +@Service +public class DaemonHandlingService { + public enum Action { + START, + STOP, + RESTART + } + + private SystemService systemService; + + DaemonHandlingService(SystemService systemService) { + this.systemService = systemService; + } + + public int getStatus(Application application) { + return systemService.executeCommand("sudo service", application.getServiceName(), "status") + .getResultCode(); + } + + public void doServiceAction(Application application, Action action) { + int applicationStatus = getStatus(application); + + if((Action.START == action && applicationStatus == 0) + || (Action.STOP == action && applicationStatus != 0)) { + throw new BadRequestException(concat("Service is already ", action.name().toLowerCase(), "ed.")); + } + + SystemResult commandResult = systemService.executeCommand("sudo service", + application.getServiceName(), + action.name().toLowerCase()); + + if(commandResult.getResultCode() != SUCCESS.val()) { + throw new ServiceUnavailableException(commandResult.getStdErr()); + } + } +} diff --git a/src/main/java/org/cerberus/services/SystemService.java b/src/main/java/org/cerberus/services/SystemService.java new file mode 100644 index 0000000..454591c --- /dev/null +++ b/src/main/java/org/cerberus/services/SystemService.java @@ -0,0 +1,74 @@ +package org.cerberus.services; + +import org.cerberus.entities.business.SystemResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import static org.cerberus.core.utils.StringUtils.concat; + +/** + * Business service to execute unix commands. + */ +@Service +public class SystemService { + private static final Logger LOG = LoggerFactory.getLogger(SystemService.class); + + public SystemResult executeCommand(final String pCommand, final String... arguments) { + final String commandWithArgs = buildCommand(pCommand, arguments); + + final SystemResult commandResults = new SystemResult(); + + try { + // Process creation and execution of the command. + final Process process = Runtime.getRuntime().exec(commandWithArgs); + + // Waiting the end of the command execution. + process.waitFor(); + + // Getting of the stantard command output, and the standard error output. + BufferedReader outputReader = new BufferedReader(new InputStreamReader(process.getInputStream())); + BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream())); + + String tempLine; + while((tempLine = outputReader.readLine()) != null) { + commandResults.addOutputLine(tempLine); + } + + while((tempLine = errorReader.readLine()) != null) { + commandResults.addErrorLine(tempLine); + } + + commandResults.setResultCode(process.exitValue()); + } catch(IOException | InterruptedException ex) { + LOG.error(concat("Error during command execution of: \"", commandWithArgs, "\"."), ex); + } + + return commandResults; + } + + /** + * Build the command in form of one string from the parameters. + * + * @param command + * The command to execute. + * @param arguments + * The command arguments, could be {@code null}. + * @return The command built. + */ + private String buildCommand(final String command, final Object... arguments) { + final StringBuilder commandBuilder = new StringBuilder(command); + + if (arguments != null) { + for (final Object arg : arguments) { + commandBuilder.append(" ").append(arg); + } + } + + return commandBuilder.toString(); + } +}