package com.sequenceiq.cloudbreak.service.stack.handler;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
import org.springframework.stereotype.Service;
import com.sequenceiq.ambari.client.AmbariClient;
import com.sequenceiq.cloudbreak.conf.ReactorConfig;
import com.sequenceiq.cloudbreak.domain.BillingStatus;
import com.sequenceiq.cloudbreak.domain.CloudPlatform;
import com.sequenceiq.cloudbreak.domain.Cluster;
import com.sequenceiq.cloudbreak.domain.Resource;
import com.sequenceiq.cloudbreak.domain.Stack;
import com.sequenceiq.cloudbreak.domain.Status;
import com.sequenceiq.cloudbreak.domain.StatusRequest;
import com.sequenceiq.cloudbreak.logger.MDCBuilder;
import com.sequenceiq.cloudbreak.repository.ClusterRepository;
import com.sequenceiq.cloudbreak.repository.RetryingStackUpdater;
import com.sequenceiq.cloudbreak.repository.StackRepository;
import com.sequenceiq.cloudbreak.service.PollingService;
import com.sequenceiq.cloudbreak.service.cluster.event.ClusterStatusUpdateRequest;
import com.sequenceiq.cloudbreak.service.cluster.flow.AmbariClusterConnector;
import com.sequenceiq.cloudbreak.service.cluster.flow.AmbariHealthCheckerTask;
import com.sequenceiq.cloudbreak.service.cluster.flow.AmbariHosts;
import com.sequenceiq.cloudbreak.service.cluster.flow.AmbariHostsJoinStatusCheckerTask;
import com.sequenceiq.cloudbreak.service.events.CloudbreakEventService;
import com.sequenceiq.cloudbreak.service.stack.connector.CloudPlatformConnector;
import com.sequenceiq.cloudbreak.service.stack.event.StackStatusUpdateRequest;
import com.sequenceiq.cloudbreak.service.stack.resource.ResourceBuilder;
import com.sequenceiq.cloudbreak.service.stack.resource.ResourceBuilderInit;
import com.sequenceiq.cloudbreak.service.stack.resource.StartStopContextObject;
import reactor.core.Reactor;
import reactor.event.Event;
import reactor.function.Consumer;
@Service
public class StackStatusUpdateHandler implements Consumer<Event<StackStatusUpdateRequest>> {
private static final Logger LOGGER = LoggerFactory.getLogger(StackStatusUpdateHandler.class);
@javax.annotation.Resource
private Map<CloudPlatform, CloudPlatformConnector> cloudPlatformConnectors;
@Autowired
private StackRepository stackRepository;
@Autowired
private RetryingStackUpdater stackUpdater;
@Autowired
private Reactor reactor;
@Autowired
private ClusterRepository clusterRepository;
@Autowired
private PollingService<AmbariHosts> ambariHostJoin;
@Autowired
private PollingService<AmbariClient> ambariHealthChecker;
@javax.annotation.Resource
private Map<CloudPlatform, List<ResourceBuilder>> instanceResourceBuilders;
@javax.annotation.Resource
private Map<CloudPlatform, List<ResourceBuilder>> networkResourceBuilders;
@javax.annotation.Resource
private ConcurrentTaskExecutor resourceBuilderExecutor;
@javax.annotation.Resource
private Map<CloudPlatform, ResourceBuilderInit> resourceBuilderInits;
@Autowired
private CloudbreakEventService cloudbreakEventService;
@Override
public void accept(Event<StackStatusUpdateRequest> event) {
StackStatusUpdateRequest statusUpdateRequest = event.getData();
final CloudPlatform cloudPlatform = statusUpdateRequest.getCloudPlatform();
StatusRequest statusRequest = statusUpdateRequest.getStatusRequest();
long stackId = statusUpdateRequest.getStackId();
Stack stack = stackRepository.findOneWithLists(stackId);
MDCBuilder.buildMdcContext(stack);
if (StatusRequest.STOPPED.equals(statusRequest)) {
boolean stopped = true;
if (cloudPlatform.isWithTemplate()) {
CloudPlatformConnector connector = cloudPlatformConnectors.get(cloudPlatform);
stopped = connector.stopAll(stack);
} else {
try {
ResourceBuilderInit resourceBuilderInit = resourceBuilderInits.get(cloudPlatform);
final StartStopContextObject sSCO = resourceBuilderInit.startStopInit(stack);
for (ResourceBuilder resourceBuilder : networkResourceBuilders.get(cloudPlatform)) {
for (Resource resource : stack.getResourcesByType(resourceBuilder.resourceType())) {
resourceBuilder.stop(sSCO, resource);
}
}
List<Future<Boolean>> futures = new ArrayList<>();
for (final ResourceBuilder resourceBuilder : instanceResourceBuilders.get(cloudPlatform)) {
List<Resource> resourceByType = stack.getResourcesByType(resourceBuilder.resourceType());
for (final Resource resource : resourceByType) {
Future<Boolean> submit = resourceBuilderExecutor.submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return resourceBuilder.stop(sSCO, resource);
}
});
futures.add(submit);
}
}
for (Future<Boolean> future : futures) {
if (!future.get()) {
stopped = false;
}
}
} catch (Exception ex) {
stopped = false;
}
}
if (stopped) {
LOGGER.info("Update stack state to: {}", Status.STOPPED);
stackUpdater.updateStackStatus(stackId, Status.STOPPED);
cloudbreakEventService.fireCloudbreakEvent(stackId, BillingStatus.BILLING_STOPPED.name(), "Stack stopped.");
} else {
LOGGER.info("Update stack state to: {}", Status.STOP_FAILED);
stackUpdater.updateStackStatus(stackId, Status.STOP_FAILED);
}
} else {
boolean started = true;
if (cloudPlatform.isWithTemplate()) {
CloudPlatformConnector connector = cloudPlatformConnectors.get(cloudPlatform);
started = connector.startAll(stack);
} else {
try {
ResourceBuilderInit resourceBuilderInit = resourceBuilderInits.get(cloudPlatform);
final StartStopContextObject sSCO = resourceBuilderInit.startStopInit(stack);
for (ResourceBuilder resourceBuilder : networkResourceBuilders.get(cloudPlatform)) {
for (Resource resource : stack.getResourcesByType(resourceBuilder.resourceType())) {
resourceBuilder.start(sSCO, resource);
}
}
List<Future<Boolean>> futures = new ArrayList<>();
for (final ResourceBuilder resourceBuilder : instanceResourceBuilders.get(cloudPlatform)) {
List<Resource> resourceByType = stack.getResourcesByType(resourceBuilder.resourceType());
for (final Resource resource : resourceByType) {
Future<Boolean> submit = resourceBuilderExecutor.submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return resourceBuilder.start(sSCO, resource);
}
});
futures.add(submit);
}
}
for (Future<Boolean> future : futures) {
if (!future.get()) {
started = false;
}
}
} catch (Exception ex) {
started = false;
}
}
if (started) {
cloudbreakEventService.fireCloudbreakEvent(stackId, BillingStatus.BILLING_STARTED.name(), "Stack started.");
waitForAmbariToStart(stack);
Cluster cluster = clusterRepository.findOneWithLists(stack.getCluster().getId());
LOGGER.info("Update stack state to: {}", Status.AVAILABLE);
stackUpdater.updateStackStatus(stackId, Status.AVAILABLE);
if (cluster != null && Status.START_REQUESTED.equals(cluster.getStatus())) {
boolean hostsJoined = waitForHostsToJoin(stack);
if (hostsJoined) {
reactor.notify(ReactorConfig.CLUSTER_STATUS_UPDATE_EVENT,
Event.wrap(new ClusterStatusUpdateRequest(stack.getId(), statusRequest)));
} else {
cluster.setStatus(Status.START_FAILED);
stack.setCluster(cluster);
stackRepository.save(stack);
}
}
} else {
LOGGER.info("Update stack state to: {}", Status.START_FAILED);
stackUpdater.updateStackStatus(stackId, Status.START_FAILED);
}
}
}
private void waitForAmbariToStart(Stack stack) {
ambariHealthChecker.pollWithTimeout(
new AmbariHealthCheckerTask(),
new AmbariClient(stack.getAmbariIp()),
AmbariClusterConnector.POLLING_INTERVAL,
AmbariClusterConnector.MAX_ATTEMPTS_FOR_HOSTS);
}
private boolean waitForHostsToJoin(Stack stack) {
AmbariHostsJoinStatusCheckerTask ambariHostsJoinStatusCheckerTask = new AmbariHostsJoinStatusCheckerTask();
AmbariHosts ambariHosts =
new AmbariHosts(stack, new AmbariClient(stack.getAmbariIp()), stack.getNodeCount() * stack.getMultiplier());
ambariHostJoin.pollWithTimeout(
ambariHostsJoinStatusCheckerTask,
ambariHosts,
AmbariClusterConnector.POLLING_INTERVAL,
AmbariClusterConnector.MAX_ATTEMPTS_FOR_HOSTS);
return ambariHostsJoinStatusCheckerTask.checkStatus(ambariHosts);
}
}