【Ovirt 笔记】spice 连接虚拟机机制的实现分析
2017-07-20 本文已影响7人
58bc06151329
分析整理的版本为 Ovirt 3.4.5 版本。
连接虚拟机
- 平台界面通过选择虚拟机,点击控制台,执行连接虚拟机的操作。该操作通过 VmListModel 调用 connectToConsoles 方法。
private void connectToConsoles() {
StringBuilder errorMessages = null;
final List<VM> list = getSelectedItems();
if (list == null || list.isEmpty()) {
return;
}
for (VM vm : list) {
try {
consoleModelsCache.getVmConsolesForEntity(vm).connect();
......
- 控制台连接虚拟机通过 VmConsolesImpl 类实现。
public void connect() throws ConsoleConnectException {
if (!canConnectToConsole()) {
throw new ConsoleConnectException(connectErrorMessage());
}
getConsoleModel(getSelectedProcotol().getBackingClass()).getConnectCommand().execute();
}
- 再调用 ConsoleModel 的 connect 方法实现不同协议的连接方式。
protected abstract void connect();
- spice 协议连接方法通过 SpiceConsoleModel 类实现。
@Override
protected void connect() {
if (spiceTime == null) {
spiceTime = new Date();
} else {
Date test = new Date();
long ste = test.getTime() - spiceTime.getTime();
if (test.getTime() - spiceTime.getTime() < 1000 * 1) {
getLogger().debug("double click too fast..."); //$NON-NLS-1$
return;
}
spiceTime = test;
}
if (getEntity() != null) {
getLogger().debug("Connecting to Spice console..."); //$NON-NLS-1$
if (!getspice().getIsInstalled()) {
getLogger().info("Spice client is not installed."); //$NON-NLS-1$
getspice().install();
return;
}
// Check a spice version.
if (getConfigurator().getIsAdmin()
&& getspice().getCurrentVersion().compareTo(getspice().getDesiredVersion()) < 0)
{
getLogger().info("Spice client version is not as desired (" + getspice().getDesiredVersion() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
getspice().install();
return;
}
// Don't connect if there VM is not running on any host.
if (getEntity().getRunOnVds() == null)
{
return;
}
// If it is not windows or SPICE guest agent is not installed, make sure the WAN options are disabled.
if (!AsyncDataProvider.isWindowsOsType(getEntity().getVmOsId()) || !getEntity().getHasSpiceDriver()) {
getspice().setWanOptionsEnabled(false);
}
UICommand setVmTicketCommand = new UICommand("setVmCommand", new BaseCommandTarget() { //$NON-NLS-1$
@Override
public void executeCommand(UICommand uiCommand) {
setVmTicket();
}
});
executeCommandWithConsoleSafenessWarning(setVmTicketCommand);
}
}
private void setVmTicket() {
// Create ticket for single sign on.
Frontend.getInstance().runAction(VdcActionType.SetVmTicket, new SetVmTicketParameters(getEntity().getId(), null, 120),
new IFrontendActionAsyncCallback() {
@Override
public void executed(FrontendActionAsyncResult result) {
SpiceConsoleModel spiceConsoleModel = (SpiceConsoleModel) result.getState();
spiceConsoleModel.postSendVmTicket(result.getReturnValue());
}
}, this);
}
- 最终通过调用 vdsm 的 setVmTicket 接口,连接虚拟机。
status = getBroker().setVmTicket(mVmId.toString(), mTicket, String.valueOf(mValidTime),connectionAction, params);
构造 vv 文件
- 构造 VmConsolesImpl 类的同时构造 SpiceConsoleModel 模型。
private void fillModels() {
SpiceConsoleModel spiceConsoleModel = new SpiceConsoleModel(vm, parentModel);
spiceConsoleModel.getErrorEvent().addListener(new ConsoleModelErrorEventListener(parentModel));
spiceConsoleModel.setForceVmStatusUp(myContext == ConsoleOptionsFrontendPersister.ConsoleContext.UP_BASIC);
consoleModels.put(ConsoleProtocol.SPICE, spiceConsoleModel);
......
- 设置默认的控制台客户端模式。
setConsoleClientMode(getDefaultConsoleMode());
protected ClientConsoleMode getDefaultConsoleMode() {
return ClientConsoleMode.valueOf((String) AsyncDataProvider
.getConfigValuePreConverted(ConfigurationValues.ClientModeSpiceDefault));
}
public enum ClientConsoleMode { Native, Plugin, Auto, Html5 }
- 平台默认的客户端模式为 Auto。
[root@localhost ~]# engine-config -g ClientModeSpiceDefault
ClientModeSpiceDefault: Auto version: general
- 不同客户端模式有着不同的处理策略类
switch (consoleMode) {
case Native:
setspice((ISpice) TypeResolver.getInstance().resolve(ISpiceNative.class));
break;
case Plugin:
setspice((ISpice) TypeResolver.getInstance().resolve(ISpicePlugin.class));
break;
case Html5:
setspice((ISpice) TypeResolver.getInstance().resolve(ISpiceHtml5.class));
break;
default:
ISpicePlugin pluginSpice = (ISpicePlugin) TypeResolver.getInstance().resolve(ISpicePlugin.class);
setspice(pluginSpice.detectBrowserPlugin() ? pluginSpice
: (ISpice) TypeResolver.getInstance().resolve(ISpiceNative.class));
break;
}
- 未使用浏览器插件,则使用 SpiceNativeImpl 实现。
} else if (type == ISpiceNative.class) {
return new SpiceNativeImpl();
- 使用控制台 spice 协议连接虚拟机时,执行调用 SpiceConsoleModel 的 executed 方法。
SpiceConsoleModel spiceConsoleModel = (SpiceConsoleModel) result.getState();
spiceConsoleModel.postSendVmTicket(result.getReturnValue());
public void postSendVmTicket(VdcReturnValueBase returnValue) {
......
executeQuery(getEntity());
private void executeQuery(final VM vm) {
......
Frontend.getInstance().runMultipleQueries(queryTypeList, parametersList, thisSpiceConsoleModel);
@Override
public void executed(FrontendMultipleQueryAsyncResult result) {
......
if (StringHelper.isNullOrEmpty(getEntity().getDisplayIp()) || StringHelper.stringsEqual(getEntity().getDisplayIp(), "0")) {
determineIpAndConnect(getEntity().getId());
} else {
// Try to connect.
spiceConnect();
}
public void spiceConnect()
{
try {
getspice().connect();
} catch (RuntimeException ex) {
getLogger().error("Exception on Spice connect", ex); //$NON-NLS-1$
}
}
- 通过 spiceConnect 方法,构建 vv 文件。
@Override
public void connect() {
StringBuilder configBuilder = new StringBuilder("[virt-viewer]"); //$NON-NLS-1$
int fullscreen = 0;
if (isFullScreen()) {
fullscreen = 1;
}
int enableSmartcard = 0;
if (isSmartcardEnabled()) {
enableSmartcard = 1;
}
int usbAutoShare = 0;
if (getUsbAutoShare()) {
usbAutoShare = 1;
}
configBuilder.append("\ntype=spice") //$NON-NLS-1$
.append("\nhost=").append(getHost()) //$NON-NLS-1$
.append("\nport=").append(Integer.toString(getPort())) //$NON-NLS-1$
.append("\npassword=").append(getPassword()) //$NON-NLS-1$
.append("\ntls-port=").append(getSecurePort()) //$NON-NLS-1$
.append("\nfullscreen=").append(fullscreen) //$NON-NLS-1$
.append("\ntitle=").append(getTitle()) //$NON-NLS-1$
.append("\nenable-smartcard=").append(enableSmartcard) //$NON-NLS-1$
.append("\nenable-usb-autoshare=").append(usbAutoShare) //$NON-NLS-1$
.append("\ndelete-this-file=1") //$NON-NLS-1$
.append("\nusb-filter=").append(getUsbFilter()) //$NON-NLS-1$
.append("\nvideo-qmax=").append(getSpiceVideoQMax()); //$NON-NLS-1$
if (getCipherSuite() != null) {
configBuilder.append("\ntls-ciphers=").append(getCipherSuite()); //$NON-NLS-1$
}
if (!StringHelper.isNullOrEmpty(getHostSubject())) {
configBuilder.append("\nhost-subject=").append(getHostSubject()); //$NON-NLS-1$
}
if (getTrustStore() != null) {
//virt-viewer-file doesn't want newlines in ca
String trustStore= getTrustStore().replace("\n", "\\n"); //$NON-NLS-1$ $NON-NLS-2$
configBuilder.append("\nca=").append(trustStore); //$NON-NLS-1$
}
if (isWanOptionsEnabled()) {
configBuilder.append("\ncolor-depth=").append(colorDepthAsInt()) //$NON-NLS-1$
.append("\ndisable-effects=").append(disableEffectsAsString()); //$NON-NLS-1$
}
if (!StringHelper.isNullOrEmpty(getToggleFullscreenHotKey())) {
configBuilder.append("\ntoggle-fullscreen=").append(getToggleFullscreenHotKey()); //$NON-NLS-1$
}
if (!StringHelper.isNullOrEmpty(getReleaseCursorHotKey())) {
configBuilder.append("\nrelease-cursor=").append(getReleaseCursorHotKey()); //$NON-NLS-1$
}
if (isRemapCtrlAltDel() && !StringHelper.isNullOrEmpty(getSecureAttentionMapping())) {
configBuilder.append("\nsecure-attention=").append(getSecureAttentionMapping()); //$NON-NLS-1$
}
if (!StringHelper.isNullOrEmpty(getSpiceProxy())) {
configBuilder.append("\nproxy=").append(getSpiceProxy()); //$NON-NLS-1$
}
if (!StringHelper.isNullOrEmpty(getSslChanels())) {
configBuilder.append("\nsecure-channels=").append(formatSecureChannels(getSslChanels())); //$NON-NLS-1$
}
ConsoleModel.makeConsoleConfigRequest("console.vv", "application/x-virt-viewer; charset=UTF-8", configBuilder.toString()); //$NON-NLS-1$ $NON-NLS-2$
}