package io.cattle.platform.process.externalevent;
import static io.cattle.platform.core.constants.ExternalEventConstants.*;
import io.cattle.platform.core.constants.CommonStatesConstants;
import io.cattle.platform.core.constants.ExternalEventConstants;
import io.cattle.platform.core.dao.GenericResourceDao;
import io.cattle.platform.core.dao.ServiceDao;
import io.cattle.platform.core.dao.StackDao;
import io.cattle.platform.core.model.ExternalEvent;
import io.cattle.platform.core.model.Service;
import io.cattle.platform.core.model.Stack;
import io.cattle.platform.deferred.util.DeferredUtils;
import io.cattle.platform.engine.handler.HandlerResult;
import io.cattle.platform.engine.process.ProcessInstance;
import io.cattle.platform.engine.process.ProcessState;
import io.cattle.platform.engine.process.impl.ProcessCancelException;
import io.cattle.platform.lock.LockCallbackNoReturn;
import io.cattle.platform.lock.LockManager;
import io.cattle.platform.object.meta.ObjectMetaDataManager;
import io.cattle.platform.object.process.StandardProcess;
import io.cattle.platform.object.resource.ResourceMonitor;
import io.cattle.platform.object.resource.ResourcePredicate;
import io.cattle.platform.object.util.DataUtils;
import io.cattle.platform.object.util.ObjectUtils;
import io.cattle.platform.process.base.AbstractDefaultProcessHandler;
import io.cattle.platform.process.common.util.ProcessUtils;
import io.cattle.platform.util.type.CollectionUtils;
import io.github.ibuildthecloud.gdapi.factory.SchemaFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Named
public class ExternalServiceEventCreate extends AbstractDefaultProcessHandler {
private static final Logger log = LoggerFactory.getLogger(ExternalServiceEventCreate.class);
@Inject
ServiceDao serviceDao;
@Inject
ResourceMonitor resourceMonitor;
@Inject
GenericResourceDao resourceDao;
@Inject
LockManager lockManager;
@Inject
@Named("CoreSchemaFactory")
SchemaFactory schemaFactory;
@Inject
StackDao stackDao;
@Override
public HandlerResult handle(ProcessState state, ProcessInstance process) {
final ExternalEvent event = (ExternalEvent)state.getResource();
if (!ExternalEventConstants.KIND_SERVICE_EVENT.equals(event.getKind())) {
return null;
}
lockManager.lock(new ExternalEventLock(SERVICE_LOCK_NAME, event.getAccountId(), event.getExternalId()), new LockCallbackNoReturn() {
@Override
public void doWithLockNoResult() {
Map<String, Object> serviceData = CollectionUtils.toMap(DataUtils.getFields(event).get(FIELD_SERVICE));
if (serviceData.isEmpty()) {
log.warn("Empty service for externalServiceEvent: {}.", event);
return;
}
String kind = serviceData.get(ObjectMetaDataManager.KIND_FIELD) != null ? serviceData.get(ObjectMetaDataManager.KIND_FIELD).toString() : null;
if (StringUtils.isEmpty(kind) || schemaFactory.getSchema(kind) == null) {
log.warn("Couldn't find schema for service type [{}]. Returning.", kind);
return;
}
if (StringUtils.equals(event.getEventType(), TYPE_SERVICE_CREATE)) {
createService(event, serviceData);
} else if (StringUtils.equals(event.getEventType(), TYPE_SERVICE_UPDATE)) {
updateService(event, serviceData);
} else if (StringUtils.equals(event.getEventType(), TYPE_SERVICE_DELETE)) {
deleteService(event, serviceData);
} else if (StringUtils.equals(event.getEventType(), TYPE_STACK_DELETE)) {
deleteStack(event, serviceData);
}
}
});
return null;
}
void createService(ExternalEvent event, Map<String, Object> serviceData) {
Service svc = serviceDao.getServiceByExternalId(event.getAccountId(), event.getExternalId());
if (svc != null) {
return;
}
Stack stack = getStack(event);
if (stack == null) {
log.info("Can't process service event. Could not get or create stack. Event: [{}]", event);
return;
}
Map<String, Object> service = new HashMap<String, Object>();
if (serviceData != null) {
service.putAll(serviceData);
}
service.put(ObjectMetaDataManager.ACCOUNT_FIELD, event.getAccountId());
service.put(FIELD_STACK_ID, stack.getId());
try {
String create = objectProcessManager.getStandardProcessName(StandardProcess.CREATE, Service.class);
String activate = objectProcessManager.getStandardProcessName(StandardProcess.ACTIVATE, Service.class);
ProcessUtils.chainInData(service, create, activate);
resourceDao.createAndSchedule(Service.class, service);
} catch (ProcessCancelException e) {
log.info("Create and activate process cancelled for service with account id {}and external id {}",
event.getAccountId(), event.getExternalId());
}
}
Stack getStack(final ExternalEvent event) {
final Map<String, Object> env = CollectionUtils.castMap(DataUtils.getFields(event).get(FIELD_ENVIRIONMENT));
Object eId = CollectionUtils.getNestedValue(env, FIELD_EXTERNAL_ID);
if (eId == null) {
return null;
}
final String envExtId = eId.toString();
Stack stack = stackDao.getStackByExternalId(event.getAccountId(), envExtId);
//If stack has not been created yet
if (stack == null) {
final Stack newEnv = objectManager.newRecord(Stack.class);
Object possibleName = CollectionUtils.getNestedValue(env, "name");
newEnv.setExternalId(envExtId);
newEnv.setAccountId(event.getAccountId());
String name = possibleName != null ? possibleName.toString() : envExtId;
newEnv.setName(name);
stack = DeferredUtils.nest(new Callable<Stack>() {
@Override
public Stack call() {
return resourceDao.createAndSchedule(newEnv);
}
});
stack = resourceMonitor.waitFor(stack, new ResourcePredicate<Stack>() {
@Override
public boolean evaluate(Stack obj) {
return obj != null && CommonStatesConstants.ACTIVE.equals(obj.getState());
}
@Override
public String getMessage() {
return "active state";
}
});
}
return stack;
}
void updateService(ExternalEvent event, Map<String, Object> serviceData) {
Service svc = serviceDao.getServiceByExternalId(event.getAccountId(), event.getExternalId());
if (svc == null) {
log.info("Unable to find service while attempting to update. Returning. Service external id: [{}], account id: [{}]", event.getExternalId(),
event.getAccountId());
return;
}
Map<String, Object> fields = DataUtils.getFields(svc);
Map<String, Object> updates = new HashMap<String, Object>();
for (Map.Entry<String, Object> resourceField : serviceData.entrySet()) {
String fieldName = resourceField.getKey();
Object newFieldValue = resourceField.getValue();
Object currentFieldValue = fields.get(fieldName);
if (ObjectUtils.hasWritableProperty(svc, fieldName)) {
Object property = ObjectUtils.getProperty(svc, fieldName);
if (newFieldValue != null && !newFieldValue.equals(property) || property == null) {
updates.put(fieldName, newFieldValue);
}
} else if ((newFieldValue != null && !newFieldValue.equals(currentFieldValue)) || currentFieldValue != null) {
updates.put(fieldName, newFieldValue);
}
}
if (!updates.isEmpty()) {
objectManager.setFields(svc, updates);
resourceDao.updateAndSchedule(svc);
}
}
void deleteService(ExternalEvent event, Map<String, Object> serviceData) {
Service svc = serviceDao.getServiceByExternalId(event.getAccountId(), event.getExternalId());
if (svc != null) {
objectProcessManager.scheduleStandardProcess(StandardProcess.REMOVE, svc, null);
}
}
void deleteStack(ExternalEvent event, Map<String, Object> stackData) {
Stack env = stackDao.getStackByExternalId(event.getAccountId(), event.getExternalId());
if (env != null) {
objectProcessManager.scheduleStandardProcess(StandardProcess.REMOVE, env, null);
}
}
String getSelector(ExternalEvent event) {
Object s = DataUtils.getFields(event).get("selector");
String selector = s != null ? s.toString() : null;
return selector;
}
@Override
public String[] getProcessNames() {
return new String[] { ExternalEventConstants.KIND_EXTERNAL_EVENT + ".create" };
}
}