package org.zstack.compute.host; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; import org.zstack.core.cascade.CascadeConstant; import org.zstack.core.cascade.CascadeFacade; import org.zstack.core.cloudbus.*; import org.zstack.core.componentloader.PluginRegistry; import org.zstack.core.config.GlobalConfigFacade; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.Q; import org.zstack.core.db.SimpleQuery; import org.zstack.core.db.SimpleQuery.Op; import org.zstack.core.errorcode.ErrorFacade; import org.zstack.core.thread.ChainTask; import org.zstack.core.thread.SyncTaskChain; import org.zstack.core.thread.ThreadFacade; import org.zstack.core.workflow.FlowChainBuilder; import org.zstack.core.workflow.ShareFlow; import org.zstack.header.allocator.HostAllocatorConstant; import org.zstack.header.apimediator.ApiMessageInterceptionException; import org.zstack.header.core.Completion; import org.zstack.header.core.NoErrorCompletion; import org.zstack.header.core.NopeCompletion; import org.zstack.header.core.workflow.*; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.errorcode.SysErrors; import org.zstack.header.host.*; import org.zstack.header.host.HostCanonicalEvents.HostDeletedData; import org.zstack.header.host.HostCanonicalEvents.HostStatusChangedData; import org.zstack.header.host.HostErrors.Opaque; import org.zstack.header.host.HostMaintenancePolicyExtensionPoint.HostMaintenancePolicy; import org.zstack.header.message.APIDeleteMessage; import org.zstack.header.message.APIMessage; import org.zstack.header.message.Message; import org.zstack.header.message.MessageReply; import org.zstack.header.vm.*; import org.zstack.utils.CollectionUtils; import org.zstack.utils.DebugUtils; import org.zstack.utils.Utils; import org.zstack.utils.function.ForEachFunction; import org.zstack.utils.logging.CLogger; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import static org.zstack.core.Platform.operr; @Configurable(preConstruction = true, autowire = Autowire.BY_TYPE) public abstract class HostBase extends AbstractHost { protected static final CLogger logger = Utils.getLogger(HostBase.class); protected HostVO self; @Autowired protected CloudBus bus; @Autowired protected DatabaseFacade dbf; @Autowired protected ThreadFacade thdf; @Autowired protected HostExtensionPointEmitter extpEmitter; @Autowired protected GlobalConfigFacade gcf; @Autowired protected HostManager hostMgr; @Autowired protected CascadeFacade casf; @Autowired protected ErrorFacade errf; @Autowired protected HostTracker tracker; @Autowired protected PluginRegistry pluginRgty; @Autowired protected EventFacade evtf; protected final String id; protected abstract void pingHook(Completion completion); protected abstract int getVmMigrateQuantity(); protected abstract void changeStateHook(HostState current, HostStateEvent stateEvent, HostState next); protected abstract void connectHook(ConnectHostInfo info, Completion complete); protected HostBase(HostVO self) { this.self = self; id = "Host-" + self.getUuid(); } protected void checkStatus() { if (HostStatus.Connected != self.getStatus()) { ErrorCode cause = errf.instantiateErrorCode(HostErrors.HOST_IS_DISCONNECTED, String.format("host[uuid:%s, name:%s] is in status[%s], cannot perform required operation", self.getUuid(), self.getName(), self.getStatus())); throw new OperationFailureException(errf.instantiateErrorCode(HostErrors.OPERATION_FAILURE_GC_ELIGIBLE, "unable to do the operation because the host is in status of Disconnected", cause)); } } protected void checkState() { if (HostState.PreMaintenance == self.getState() || HostState.Maintenance == self.getState()) { throw new OperationFailureException(operr("host[uuid:%s, name:%s] is in state[%s], cannot perform required operation", self.getUuid(), self.getName(), self.getState())); } } protected void checkStateAndStatus() { checkState(); checkStatus(); } protected int getHostSyncLevel() { return 10; } protected void handleApiMessage(APIMessage msg) { if (msg instanceof APIChangeHostStateMsg) { handle((APIChangeHostStateMsg) msg); } else if (msg instanceof APIDeleteHostMsg) { handle((APIDeleteHostMsg) msg); } else if (msg instanceof APIReconnectHostMsg) { handle((APIReconnectHostMsg) msg); } else if (msg instanceof APIUpdateHostMsg) { handle((APIUpdateHostMsg) msg); } else { bus.dealWithUnknownMessage(msg); } } protected HostVO updateHost(APIUpdateHostMsg msg) { boolean update = false; if (msg.getName() != null) { self.setName(msg.getName()); update = true; } if (msg.getDescription() != null) { self.setDescription(msg.getDescription()); update = true; } if (msg.getManagementIp() != null) { self.setManagementIp(msg.getManagementIp()); update = true; } return update ? self : null; } private void handle(APIUpdateHostMsg msg) { HostVO vo = updateHost(msg); if (vo != null) { self = dbf.updateAndRefresh(vo); } APIUpdateHostEvent evt = new APIUpdateHostEvent(msg.getId()); evt.setInventory(getSelfInventory()); bus.publish(evt); } protected void maintenanceHook(final Completion completion) { FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("maintenance-mode-host-%s-ip-%s", self.getUuid(), self.getManagementIp())); HostMaintenancePolicy policy = HostMaintenancePolicy.MigrateVm; for (HostMaintenancePolicyExtensionPoint ext : pluginRgty.getExtensionList(HostMaintenancePolicyExtensionPoint.class)) { HostMaintenancePolicy p = ext.getHostMaintenancePolicy(getSelfInventory()); if (p != null) { policy = p; logger.debug(String.format("HostMaintenancePolicyExtensionPoint[%s] set maintenance policy for host[uuid:%s] to %s", ext.getClass(), self.getUuid(), policy)); } } final int quantity = getVmMigrateQuantity(); DebugUtils.Assert(quantity != 0, "getVmMigrateQuantity() cannot return 0"); final HostMaintenancePolicy finalPolicy = policy; chain.then(new ShareFlow() { List<String> vmFailedToMigrate = new ArrayList<String>(); @Override public void setup() { if (finalPolicy == HostMaintenancePolicy.MigrateVm) { flow(new NoRollbackFlow() { String __name__ = "try-migrate-vm"; @Override public void run(final FlowTrigger trigger, Map data) { SimpleQuery<VmInstanceVO> q = dbf.createQuery(VmInstanceVO.class); q.select(VmInstanceVO_.uuid); q.add(VmInstanceVO_.hostUuid, Op.EQ, self.getUuid()); q.add(VmInstanceVO_.state, Op.NOT_EQ, VmInstanceState.Unknown); List<String> vmUuids = q.listValue(); if (vmUuids.isEmpty()) { trigger.next(); return; } int migrateQuantity = quantity; HostInventory host = getSelfInventory(); for (OrderVmBeforeMigrationDuringHostMaintenanceExtensionPoint ext : pluginRgty.getExtensionList(OrderVmBeforeMigrationDuringHostMaintenanceExtensionPoint.class)) { List<String> ordered = ext.orderVmBeforeMigrationDuringHostMaintenance(host, vmUuids); if (ordered != null) { vmUuids = ordered; logger.debug(String.format("%s ordered VMs for host maintenance, to keep the order, we will migrate VMs one by one", ext.getClass())); migrateQuantity = 1; } } final List<MigrateVmMsg> msgs = new ArrayList<MigrateVmMsg>(); for (String uuid : vmUuids) { MigrateVmMsg msg = new MigrateVmMsg(); msg.setVmInstanceUuid(uuid); bus.makeTargetServiceIdByResourceUuid(msg, VmInstanceConstant.SERVICE_ID, uuid); msgs.add(msg); } bus.send(msgs, migrateQuantity, new CloudBusListCallBack(trigger) { @Override public void run(List<MessageReply> replies) { for (MessageReply reply : replies) { if (!reply.isSuccess()) { MigrateVmMsg msg = msgs.get(replies.indexOf(reply)); logger.warn(String.format("failed to migrate vm[uuid:%s] on host[uuid:%s, name:%s, ip:%s], will try stopping it. %s", msg.getVmInstanceUuid(), self.getUuid(), self.getName(), self.getManagementIp(), reply.getError())); vmFailedToMigrate.add(msg.getVmInstanceUuid()); } } trigger.next(); } }); } }); } else { // the policy is not to migrate vm // put all vms in vmFailedToMigrate so the next flow will stop all of them SimpleQuery<VmInstanceVO> q = dbf.createQuery(VmInstanceVO.class); q.select(VmInstanceVO_.uuid); q.add(VmInstanceVO_.hostUuid, Op.EQ, self.getUuid()); q.add(VmInstanceVO_.state, Op.NOT_EQ, VmInstanceState.Unknown); List<String> vmUuids = q.listValue(); vmFailedToMigrate.addAll(vmUuids); } flow(new NoRollbackFlow() { String __name__ = "stop-vm-not-migrated"; @Override public void run(FlowTrigger trigger, Map data) { List<String> vmUuids = new ArrayList<String>(); vmUuids.addAll(vmFailedToMigrate); vmUuids = CollectionUtils.removeDuplicateFromList(vmUuids); if (vmUuids.isEmpty()) { trigger.next(); return; } stopFailedToMigrateVms(vmUuids, trigger); } private void stopFailedToMigrateVms(List<String> vmUuids, final FlowTrigger trigger) { final List<StopVmInstanceMsg> msgs = new ArrayList<StopVmInstanceMsg>(); for (String vmUuid : vmUuids) { StopVmInstanceMsg msg = new StopVmInstanceMsg(); msg.setVmInstanceUuid(vmUuid); bus.makeTargetServiceIdByResourceUuid(msg, VmInstanceConstant.SERVICE_ID, vmUuid); msgs.add(msg); } bus.send(msgs, quantity, new CloudBusListCallBack(trigger) { @Override public void run(List<MessageReply> replies) { StringBuilder sb = new StringBuilder(); boolean success = true; for (MessageReply r : replies) { if (!r.isSuccess()) { StopVmInstanceMsg msg = msgs.get(replies.indexOf(r)); String err = String.format("\nfailed to stop vm[uuid:%s] on host[uuid:%s, name:%s, ip:%s], %s", msg.getVmInstanceUuid(), self.getUuid(), self.getName(), self.getManagementIp(), r.getError()); sb.append(err); success = false; } } if (!success) { logger.warn(sb.toString()); } if (success || HostGlobalConfig.IGNORE_ERROR_ON_MAINTENANCE_MODE.value(Boolean.class)) { trigger.next(); } else { trigger.fail(operr(sb.toString())); } } }); } }); done(new FlowDoneHandler(completion) { @Override public void handle(Map data) { completion.success(); } }); error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { completion.fail(errCode); } }); } }).start(); } private void handle(final APIReconnectHostMsg msg) { ReconnectHostMsg rmsg = new ReconnectHostMsg(); rmsg.setHostUuid(self.getUuid()); bus.makeTargetServiceIdByResourceUuid(rmsg, HostConstant.SERVICE_ID, self.getUuid()); bus.send(rmsg, new CloudBusCallBack(msg) { @Override public void run(MessageReply reply) { APIReconnectHostEvent evt = new APIReconnectHostEvent(msg.getId()); if (!reply.isSuccess()) { evt.setError(errf.instantiateErrorCode(HostErrors.UNABLE_TO_RECONNECT_HOST, reply.getError())); logger.debug(String.format("failed to reconnect host[uuid:%s] because %s", self.getUuid(), reply.getError())); }else{ evt.setInventory((getSelfInventory())); } bus.publish(evt); } }); } private void deleteHostByApiMessage(APIDeleteHostMsg msg) { final APIDeleteHostEvent evt = new APIDeleteHostEvent(msg.getId()); final String issuer = HostVO.class.getSimpleName(); final List<HostInventory> ctx = Arrays.asList(HostInventory.valueOf(self)); HostInventory hinv = HostInventory.valueOf(self); FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); chain.setName(String.format("delete-host-%s", msg.getUuid())); if (msg.getDeletionMode() == APIDeleteMessage.DeletionMode.Permissive) { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { casf.asyncCascade(CascadeConstant.DELETION_CHECK_CODE, issuer, ctx, new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }).then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { casf.asyncCascade(CascadeConstant.DELETION_DELETE_CODE, issuer, ctx, new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); } else { chain.then(new NoRollbackFlow() { @Override public void run(final FlowTrigger trigger, Map data) { casf.asyncCascade(CascadeConstant.DELETION_FORCE_DELETE_CODE, issuer, ctx, new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); } chain.done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { casf.asyncCascadeFull(CascadeConstant.DELETION_CLEANUP_CODE, issuer, ctx, new NopeCompletion()); bus.publish(evt); HostDeletedData d = new HostDeletedData(); d.setInventory(HostInventory.valueOf(self)); d.setHostUuid(self.getUuid()); evtf.fire(HostCanonicalEvents.HOST_DELETED_PATH, d); } }).error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { evt.setError(errf.instantiateErrorCode(SysErrors.DELETE_RESOURCE_ERROR, errCode)); bus.publish(evt); } }).start(); } private void handle(final APIDeleteHostMsg msg) { deleteHostByApiMessage(msg); } protected HostState changeState(HostStateEvent event) { HostState currentState = self.getState(); HostState next = currentState.nextState(event); changeStateHook(currentState, event, next); extpEmitter.beforeChange(self, event); self.setState(next); self = dbf.updateAndRefresh(self); extpEmitter.afterChange(self, event, currentState); logger.debug(String.format("Host[%s]'s state changed from %s to %s", self.getUuid(), currentState, self.getState())); return self.getState(); } protected void changeStateByApiMessage(final APIChangeHostStateMsg msg, final NoErrorCompletion completion) { thdf.chainSubmit(new ChainTask(msg, completion) { @Override public String getSyncSignature() { return String.format("change-host-state-%s", self.getUuid()); } private void done(SyncTaskChain chain) { completion.done(); chain.next(); } @Override public void run(final SyncTaskChain chain) { final APIChangeHostStateEvent evt = new APIChangeHostStateEvent(msg.getId()); HostStateEvent stateEvent = HostStateEvent.valueOf(msg.getStateEvent()); if (self.getStatus() == HostStatus.Disconnected && stateEvent == HostStateEvent.maintain) { throw new ApiMessageInterceptionException(operr("cannot change the state of Disconnected host into Maintenance ")); } stateEvent = stateEvent == HostStateEvent.maintain ? HostStateEvent.preMaintain : stateEvent; try { extpEmitter.preChange(self, stateEvent); } catch (HostException he) { evt.setError(errf.instantiateErrorCode(SysErrors.CHANGE_RESOURCE_STATE_ERROR, he.getMessage())); bus.publish(evt); done(chain); return; } if (HostStateEvent.preMaintain == stateEvent) { changeState(HostStateEvent.preMaintain); maintenanceHook(new Completion(msg, chain) { @Override public void success() { changeState(HostStateEvent.maintain); evt.setInventory(HostInventory.valueOf(self)); bus.publish(evt); done(chain); } @Override public void fail(ErrorCode errorCode) { evt.setError(errf.instantiateErrorCode(HostErrors.UNABLE_TO_ENTER_MAINTENANCE_MODE, errorCode.getDetails(), errorCode)); changeState(HostStateEvent.enable); bus.publish(evt); done(chain); } }); } else { HostState origState = self.getState(); HostState state = changeState(stateEvent); if (origState == HostState.Maintenance && state != HostState.Maintenance) { // host is out of maintenance mode, track and reconnect it. tracker.trackHost(self.getUuid()); ReconnectHostMsg rmsg = new ReconnectHostMsg(); rmsg.setHostUuid(self.getUuid()); bus.makeTargetServiceIdByResourceUuid(rmsg, HostConstant.SERVICE_ID, self.getUuid()); bus.send(rmsg); } HostInventory inv = HostInventory.valueOf(self); evt.setInventory(inv); bus.publish(evt); done(chain); } } @Override public String getName() { return String.format("change-host-state-%s", self.getUuid()); } }); } protected void handle(final APIChangeHostStateMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getName() { return "change-host-state-" + self.getUuid(); } @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { changeStateByApiMessage(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public int getSyncLevel() { return getHostSyncLevel(); } }); } protected void handleLocalMessage(Message msg) { if (msg instanceof ChangeHostStateMsg) { handle((ChangeHostStateMsg) msg); } else if (msg instanceof HostDeletionMsg) { handle((HostDeletionMsg) msg); } else if (msg instanceof ConnectHostMsg) { handle((ConnectHostMsg) msg); } else if (msg instanceof ReconnectHostMsg) { handle((ReconnectHostMsg) msg); } else if (msg instanceof ChangeHostConnectionStateMsg) { handle((ChangeHostConnectionStateMsg) msg); } else if (msg instanceof PingHostMsg) { handle((PingHostMsg) msg); } else { HostBaseExtensionFactory ext = hostMgr.getHostBaseExtensionFactory(msg); if (ext != null) { Host h = ext.getHost(self); h.handleMessage(msg); } else { bus.dealWithUnknownMessage(msg); } } } private void handle(final PingHostMsg msg) { final PingHostReply reply = new PingHostReply(); if (self.getStatus() == HostStatus.Connecting) { reply.setError(operr("host is connecting")); bus.reply(msg, reply); return; } pingHook(new Completion(msg) { @Override public void success() { reply.setConnected(true); reply.setCurrentHostStatus(self.getStatus().toString()); bus.reply(msg, reply); extpEmitter.hostPingTask(HypervisorType.valueOf(self.getHypervisorType()), getSelfInventory()); } @Override public void fail(ErrorCode errorCode) { reply.setConnected(false); reply.setCurrentHostStatus(self.getStatus().toString()); reply.setError(errorCode); reply.setSuccess(true); Boolean noReconnect = (Boolean) errorCode.getFromOpaque(Opaque.NO_RECONNECT_AFTER_PING_FAILURE.toString()); reply.setNoReconnect(noReconnect != null && noReconnect); if(!Q.New(HostVO.class).eq(HostVO_.uuid, msg.getHostUuid()).isExists()){ reply.setNoReconnect(true); } changeConnectionState(HostStatusEvent.disconnected); bus.reply(msg, reply); } }); } private void handle(final HostDeletionMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(SyncTaskChain chain) { HostInventory hinv = HostInventory.valueOf(self); extpEmitter.beforeDelete(hinv); deleteHook(); extpEmitter.afterDelete(hinv); bus.reply(msg, new HostDeletionReply()); tracker.untrackHost(self.getUuid()); chain.next(); } @Override public int getSyncLevel() { return getHostSyncLevel(); } @Override public String getName() { return String.format("host-deletion-%s", self.getUuid()); } }); } private void reconnectHost(final ReconnectHostMsg msg, final NoErrorCompletion completion) { thdf.chainSubmit(new ChainTask(msg, completion) { @Override public String getSyncSignature() { return String.format("reconnect-host-%s", self.getUuid()); } private void finish(SyncTaskChain chain) { chain.next(); completion.done(); } @Override public void run(final SyncTaskChain chain) { checkState(); if (msg.isSkipIfHostConnected() && HostStatus.Connected == self.getStatus()) { finish(chain); return; } changeConnectionState(HostStatusEvent.disconnected); ConnectHostMsg connectMsg = new ConnectHostMsg(self.getUuid()); connectMsg.setNewAdd(false); bus.makeTargetServiceIdByResourceUuid(connectMsg, HostConstant.SERVICE_ID, self.getUuid()); bus.send(connectMsg, new CloudBusCallBack(msg, chain, completion) { @Override public void run(MessageReply reply) { ReconnectHostReply r = new ReconnectHostReply(); if (reply.isSuccess()) { logger.debug(String.format("Successfully reconnect host[uuid:%s]", self.getUuid())); } else { r.setError(errf.instantiateErrorCode(HostErrors.UNABLE_TO_RECONNECT_HOST, reply.getError())); logger.debug(String.format("Failed to reconnect host[uuid:%s] because %s", self.getUuid(), reply.getError())); } bus.reply(msg, r); } }); // no need to block the queue, because the ConnectHostMsg will be queued as well finish(chain); } @Override public String getName() { return getSyncSignature(); } }); } private void handle(final ReconnectHostMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getName() { return "reconnect-host-" + self.getUuid(); } @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { reconnectHost(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public int getSyncLevel() { return getHostSyncLevel(); } }); } private void handle(final ChangeHostConnectionStateMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getName() { return "change-host-connection-state-" + self.getUuid(); } private boolean reestablishConnection() { try { extpEmitter.connectionReestablished(HypervisorType.valueOf(self.getHypervisorType()), getSelfInventory()); } catch (HostException e) { logger.warn(String.format("unable to reestablish connection to kvm host[uuid:%s, ip:%s], %s", self.getUuid(), self.getManagementIp(), e.getMessage()), e); return false; } return true; } @Override public String getSyncSignature() { return id; } @Override public void run(SyncTaskChain chain) { HostStatusEvent cevt = HostStatusEvent.valueOf(msg.getConnectionStateEvent()); HostStatus next = self.getStatus().nextStatus(cevt); if (self.getStatus() == HostStatus.Disconnected && next == HostStatus.Connected) { if (!reestablishConnection()) { cevt = HostStatusEvent.disconnected; } } changeConnectionState(cevt); bus.reply(msg, new ChangeHostConnectionStateReply()); chain.next(); } @Override public int getSyncLevel() { return getHostSyncLevel(); } }); } protected HostInventory getSelfInventory() { return HostInventory.valueOf(self); } protected boolean changeConnectionState(final HostStatusEvent event) { HostStatus before = self.getStatus(); HostStatus next = before.nextStatus(event); if (before == next) { return false; } self.setStatus(next); self = dbf.updateAndRefresh(self); logger.debug(String.format("Host %s [uuid:%s] changed connection state from %s to %s", self.getName(), self.getUuid(), before, next)); HostStatusChangedData data = new HostStatusChangedData(); data.setHostUuid(self.getUuid()); data.setNewStatus(next.toString()); data.setOldStatus(before.toString()); data.setInventory(HostInventory.valueOf(self)); evtf.fire(HostCanonicalEvents.HOST_STATUS_CHANGED_PATH, data); CollectionUtils.safeForEach(pluginRgty.getExtensionList(AfterChangeHostStatusExtensionPoint.class), new ForEachFunction<AfterChangeHostStatusExtensionPoint>() { @Override public void run(AfterChangeHostStatusExtensionPoint arg) { arg.afterChangeHostStatus(self.getUuid(), before, next); } }); return true; } private void handle(final ConnectHostMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return String.format("connect-host-%s", self.getUuid()); } @Override public void run(SyncTaskChain chain) { checkState(); final ConnectHostReply reply = new ConnectHostReply(); final FlowChain flowChain = FlowChainBuilder.newShareFlowChain(); flowChain.setName(String.format("connect-host-%s", self.getUuid())); flowChain.then(new ShareFlow() { @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "connect-host"; @Override public void run(final FlowTrigger trigger, Map data) { changeConnectionState(HostStatusEvent.connecting); connectHook(ConnectHostInfo.fromConnectHostMsg(msg), new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flow(new NoRollbackFlow() { String __name__ = "check-license"; @Override public void run(FlowTrigger trigger, Map data) { for (PostHostConnectExtensionPoint p : pluginRgty.getExtensionList(PostHostConnectExtensionPoint.class)) { p.postHostConnect(getSelfInventory()); } trigger.next(); } }); flow(new NoRollbackFlow() { String __name__ = "recalculate-host-capacity"; @Override public void run(FlowTrigger trigger, Map data) { RecalculateHostCapacityMsg msg = new RecalculateHostCapacityMsg(); msg.setHostUuid(self.getUuid()); bus.makeLocalServiceId(msg, HostAllocatorConstant.SERVICE_ID); bus.send(msg); trigger.next(); } }); done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { changeConnectionState(HostStatusEvent.connected); tracker.trackHost(self.getUuid()); CollectionUtils.safeForEach(pluginRgty.getExtensionList(HostAfterConnectedExtensionPoint.class), new ForEachFunction<HostAfterConnectedExtensionPoint>() { @Override public void run(HostAfterConnectedExtensionPoint ext) { ext.afterHostConnected(getSelfInventory()); } }); bus.reply(msg, reply); } }); error(new FlowErrorHandler(msg) { @Override public void handle(ErrorCode errCode, Map data) { changeConnectionState(HostStatusEvent.disconnected); reply.setError(errCode); bus.reply(msg, reply); } }); Finally(new FlowFinallyHandler(msg) { @Override public void Finally() { chain.next(); } }); } }).start(); } @Override public String getName() { return "connect-host"; } }); } private void changeStateByLocalMessage(final ChangeHostStateMsg msg, final NoErrorCompletion completion) { thdf.chainSubmit(new ChainTask(msg, completion) { @Override public String getSyncSignature() { return String.format("change-host-state-%s", self.getUuid()); } private void done(SyncTaskChain chain) { completion.done(); chain.next(); } @Override public void run(SyncTaskChain chain) { ChangeHostStateReply reply = new ChangeHostStateReply(); if (self.getState() != HostState.Enabled && self.getState() != HostState.Disabled) { done(chain); return; } HostStateEvent stateEvent = HostStateEvent.valueOf(msg.getStateEvent()); changeState(stateEvent); HostInventory inv = HostInventory.valueOf(self); reply.setInventory(inv); bus.reply(msg, reply); done(chain); } @Override public String getName() { return String.format("change-host-state-%s", self.getUuid()); } }); } private void handle(final ChangeHostStateMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getName() { return "change-host-state-" + self.getUuid(); } @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { changeStateByLocalMessage(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public int getSyncLevel() { return getHostSyncLevel(); } }); } @Override @MessageSafe public void handleMessage(Message msg) { if (msg instanceof APIMessage) { handleApiMessage((APIMessage) msg); } else { handleLocalMessage(msg); } } }