package org.zstack.core.gc; import org.springframework.beans.factory.annotation.Autowired; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.MessageSafe; import org.zstack.core.cloudbus.ResourceDestinationMaker; import org.zstack.core.config.GlobalConfig; import org.zstack.core.config.GlobalConfigUpdateExtensionPoint; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.Q; import org.zstack.core.errorcode.ErrorFacade; import org.zstack.core.thread.PeriodicTask; import org.zstack.core.thread.ThreadFacade; import org.zstack.header.AbstractService; import org.zstack.header.Component; import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.managementnode.ManagementNodeReadyExtensionPoint; import org.zstack.header.message.APIMessage; import org.zstack.header.message.Message; import org.zstack.utils.DebugUtils; import org.zstack.utils.Utils; import org.zstack.utils.logging.CLogger; import static org.zstack.core.Platform.operr; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /** * Created by xing5 on 2017/3/1. */ public class GarbageCollectorManagerImpl extends AbstractService implements GarbageCollectorManager, Component, ManagementNodeReadyExtensionPoint { static final CLogger logger = Utils.getLogger(GarbageCollectorManagerImpl.class); @Autowired private ResourceDestinationMaker destinationMaker; @Autowired private ThreadFacade thdf; @Autowired private CloudBus bus; @Autowired private DatabaseFacade dbf; @Autowired private ErrorFacade errf; private Future<Void> scanOrphanJobsTask; private ConcurrentHashMap<String, GarbageCollector> managedGarbageCollectors = new ConcurrentHashMap<>(); private void startScanOrphanJobs() { if (scanOrphanJobsTask != null) { scanOrphanJobsTask.cancel(true); } scanOrphanJobsTask = thdf.submitPeriodicTask(new PeriodicTask() { @Override public TimeUnit getTimeUnit() { return TimeUnit.SECONDS; } @Override public long getInterval() { return GCGlobalConfig.SCAN_ORPHAN_JOB_INTERVAL.value(Long.class); } @Override public String getName() { return "scan-orphan-gc-jobs"; } @Override public void run() { try { loadOrphanJobs(); } catch (Exception e) { throw new CloudRuntimeException(e); } } }); logger.debug(String.format("[GC] starts scanning orphan job thread with the interval[%ss]", GCGlobalConfig.SCAN_ORPHAN_JOB_INTERVAL.value(Integer.class))); } void registerGC(GarbageCollector gc) { managedGarbageCollectors.put(gc.uuid, gc); } void deregisterGC(GarbageCollector gc) { managedGarbageCollectors.remove(gc.uuid); } @Override public boolean start() { startScanOrphanJobs(); GCGlobalConfig.SCAN_ORPHAN_JOB_INTERVAL.installUpdateExtension(new GlobalConfigUpdateExtensionPoint() { @Override public void updateGlobalConfig(GlobalConfig oldConfig, GlobalConfig newConfig) { startScanOrphanJobs(); } }); return true; } @Override public boolean stop() { return true; } private GarbageCollector loadGCJob(GarbageCollectorVO vo) { try { GarbageCollector ret = null; Class clz = Class.forName(vo.getRunnerClass()); if (vo.getType().equals(GarbageCollectorType.EventBased.toString())) { EventBasedGarbageCollector gc = (EventBasedGarbageCollector) clz.newInstance(); gc.load(vo); ret = gc; } else if (vo.getType().equals(GarbageCollectorType.TimeBased.toString())) { TimeBasedGarbageCollector gc = (TimeBasedGarbageCollector) clz.newInstance(); gc.load(vo); ret = gc; } else { DebugUtils.Assert(false, "should not be here"); } return ret; } catch (Exception e) { throw new CloudRuntimeException(e); } } private void loadOrphanJobs() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { List<GarbageCollectorVO> vos = Q.New(GarbageCollectorVO.class) .isNull(GarbageCollectorVO_.managementNodeUuid).list(); int count = 0; for (GarbageCollectorVO vo : vos) { if (!destinationMaker.isManagedByUs(vo.getUuid())) { continue; } loadGCJob(vo); count ++; } logger.debug(String.format("[GC] loaded %s orphan jobs", count)); } @Override public void managementNodeReady() { try { loadOrphanJobs(); } catch (Exception e) { throw new CloudRuntimeException(e); } } @Override @MessageSafe public void handleMessage(Message msg) { if (msg instanceof APIMessage) { handleApiMessage((APIMessage) msg); } else { handleLocalMessage(msg); } } private void handleLocalMessage(Message msg) { bus.dealWithUnknownMessage(msg); } private void handleApiMessage(APIMessage msg) { if (msg instanceof APITriggerGCJobMsg) { handle((APITriggerGCJobMsg) msg); } else if (msg instanceof APIDeleteGCJobMsg) { handle((APIDeleteGCJobMsg) msg); } else { bus.dealWithUnknownMessage(msg); } } private void handle(APIDeleteGCJobMsg msg) { GarbageCollector gc = managedGarbageCollectors.get(msg.getUuid()); if (gc != null) { gc.cancel(); } GarbageCollectorVO vo = dbf.findByUuid(msg.getUuid(), GarbageCollectorVO.class); dbf.remove(vo); APIDeleteGCJobEvent evt = new APIDeleteGCJobEvent(msg.getId()); bus.publish(evt); } private void handle(APITriggerGCJobMsg msg) { GarbageCollector gc = managedGarbageCollectors.get(msg.getUuid()); if (gc != null) { gc.runTrigger(); } else { GarbageCollectorVO vo = dbf.findByUuid(msg.getUuid(), GarbageCollectorVO.class); if (vo.getStatus() == GCStatus.Done) { throw new OperationFailureException(operr("cannot trigger a finished GC job[uuid:%s, name:%s]", vo.getUuid(), vo.getName())); } gc = loadGCJob(vo); gc.runTrigger(); } APITriggerGCJobEvent evt = new APITriggerGCJobEvent(msg.getId()); bus.publish(evt); } @Override public String getId() { return bus.makeLocalServiceId(GCConstants.SERVICE_ID); } }