package org.zstack.kvm; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.RestClientException; import org.springframework.web.util.UriComponentsBuilder; import org.zstack.compute.host.HostBase; import org.zstack.compute.host.HostSystemTags; import org.zstack.compute.vm.VmGlobalConfig; import org.zstack.compute.vm.VmSystemTags; import org.zstack.core.CoreGlobalProperty; import org.zstack.core.MessageCommandRecorder; import org.zstack.core.Platform; import org.zstack.core.ansible.AnsibleConstant; import org.zstack.core.ansible.AnsibleGlobalProperty; import org.zstack.core.ansible.AnsibleRunner; import org.zstack.core.ansible.SshFileMd5Checker; 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.timeout.ApiTimeoutManager; import org.zstack.core.workflow.FlowChainBuilder; import org.zstack.core.workflow.ShareFlow; import org.zstack.header.Constants; import org.zstack.header.core.AsyncLatch; import org.zstack.header.core.Completion; import org.zstack.header.core.NoErrorCompletion; import org.zstack.header.core.ReturnValueCompletion; 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.exception.CloudRuntimeException; import org.zstack.header.host.*; import org.zstack.header.host.MigrateVmOnHypervisorMsg.StorageMigrationPolicy; import org.zstack.header.image.ImagePlatform; import org.zstack.header.message.APIMessage; import org.zstack.header.message.Message; import org.zstack.header.message.NeedReplyMessage; import org.zstack.header.network.l2.*; import org.zstack.header.rest.JsonAsyncRESTCallback; import org.zstack.header.rest.RESTFacade; import org.zstack.header.storage.snapshot.VolumeSnapshotInventory; import org.zstack.header.tag.SystemTagInventory; import org.zstack.header.vm.*; import org.zstack.header.volume.VolumeInventory; import org.zstack.header.volume.VolumeType; import org.zstack.header.volume.VolumeVO; import org.zstack.kvm.KVMAgentCommands.*; import org.zstack.kvm.KVMConstant.KvmVmState; import org.zstack.tag.SystemTagCreator; import org.zstack.tag.TagManager; import org.zstack.utils.*; import org.zstack.utils.gson.JSONObjectUtil; import org.zstack.utils.logging.CLogger; import org.zstack.utils.path.PathUtil; import org.zstack.utils.ssh.Ssh; import org.zstack.utils.ssh.SshResult; import org.zstack.utils.ssh.SshShell; import javax.persistence.TypedQuery; import java.util.*; import java.util.concurrent.TimeUnit; import static org.zstack.core.Platform.operr; import static org.zstack.utils.CollectionDSL.e; import static org.zstack.utils.CollectionDSL.map; public class KVMHost extends HostBase implements Host { private static final CLogger logger = Utils.getLogger(KVMHost.class); @Autowired @Qualifier("KVMHostFactory") private KVMHostFactory factory; @Autowired private RESTFacade restf; @Autowired private KVMExtensionEmitter extEmitter; @Autowired private ErrorFacade errf; @Autowired private TagManager tagmgr; @Autowired private ApiTimeoutManager timeoutManager; private KVMHostContext context; // ///////////////////// REST URL ////////////////////////// private String baseUrl; private String connectPath; private String pingPath; private String checkPhysicalNetworkInterfacePath; private String startVmPath; private String stopVmPath; private String pauseVmPath; private String resumeVmPath; private String rebootVmPath; private String destroyVmPath; private String attachDataVolumePath; private String detachDataVolumePath; private String echoPath; private String attachNicPath; private String detachNicPath; private String migrateVmPath; private String snapshotPath; private String mergeSnapshotPath; private String hostFactPath; private String attachIsoPath; private String detachIsoPath; private String checkVmStatePath; private String getConsolePortPath; private String onlineIncreaseCpuPath; private String onlineIncreaseMemPath; private String deleteConsoleFirewall; private String agentPackageName = KVMGlobalProperty.AGENT_PACKAGE_NAME; KVMHost(KVMHostVO self, KVMHostContext context) { super(self); this.context = context; baseUrl = context.getBaseUrl(); UriComponentsBuilder ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_CONNECT_PATH); connectPath = ub.build().toUriString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_PING_PATH); pingPath = ub.build().toUriString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_CHECK_PHYSICAL_NETWORK_INTERFACE_PATH); checkPhysicalNetworkInterfacePath = ub.build().toUriString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_START_VM_PATH); startVmPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_STOP_VM_PATH); stopVmPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_PAUSE_VM_PATH); pauseVmPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_RESUME_VM_PATH); resumeVmPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_REBOOT_VM_PATH); rebootVmPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_DESTROY_VM_PATH); destroyVmPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_ATTACH_VOLUME); attachDataVolumePath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_DETACH_VOLUME); detachDataVolumePath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_ECHO_PATH); echoPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_ATTACH_NIC_PATH); attachNicPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_DETACH_NIC_PATH); detachNicPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_MIGRATE_VM_PATH); migrateVmPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_TAKE_VOLUME_SNAPSHOT_PATH); snapshotPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_MERGE_SNAPSHOT_PATH); mergeSnapshotPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_HOST_FACT_PATH); hostFactPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_ATTACH_ISO_PATH); attachIsoPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_DETACH_ISO_PATH); detachIsoPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_VM_CHECK_STATE); checkVmStatePath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_GET_VNC_PORT_PATH); getConsolePortPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_VM_ONLINE_INCREASE_CPU); onlineIncreaseCpuPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_VM_ONLINE_INCREASE_MEMORY); onlineIncreaseMemPath = ub.build().toString(); ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_DELETE_CONSOLE_FIREWALL_PATH); deleteConsoleFirewall = ub.build().toString(); } class Http<T> { String path; AgentCommand cmd; Class<T> responseClass; String commandStr; TimeUnit unit; Long timeout; public Http(String path, String cmd, Class<T> rspClz, TimeUnit unit, Long timeout) { this.path = path; this.commandStr = cmd; this.responseClass = rspClz; this.unit = unit; this.timeout = timeout; } public Http(String path, AgentCommand cmd, Class<T> rspClz) { this.path = path; this.cmd = cmd; this.responseClass = rspClz; } void call(ReturnValueCompletion<T> completion) { Map<String, String> header = new HashMap<>(); header.put(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID, self.getUuid()); if (commandStr != null) { restf.asyncJsonPost(path, commandStr, header, new JsonAsyncRESTCallback<T>(completion) { @Override public void fail(ErrorCode err) { completion.fail(err); } @Override public void success(T ret) { completion.success(ret); } @Override public Class<T> getReturnClass() { return responseClass; } }, unit, timeout); } else { restf.asyncJsonPost(path, cmd, header, new JsonAsyncRESTCallback<T>(completion) { @Override public void fail(ErrorCode err) { completion.fail(err); } @Override public void success(T ret) { completion.success(ret); } @Override public Class<T> getReturnClass() { return responseClass; } }); // DO NOT pass unit, timeout here, they are null } } } @Override protected void handleApiMessage(APIMessage msg) { super.handleApiMessage(msg); } @Override protected void handleLocalMessage(Message msg) { if (msg instanceof CheckNetworkPhysicalInterfaceMsg) { handle((CheckNetworkPhysicalInterfaceMsg) msg); } else if (msg instanceof StartVmOnHypervisorMsg) { handle((StartVmOnHypervisorMsg) msg); } else if (msg instanceof CreateVmOnHypervisorMsg) { handle((CreateVmOnHypervisorMsg) msg); } else if (msg instanceof StopVmOnHypervisorMsg) { handle((StopVmOnHypervisorMsg) msg); } else if (msg instanceof RebootVmOnHypervisorMsg) { handle((RebootVmOnHypervisorMsg) msg); } else if (msg instanceof DestroyVmOnHypervisorMsg) { handle((DestroyVmOnHypervisorMsg) msg); } else if (msg instanceof AttachVolumeToVmOnHypervisorMsg) { handle((AttachVolumeToVmOnHypervisorMsg) msg); } else if (msg instanceof DetachVolumeFromVmOnHypervisorMsg) { handle((DetachVolumeFromVmOnHypervisorMsg) msg); } else if (msg instanceof VmAttachNicOnHypervisorMsg) { handle((VmAttachNicOnHypervisorMsg) msg); } else if (msg instanceof MigrateVmOnHypervisorMsg) { handle((MigrateVmOnHypervisorMsg) msg); } else if (msg instanceof TakeSnapshotOnHypervisorMsg) { handle((TakeSnapshotOnHypervisorMsg) msg); } else if (msg instanceof MergeVolumeSnapshotOnKvmMsg) { handle((MergeVolumeSnapshotOnKvmMsg) msg); } else if (msg instanceof KVMHostAsyncHttpCallMsg) { handle((KVMHostAsyncHttpCallMsg) msg); } else if (msg instanceof KVMHostSyncHttpCallMsg) { handle((KVMHostSyncHttpCallMsg) msg); } else if (msg instanceof DetachNicFromVmOnHypervisorMsg) { handle((DetachNicFromVmOnHypervisorMsg) msg); } else if (msg instanceof AttachIsoOnHypervisorMsg) { handle((AttachIsoOnHypervisorMsg) msg); } else if (msg instanceof DetachIsoOnHypervisorMsg) { handle((DetachIsoOnHypervisorMsg) msg); } else if (msg instanceof CheckVmStateOnHypervisorMsg) { handle((CheckVmStateOnHypervisorMsg) msg); } else if (msg instanceof GetVmConsoleAddressFromHostMsg) { handle((GetVmConsoleAddressFromHostMsg) msg); } else if (msg instanceof KvmRunShellMsg) { handle((KvmRunShellMsg) msg); } else if (msg instanceof VmDirectlyDestroyOnHypervisorMsg) { handle((VmDirectlyDestroyOnHypervisorMsg) msg); } else if (msg instanceof OnlineChangeVmCpuMemoryMsg) { handle((OnlineChangeVmCpuMemoryMsg) msg); } else if (msg instanceof IncreaseVmCpuMsg) { handle((IncreaseVmCpuMsg) msg); } else if (msg instanceof IncreaseVmMemoryMsg) { handle((IncreaseVmMemoryMsg) msg); } else if (msg instanceof PauseVmOnHypervisorMsg) { handle((PauseVmOnHypervisorMsg) msg); } else if (msg instanceof ResumeVmOnHypervisorMsg) { handle((ResumeVmOnHypervisorMsg) msg); } else { super.handleLocalMessage(msg); } } private void handle(final IncreaseVmCpuMsg msg) { IncreaseVmCpuReply reply = new IncreaseVmCpuReply(); IncreaseCpuCmd cmd = new IncreaseCpuCmd(); cmd.setVmUuid(msg.getVmInstanceUuid()); cmd.setCpuNum(msg.getCpuNum()); new Http<>(onlineIncreaseCpuPath, cmd, IncreaseCpuResponse.class).call(new ReturnValueCompletion<IncreaseCpuResponse>(msg) { @Override public void success(IncreaseCpuResponse ret) { if (!ret.isSuccess()) { reply.setError(operr(ret.getError())); } else { reply.setCpuNum(ret.getCpuNum()); } bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } private void handle(final IncreaseVmMemoryMsg msg) { IncreaseVmMemoryReply reply = new IncreaseVmMemoryReply(); IncreaseMemoryCmd cmd = new IncreaseMemoryCmd(); cmd.setVmUuid(msg.getVmInstanceUuid()); cmd.setMemorySize(msg.getMemorySize()); new Http<>(onlineIncreaseMemPath, cmd, IncreaseMemoryResponse.class).call(new ReturnValueCompletion<IncreaseMemoryResponse>(msg) { @Override public void success(IncreaseMemoryResponse ret) { if (!ret.isSuccess()) { reply.setError(operr(ret.getError())); } else { reply.setMemorySize(ret.getMemorySize()); } bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } private void directlyDestroy(final VmDirectlyDestroyOnHypervisorMsg msg, final NoErrorCompletion completion) { checkStatus(); final VmDirectlyDestroyOnHypervisorReply reply = new VmDirectlyDestroyOnHypervisorReply(); DestroyVmCmd cmd = new DestroyVmCmd(); cmd.setUuid(msg.getVmUuid()); new Http<>(destroyVmPath, cmd, DestroyVmResponse.class).call(new ReturnValueCompletion<DestroyVmResponse>(completion) { @Override public void success(DestroyVmResponse ret) { if (!ret.isSuccess()) { reply.setError(errf.instantiateErrorCode(HostErrors.FAILED_TO_DESTROY_VM_ON_HYPERVISOR, ret.getError())); } bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); completion.done(); } }); } private void handle(final VmDirectlyDestroyOnHypervisorMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { directlyDestroy(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("directly-delete-vm-%s-msg-on-kvm-%s", msg.getVmUuid(), self.getUuid()); } }); } private SshResult runShell(String script) { Ssh ssh = new Ssh(); ssh.setHostname(self.getManagementIp()); ssh.setPort(getSelf().getPort()); ssh.setUsername(getSelf().getUsername()); ssh.setPassword(getSelf().getPassword()); ssh.shell(script); return ssh.runAndClose(); } private void handle(KvmRunShellMsg msg) { SshResult result = runShell(msg.getScript()); KvmRunShellReply reply = new KvmRunShellReply(); if (result.isSshFailure()) { reply.setError(operr("unable to connect to KVM[ip:%s, username:%s, sshPort:%d ] to do DNS check," + " please check if username/password is wrong; %s", self.getManagementIp(), getSelf().getUsername(), getSelf().getPort(), result.getExitErrorMessage())); } else { reply.setStdout(result.getStdout()); reply.setStderr(result.getStderr()); reply.setReturnCode(result.getReturnCode()); } bus.reply(msg, reply); } private void handle(final OnlineChangeVmCpuMemoryMsg msg) { // final OnlineChangeVmCpuMemoryReply reply = new OnlineChangeVmCpuMemoryReply(); // // ChangeCpuMemoryCmd cmd = new ChangeCpuMemoryCmd(); // cmd.setVmUuid(msg.getVmInstanceUuid()); // cmd.setCpuNum(msg.getCpuNum()); // cmd.setMemorySize(msg.getMemorySize()); // new Http<>(onlineChangeCpuMemoryPath, cmd, ChangeCpuMemoryResponse.class).call(new ReturnValueCompletion<ChangeCpuMemoryResponse>(msg) { // @Override // public void success(ChangeCpuMemoryResponse ret) { // if (!ret.isSuccess()) { // reply.setError(operr(ret.getError())); // } else { // reply.setCpuNum(ret.getCpuNum()); // reply.setMemorySize(ret.getMemorySize()); // } // bus.reply(msg, reply); // } // // @Override // public void fail(ErrorCode errorCode) { // reply.setError(errorCode); // bus.reply(msg, reply); // } // }); } private void handle(final GetVmConsoleAddressFromHostMsg msg) { final GetVmConsoleAddressFromHostReply reply = new GetVmConsoleAddressFromHostReply(); GetVncPortCmd cmd = new GetVncPortCmd(); cmd.setVmUuid(msg.getVmInstanceUuid()); new Http<>(getConsolePortPath, cmd, GetVncPortResponse.class).call(new ReturnValueCompletion<GetVncPortResponse>(msg) { @Override public void success(GetVncPortResponse ret) { if (!ret.isSuccess()) { reply.setError(operr(ret.getError())); } else { reply.setHostIp(self.getManagementIp()); reply.setProtocol(ret.getProtocol()); reply.setPort(ret.getPort()); } bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } private void handle(final CheckVmStateOnHypervisorMsg msg) { final CheckVmStateOnHypervisorReply reply = new CheckVmStateOnHypervisorReply(); if (self.getStatus() != HostStatus.Connected) { reply.setError(operr("the host[uuid:%s, status:%s] is not Connected", self.getUuid(), self.getStatus())); bus.reply(msg, reply); return; } // NOTE: don't run this message in the sync task // there can be many such kind of messages // running in the sync task may cause other tasks starved CheckVmStateCmd cmd = new CheckVmStateCmd(); cmd.vmUuids = msg.getVmInstanceUuids(); cmd.hostUuid = self.getUuid(); new Http<>(checkVmStatePath, cmd, CheckVmStateRsp.class).call(new ReturnValueCompletion<CheckVmStateRsp>(msg) { @Override public void success(CheckVmStateRsp ret) { if (!ret.isSuccess()) { reply.setError(operr(ret.getError())); } else { Map<String, String> m = new HashMap<>(); for (Map.Entry<String, String> e : ret.states.entrySet()) { m.put(e.getKey(), KvmVmState.valueOf(e.getValue()).toVmInstanceState().toString()); } reply.setStates(m); } bus.reply(msg, reply); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); } }); } private void handle(final DetachIsoOnHypervisorMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { detachIso(msg, new NoErrorCompletion(msg, chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("detach-iso-%s-on-host-%s", msg.getIsoUuid(), self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private void detachIso(final DetachIsoOnHypervisorMsg msg, final NoErrorCompletion completion) { final DetachIsoOnHypervisorReply reply = new DetachIsoOnHypervisorReply(); DetachIsoCmd cmd = new DetachIsoCmd(); cmd.isoUuid = msg.getIsoUuid(); cmd.vmUuid = msg.getVmInstanceUuid(); KVMHostInventory inv = (KVMHostInventory) getSelfInventory(); for (KVMPreDetachIsoExtensionPoint ext : pluginRgty.getExtensionList(KVMPreDetachIsoExtensionPoint.class)) { ext.preDetachIsoExtensionPoint(inv, cmd); } new Http<>(detachIsoPath, cmd, DetachIsoRsp.class).call(new ReturnValueCompletion<DetachIsoRsp>(msg, completion) { @Override public void success(DetachIsoRsp ret) { if (!ret.isSuccess()) { reply.setError(operr(ret.getError())); } bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); completion.done(); } }); } private void handle(final AttachIsoOnHypervisorMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { attachIso(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("attach-iso-%s-on-host-%s", msg.getIsoSpec().getImageUuid(), self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private void attachIso(final AttachIsoOnHypervisorMsg msg, final NoErrorCompletion completion) { final AttachIsoOnHypervisorReply reply = new AttachIsoOnHypervisorReply(); IsoTO iso = new IsoTO(); iso.setImageUuid(msg.getIsoSpec().getImageUuid()); iso.setPath(msg.getIsoSpec().getInstallPath()); AttachIsoCmd cmd = new AttachIsoCmd(); cmd.vmUuid = msg.getVmInstanceUuid(); cmd.iso = iso; KVMHostInventory inv = (KVMHostInventory) getSelfInventory(); for (KVMPreAttachIsoExtensionPoint ext : pluginRgty.getExtensionList(KVMPreAttachIsoExtensionPoint.class)) { ext.preAttachIsoExtensionPoint(inv, cmd); } new Http<>(attachIsoPath, cmd, AttachIsoRsp.class).call(new ReturnValueCompletion<AttachIsoRsp>(msg, completion) { @Override public void success(AttachIsoRsp ret) { if (!ret.isSuccess()) { reply.setError(operr(ret.getError())); } bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode err) { reply.setError(err); bus.reply(msg, reply); completion.done(); } }); } private void handle(final DetachNicFromVmOnHypervisorMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { detachNic(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return "detach-nic-on-kvm-host-" + self.getUuid(); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private void detachNic(final DetachNicFromVmOnHypervisorMsg msg, final NoErrorCompletion completion) { final DetachNicFromVmOnHypervisorReply reply = new DetachNicFromVmOnHypervisorReply(); NicTO to = completeNicInfo(msg.getNic()); DetachNicCommand cmd = new DetachNicCommand(); cmd.setVmUuid(msg.getVmInstanceUuid()); cmd.setNic(to); new Http<>(detachNicPath, cmd, DetachNicRsp.class).call(new ReturnValueCompletion<DetachNicRsp>(msg, completion) { @Override public void success(DetachNicRsp ret) { if (!ret.isSuccess()) { reply.setError(operr(ret.getError())); } bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); completion.done(); } }); } private void handle(final KVMHostSyncHttpCallMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { executeSyncHttpCall(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("execute-sync-http-call-on-kvm-host-%s", self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private void executeSyncHttpCall(KVMHostSyncHttpCallMsg msg, NoErrorCompletion completion) { if (!msg.isNoStatusCheck()) { checkStatus(); } String url = buildUrl(msg.getPath()); MessageCommandRecorder.record(msg.getCommandClassName()); Map<String, String> headers = new HashMap<>(); headers.put(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID, self.getUuid()); LinkedHashMap rsp = restf.syncJsonPost(url, msg.getCommand(), headers, LinkedHashMap.class); KVMHostSyncHttpCallReply reply = new KVMHostSyncHttpCallReply(); reply.setResponse(rsp); bus.reply(msg, reply); completion.done(); } private void handle(final KVMHostAsyncHttpCallMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { executeAsyncHttpCall(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("execute-async-http-call-on-kvm-host-%s", self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private String buildUrl(String path) { UriComponentsBuilder ub = UriComponentsBuilder.newInstance(); ub.scheme(KVMGlobalProperty.AGENT_URL_SCHEME); ub.host(self.getManagementIp()); ub.port(KVMGlobalProperty.AGENT_PORT); if (!"".equals(KVMGlobalProperty.AGENT_URL_ROOT_PATH)) { ub.path(KVMGlobalProperty.AGENT_URL_ROOT_PATH); } ub.path(path); return ub.build().toUriString(); } private void executeAsyncHttpCall(final KVMHostAsyncHttpCallMsg msg, final NoErrorCompletion completion) { if (!msg.isNoStatusCheck()) { checkStatus(); } String url = buildUrl(msg.getPath()); MessageCommandRecorder.record(msg.getCommandClassName()); new Http<>(url, msg.getCommand(), LinkedHashMap.class, TimeUnit.SECONDS, msg.getCommandTimeout()) .call(new ReturnValueCompletion<LinkedHashMap>(msg, completion) { @Override public void success(LinkedHashMap ret) { KVMHostAsyncHttpCallReply reply = new KVMHostAsyncHttpCallReply(); reply.setResponse(ret); bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode err) { KVMHostAsyncHttpCallReply reply = new KVMHostAsyncHttpCallReply(); if (err.isError(SysErrors.HTTP_ERROR, SysErrors.IO_ERROR)) { reply.setError(errf.instantiateErrorCode(HostErrors.OPERATION_FAILURE_GC_ELIGIBLE, "cannot do the operation on the KVM host", err)); } else { reply.setError(err); } bus.reply(msg, reply); completion.done(); } }); } private void handle(final MergeVolumeSnapshotOnKvmMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { mergeVolumeSnapshot(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("merge-volume-snapshot-on-kvm-%s", self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private void mergeVolumeSnapshot(final MergeVolumeSnapshotOnKvmMsg msg, final NoErrorCompletion completion) { checkStateAndStatus(); final MergeVolumeSnapshotOnKvmReply reply = new MergeVolumeSnapshotOnKvmReply(); VolumeInventory volume = msg.getTo(); if (volume.getVmInstanceUuid() != null) { SimpleQuery<VmInstanceVO> q = dbf.createQuery(VmInstanceVO.class); q.select(VmInstanceVO_.state); q.add(VmInstanceVO_.uuid, Op.EQ, volume.getVmInstanceUuid()); VmInstanceState state = q.findValue(); if (state != VmInstanceState.Stopped && state != VmInstanceState.Running) { throw new OperationFailureException(operr("cannot do volume snapshot merge when vm[uuid:%s] is in state of %s." + " The operation is only allowed when vm is Running or Stopped", volume.getUuid(), state)); } if (state == VmInstanceState.Running) { String libvirtVersion = KVMSystemTags.LIBVIRT_VERSION.getTokenByResourceUuid(self.getUuid(), KVMSystemTags.LIBVIRT_VERSION_TOKEN); if (new VersionComparator(KVMConstant.MIN_LIBVIRT_LIVE_BLOCK_COMMIT_VERSION).compare(libvirtVersion) > 0) { throw new OperationFailureException(operr("live volume snapshot merge needs libvirt version greater than %s," + " current libvirt version is %s. Please stop vm and redo the operation or detach the volume if it's data volume", KVMConstant.MIN_LIBVIRT_LIVE_BLOCK_COMMIT_VERSION, libvirtVersion)); } } } VolumeSnapshotInventory snapshot = msg.getFrom(); MergeSnapshotCmd cmd = new MergeSnapshotCmd(); cmd.setFullRebase(msg.isFullRebase()); cmd.setDestPath(volume.getInstallPath()); cmd.setSrcPath(snapshot.getPrimaryStorageInstallPath()); cmd.setVmUuid(volume.getVmInstanceUuid()); cmd.setDeviceId(volume.getDeviceId()); new Http<>(mergeSnapshotPath, cmd, MergeSnapshotRsp.class) .call(new ReturnValueCompletion<MergeSnapshotRsp>(msg, completion) { @Override public void success(MergeSnapshotRsp ret) { if (!ret.isSuccess()) { reply.setError(operr(ret.getError())); } bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); completion.done(); } }); } private void handle(final TakeSnapshotOnHypervisorMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { takeSnapshot(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("take-snapshot-on-kvm-%s", self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private void takeSnapshot(final TakeSnapshotOnHypervisorMsg msg, final NoErrorCompletion completion) { checkStateAndStatus(); final TakeSnapshotOnHypervisorReply reply = new TakeSnapshotOnHypervisorReply(); TakeSnapshotCmd cmd = new TakeSnapshotCmd(); if (msg.getVmUuid() != null) { SimpleQuery<VmInstanceVO> q = dbf.createQuery(VmInstanceVO.class); q.select(VmInstanceVO_.state); q.add(VmInstanceVO_.uuid, SimpleQuery.Op.EQ, msg.getVmUuid()); VmInstanceState vmState = q.findValue(); if (vmState != VmInstanceState.Running && vmState != VmInstanceState.Stopped && vmState != VmInstanceState.Paused) { throw new OperationFailureException(operr("vm[uuid:%s] is not Running or Stopped, current state[%s]", msg.getVmUuid(), vmState)); } if (!HostSystemTags.LIVE_SNAPSHOT.hasTag(self.getUuid())) { if (vmState != VmInstanceState.Stopped) { throw new OperationFailureException(errf.instantiateErrorCode(SysErrors.NO_CAPABILITY_ERROR, String.format("kvm host[uuid:%s, name:%s, ip:%s] doesn't not support live snapshot. please stop vm[uuid:%s] and try again", self.getUuid(), self.getName(), self.getManagementIp(), msg.getVmUuid()) )); } } cmd.setVolumeUuid(msg.getVolume().getUuid()); cmd.setVmUuid(msg.getVmUuid()); cmd.setDeviceId(msg.getVolume().getDeviceId()); } cmd.setVolumeInstallPath(msg.getVolume().getInstallPath()); cmd.setInstallPath(msg.getInstallPath()); cmd.setFullSnapshot(msg.isFullSnapshot()); new Http<>(snapshotPath, cmd, TakeSnapshotResponse.class).call(new ReturnValueCompletion<TakeSnapshotResponse>(msg, completion) { @Override public void success(TakeSnapshotResponse ret) { if (ret.isSuccess()) { reply.setNewVolumeInstallPath(ret.getNewVolumeInstallPath()); reply.setSnapshotInstallPath(ret.getSnapshotInstallPath()); reply.setSize(ret.getSize()); } else { reply.setError(operr(ret.getError())); } bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); completion.done(); } }); } private void migrateVm(final Iterator<MigrateStruct> it, final Completion completion) { final String hostIp; final String vmUuid; final StorageMigrationPolicy storageMigrationPolicy; synchronized (it) { if (!it.hasNext()) { completion.success(); return; } MigrateStruct s = it.next(); vmUuid = s.vmUuid; hostIp = s.dstHostIp; storageMigrationPolicy = s.storageMigrationPolicy; } SimpleQuery<VmInstanceVO> q = dbf.createQuery(VmInstanceVO.class); q.select(VmInstanceVO_.internalId); q.add(VmInstanceVO_.uuid, Op.EQ, vmUuid); final Long vmInternalId = q.findValue(); FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("migrate-vm-%s-on-kvm-host-%s", vmUuid, self.getUuid())); chain.then(new ShareFlow() { @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "migrate-vm"; @Override public void run(final FlowTrigger trigger, Map data) { MigrateVmCmd cmd = new MigrateVmCmd(); cmd.setDestHostIp(hostIp); cmd.setSrcHostIp(self.getManagementIp()); cmd.setStorageMigrationPolicy(storageMigrationPolicy == null ? null : storageMigrationPolicy.toString()); cmd.setVmUuid(vmUuid); cmd.setUseNuma(VmGlobalConfig.NUMA.value(Boolean.class)); new Http<>(migrateVmPath, cmd, MigrateVmResponse.class).call(new ReturnValueCompletion<MigrateVmResponse>(trigger) { @Override public void success(MigrateVmResponse ret) { if (!ret.isSuccess()) { ErrorCode err = errf.instantiateErrorCode(HostErrors.FAILED_TO_MIGRATE_VM_ON_HYPERVISOR, String.format("failed to migrate vm[uuid:%s] from kvm host[uuid:%s, ip:%s] to dest host[ip:%s], %s", vmUuid, self.getUuid(), self.getManagementIp(), hostIp, ret.getError()) ); trigger.fail(err); } else { String info = String.format("successfully migrated vm[uuid:%s] from kvm host[uuid:%s, ip:%s] to dest host[ip:%s]", vmUuid, self.getUuid(), self.getManagementIp(), hostIp); logger.debug(info); trigger.next(); } } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flow(new NoRollbackFlow() { String __name__ = "harden-vm-console-on-dst-host"; @Override public void run(final FlowTrigger trigger, Map data) { HardenVmConsoleCmd cmd = new HardenVmConsoleCmd(); cmd.vmInternalId = vmInternalId; cmd.vmUuid = vmUuid; cmd.hostManagementIp = hostIp; UriComponentsBuilder ub = UriComponentsBuilder.newInstance(); ub.scheme(KVMGlobalProperty.AGENT_URL_SCHEME); ub.host(hostIp); ub.port(KVMGlobalProperty.AGENT_PORT); ub.path(KVMConstant.KVM_HARDEN_CONSOLE_PATH); String url = ub.build().toString(); new Http<>(url, cmd, AgentResponse.class).call(new ReturnValueCompletion<AgentResponse>(trigger) { @Override public void success(AgentResponse ret) { if (!ret.isSuccess()) { //TODO: add GC logger.warn(String.format("failed to harden VM[uuid:%s]'s console, %s", vmUuid, ret.getError())); } trigger.next(); } @Override public void fail(ErrorCode errorCode) { //TODO add GC logger.warn(String.format("failed to harden VM[uuid:%s]'s console, %s", vmUuid, errorCode)); // continue trigger.next(); } }); } }); flow(new NoRollbackFlow() { String __name__ = "delete-vm-console-firewall-on-source-host"; @Override public void run(final FlowTrigger trigger, Map data) { DeleteVmConsoleFirewallCmd cmd = new DeleteVmConsoleFirewallCmd(); cmd.vmInternalId = vmInternalId; cmd.vmUuid = vmUuid; cmd.hostManagementIp = self.getManagementIp(); new Http<>(deleteConsoleFirewall, cmd, AgentResponse.class).call(new ReturnValueCompletion<AgentResponse>(trigger) { @Override public void success(AgentResponse ret) { if (!ret.isSuccess()) { logger.warn(String.format("failed to delete console firewall rule for the vm[uuid:%s] on" + " the source host[uuid:%s, ip:%s], %s", vmUuid, self.getUuid(), self.getManagementIp(), ret.getError())); } trigger.next(); } @Override public void fail(ErrorCode errorCode) { //TODO logger.warn(String.format("failed to delete console firewall rule for the vm[uuid:%s] on" + " the source host[uuid:%s, ip:%s], %s", vmUuid, self.getUuid(), self.getManagementIp(), errorCode)); trigger.next(); } }); } }); done(new FlowDoneHandler(completion) { @Override public void handle(Map data) { String info = String.format("successfully migrated vm[uuid:%s] from kvm host[uuid:%s, ip:%s] to dest host[ip:%s]", vmUuid, self.getUuid(), self.getManagementIp(), hostIp); logger.debug(info); migrateVm(it, completion); } }); error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { completion.fail(errCode); } }); } }).start(); } private void handle(final MigrateVmOnHypervisorMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { migrateVm(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("migrate-vm-on-kvm-%s", self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } class MigrateStruct { String vmUuid; String dstHostIp; StorageMigrationPolicy storageMigrationPolicy; } private void migrateVm(final MigrateVmOnHypervisorMsg msg, final NoErrorCompletion completion) { checkStatus(); List<MigrateStruct> lst = new ArrayList<>(); MigrateStruct s = new MigrateStruct(); s.vmUuid = msg.getVmInventory().getUuid(); s.dstHostIp = msg.getDestHostInventory().getManagementIp(); s.storageMigrationPolicy = msg.getStorageMigrationPolicy(); lst.add(s); final MigrateVmOnHypervisorReply reply = new MigrateVmOnHypervisorReply(); migrateVm(lst.iterator(), new Completion(msg, completion) { @Override public void success() { bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); completion.done(); } }); } private void handle(final VmAttachNicOnHypervisorMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { attachNic(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("attach-nic-on-kvm-%s", self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private void attachNic(final VmAttachNicOnHypervisorMsg msg, final NoErrorCompletion completion) { checkStateAndStatus(); NicTO to = completeNicInfo(msg.getNicInventory()); final VmAttachNicOnHypervisorReply reply = new VmAttachNicOnHypervisorReply(); AttachNicCommand cmd = new AttachNicCommand(); cmd.setVmUuid(msg.getNicInventory().getVmInstanceUuid()); cmd.setNic(to); KVMHostInventory inv = (KVMHostInventory) getSelfInventory(); for (KvmPreAttachNicExtensionPoint ext : pluginRgty.getExtensionList(KvmPreAttachNicExtensionPoint.class)) { ext.preAttachNicExtensionPoint(inv, cmd); } new Http<>(attachNicPath, cmd, AttachNicResponse.class).call(new ReturnValueCompletion<AttachNicResponse>(msg, completion) { @Override public void success(AttachNicResponse ret) { if (!ret.isSuccess()) { reply.setError(operr("failed to attach nic[uuid:%s, vm:%s] on kvm host[uuid:%s, ip:%s]," + "because %s", msg.getNicInventory().getUuid(), msg.getNicInventory().getVmInstanceUuid(), self.getUuid(), self.getManagementIp(), ret.getError())); } bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode errorCode) { reply.setError(errorCode); bus.reply(msg, reply); completion.done(); } }); } private void handle(final DetachVolumeFromVmOnHypervisorMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { detachVolume(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("detach-volume-on-kvm-%s", self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private void detachVolume(final DetachVolumeFromVmOnHypervisorMsg msg, final NoErrorCompletion completion) { checkStateAndStatus(); VolumeTO to = new VolumeTO(); final VolumeInventory vol = msg.getInventory(); final VmInstanceInventory vm = msg.getVmInventory(); to.setInstallPath(vol.getInstallPath()); if (vol.getDeviceId() != null) { to.setDeviceId(vol.getDeviceId()); } to.setDeviceType(getVolumeTOType(vol)); to.setVolumeUuid(vol.getUuid()); // volumes can only be attached on Windows if the virtio is enabled // so for Windows, use virtio as well to.setUseVirtio(ImagePlatform.Windows.toString().equals(vm.getPlatform()) || ImagePlatform.valueOf(vm.getPlatform()).isParaVirtualization()); to.setUseVirtioSCSI(KVMSystemTags.VOLUME_VIRTIO_SCSI.hasTag(vol.getUuid())); to.setWwn(setVolumeWwn(vol.getUuid())); to.setShareable(vol.isShareable()); final DetachVolumeFromVmOnHypervisorReply reply = new DetachVolumeFromVmOnHypervisorReply(); final DetachDataVolumeCmd cmd = new DetachDataVolumeCmd(); cmd.setVolume(to); cmd.setVmUuid(vm.getUuid()); extEmitter.beforeDetachVolume((KVMHostInventory) getSelfInventory(), vm, vol, cmd); new Http<>(detachDataVolumePath, cmd, DetachDataVolumeResponse.class).call(new ReturnValueCompletion<DetachDataVolumeResponse>(msg, completion) { @Override public void success(DetachDataVolumeResponse ret) { if (!ret.isSuccess()) { ErrorCode err = operr("failed to detach data volume[uuid:%s, installPath:%s] from vm[uuid:%s, name:%s] on kvm host[uuid:%s, ip:%s], because %s", vol.getUuid(), vol.getInstallPath(), vm.getUuid(), vm.getName(), getSelf().getUuid(), getSelf().getManagementIp(), ret.getError()); reply.setError(err); extEmitter.detachVolumeFailed((KVMHostInventory) getSelfInventory(), vm, vol, cmd, reply.getError()); } else { extEmitter.afterDetachVolume((KVMHostInventory) getSelfInventory(), vm, vol, cmd); } bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode err) { reply.setError(err); bus.reply(msg, reply); extEmitter.detachVolumeFailed((KVMHostInventory) getSelfInventory(), vm, vol, cmd, err); completion.done(); } }); } private void handle(final AttachVolumeToVmOnHypervisorMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { attachVolume(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("attach-volume-on-kvm-%s", self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private String setVolumeWwn(String volumeUUid) { String wwn; String tag = KVMSystemTags.VOLUME_WWN.getTag(volumeUUid); if (tag != null) { wwn = KVMSystemTags.VOLUME_WWN.getTokenByTag(tag, KVMSystemTags.VOLUME_WWN_TOKEN); } else { SystemTagCreator creator = KVMSystemTags.VOLUME_WWN.newSystemTagCreator(volumeUUid); creator.ignoreIfExisting = true; creator.inherent = true; creator.setTagByTokens(map(e(KVMSystemTags.VOLUME_WWN_TOKEN, new WwnUtils().getRandomWwn()))); SystemTagInventory inv = creator.create(); wwn = KVMSystemTags.VOLUME_WWN.getTokenByTag(inv.getTag(), KVMSystemTags.VOLUME_WWN_TOKEN); } DebugUtils.Assert(new WwnUtils().isValidWwn(wwn), String.format("Not a valid wwn[%s] for volume[uuid:%s]", wwn, volumeUUid)); return wwn; } private void attachVolume(final AttachVolumeToVmOnHypervisorMsg msg, final NoErrorCompletion completion) { checkStateAndStatus(); VolumeTO to = new VolumeTO(); final VolumeInventory vol = msg.getInventory(); final VmInstanceInventory vm = msg.getVmInventory(); to.setInstallPath(vol.getInstallPath()); to.setDeviceId(vol.getDeviceId()); to.setDeviceType(getVolumeTOType(vol)); to.setVolumeUuid(vol.getUuid()); // volumes can only be attached on Windows if the virtio is enabled // so for Windows, use virtio as well to.setUseVirtio(ImagePlatform.Windows.toString().equals(vm.getPlatform()) || ImagePlatform.valueOf(vm.getPlatform()).isParaVirtualization()); to.setUseVirtioSCSI(KVMSystemTags.VOLUME_VIRTIO_SCSI.hasTag(vol.getUuid())); to.setWwn(setVolumeWwn(vol.getUuid())); to.setShareable(vol.isShareable()); to.setCacheMode(KVMGlobalConfig.LIBVIRT_CACHE_MODE.value()); final AttachVolumeToVmOnHypervisorReply reply = new AttachVolumeToVmOnHypervisorReply(); final AttachDataVolumeCmd cmd = new AttachDataVolumeCmd(); cmd.setVolume(to); cmd.setVmUuid(msg.getVmInventory().getUuid()); extEmitter.beforeAttachVolume((KVMHostInventory) getSelfInventory(), vm, vol, cmd); new Http<>(attachDataVolumePath, cmd, AttachDataVolumeResponse.class).call(new ReturnValueCompletion<AttachDataVolumeResponse>(msg, completion) { @Override public void success(AttachDataVolumeResponse ret) { if (!ret.isSuccess()) { reply.setError(operr("failed to attach data volume[uuid:%s, installPath:%s] to vm[uuid:%s, name:%s]" + " on kvm host[uuid:%s, ip:%s], because %s", vol.getUuid(), vol.getInstallPath(), vm.getUuid(), vm.getName(), getSelf().getUuid(), getSelf().getManagementIp(), ret.getError())); extEmitter.attachVolumeFailed((KVMHostInventory) getSelfInventory(), vm, vol, cmd, reply.getError()); } else { extEmitter.afterAttachVolume((KVMHostInventory) getSelfInventory(), vm, vol, cmd); } bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode err) { extEmitter.attachVolumeFailed((KVMHostInventory) getSelfInventory(), vm, vol, cmd, err); reply.setError(err); bus.reply(msg, reply); completion.done(); } }); } private void handle(final DestroyVmOnHypervisorMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { destroyVm(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("destroy-vm-on-kvm-%s", self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private void destroyVm(final DestroyVmOnHypervisorMsg msg, final NoErrorCompletion completion) { checkStatus(); final VmInstanceInventory vminv = msg.getVmInventory(); try { extEmitter.beforeDestroyVmOnKvm(KVMHostInventory.valueOf(getSelf()), vminv); } catch (KVMException e) { ErrorCode err = operr("failed to destroy vm[uuid:%s name:%s] on kvm host[uuid:%s, ip:%s], because %s", vminv.getUuid(), vminv.getName(), self.getUuid(), self.getManagementIp(), e.getMessage()); throw new OperationFailureException(err); } DestroyVmCmd cmd = new DestroyVmCmd(); cmd.setUuid(vminv.getUuid()); new Http<>(destroyVmPath, cmd, DestroyVmResponse.class).call(new ReturnValueCompletion<DestroyVmResponse>(msg, completion) { @Override public void success(DestroyVmResponse ret) { DestroyVmOnHypervisorReply reply = new DestroyVmOnHypervisorReply(); if (!ret.isSuccess()) { String err = String.format("unable to destroy vm[uuid:%s, name:%s] on kvm host [uuid:%s, ip:%s], because %s", vminv.getUuid(), vminv.getName(), self.getUuid(), self.getManagementIp(), ret.getError()); reply.setError(errf.instantiateErrorCode(HostErrors.FAILED_TO_DESTROY_VM_ON_HYPERVISOR, err)); extEmitter.destroyVmOnKvmFailed(KVMHostInventory.valueOf(getSelf()), vminv, reply.getError()); } else { logger.debug(String.format("successfully destroyed vm[uuid:%s] on kvm host[uuid:%s]", vminv.getUuid(), self.getUuid())); extEmitter.destroyVmOnKvmSuccess(KVMHostInventory.valueOf(getSelf()), vminv); } bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode err) { DestroyVmOnHypervisorReply reply = new DestroyVmOnHypervisorReply(); if (err.isError(SysErrors.HTTP_ERROR, SysErrors.IO_ERROR)) { err = errf.instantiateErrorCode(HostErrors.OPERATION_FAILURE_GC_ELIGIBLE, "unable to destroy a vm", err); } reply.setError(err); extEmitter.destroyVmOnKvmFailed(KVMHostInventory.valueOf(getSelf()), vminv, reply.getError()); bus.reply(msg, reply); completion.done(); } }); } private void handle(final RebootVmOnHypervisorMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { rebootVm(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("reboot-vm-on-kvm-%s", self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private List<String> toKvmBootDev(List<String> order) { List<String> ret = new ArrayList<String>(); for (String o : order) { if (VmBootDevice.HardDisk.toString().equals(o)) { ret.add(BootDev.hd.toString()); } else if (VmBootDevice.CdRom.toString().equals(o)) { ret.add(BootDev.cdrom.toString()); } else { throw new CloudRuntimeException(String.format("unknown boot device[%s]", o)); } } return ret; } private void rebootVm(final RebootVmOnHypervisorMsg msg, final NoErrorCompletion completion) { checkStateAndStatus(); final VmInstanceInventory vminv = msg.getVmInventory(); try { extEmitter.beforeRebootVmOnKvm(KVMHostInventory.valueOf(getSelf()), vminv); } catch (KVMException e) { String err = String.format("failed to reboot vm[uuid:%s name:%s] on kvm host[uuid:%s, ip:%s], because %s", vminv.getUuid(), vminv.getName(), self.getUuid(), self.getManagementIp(), e.getMessage()); logger.warn(err, e); throw new OperationFailureException(operr(err)); } RebootVmCmd cmd = new RebootVmCmd(); long timeout = TimeUnit.MILLISECONDS.toSeconds(msg.getTimeout()); cmd.setUuid(vminv.getUuid()); cmd.setTimeout(timeout); cmd.setBootDev(toKvmBootDev(msg.getBootOrders())); new Http<>(rebootVmPath, cmd, RebootVmResponse.class).call(new ReturnValueCompletion<RebootVmResponse>(msg, completion) { @Override public void success(RebootVmResponse ret) { RebootVmOnHypervisorReply reply = new RebootVmOnHypervisorReply(); if (!ret.isSuccess()) { String err = String.format("unable to reboot vm[uuid:%s, name:%s] on kvm host[uuid:%s, ip:%s], because %s", vminv.getUuid(), vminv.getName(), self.getUuid(), self.getManagementIp(), ret.getError()); reply.setError(errf.instantiateErrorCode(HostErrors.FAILED_TO_REBOOT_VM_ON_HYPERVISOR, err)); extEmitter.rebootVmOnKvmFailed(KVMHostInventory.valueOf(getSelf()), vminv, reply.getError()); } else { extEmitter.rebootVmOnKvmSuccess(KVMHostInventory.valueOf(getSelf()), vminv); } bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode err) { RebootVmOnHypervisorReply reply = new RebootVmOnHypervisorReply(); reply.setError(err); extEmitter.rebootVmOnKvmFailed(KVMHostInventory.valueOf(getSelf()), vminv, err); bus.reply(msg, reply); completion.done(); } }); } private void handle(final StopVmOnHypervisorMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { stopVm(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("stop-vm-on-kvm-%s", self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private void stopVm(final StopVmOnHypervisorMsg msg, final NoErrorCompletion completion) { checkStatus(); final VmInstanceInventory vminv = msg.getVmInventory(); try { extEmitter.beforeStopVmOnKvm(KVMHostInventory.valueOf(getSelf()), vminv); } catch (KVMException e) { ErrorCode err = operr("failed to stop vm[uuid:%s name:%s] on kvm host[uuid:%s, ip:%s], because %s", vminv.getUuid(), vminv.getName(), self.getUuid(), self.getManagementIp(), e.getMessage()); throw new OperationFailureException(err); } StopVmCmd cmd = new StopVmCmd(); cmd.setUuid(vminv.getUuid()); cmd.setType(msg.getType()); cmd.setTimeout(120); new Http<>(stopVmPath, cmd, StopVmResponse.class).call(new ReturnValueCompletion<StopVmResponse>(msg, completion) { @Override public void success(StopVmResponse ret) { StopVmOnHypervisorReply reply = new StopVmOnHypervisorReply(); if (!ret.isSuccess()) { String err = String.format("unable to stop vm[uuid:%s, name:%s] on kvm host[uuid:%s, ip:%s], because %s", vminv.getUuid(), vminv.getName(), self.getUuid(), self.getManagementIp(), ret.getError()); reply.setError(errf.instantiateErrorCode(HostErrors.FAILED_TO_STOP_VM_ON_HYPERVISOR, err)); logger.warn(err); extEmitter.stopVmOnKvmFailed(KVMHostInventory.valueOf(getSelf()), vminv, reply.getError()); } else { extEmitter.stopVmOnKvmSuccess(KVMHostInventory.valueOf(getSelf()), vminv); } bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode err) { StopVmOnHypervisorReply reply = new StopVmOnHypervisorReply(); if (err.isError(SysErrors.IO_ERROR, SysErrors.HTTP_ERROR)) { err = errf.instantiateErrorCode(HostErrors.OPERATION_FAILURE_GC_ELIGIBLE, "unable to stop a vm", err); } reply.setError(err); extEmitter.stopVmOnKvmFailed(KVMHostInventory.valueOf(getSelf()), vminv, err); bus.reply(msg, reply); completion.done(); } }); } @Transactional private void setDataVolumeUseVirtIOSCSI(final VmInstanceSpec spec) { String vmUuid = spec.getVmInventory().getUuid(); Map<String, Integer> diskOfferingUuid_Num = new HashMap<>(); List<Map<String, String>> tokenList = KVMSystemTags.DISK_OFFERING_VIRTIO_SCSI.getTokensOfTagsByResourceUuid(vmUuid); for (Map<String, String> tokens : tokenList) { String diskOfferingUuid = tokens.get(KVMSystemTags.DISK_OFFERING_VIRTIO_SCSI_TOKEN); Integer num = Integer.parseInt(tokens.get(KVMSystemTags.DISK_OFFERING_VIRTIO_SCSI_NUM_TOKEN)); diskOfferingUuid_Num.put(diskOfferingUuid, num); } for (VolumeInventory volumeInv : spec.getDestDataVolumes()) { if (volumeInv.getType().equals(VolumeType.Root.toString())) { continue; } if (diskOfferingUuid_Num.containsKey(volumeInv.getDiskOfferingUuid()) && diskOfferingUuid_Num.get(volumeInv.getDiskOfferingUuid()) > 0) { tagmgr.createNonInherentSystemTag(volumeInv.getUuid(), KVMSystemTags.VOLUME_VIRTIO_SCSI.getTagFormat(), VolumeVO.class.getSimpleName()); diskOfferingUuid_Num.put(volumeInv.getDiskOfferingUuid(), diskOfferingUuid_Num.get(volumeInv.getDiskOfferingUuid()) - 1); } } } private void handle(final CreateVmOnHypervisorMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { setDataVolumeUseVirtIOSCSI(msg.getVmSpec()); startVm(msg.getVmSpec(), msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("start-vm-on-kvm-%s", self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } @Transactional private L2NetworkInventory getL2NetworkTypeFromL3NetworkUuid(String l3NetworkUuid) { String sql = "select l2 from L2NetworkVO l2 where l2.uuid = (select l3.l2NetworkUuid from L3NetworkVO l3 where l3.uuid = :l3NetworkUuid)"; TypedQuery<L2NetworkVO> query = dbf.getEntityManager().createQuery(sql, L2NetworkVO.class); query.setParameter("l3NetworkUuid", l3NetworkUuid); L2NetworkVO l2vo = query.getSingleResult(); return L2NetworkInventory.valueOf(l2vo); } private NicTO completeNicInfo(VmNicInventory nic) { L2NetworkInventory l2inv = getL2NetworkTypeFromL3NetworkUuid(nic.getL3NetworkUuid()); KVMCompleteNicInformationExtensionPoint extp = factory.getCompleteNicInfoExtension(L2NetworkType.valueOf(l2inv.getType())); NicTO to = extp.completeNicInformation(l2inv, nic); if (to.getUseVirtio() == null) { SimpleQuery<VmInstanceVO> q = dbf.createQuery(VmInstanceVO.class); q.select(VmInstanceVO_.platform); q.add(VmInstanceVO_.uuid, Op.EQ, nic.getVmInstanceUuid()); String platform = q.findValue(); to.setUseVirtio(ImagePlatform.valueOf(platform).isParaVirtualization()); if (!(nic.getIp().isEmpty() || nic.getIp() == null) && VmGlobalConfig.VM_CLEAN_TRAFFIC.value(Boolean.class)) { to.setIp(nic.getIp()); } } return to; } private String getVolumeTOType(VolumeInventory vol) { return vol.getInstallPath().startsWith("iscsi") ? VolumeTO.ISCSI : VolumeTO.FILE; } private void startVm(final VmInstanceSpec spec, final NeedReplyMessage msg, final NoErrorCompletion completion) { checkStateAndStatus(); final StartVmCmd cmd = new StartVmCmd(); boolean virtio; String consoleMode; String nestedVirtualization; String platform = spec.getVmInventory().getPlatform() == null ? spec.getImageSpec().getInventory().getPlatform() : spec.getVmInventory().getPlatform(); if (ImagePlatform.Windows.toString().equals(platform)) { virtio = VmSystemTags.WINDOWS_VOLUME_ON_VIRTIO.hasTag(spec.getVmInventory().getUuid()); } else { virtio = ImagePlatform.valueOf(platform).isParaVirtualization(); } int cpuNum = spec.getVmInventory().getCpuNum(); cmd.setCpuNum(cpuNum); int socket; int cpuOnSocket; //TODO: this is a HACK!!! if (ImagePlatform.Windows.toString().equals(platform) || ImagePlatform.WindowsVirtio.toString().equals(platform)) { if (cpuNum == 1) { socket = 1; cpuOnSocket = 1; } else if (cpuNum % 2 == 0) { socket = 2; cpuOnSocket = cpuNum / 2; } else { socket = cpuNum; cpuOnSocket = 1; } } else { socket = 1; cpuOnSocket = cpuNum; } cmd.setSocketNum(socket); cmd.setCpuOnSocket(cpuOnSocket); cmd.setVmName(spec.getVmInventory().getName()); cmd.setVmInstanceUuid(spec.getVmInventory().getUuid()); cmd.setCpuSpeed(spec.getVmInventory().getCpuSpeed()); cmd.setMemory(spec.getVmInventory().getMemorySize()); cmd.setMaxMemory(self.getCapacity().getTotalPhysicalMemory()); cmd.setUseVirtio(virtio); cmd.setClock(ImagePlatform.isType(platform, ImagePlatform.Windows, ImagePlatform.WindowsVirtio) ? "localtime" : "utc"); cmd.setVideoType(VmGlobalConfig.VM_VIDEO_TYPE.value(String.class)); cmd.setInstanceOfferingOnlineChange(VmSystemTags.INSTANCEOFFERING_ONLIECHANGE.getTokenByResourceUuid(spec.getVmInventory().getUuid(), VmSystemTags.INSTANCEOFFERING_ONLINECHANGE_TOKEN) != null); VolumeTO rootVolume = new VolumeTO(); rootVolume.setInstallPath(spec.getDestRootVolume().getInstallPath()); rootVolume.setDeviceId(spec.getDestRootVolume().getDeviceId()); rootVolume.setDeviceType(getVolumeTOType(spec.getDestRootVolume())); rootVolume.setVolumeUuid(spec.getDestRootVolume().getUuid()); rootVolume.setUseVirtio(virtio); rootVolume.setUseVirtioSCSI(KVMSystemTags.VOLUME_VIRTIO_SCSI.hasTag(spec.getDestRootVolume().getUuid())); rootVolume.setWwn(setVolumeWwn(spec.getDestRootVolume().getUuid())); rootVolume.setCacheMode(KVMGlobalConfig.LIBVIRT_CACHE_MODE.value()); consoleMode = KVMGlobalConfig.VM_CONSOLE_MODE.value(String.class); nestedVirtualization = KVMGlobalConfig.NESTED_VIRTUALIZATION.value(String.class); cmd.setConsoleMode(consoleMode); cmd.setNestedVirtualization(nestedVirtualization); cmd.setRootVolume(rootVolume); List<VolumeTO> dataVolumes = new ArrayList<>(spec.getDestDataVolumes().size()); for (VolumeInventory data : spec.getDestDataVolumes()) { VolumeTO v = new VolumeTO(); v.setInstallPath(data.getInstallPath()); v.setDeviceId(data.getDeviceId()); v.setDeviceType(getVolumeTOType(data)); v.setVolumeUuid(data.getUuid()); // always use virtio driver for data volume // set bug https://github.com/zxwing/premium/issues/1050 v.setUseVirtio(true); v.setUseVirtioSCSI(KVMSystemTags.VOLUME_VIRTIO_SCSI.hasTag(data.getUuid())); v.setWwn(setVolumeWwn(data.getUuid())); v.setShareable(data.isShareable()); v.setCacheMode(KVMGlobalConfig.LIBVIRT_CACHE_MODE.value()); dataVolumes.add(v); } cmd.setDataVolumes(dataVolumes); cmd.setVmInternalId(spec.getVmInventory().getInternalId()); List<NicTO> nics = new ArrayList<>(spec.getDestNics().size()); for (VmNicInventory nic : spec.getDestNics()) { if (!spec.getVmInventory().getType().equals(VmInstanceConstant.USER_VM_TYPE)) { nic.setIp(""); } nics.add(completeNicInfo(nic)); } cmd.setNics(nics); if (spec.getDestIso() != null) { IsoTO bootIso = new IsoTO(); bootIso.setPath(spec.getDestIso().getInstallPath()); bootIso.setImageUuid(spec.getDestIso().getImageUuid()); cmd.setBootIso(bootIso); } cmd.setBootDev(toKvmBootDev(spec.getBootOrders())); cmd.setHostManagementIp(self.getManagementIp()); cmd.setConsolePassword(spec.getConsolePassword()); cmd.setUseNuma(VmGlobalConfig.NUMA.value(Boolean.class)); addons(spec, cmd); KVMHostInventory khinv = KVMHostInventory.valueOf(getSelf()); try { extEmitter.beforeStartVmOnKvm(khinv, spec, cmd); } catch (KVMException e) { ErrorCode err = operr("failed to start vm[uuid:%s name:%s] on kvm host[uuid:%s, ip:%s], because %s", spec.getVmInventory().getUuid(), spec.getVmInventory().getName(), self.getUuid(), self.getManagementIp(), e.getMessage()); throw new OperationFailureException(err); } extEmitter.addOn(khinv, spec, cmd); new Http<>(startVmPath, cmd, StartVmResponse.class).call(new ReturnValueCompletion<StartVmResponse>(msg, completion) { @Override public void success(StartVmResponse ret) { StartVmOnHypervisorReply reply = new StartVmOnHypervisorReply(); if (ret.isSuccess()) { String info = String.format("successfully start vm[uuid:%s name:%s] on kvm host[uuid:%s, ip:%s]", spec.getVmInventory().getUuid(), spec.getVmInventory().getName(), self.getUuid(), self.getManagementIp()); logger.debug(info); extEmitter.startVmOnKvmSuccess(KVMHostInventory.valueOf(getSelf()), spec); } else { String err = String.format("failed to start vm[uuid:%s name:%s] on kvm host[uuid:%s, ip:%s], because %s", spec.getVmInventory().getUuid(), spec.getVmInventory().getName(), self.getUuid(), self.getManagementIp(), ret.getError()); reply.setError(errf.instantiateErrorCode(HostErrors.FAILED_TO_START_VM_ON_HYPERVISOR, err)); logger.warn(err); extEmitter.startVmOnKvmFailed(KVMHostInventory.valueOf(getSelf()), spec, reply.getError()); } bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode err) { StartVmOnHypervisorReply reply = new StartVmOnHypervisorReply(); reply.setError(err); reply.setSuccess(false); extEmitter.startVmOnKvmFailed(KVMHostInventory.valueOf(getSelf()), spec, err); bus.reply(msg, reply); completion.done(); } }); } private void addons(final VmInstanceSpec spec, StartVmCmd cmd) { KVMAddons.Channel chan = new KVMAddons.Channel(); chan.setSocketPath(makeChannelSocketPath(spec.getVmInventory().getUuid())); chan.setTargetName("org.qemu.guest_agent.0"); cmd.getAddons().put(KVMAddons.Channel.NAME, chan); logger.debug(String.format("make kvm channel device[path:%s, target:%s]", chan.getSocketPath(), chan.getTargetName())); } private String makeChannelSocketPath(String apvmuuid) { return PathUtil.join(String.format("/var/lib/libvirt/qemu/%s", apvmuuid)); } private void handle(final StartVmOnHypervisorMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { startVm(msg.getVmSpec(), msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("start-vm-on-kvm-%s", self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private void handle(final CheckNetworkPhysicalInterfaceMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { checkPhysicalInterface(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("check-network-physical-interface-on-host-%s", self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private void pauseVm(final PauseVmOnHypervisorMsg msg, final NoErrorCompletion completion) { checkStatus(); final VmInstanceInventory vminv = msg.getVmInventory(); PauseVmOnHypervisorReply reply = new PauseVmOnHypervisorReply(); PauseVmCmd cmd = new PauseVmCmd(); cmd.setUuid(vminv.getUuid()); cmd.setTimeout(120); new Http<>(pauseVmPath, cmd, PauseVmResponse.class).call(new ReturnValueCompletion<PauseVmResponse>(msg, completion) { @Override public void success(PauseVmResponse ret) { if (!ret.isSuccess()) { String err = String.format("unable to pause vm[uuid:%s, name:%s] on kvm host[uuid:%s, ip:%s], because %s", vminv.getUuid(), vminv.getName(), self.getUuid(), self.getManagementIp(), ret.getError()); reply.setError(errf.instantiateErrorCode(HostErrors.FAILED_TO_STOP_VM_ON_HYPERVISOR, err)); logger.warn(err); } bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode err) { reply.setError(err); bus.reply(msg, reply); completion.done(); } }); } private void handle(final PauseVmOnHypervisorMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { pauseVm(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("pause-vm-%s-on-host-%s", msg.getVmInventory().getUuid(), self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private void handle(final ResumeVmOnHypervisorMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override public String getSyncSignature() { return id; } @Override public void run(final SyncTaskChain chain) { resumeVm(msg, new NoErrorCompletion(chain) { @Override public void done() { chain.next(); } }); } @Override public String getName() { return String.format("resume-vm-%s-on-host-%s", msg.getVmInventory().getUuid(), self.getUuid()); } @Override protected int getSyncLevel() { return getHostSyncLevel(); } }); } private void resumeVm(final ResumeVmOnHypervisorMsg msg, final NoErrorCompletion completion) { checkStatus(); final VmInstanceInventory vminv = msg.getVmInventory(); ResumeVmOnHypervisorReply reply = new ResumeVmOnHypervisorReply(); ResumeVmCmd cmd = new ResumeVmCmd(); cmd.setUuid(vminv.getUuid()); cmd.setTimeout(120); new Http<>(resumeVmPath, cmd, ResumeVmResponse.class).call(new ReturnValueCompletion<ResumeVmResponse>(msg, completion) { @Override public void success(ResumeVmResponse ret) { if (!ret.isSuccess()) { String err = String.format("unable to resume vm[uuid:%s, name:%s] on kvm host[uuid:%s, ip:%s], because %s", vminv.getUuid(), vminv.getName(), self.getUuid(), self.getManagementIp(), ret.getError()); reply.setError(errf.instantiateErrorCode(HostErrors.FAILED_TO_STOP_VM_ON_HYPERVISOR, err)); logger.warn(err); } bus.reply(msg, reply); completion.done(); } @Override public void fail(ErrorCode err) { reply.setError(err); bus.reply(msg, reply); completion.done(); } }); } private void checkPhysicalInterface(CheckNetworkPhysicalInterfaceMsg msg, NoErrorCompletion completion) { checkState(); CheckPhysicalNetworkInterfaceCmd cmd = new CheckPhysicalNetworkInterfaceCmd(); cmd.addInterfaceName(msg.getPhysicalInterface()); CheckNetworkPhysicalInterfaceReply reply = new CheckNetworkPhysicalInterfaceReply(); CheckPhysicalNetworkInterfaceResponse rsp = restf.syncJsonPost(checkPhysicalNetworkInterfacePath, cmd, CheckPhysicalNetworkInterfaceResponse.class); if (!rsp.isSuccess()) { if (rsp.getFailedInterfaceNames().isEmpty()) { reply.setError(operr(rsp.getError())); } else { reply.setError(operr("%s, failed to check physical network interfaces[names : %s] on kvm host[uuid:%s, ip:%s]", rsp.getError(), msg.getPhysicalInterface(), context.getInventory().getUuid(), context.getInventory().getManagementIp())); } } bus.reply(msg, reply); completion.done(); } @Override public void handleMessage(Message msg) { try { if (msg instanceof APIMessage) { handleApiMessage((APIMessage) msg); } else { handleLocalMessage(msg); } } catch (Exception e) { bus.logExceptionWithMessageDump(msg, e); bus.replyErrorByMessageType(msg, e); } } @Override public void changeStateHook(HostState current, HostStateEvent stateEvent, HostState next) { } @Override public void deleteHook() { } @Override protected HostInventory getSelfInventory() { return KVMHostInventory.valueOf(getSelf()); } @Override protected void pingHook(final Completion completion) { FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("ping-kvm-host-%s", self.getUuid())); chain.then(new ShareFlow() { @Override public void setup() { flow(new NoRollbackFlow() { String __name__ = "ping-host"; @AfterDone List<Runnable> afterDone = new ArrayList<>(); @Override public void run(FlowTrigger trigger, Map data) { PingCmd cmd = new PingCmd(); cmd.hostUuid = self.getUuid(); restf.asyncJsonPost(pingPath, cmd, new JsonAsyncRESTCallback<PingResponse>(trigger) { @Override public void fail(ErrorCode err) { trigger.fail(err); } @Override public void success(PingResponse ret) { if (ret.isSuccess()) { if (!self.getUuid().equals(ret.getHostUuid())) { afterDone.add(() -> { String info = String.format("detected abnormal status[host uuid change, expected: %s but: %s] of kvmagent," + "it's mainly caused by kvmagent restarts behind zstack management server. Report this to ping task, it will issue a reconnect soon", self.getUuid(), ret.getHostUuid()); logger.warn(info); ReconnectHostMsg rmsg = new ReconnectHostMsg(); rmsg.setHostUuid(self.getUuid()); bus.makeTargetServiceIdByResourceUuid(rmsg, HostConstant.SERVICE_ID, self.getUuid()); bus.send(rmsg); }); } trigger.next(); } else { trigger.fail(operr(ret.getError())); } } @Override public Class<PingResponse> getReturnClass() { return PingResponse.class; } }); } }); flow(new NoRollbackFlow() { String __name__ = "call-ping-no-failure-plugins"; @Override public void run(FlowTrigger trigger, Map data) { List<KVMPingAgentNoFailureExtensionPoint> exts = pluginRgty.getExtensionList(KVMPingAgentNoFailureExtensionPoint.class); if (exts.isEmpty()) { trigger.next(); return; } AsyncLatch latch = new AsyncLatch(exts.size(), new NoErrorCompletion(trigger) { @Override public void done() { trigger.next(); } }); KVMHostInventory inv = (KVMHostInventory) getSelfInventory(); for (KVMPingAgentNoFailureExtensionPoint ext : exts) { ext.kvmPingAgentNoFailure(inv, new NoErrorCompletion(latch) { @Override public void done() { latch.ack(); } }); } } }); flow(new NoRollbackFlow() { String __name__ = "call-ping-plugins"; @Override public void run(FlowTrigger trigger, Map data) { List<KVMPingAgentExtensionPoint> exts = pluginRgty.getExtensionList(KVMPingAgentExtensionPoint.class); Iterator<KVMPingAgentExtensionPoint> it = exts.iterator(); callPlugin(it, trigger); } private void callPlugin(Iterator<KVMPingAgentExtensionPoint> it, FlowTrigger trigger) { if (!it.hasNext()) { trigger.next(); return; } KVMPingAgentExtensionPoint ext = it.next(); logger.debug(String.format("calling KVMPingAgentExtensionPoint[%s]", ext.getClass())); ext.kvmPingAgent((KVMHostInventory) getSelfInventory(), new Completion(trigger) { @Override public void success() { callPlugin(it, trigger); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); 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(); } @Override protected int getVmMigrateQuantity() { return KVMGlobalConfig.VM_MIGRATION_QUANTITY.value(Integer.class); } private ErrorCode connectToAgent() { ErrorCode errCode = null; try { ConnectCmd cmd = new ConnectCmd(); cmd.setHostUuid(self.getUuid()); cmd.setSendCommandUrl(restf.getSendCommandUrl()); cmd.setIptablesRules(KVMGlobalProperty.IPTABLES_RULES); ConnectResponse rsp = restf.syncJsonPost(connectPath, cmd, ConnectResponse.class); if (!rsp.isSuccess() || !rsp.isIptablesSucc()) { errCode = operr("unable to connect to kvm host[uuid:%s, ip:%s, url:%s], because %s", self.getUuid(), self.getManagementIp(), connectPath, rsp.getError()); } else { VersionComparator libvirtVersion = new VersionComparator(rsp.getLibvirtVersion()); VersionComparator qemuVersion = new VersionComparator(rsp.getQemuVersion()); boolean liveSnapshot = libvirtVersion.compare(KVMConstant.MIN_LIBVIRT_LIVESNAPSHOT_VERSION) >= 0 && qemuVersion.compare(KVMConstant.MIN_QEMU_LIVESNAPSHOT_VERSION) >= 0; String hostOS = HostSystemTags.OS_DISTRIBUTION.getTokenByResourceUuid(self.getUuid(), HostSystemTags.OS_DISTRIBUTION_TOKEN); //liveSnapshot = liveSnapshot && (!"CentOS".equals(hostOS) || KVMGlobalConfig.ALLOW_LIVE_SNAPSHOT_ON_REDHAT.value(Boolean.class)); if (liveSnapshot) { logger.debug(String.format("kvm host[OS:%s, uuid:%s, name:%s, ip:%s] supports live snapshot with libvirt[version:%s], qemu[version:%s]", hostOS, self.getUuid(), self.getName(), self.getManagementIp(), rsp.getLibvirtVersion(), rsp.getQemuVersion())); SystemTagCreator creator = HostSystemTags.LIVE_SNAPSHOT.newSystemTagCreator(self.getUuid()); creator.recreate = true; creator.create(); } else { HostSystemTags.LIVE_SNAPSHOT.deleteInherentTag(self.getUuid()); } } } catch (RestClientException e) { errCode = operr("unable to connect to kvm host[uuid:%s, ip:%s, url:%s], because %s", self.getUuid(), self.getManagementIp(), connectPath, e.getMessage()); } catch (Throwable t) { logger.warn(t.getMessage(), t); errCode = errf.throwableToInternalError(t); } return errCode; } private KVMHostVO getSelf() { return (KVMHostVO) self; } private void continueConnect(final boolean newAdded, final Completion completion) { ErrorCode errCode = connectToAgent(); if (errCode != null) { throw new OperationFailureException(errCode); } FlowChain chain = FlowChainBuilder.newSimpleFlowChain(); chain.setName(String.format("continue-connecting-kvm-host-%s-%s", self.getManagementIp(), self.getUuid())); for (KVMHostConnectExtensionPoint extp : factory.getConnectExtensions()) { KVMHostConnectedContext ctx = new KVMHostConnectedContext(); ctx.setInventory((KVMHostInventory) getSelfInventory()); ctx.setNewAddedHost(newAdded); chain.then(extp.createKvmHostConnectingFlow(ctx)); } chain.done(new FlowDoneHandler(completion) { @Override public void handle(Map data) { completion.success(); } }).error(new FlowErrorHandler(completion) { @Override public void handle(ErrorCode errCode, Map data) { String err = String.format("connection error for KVM host[uuid:%s, ip:%s]", self.getUuid(), self.getManagementIp()); completion.fail(errf.instantiateErrorCode(HostErrors.CONNECTION_ERROR, err, errCode)); } }).start(); } private void createHostVersionSystemTags(String distro, String release, String version) { SystemTagCreator creator = HostSystemTags.OS_DISTRIBUTION.newSystemTagCreator(self.getUuid()); creator.inherent = true; creator.setTagByTokens(map(e(HostSystemTags.OS_DISTRIBUTION_TOKEN, distro))); creator.create(); creator = HostSystemTags.OS_RELEASE.newSystemTagCreator(self.getUuid()); creator.inherent = true; creator.setTagByTokens(map(e(HostSystemTags.OS_RELEASE_TOKEN, release))); creator.create(); creator = HostSystemTags.OS_VERSION.newSystemTagCreator(self.getUuid()); creator.inherent = true; creator.setTagByTokens(map(e(HostSystemTags.OS_VERSION_TOKEN, version))); creator.create(); } @Override public void connectHook(final ConnectHostInfo info, final Completion complete) { if (CoreGlobalProperty.UNIT_TEST_ON) { if (info.isNewAdded()) { createHostVersionSystemTags("zstack", "kvmSimulator", "0.1"); SystemTagCreator creator = KVMSystemTags.LIBVIRT_VERSION.newSystemTagCreator(self.getUuid()); creator.inherent = true; creator.setTagByTokens(map(e(KVMSystemTags.LIBVIRT_VERSION_TOKEN, "1.2.9"))); creator.create(); creator = KVMSystemTags.QEMU_IMG_VERSION.newSystemTagCreator(self.getUuid()); creator.inherent = true; creator.setTagByTokens(map(e(KVMSystemTags.QEMU_IMG_VERSION_TOKEN, "2.0.0"))); creator.create(); } continueConnect(info.isNewAdded(), complete); } else { FlowChain chain = FlowChainBuilder.newShareFlowChain(); chain.setName(String.format("run-ansible-for-kvm-%s", self.getUuid())); chain.then(new ShareFlow() { @Override public void setup() { if (info.isNewAdded()) { if ((!AnsibleGlobalProperty.ZSTACK_REPO.contains("zstack-mn")) && (!AnsibleGlobalProperty.ZSTACK_REPO.equals("false"))) { flow(new NoRollbackFlow() { String __name__ = "ping-DNS-check-list"; @Override public void run(FlowTrigger trigger, Map data) { String checkList; if (AnsibleGlobalProperty.ZSTACK_REPO.contains(KVMConstant.ALI_REPO)) { checkList = KVMGlobalConfig.HOST_DNS_CHECK_ALIYUN.value(); } else if (AnsibleGlobalProperty.ZSTACK_REPO.contains(KVMConstant.NETEASE_REPO)) { checkList = KVMGlobalConfig.HOST_DNS_CHECK_163.value(); } else { checkList = KVMGlobalConfig.HOST_DNS_CHECK_LIST.value(); } checkList = checkList.replaceAll(",", " "); SshShell sshShell = new SshShell(); sshShell.setHostname(getSelf().getManagementIp()); sshShell.setUsername(getSelf().getUsername()); sshShell.setPassword(getSelf().getPassword()); sshShell.setPort(getSelf().getPort()); SshResult ret = sshShell.runScriptWithToken("scripts/check-public-dns-name.sh", map(e("dnsCheckList", checkList))); if (ret.isSshFailure()) { trigger.fail(operr("unable to connect to KVM[ip:%s, username:%s, sshPort: %d, ] to do DNS check, please check if username/password is wrong; %s", self.getManagementIp(), getSelf().getUsername(), getSelf().getPort(), ret.getExitErrorMessage())); } else if (ret.getReturnCode() != 0) { trigger.fail(operr("failed to ping all DNS/IP in %s; please check /etc/resolv.conf to make sure your host is able to reach public internet", checkList)); } else { trigger.next(); } } }); } } flow(new NoRollbackFlow() { String __name__ = "check-if-host-can-reach-management-node"; @Override public void run(FlowTrigger trigger, Map data) { SshShell sshShell = new SshShell(); sshShell.setHostname(getSelf().getManagementIp()); sshShell.setUsername(getSelf().getUsername()); sshShell.setPassword(getSelf().getPassword()); sshShell.setPort(getSelf().getPort()); ShellUtils.run(String.format("arp -d %s || true", getSelf().getManagementIp())); SshResult ret = sshShell.runCommand(String.format("curl --connect-timeout 10 %s", restf.getCallbackUrl())); if (ret.isSshFailure()) { throw new OperationFailureException(operr("unable to connect to KVM[ip:%s, username:%s, sshPort:%d] to check the management node connectivity," + "please check if username/password is wrong; %s", self.getManagementIp(), getSelf().getUsername(), getSelf().getPort(), ret.getExitErrorMessage())); } else if (ret.getReturnCode() != 0) { throw new OperationFailureException(operr("the KVM host[ip:%s] cannot access the management node's callback url. It seems" + " that the KVM host cannot reach the management IP[%s]. %s %s", self.getManagementIp(), Platform.getManagementServerIp(), ret.getStderr(), ret.getExitErrorMessage())); } trigger.next(); } }); flow(new NoRollbackFlow() { String __name__ = "apply-ansible-playbook"; @Override public void run(final FlowTrigger trigger, Map data) { String srcPath = PathUtil.findFileOnClassPath(String.format("ansible/kvm/%s", agentPackageName), true).getAbsolutePath(); String destPath = String.format("/var/lib/zstack/kvm/package/%s", agentPackageName); SshFileMd5Checker checker = new SshFileMd5Checker(); checker.setUsername(getSelf().getUsername()); checker.setPassword(getSelf().getPassword()); checker.setSshPort(getSelf().getPort()); checker.setTargetIp(getSelf().getManagementIp()); checker.addSrcDestPair(SshFileMd5Checker.ZSTACKLIB_SRC_PATH, String.format("/var/lib/zstack/kvm/package/%s", AnsibleGlobalProperty.ZSTACKLIB_PACKAGE_NAME)); checker.addSrcDestPair(srcPath, destPath); AnsibleRunner runner = new AnsibleRunner(); runner.installChecker(checker); runner.setAgentPort(KVMGlobalProperty.AGENT_PORT); runner.setTargetIp(getSelf().getManagementIp()); runner.setPlayBookName(KVMConstant.ANSIBLE_PLAYBOOK_NAME); runner.setUsername(getSelf().getUsername()); runner.setPassword(getSelf().getPassword()); runner.setSshPort(getSelf().getPort()); if (info.isNewAdded()) { runner.putArgument("init", "true"); runner.setFullDeploy(true); } runner.putArgument("pkg_kvmagent", agentPackageName); runner.putArgument("hostname", String.format("%s.zstack.org", self.getManagementIp().replaceAll("\\.", "-"))); UriComponentsBuilder ub = UriComponentsBuilder.fromHttpUrl(restf.getBaseUrl()); ub.path(new StringBind(KVMConstant.KVM_ANSIBLE_LOG_PATH_FROMAT).bind("uuid", self.getUuid()).toString()); String postUrl = ub.build().toString(); runner.putArgument("post_url", postUrl); runner.run(new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flow(new NoRollbackFlow() { String __name__ = "echo-host"; @Override public void run(final FlowTrigger trigger, Map data) { restf.echo(echoPath, new Completion(trigger) { @Override public void success() { trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); if (info.isNewAdded()) { flow(new NoRollbackFlow() { String __name__ = "ansbile-get-kvm-host-facts"; @Override public void run(FlowTrigger trigger, Map data) { String privKeyFile = PathUtil.findFileOnClassPath(AnsibleConstant.RSA_PRIVATE_KEY).getAbsolutePath(); ShellResult ret = ShellUtils.runAndReturn(String.format("ansible -i %s --private-key %s -m setup -a filter=ansible_distribution* %s -e 'ansible_ssh_port=%d ansible_ssh_user=%s'", AnsibleConstant.INVENTORY_FILE, privKeyFile, self.getManagementIp(), getSelf().getPort(), getSelf().getUsername()), AnsibleConstant.ROOT_DIR); if (!ret.isReturnCode(0)) { trigger.fail(operr("unable to get kvm host[uuid:%s, ip:%s] facts by ansible\n%s", self.getUuid(), self.getManagementIp(), ret.getExecutionLog())); return; } String[] pairs = ret.getStdout().split(">>"); if (pairs.length != 2) { trigger.fail(operr("unrecognized ansible facts mediaType, %s", ret.getStdout())); return; } LinkedHashMap output = JSONObjectUtil.toObject(pairs[1], LinkedHashMap.class); LinkedHashMap facts = (LinkedHashMap) output.get("ansible_facts"); if (facts == null) { trigger.fail(operr("unrecognized ansible facts mediaType, cannot find field 'ansible_facts', %s", ret.getStdout())); return; } String distro = (String) facts.get("ansible_distribution"); String release = (String) facts.get("ansible_distribution_release"); String version = (String) facts.get("ansible_distribution_version"); createHostVersionSystemTags(distro, release, version); trigger.next(); } }); } flow(new NoRollbackFlow() { String __name__ = "collect-kvm-host-facts"; @Override public void run(final FlowTrigger trigger, Map data) { HostFactCmd cmd = new HostFactCmd(); new Http<>(hostFactPath, cmd, HostFactResponse.class) .call(new ReturnValueCompletion<HostFactResponse>(trigger) { @Override public void success(HostFactResponse ret) { if (!ret.isSuccess()) { trigger.fail(operr(ret.getError())); return; } if (ret.getHvmCpuFlag() == null) { trigger.fail(operr("cannot find either 'vmx' or 'svm' in /proc/cpuinfo, please make sure you have enabled virtualization in your BIOS setting")); return; } SystemTagCreator creator = KVMSystemTags.QEMU_IMG_VERSION.newSystemTagCreator(self.getUuid()); creator.setTagByTokens(map(e(KVMSystemTags.QEMU_IMG_VERSION_TOKEN, ret.getQemuImgVersion()))); creator.recreate = true; creator.create(); creator = KVMSystemTags.LIBVIRT_VERSION.newSystemTagCreator(self.getUuid()); creator.setTagByTokens(map(e(KVMSystemTags.LIBVIRT_VERSION_TOKEN, ret.getLibvirtVersion()))); creator.recreate = true; creator.create(); creator = KVMSystemTags.HVM_CPU_FLAG.newSystemTagCreator(self.getUuid()); creator.setTagByTokens(map(e(KVMSystemTags.HVM_CPU_FLAG_TOKEN, ret.getHvmCpuFlag()))); creator.recreate = true; creator.create(); if (ret.getLibvirtVersion().compareTo(KVMConstant.MIN_LIBVIRT_VIRTIO_SCSI_VERSION) >= 0) { creator = KVMSystemTags.VIRTIO_SCSI.newSystemTagCreator(self.getUuid()); creator.recreate = true; creator.create(); } trigger.next(); } @Override public void fail(ErrorCode errorCode) { trigger.fail(errorCode); } }); } }); flow(new NoRollbackFlow() { String __name__ = "prepare-host-env"; @Override public void run(FlowTrigger trigger, Map data) { String script = "which iptables > /dev/null && iptables -C FORWARD -j REJECT --reject-with icmp-host-prohibited > /dev/null 2>&1 && iptables -D FORWARD -j REJECT --reject-with icmp-host-prohibited > /dev/null 2>&1 || true"; runShell(script); trigger.next(); } }); error(new FlowErrorHandler(complete) { @Override public void handle(ErrorCode errCode, Map data) { complete.fail(errCode); } }); done(new FlowDoneHandler(complete) { @Override public void handle(Map data) { continueConnect(info.isNewAdded(), complete); } }); } }).start(); } } @Override protected int getHostSyncLevel() { return KVMGlobalConfig.HOST_SYNC_LEVEL.value(Integer.class); } @Override protected HostVO updateHost(APIUpdateHostMsg msg) { if (!(msg instanceof APIUpdateKVMHostMsg)) { return super.updateHost(msg); } KVMHostVO vo = (KVMHostVO) super.updateHost(msg); vo = vo == null ? getSelf() : vo; APIUpdateKVMHostMsg umsg = (APIUpdateKVMHostMsg) msg; if (umsg.getUsername() != null) { vo.setUsername(umsg.getUsername()); } if (umsg.getPassword() != null) { vo.setPassword(umsg.getPassword()); } if (umsg.getSshPort() != null && umsg.getSshPort() > 0 && umsg.getSshPort() <= 65535) { vo.setPort(umsg.getSshPort()); } return vo; } }