package eu.openaire.urls_controller.services; import eu.openaire.urls_controller.controllers.UrlsController; import eu.openaire.urls_controller.models.WorkerInfo; import eu.openaire.urls_controller.util.UriBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.RestTemplate; import java.net.ConnectException; import java.util.regex.Pattern; @Service public class ShutdownServiceImpl implements ShutdownService { private static final Logger logger = LoggerFactory.getLogger(ShutdownServiceImpl.class); // Private Addresses, according to RFC 1918: https://www.rfc-editor.org/rfc/rfc1918 private static final Pattern PRIVATE_IP_ADDRESSES_RFC_1918 = Pattern.compile("(?:10.|172.(?:1[6-9]|2[0-9]|3[0-1])|192.168.)[0-9.]+"); @Value("${services.pdfaggregation.worker.port}") private String workerPort; public ResponseEntity passSecurityChecks(String remoteAddr, String initMsg) { // In case the Controller is running inside a docker container, and we want to send the "shutdownServiceRequest" from the terminal (with curl), without entering inside the container, // then the request will appear coming from a local (private) IP, instead of localhost. if ( ! (remoteAddr.equals("127.0.0.1") || remoteAddr.equals(UriBuilder.ip) || PRIVATE_IP_ADDRESSES_RFC_1918.matcher(remoteAddr).matches()) ) { logger.error(initMsg + "The request came from another IP: " + remoteAddr + " | while the Controller has the IP: " + UriBuilder.ip); return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); } return null; // The checks are passing. } public void postShutdownOrCancelRequestsToAllWorkers(boolean shouldCancel) { // Send "shutdownWorker" requests to all active Workers. for ( String workerId : UrlsController.workersInfoMap.keySet() ) { WorkerInfo workerInfo = UrlsController.workersInfoMap.get(workerId); if ( ! workerInfo.getHasShutdown() ) // A worker may have shutdown on its own (by sending it a shutDown request manually), so it will have told the Controller when it shut down. In case of a Worker-crash, the Controller will not know about it. postShutdownOrCancelRequestToWorker(workerId, workerInfo.getWorkerIP(), shouldCancel); else logger.warn("Will not post " + (shouldCancel ? "Cancel-" : "") + "ShutdownRequest to Worker \"" + workerId + "\", since is it has already shutdown."); } } private static final RestTemplate restTemplate = new RestTemplate(); public boolean postShutdownOrCancelRequestToWorker(String workerId, String workerIp, boolean shouldCancel) { String url = "http://" + workerIp + ":" + workerPort + "/api/" + (shouldCancel ? "cancelShutdownWorker" : "shutdownWorker"); try { ResponseEntity responseEntity = restTemplate.postForEntity(url, null, String.class); int responseCode = responseEntity.getStatusCodeValue(); if ( responseCode != HttpStatus.OK.value() ) { logger.error("HTTP-Connection problem with the submission of the \"postShutdownOrCancelRequestToWorker\" of worker \"" + workerId + "\"! Error-code was: " + responseCode); return false; } else return true; } catch (HttpServerErrorException hsee) { logger.error("The Worker \"" + workerId + "\" failed to handle the \"postShutdownOrCancelRequestToWorker\": " + hsee.getMessage()); return false; } catch (Exception e) { // The Spring RestTemplate may return a "ResourceAccessException", but the actual cause will has to be identified, in order to set the worker as shutdown. Throwable cause = e.getCause(); // No need to check explicitly for null. if ( cause instanceof ConnectException ) { // This includes the "ConnectException". logger.error("Got a \"ConnectException\" when doing a \"postShutdownOrCancelRequestToWorker\", to the Worker: \"" + workerId + "\". | Will register this worker as \"shutdown\".\n" + cause.getMessage()); UrlsController.workersInfoMap.get(workerId).setHasShutdown(true); } else { logger.error("Error for \"postShutdownOrCancelRequestToWorker\", to the Worker: " + workerId, e); // TODO - What should we do? If there was some error from the Controller, side, it does not mean that the worker has shutdown.. // For now, let's handle that case manually, by check with that specific worker and sending it a shutdownRequest from inside its VM. // Then the Worker will automatically send a "shutdownReport" to the Controller, causing it to shutdown (when all other workers have shutdown as well). } return false; } } }