package org.ovirt.engine.core.bll;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Objects;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.branding.BrandingManager;
import org.ovirt.engine.core.common.action.SetVmTicketParameters;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.core.common.businessentities.GraphicsInfo;
import org.ovirt.engine.core.common.businessentities.GraphicsType;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.console.ConsoleOptions;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.common.queries.ConfigureConsoleOptionsParams;
import org.ovirt.engine.core.common.queries.IdQueryParameters;
import org.ovirt.engine.core.common.queries.VdcQueryParametersBase;
import org.ovirt.engine.core.common.queries.VdcQueryReturnValue;
import org.ovirt.engine.core.common.queries.VdcQueryType;
import org.ovirt.engine.core.utils.EngineLocalConfig;
/**
* Query for filling required backend data to given ConsoleOptions.
* Clients (frontend and restapi) use this query before initiating console session.
*
* @param <P> ConsoleOptions instance filled with 2 required parameters
* - vmId - id of VM for which options are configured
* - graphicsType - protocol for which options are configured
*/
public class ConfigureConsoleOptionsQuery<P extends ConfigureConsoleOptionsParams> extends QueriesCommandBase<P> {
private VM cachedVm;
static final String ENGINE_BASE_URL = "${engine_base_url}";
static final String CONSOLE_CLIENT_RESOURCES_URL = "${console_client_resources_url}";
public ConfigureConsoleOptionsQuery(P parameters) {
super(parameters);
}
@Override
protected boolean validateInputs() {
if (!super.validateInputs()) {
return false;
}
ConsoleOptions options = getParameters().getOptions();
if (options == null) {
getQueryReturnValue().setExceptionString("Console options can't be null.");
return false;
}
if (options.getGraphicsType() == null) {
getQueryReturnValue().setExceptionString("Graphics type must be filled in console options.");
return false;
}
if (options.getVmId() == null) {
getQueryReturnValue().setExceptionString("VM id must be filled in console options.");
return false;
}
return validateVm();
}
private boolean validateVm() {
if (getCachedVm() == null) {
getQueryReturnValue().setExceptionString(String.format("Can't find VM with id %s",
getParameters().getOptions().getVmId()));
return false;
} else if (!getCachedVm().isQualifiedForConsoleConnect()) {
getQueryReturnValue().setExceptionString(String.format("Vm %s is not running.", getCachedVm().getName()));
return false;
} else {
GraphicsType graphicsType = getParameters().getOptions().getGraphicsType();
if (!getCachedVm().getGraphicsInfos().containsKey(graphicsType)) {
getQueryReturnValue().setExceptionString(String.format("Vm %s doesn't have %s console.",
getCachedVm().getName(), graphicsType));
return false;
} else {
return true;
}
}
}
@Override
protected void executeQueryCommand() {
try {
executeCommandQueryUnchecked();
} catch (TicketGenerationException ex) {
handleTicketGenerationError(ex.getCommandResult());
}
}
private void executeCommandQueryUnchecked() {
ConsoleOptions options = getParameters().getOptions();
fillCommonPart(options);
// fill additional SPICE data
if (options.getGraphicsType() == GraphicsType.SPICE) {
fillSpice(options);
}
if (getQueryReturnValue().getSucceeded()) {
setReturnValue(options);
}
}
private void fillCommonPart(ConsoleOptions options) {
GraphicsInfo graphicsInfo = getCachedVm().getGraphicsInfos().get(options.getGraphicsType());
options.setHost(determineHost());
options.setPort(graphicsInfo.getPort());
options.setSmartcardEnabled(getCachedVm().isSmartcardEnabled());
if (getParameters().isSetTicket()) {
options.setTicket(generateTicket());
}
options.setToggleFullscreenHotKey(Config.getValue(ConfigValues.ConsoleToggleFullScreenKeys));
options.setReleaseCursorHotKey(Config.getValue(ConfigValues.ConsoleReleaseCursorKeys));
options.setRemapCtrlAltDelete(Config.getValue(ConfigValues.RemapCtrlAltDelDefault));
options.setFullScreen(Config.getValue(ConfigValues.FullScreenWebadminDefault));
fillRemoteViewerVersions(options);
final String engineHost = EngineLocalConfig.getInstance().getHost()
+ ':'
+ (EngineLocalConfig.getInstance().isProxyEnabled()
? EngineLocalConfig.getInstance().getProxyHttpsPort()
: EngineLocalConfig.getInstance().getHttpsPort());
options.setOvirtHost(engineHost);
final String ssoToken = getSessionDataContainer().getSsoAccessToken(getParameters().getSessionId());
options.setSsoToken(ssoToken);
options.setAdminConsole(!getParameters().isFiltered());
final VdcQueryReturnValue caCertificateReturnValue = getCACertificate();
if (!caCertificateReturnValue.getSucceeded()) {
getQueryReturnValue().setExceptionString("No CA found!");
getQueryReturnValue().setSucceeded(false);
return;
}
options.setTrustStore(caCertificateReturnValue.getReturnValue());
options.setCustomHttpsCertificateUsed(
!Objects.equals(
EngineLocalConfig.getInstance().getPKITrustStorePath(),
EngineLocalConfig.getInstance().getHttpsPKITrustStorePath()));
}
private void fillRemoteViewerVersions(ConsoleOptions options) {
String remoteViewerSupportedVersions = Config.getValue(ConfigValues.RemoteViewerSupportedVersions);
String remoteViewerNewerVersionUrl = Config.getValue(ConfigValues.RemoteViewerNewerVersionUrl);
if (StringUtils.isEmpty(remoteViewerSupportedVersions) || StringUtils.isEmpty(remoteViewerNewerVersionUrl)) {
return;
}
String engineBaseUrlString = calculateEngineBaseUrl(sanitizeUrl(getParameters().getEngineBaseUrl()));
String consoleClientResourcesUrl = calculateResourcesUrl(sanitizeUrl(getParameters().getConsoleClientResourcesUrl()));
options.setRemoteViewerSupportedVersions(remoteViewerSupportedVersions);
fillRemoteViewerUrl(options, remoteViewerNewerVersionUrl, engineBaseUrlString, consoleClientResourcesUrl);
}
private String calculateResourcesUrl(String passedUrl) {
if (!StringUtils.isEmpty(passedUrl)) {
return passedUrl;
}
return sanitizeUrl(BrandingManager.getInstance().getMessage("obrand.common.console_client_resources_url"));
}
/**
* If passed explicitly, just return it. If not, return the one calculated from ENGINE_FQDN and ENGINE_PROXY_HTTPS_PORT
*/
private String calculateEngineBaseUrl(String passedUrl) {
if (!StringUtils.isEmpty(passedUrl)) {
return passedUrl;
}
try {
return EngineLocalConfig.getInstance().getExternalHttpsBaseUrl("").toString();
} catch (MalformedURLException e) {
return "";
}
}
private String sanitizeUrl(String url) {
if (StringUtils.isEmpty(url)) {
return "";
}
if (url.endsWith("/")) {
return url.substring(0, url.length() - 1);
}
return url;
}
void fillRemoteViewerUrl(ConsoleOptions options, String remoteViewerNewerVersionUrl, String engineBaseUrlString, String consoleClientResourcesUrl) {
URL engineBaseUrl = null;
try {
engineBaseUrl = new URL(engineBaseUrlString);
} catch (MalformedURLException e) {
log.warn("Malformed engineBaseUrl sent '{}'", engineBaseUrlString);
engineBaseUrlString = null;
}
URI consoleClientResourcesUri = null;
try {
consoleClientResourcesUri = new URI(consoleClientResourcesUrl);
} catch (URISyntaxException e) {
log.warn("Malformed consoleClientResourcesUrl '{}'", consoleClientResourcesUrl);
consoleClientResourcesUrl = null;
}
if (engineBaseUrlString != null) {
remoteViewerNewerVersionUrl = StringUtils.replace(remoteViewerNewerVersionUrl, ENGINE_BASE_URL, engineBaseUrlString);
}
if (consoleClientResourcesUrl != null) {
if (!consoleClientResourcesUri.isAbsolute() && engineBaseUrlString != null) {
try {
consoleClientResourcesUrl = new URL(engineBaseUrl, consoleClientResourcesUrl).toString();
} catch (MalformedURLException e) {
log.warn("Can not create a join engineBaseUrl '{}' with consoleClientResourcesUrl '{}'", engineBaseUrlString, consoleClientResourcesUrl);
}
} else if (!consoleClientResourcesUri.isAbsolute()) {
log.warn("ConsoleClientResourcesUrl is relative but the engineBaseUrl is not set");
}
}
if (consoleClientResourcesUrl != null) {
remoteViewerNewerVersionUrl = StringUtils.replace(remoteViewerNewerVersionUrl, CONSOLE_CLIENT_RESOURCES_URL, consoleClientResourcesUrl);
}
options.setRemoteViewerNewerVersionUrl(remoteViewerNewerVersionUrl);
}
/**
* Fills SPICE specific data to options.
*
* If SPICE root certificate validation is enabled but the root certificate cannot be retrieved,
* the query fails (succeeded flag is set to false).
*
* @param options to be filled
*/
private void fillSpice(ConsoleOptions options) {
GraphicsInfo graphicsInfo = getCachedVm().getGraphicsInfos().get(options.getGraphicsType());
options.setSmartcardEnabled(getCachedVm().isSmartcardEnabled());
options.setNumberOfMonitors(getCachedVm().getNumOfMonitors());
if (graphicsInfo.getTlsPort() != null) {
options.setSecurePort(graphicsInfo.getTlsPort());
}
if (Config.getValue(ConfigValues.SSLEnabled)) {
String spiceSecureChannels = Config.getValue(ConfigValues.SpiceSecureChannels);
if (!StringUtils.isBlank(spiceSecureChannels)) {
options.setSslChanels(spiceSecureChannels);
}
String cipherSuite = Config.getValue(ConfigValues.CipherSuite);
if (!StringUtils.isBlank(cipherSuite)) {
options.setCipherSuite(cipherSuite);
}
}
options.setHostSubject(
Config.getValue(ConfigValues.EnableSpiceRootCertificateValidation)
? getVdsCertificateSubject()
: null);
options.setSpiceProxy(determineSpiceProxy());
}
private String generateTicket() {
SetVmTicketParameters parameters = new SetVmTicketParameters(
getParameters().getOptions().getVmId(),
null,
ConsoleOptions.TICKET_VALIDITY_SECONDS,
getParameters().getOptions().getGraphicsType());
// we need these two params because SetVmTicket needs to know current user
parameters.setSessionId(getEngineContext().getSessionId());
parameters.setParametersCurrentUser(getUser());
VdcReturnValueBase result = backend.runAction(VdcActionType.SetVmTicket, parameters);
if (!result.getSucceeded()) {
throw new TicketGenerationException(result);
}
return result.getActionReturnValue();
}
private void handleTicketGenerationError(VdcReturnValueBase commandResult) {
getQueryReturnValue().setSucceeded(false);
if (commandResult.getValidationMessages().contains(
EngineMessage.USER_CANNOT_FORCE_RECONNECT_TO_VM.name())) {
getQueryReturnValue().setExceptionString(
EngineMessage.USER_CANNOT_FORCE_RECONNECT_TO_VM.name());
return;
}
getQueryReturnValue().setExceptionString(EngineMessage.SETTING_VM_TICKET_FAILED.name());
}
private String getVdsCertificateSubject() {
return backend.runInternalQuery(
VdcQueryType.GetVdsCertificateSubjectByVmId,
new IdQueryParameters(getCachedVm().getId())).getReturnValue();
}
private VdcQueryReturnValue getCACertificate() {
return backend.runInternalQuery(VdcQueryType.GetCACertificate, new VdcQueryParametersBase());
}
private String determineHost() {
GraphicsInfo graphicsInfo = getCachedVm().getGraphicsInfos().get(getParameters().getOptions().getGraphicsType());
String result = graphicsInfo.getIp();
// if we don't have display ip, we try management network of host
if (StringUtils.isBlank(result) || "0".equals(result)) {
VdcQueryReturnValue returnValue = backend.runInternalQuery(
VdcQueryType.GetManagementInterfaceAddressByVmId,
new IdQueryParameters(getCachedVm().getId()));
result = returnValue.getReturnValue();
}
return result;
}
private String determineSpiceProxy() {
if (StringUtils.isNotBlank(getCachedVm().getVmPoolSpiceProxy())) {
return getCachedVm().getVmPoolSpiceProxy();
}
if (StringUtils.isNotBlank(getCachedVm().getClusterSpiceProxy())) {
return getCachedVm().getClusterSpiceProxy();
}
String globalSpiceProxy = Config.getValue(ConfigValues.SpiceProxyDefault);
if (StringUtils.isNotBlank(globalSpiceProxy)) {
return globalSpiceProxy;
}
return null;
}
VM getCachedVm() {
if (cachedVm == null) {
IdQueryParameters params = new IdQueryParameters(getParameters().getOptions().getVmId());
params.setFiltered(getParameters().isFiltered());
params.setSessionId(getParameters().getSessionId());
cachedVm = backend.runInternalQuery(
VdcQueryType.GetVmByVmId,
params).getReturnValue();
}
return cachedVm;
}
private static class TicketGenerationException extends RuntimeException {
private final VdcReturnValueBase commandResult;
public TicketGenerationException(VdcReturnValueBase commandResult) {
this.commandResult = commandResult;
}
public VdcReturnValueBase getCommandResult() {
return commandResult;
}
}
}