package alien4cloud.paas;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.elasticsearch.mapping.QueryHelper;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import alien4cloud.dao.IGenericSearchDAO;
import alien4cloud.dao.model.GetMultipleDataResult;
import alien4cloud.model.deployment.Deployment;
import alien4cloud.paas.model.AbstractMonitorEvent;
import alien4cloud.paas.model.PaaSDeploymentStatusMonitorEvent;
import alien4cloud.utils.MapUtil;
import alien4cloud.utils.TypeScanner;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
/**
* Monitor service to watch a deployed topologies for a given PaaS provider.
*/
@SuppressWarnings("unchecked")
@Slf4j
public class PaaSProviderPollingMonitor implements Runnable {
private static final int MAX_POLLED_EVENTS = 500;
private static final int MAX_LISTENER_RETRY = 3;
private static final long LISTENER_FAIL_RETRY_SLEEP_MS = 10;
private final IGenericSearchDAO dao;
private final IGenericSearchDAO monitorDAO;
private final IPaaSProvider paaSProvider;
private Date lastPollingDate;
@SuppressWarnings("rawtypes")
private List<IPaasEventListener> listeners;
private PaaSEventsCallback paaSEventsCallback;
private String orchestratorId;
private boolean hasDeployments = false;
private boolean getEventsInProgress = false;
/**
* Create a new instance of the {@link PaaSProviderPollingMonitor} to monitor the given paas provider.
*
* @param paaSProvider The paas provider to monitor.
*/
@SuppressWarnings("rawtypes")
public PaaSProviderPollingMonitor(IGenericSearchDAO dao, IGenericSearchDAO monitorDAO, IPaaSProvider paaSProvider, List<IPaasEventListener> listeners,
String orchestratorId) {
this.orchestratorId = orchestratorId;
this.dao = dao;
this.monitorDAO = monitorDAO;
this.paaSProvider = paaSProvider;
this.listeners = listeners;
Set<Class<?>> eventClasses = Sets.newHashSet();
try {
eventClasses = TypeScanner.scanTypes("alien4cloud.paas.model", AbstractMonitorEvent.class);
/**
* FIXME below is true for our own cloudify 3 provider implementation but this is not documented and orchestrators may not implement it that way.
* We should do that in a different fashion in cloudify 3 provider probably and not impact alien that way
**/
// The PaaSDeploymentStatusMonitorEvent is an internal generated event and so do not take into account
eventClasses.remove(PaaSDeploymentStatusMonitorEvent.class);
} catch (ClassNotFoundException e) {
log.info("No event class derived from {} found", AbstractMonitorEvent.class.getName());
}
Map<String, String[]> filter = Maps.newHashMap();
filter.put("orchestratorId", new String[] { this.orchestratorId });
// sort by filed date DESC
QueryHelper.ISearchQueryBuilderHelper searchQueryHelperBuilder = monitorDAO.getQueryHelper().buildQuery()
.types(eventClasses.toArray(new Class<?>[eventClasses.size()])).filters(filter).prepareSearch("deploymentmonitorevents")
.fieldSort("date", true);
// the first one is the one with the latest date
GetMultipleDataResult lastestEventResult = monitorDAO.search(searchQueryHelperBuilder, 0, 10);
if (lastestEventResult.getData().length > 0) {
AbstractMonitorEvent lastEvent = (AbstractMonitorEvent) lastestEventResult.getData()[0];
Date lastEventDate = new Date(lastEvent.getDate());
log.info("Recovering events from the last in elasticsearch {} of type {}", lastEventDate, lastEvent.getClass().getName());
this.lastPollingDate = lastEventDate;
} else {
this.lastPollingDate = new Date();
log.debug("No monitor events found, the last polling date will be current date {}", this.lastPollingDate);
}
paaSEventsCallback = new PaaSEventsCallback();
}
private class PaaSEventsCallback implements IPaaSCallback<AbstractMonitorEvent[]> {
@Override
public void onSuccess(AbstractMonitorEvent[] auditEvents) {
synchronized (PaaSProviderPollingMonitor.this) {
if (log.isTraceEnabled()) {
log.trace("Polled from date {}", lastPollingDate);
}
if (log.isDebugEnabled() && auditEvents != null && auditEvents.length > 0) {
log.debug("Saving events for orchestrator {}", orchestratorId);
for (AbstractMonitorEvent event : auditEvents) {
log.debug(event.toString());
}
}
if (auditEvents != null && auditEvents.length > 0) {
Date lastEventDate = lastPollingDate;
for (AbstractMonitorEvent event : auditEvents) {
// Enrich event with cloud id before saving them
event.setOrchestratorId(orchestratorId);
// If not set initialize a date for event or update the last event date (last polling)
if (event.getDate() > 0) {
Date eventDate = new Date(event.getDate());
lastEventDate = eventDate.after(lastEventDate) ? eventDate : lastEventDate;
} else {
event.setDate(System.currentTimeMillis());
}
// dispatch the event to all listeners
for (IPaasEventListener listener : listeners) {
dispatchEvent(listener, event);
}
}
monitorDAO.save(auditEvents);
if (lastEventDate != null) {
lastPollingDate = lastEventDate;
}
}
getEventsInProgress = false;
}
}
@Override
public void onFailure(Throwable throwable) {
synchronized (PaaSProviderPollingMonitor.this) {
getEventsInProgress = false;
// Make it re-verify if has deployment returns something in order to no loop infinitely
// If the PaaS is down, there might be a chance that the deployment has been marked as failed
hasDeployments = false;
log.error("Error happened while trying to retrieve events from PaaS provider", throwable);
}
}
}
/**
* Dispatch an event to the registered listener.
*
* @param listener The listener to which to send the event.
* @param event The event to dispatch.
*/
private void dispatchEvent(IPaasEventListener listener, AbstractMonitorEvent event) {
dispatchEvent(listener, event, 0);
}
/**
* Dispatch an event to the registered listener.
*
* @param listener The listener to which to send the event.
* @param event The event to dispatch.
* @param retry The current retry index (0 for first dispatch)
*/
@SneakyThrows
private void dispatchEvent(IPaasEventListener listener, AbstractMonitorEvent event, int retry) {
try {
if (listener.canHandle(event)) {
listener.eventHappened(event);
}
} catch (Exception e) {
log.error("Failed to dispatch event {} to listener {} retry {} on {}.", event.toString(), listener.toString(), retry, MAX_LISTENER_RETRY, e);
// Even if that fails
if (retry < MAX_LISTENER_RETRY) {
Thread.sleep(LISTENER_FAIL_RETRY_SLEEP_MS);
dispatchEvent(listener, event, retry + 1);
}
}
}
@Override
public synchronized void run() {
if (log.isTraceEnabled()) {
log.trace("Poll scheduled");
}
if (getEventsInProgress) {
// Get events since is running
return;
}
getEventsInProgress = true;
if (hasDeployments) {
paaSProvider.getEventsSince(lastPollingDate, MAX_POLLED_EVENTS, paaSEventsCallback);
} else {
getEventsInProgress = false;
hasDeployments = getActiveDeployment() != null;
}
}
private Deployment getActiveDeployment() {
Deployment deployment = null;
GetMultipleDataResult<Deployment> dataResult = dao.search(Deployment.class, null,
MapUtil.newHashMap(new String[] { "orchestratorId", "endDate" }, new String[][] { new String[] { orchestratorId }, new String[] { null } }), 1);
if (dataResult.getData() != null && dataResult.getData().length > 0) {
deployment = dataResult.getData()[0];
}
return deployment;
}
}