/* * Copyright 2014-2017 the original author or authors. * * Licensed 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 io.pivotal.strepsirrhini.chaoslemur; import io.pivotal.strepsirrhini.chaoslemur.infrastructure.DestructionException; import io.pivotal.strepsirrhini.chaoslemur.infrastructure.Infrastructure; import io.pivotal.strepsirrhini.chaoslemur.reporter.Event; import io.pivotal.strepsirrhini.chaoslemur.reporter.Reporter; import io.pivotal.strepsirrhini.chaoslemur.state.State; import io.pivotal.strepsirrhini.chaoslemur.state.StateProvider; import io.pivotal.strepsirrhini.chaoslemur.task.Task; import io.pivotal.strepsirrhini.chaoslemur.task.TaskRepository; import io.pivotal.strepsirrhini.chaoslemur.task.TaskUriBuilder; import io.pivotal.strepsirrhini.chaoslemur.task.Trigger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @RestController final class Destroyer { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final Boolean dryRun; private final ExecutorService executorService; private final FateEngine fateEngine; private final Infrastructure infrastructure; private final Reporter reporter; private final StateProvider stateProvider; private final TaskRepository taskRepository; private final TaskUriBuilder taskUriBuilder; @Autowired Destroyer(@Value("${dryRun:false}") Boolean dryRun, ExecutorService executorService, FateEngine fateEngine, Infrastructure infrastructure, Reporter reporter, StateProvider stateProvider, @Value("${schedule:0 0 * * * *}") String schedule, TaskRepository taskRepository, TaskUriBuilder taskUriBuilder) { this.logger.info("Destruction schedule: {}", schedule); this.dryRun = dryRun; this.executorService = executorService; this.fateEngine = fateEngine; this.infrastructure = infrastructure; this.reporter = reporter; this.stateProvider = stateProvider; this.taskRepository = taskRepository; this.taskUriBuilder = taskUriBuilder; } /** * Trigger method for destruction of members. This method is invoked on a schedule defined by the cron statement stored in the {@code schedule} configuration property. By default this schedule is * {@code 0 0 * * * *}. */ @Scheduled(cron = "${schedule:0 0 * * * *}") public void destroy() { if (State.STOPPED == this.stateProvider.get()) { this.logger.info("Chaos Lemur stopped"); return; } doDestroy(this.taskRepository.create(Trigger.SCHEDULED)); } @RequestMapping(method = RequestMethod.POST, value = "/chaos", consumes = MediaType.APPLICATION_JSON_VALUE) ResponseEntity<?> eventRequest(@RequestBody Map<String, String> payload) { String value = payload.get("event"); if (value == null) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } HttpHeaders responseHeaders = new HttpHeaders(); if ("destroy".equals(value.toLowerCase())) { Task task = this.taskRepository.create(Trigger.MANUAL); this.executorService.execute(() -> doDestroy(task)); responseHeaders.setLocation(this.taskUriBuilder.getUri(task)); } else { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } return new ResponseEntity<>(responseHeaders, HttpStatus.ACCEPTED); } private void doDestroy(Task task) { List<Member> destroyedMembers = new CopyOnWriteArrayList<>(); UUID identifier = UUID.randomUUID(); this.logger.info("{} Beginning run...", identifier); this.infrastructure.getMembers().stream() .map(member -> this.executorService.submit(() -> { if (this.fateEngine.shouldDie(member)) { try { this.logger.debug("{} Destroying: {}", identifier, member); if (this.dryRun) { this.logger.info("{} Destroyed (Dry Run): {}", identifier, member); } else { this.infrastructure.destroy(member); this.logger.info("{} Destroyed: {}", identifier, member); } destroyedMembers.add(member); } catch (DestructionException e) { this.logger.warn("{} Destroy failed: {}", identifier, member, e); } } })) .forEach(future -> { try { future.get(); } catch (InterruptedException | ExecutionException e) { this.logger.warn("{} Failed to destroy member", identifier, e); } }); this.reporter.sendEvent(new Event(identifier, destroyedMembers)); task.stop(); } }