package eu.openaire.urls_controller.services; import eu.openaire.urls_controller.controllers.UrlsController; import eu.openaire.urls_controller.util.UriBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.]+"); 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. } private static final RestTemplate restTemplate = new RestTemplate(); public boolean postShutdownOrCancelRequestToWorker(String workerId, String workerIp, boolean shouldCancel) { String url = "http://" + workerIp + ":1881/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; } } }