mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
vmware: Add support for VMware 7 (#4300)
This commit is contained in:
parent
8fec222af0
commit
0302750aac
@ -0,0 +1,34 @@
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
//
|
||||
package com.cloud.agent.api;
|
||||
|
||||
public class GetVmVncTicketAnswer extends Answer {
|
||||
|
||||
private String ticket;
|
||||
|
||||
public GetVmVncTicketAnswer(String ticket, boolean result, String details) {
|
||||
this.ticket = ticket;
|
||||
this.result = result;
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
public String getTicket() {
|
||||
return ticket;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
//
|
||||
package com.cloud.agent.api;
|
||||
|
||||
public class GetVmVncTicketCommand extends Command {
|
||||
|
||||
private String vmInternalName;
|
||||
|
||||
public GetVmVncTicketCommand(String vmInternalName) {
|
||||
this.vmInternalName = vmInternalName;
|
||||
}
|
||||
|
||||
public String getVmInternalName() {
|
||||
return this.vmInternalName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean executeInSequence() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -18,7 +18,6 @@
|
||||
--;
|
||||
-- Schema upgrade from 4.15.0.0 to 4.15.1.0
|
||||
--;
|
||||
|
||||
-- Correct guest OS names
|
||||
UPDATE `cloud`.`guest_os` SET display_name='Fedora Linux (32 bit)' WHERE id=320;
|
||||
UPDATE `cloud`.`guest_os` SET display_name='Mandriva Linux (32 bit)' WHERE id=323;
|
||||
@ -56,3 +55,80 @@ INSERT IGNORE INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervis
|
||||
-- Add support for Ubuntu Focal Fossa 20.04 for Xenserver 8.2.0
|
||||
INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name, created) VALUES (335, UUID(), 10, 'Ubuntu 20.04 LTS', now());
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'Xenserver', '8.2.0', 'Ubuntu Focal Fossa 20.04', 330, now(), 0);
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- Add support for VMware 7.0
|
||||
INSERT IGNORE INTO `cloud`.`hypervisor_capabilities` (uuid, hypervisor_type, hypervisor_version, max_guests_limit, security_group_enabled, max_data_volumes_limit, max_hosts_per_cluster, storage_motion_supported, vm_snapshot_enabled) values (UUID(), 'VMware', '7.0', 1024, 0, 59, 64, 1, 1);
|
||||
INSERT IGNORE INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) SELECT UUID(),'VMware', '7.0', guest_os_name, guest_os_id, utc_timestamp(), 0 FROM `cloud`.`guest_os_hypervisor` WHERE hypervisor_type='VMware' AND hypervisor_version='6.7';
|
||||
|
||||
-- Add support for darwin19_64Guest from VMware 7.0
|
||||
INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name, created) VALUES (336, UUID(), 7, 'macOS 10.15 (64 bit)', now());
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0', 'darwin19_64Guest', 336, now(), 0);
|
||||
|
||||
-- Add support for debian11_64Guest from VMware 7.0
|
||||
INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name, created) VALUES (337, UUID(), 2, 'Debian GNU/Linux 11 (64-bit)', now());
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0', 'debian11_64Guest', 337, now(), 0);
|
||||
|
||||
-- Add support for debian11Guest from VMware 7.0
|
||||
INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name, created) VALUES (338, UUID(), 2, 'Debian GNU/Linux 11 (32-bit)', now());
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0', 'debian11Guest', 338, now(), 0);
|
||||
|
||||
-- Add support for windows2019srv_64Guest from VMware 7.0
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0', 'windows2019srv_64Guest', 276, now(), 0);
|
||||
|
||||
|
||||
-- Add support for VMware 7.0.1.0
|
||||
INSERT IGNORE INTO `cloud`.`hypervisor_capabilities` (uuid, hypervisor_type, hypervisor_version, max_guests_limit, security_group_enabled, max_data_volumes_limit, max_hosts_per_cluster, storage_motion_supported, vm_snapshot_enabled) values (UUID(), 'VMware', '7.0.1.0', 1024, 0, 59, 64, 1, 1);
|
||||
INSERT IGNORE INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) SELECT UUID(),'VMware', '7.0.1.0', guest_os_name, guest_os_id, utc_timestamp(), 0 FROM `cloud`.`guest_os_hypervisor` WHERE hypervisor_type='VMware' AND hypervisor_version='7.0';
|
||||
|
||||
-- Add support for amazonlinux3_64Guest from VMware 7.0.1.0
|
||||
INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name, created) VALUES (339, UUID(), 7, 'Amazon Linux 3 (64 bit)', now());
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0.1.0', 'amazonlinux3_64Guest', 339, now(), 0);
|
||||
|
||||
-- Add support for asianux9_64Guest from VMware 7.0.1.0
|
||||
INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name, created) VALUES (340, UUID(), 7, 'Asianux Server 9 (64 bit)', now());
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0.1.0', 'asianux9_64Guest', 340, now(), 0);
|
||||
|
||||
-- Add support for centos9_64Guest from VMware 7.0.1.0
|
||||
INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name, created) VALUES (341, UUID(), 1, 'CentOS 9', now());
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0.1.0', 'centos9_64Guest', 341, now(), 0);
|
||||
|
||||
-- Add support for darwin20_64Guest from VMware 7.0.1.0
|
||||
INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name, created) VALUES (342, UUID(), 7, 'macOS 11 (64 bit)', now());
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0.1.0', 'darwin20_64Guest', 342, now(), 0);
|
||||
|
||||
-- Add support for darwin21_64Guest from VMware 7.0.1.0
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0.1.0', 'darwin21_64Guest', 342, now(), 0);
|
||||
|
||||
-- Add support for freebsd13_64Guest from VMware 7.0.1.0
|
||||
INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name, created) VALUES (343, UUID(), 9, 'FreeBSD 13 (64-bit)', now());
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0.1.0', 'freebsd13_64Guest', 343, now(), 0);
|
||||
|
||||
-- Add support for freebsd13Guest from VMware 7.0.1.0
|
||||
INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name, created) VALUES (344, UUID(), 9, 'FreeBSD 13 (32-bit)', now());
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0.1.0', 'freebsd13Guest', 344, now(), 0);
|
||||
|
||||
-- Add support for oracleLinux9_64Guest from VMware 7.0.1.0
|
||||
INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name, created) VALUES (345, UUID(), 3, 'Oracle Linux 9', now());
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0.1.0', 'oracleLinux9_64Guest', 345, now(), 0);
|
||||
|
||||
-- Add support for other5xLinux64Guest from VMware 7.0.1.0
|
||||
INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name, created) VALUES (346, UUID(), 2, 'Linux 5.x Kernel (64-bit)', now());
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0.1.0', 'other5xLinux64Guest', 346, now(), 0);
|
||||
|
||||
-- Add support for other5xLinuxGuest from VMware 7.0.1.0
|
||||
INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name, created) VALUES (347, UUID(), 2, 'Linux 5.x Kernel (32-bit)', now());
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0.1.0', 'other5xLinuxGuest', 347, now(), 0);
|
||||
|
||||
-- Add support for rhel9_64Guest from VMware 7.0.1.0
|
||||
INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name, created) VALUES (348, UUID(), 4, 'Red Hat Enterprise Linux 9.0', now());
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0.1.0', 'rhel9_64Guest', 348, now(), 0);
|
||||
|
||||
-- Add support for sles16_64Guest from VMware 7.0.1.0
|
||||
INSERT INTO `cloud`.`guest_os` (id, uuid, category_id, display_name, created) VALUES (349, UUID(), 5, 'SUSE Linux Enterprise Server 16 (64-bit)', now());
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0.1.0', 'sles16_64Guest', 349, now(), 0);
|
||||
|
||||
-- Add support for windows2019srvNext_64Guest from VMware 7.0.1.0
|
||||
INSERT INTO `cloud`.`guest_os_hypervisor` (uuid,hypervisor_type, hypervisor_version, guest_os_name, guest_os_id, created, is_user_defined) VALUES (UUID(),'VMware', '7.0.1.0', 'windows2019srvNext_64Guest', 276, now(), 0);
|
||||
|
||||
|
||||
@ -101,6 +101,8 @@ import com.cloud.agent.api.GetUnmanagedInstancesCommand;
|
||||
import com.cloud.agent.api.GetVmDiskStatsAnswer;
|
||||
import com.cloud.agent.api.GetVmDiskStatsCommand;
|
||||
import com.cloud.agent.api.GetVmIpAddressCommand;
|
||||
import com.cloud.agent.api.GetVmVncTicketCommand;
|
||||
import com.cloud.agent.api.GetVmVncTicketAnswer;
|
||||
import com.cloud.agent.api.GetVmNetworkStatsAnswer;
|
||||
import com.cloud.agent.api.GetVmNetworkStatsCommand;
|
||||
import com.cloud.agent.api.GetVmStatsAnswer;
|
||||
@ -578,6 +580,8 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
|
||||
answer = execute((PrepareUnmanageVMInstanceCommand) cmd);
|
||||
} else if (clz == ValidateVcenterDetailsCommand.class) {
|
||||
answer = execute((ValidateVcenterDetailsCommand) cmd);
|
||||
} else if (clz == GetVmVncTicketCommand.class) {
|
||||
answer = execute((GetVmVncTicketCommand) cmd);
|
||||
} else {
|
||||
answer = Answer.createUnsupportedCommandAnswer(cmd);
|
||||
}
|
||||
@ -7562,4 +7566,25 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
|
||||
return new Answer(cmd, false, "Provided vCenter server address is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
public String acquireVirtualMachineVncTicket(String vmInternalCSName) throws Exception {
|
||||
VmwareContext context = getServiceContext();
|
||||
VmwareHypervisorHost hyperHost = getHyperHost(context);
|
||||
DatacenterMO dcMo = new DatacenterMO(hyperHost.getContext(), hyperHost.getHyperHostDatacenter());
|
||||
VirtualMachineMO vmMo = dcMo.findVm(vmInternalCSName);
|
||||
return vmMo.acquireVncTicket();
|
||||
}
|
||||
|
||||
private GetVmVncTicketAnswer execute(GetVmVncTicketCommand cmd) {
|
||||
String vmInternalName = cmd.getVmInternalName();
|
||||
s_logger.info("Getting VNC ticket for VM " + vmInternalName);
|
||||
try {
|
||||
String ticket = acquireVirtualMachineVncTicket(vmInternalName);
|
||||
boolean result = StringUtils.isNotBlank(ticket);
|
||||
return new GetVmVncTicketAnswer(ticket, result, result ? "" : "Empty ticket obtained");
|
||||
} catch (Exception e) {
|
||||
s_logger.error("Error getting VNC ticket for VM " + vmInternalName, e);
|
||||
return new GetVmVncTicketAnswer(null, false, e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
pom.xml
2
pom.xml
@ -166,7 +166,7 @@
|
||||
<cs.servlet.version>4.0.1</cs.servlet.version>
|
||||
<cs.tomcat-embed-core.version>8.5.61</cs.tomcat-embed-core.version>
|
||||
<cs.trilead.version>build-217-jenkins-27</cs.trilead.version>
|
||||
<cs.vmware.api.version>6.7</cs.vmware.api.version>
|
||||
<cs.vmware.api.version>7.0</cs.vmware.api.version>
|
||||
<cs.winrm4j.version>0.5.0</cs.winrm4j.version>
|
||||
<cs.xapi.version>6.2.0-3.1</cs.xapi.version>
|
||||
<cs.xmlrpc.version>3.1.3</cs.xmlrpc.version>
|
||||
|
||||
@ -34,6 +34,7 @@ public class ConsoleProxyClientParam {
|
||||
private String password;
|
||||
|
||||
private String sourceIP;
|
||||
private String websocketUrl;
|
||||
|
||||
public ConsoleProxyClientParam() {
|
||||
clientHostPort = 0;
|
||||
@ -150,4 +151,12 @@ public class ConsoleProxyClientParam {
|
||||
public void setSourceIP(String sourceIP) {
|
||||
this.sourceIP = sourceIP;
|
||||
}
|
||||
|
||||
public String getWebsocketUrl() {
|
||||
return websocketUrl;
|
||||
}
|
||||
|
||||
public void setWebsocketUrl(String websocketUrl) {
|
||||
this.websocketUrl = websocketUrl;
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,6 +37,13 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import com.cloud.agent.AgentManager;
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.GetVmVncTicketAnswer;
|
||||
import com.cloud.agent.api.GetVmVncTicketCommand;
|
||||
import com.cloud.exception.AgentUnavailableException;
|
||||
import com.cloud.exception.OperationTimedoutException;
|
||||
import com.cloud.utils.StringUtils;
|
||||
import org.apache.cloudstack.framework.security.keys.KeysManager;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.log4j.Logger;
|
||||
@ -94,6 +101,8 @@ public class ConsoleProxyServlet extends HttpServlet {
|
||||
UserVmDetailsDao _userVmDetailsDao;
|
||||
@Inject
|
||||
KeysManager _keysMgr;
|
||||
@Inject
|
||||
AgentManager agentManager;
|
||||
|
||||
static KeysManager s_keysMgr;
|
||||
|
||||
@ -427,6 +436,47 @@ public class ConsoleProxyServlet extends HttpServlet {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL to establish a VNC over websocket connection
|
||||
*/
|
||||
private void setWebsocketUrl(VirtualMachine vm, ConsoleProxyClientParam param) {
|
||||
String ticket = acquireVncTicketForVmwareVm(vm);
|
||||
if (StringUtils.isBlank(ticket)) {
|
||||
s_logger.error("Could not obtain VNC ticket for VM " + vm.getInstanceName());
|
||||
return;
|
||||
}
|
||||
String wsUrl = composeWebsocketUrlForVmwareVm(ticket, param);
|
||||
param.setWebsocketUrl(wsUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format expected: wss://<ESXi_HOST_IP>:443/ticket/<TICKET_ID>
|
||||
*/
|
||||
private String composeWebsocketUrlForVmwareVm(String ticket, ConsoleProxyClientParam param) {
|
||||
param.setClientHostPort(443);
|
||||
return String.format("wss://%s:%s/ticket/%s", param.getClientHostAddress(), param.getClientHostPort(), ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires a ticket to be used for console proxy as described in 'Removal of VNC Server from ESXi' on:
|
||||
* https://docs.vmware.com/en/VMware-vSphere/7.0/rn/vsphere-esxi-vcenter-server-70-release-notes.html
|
||||
*/
|
||||
private String acquireVncTicketForVmwareVm(VirtualMachine vm) {
|
||||
try {
|
||||
s_logger.info("Acquiring VNC ticket for VM = " + vm.getHostName());
|
||||
GetVmVncTicketCommand cmd = new GetVmVncTicketCommand(vm.getInstanceName());
|
||||
Answer answer = agentManager.send(vm.getHostId(), cmd);
|
||||
GetVmVncTicketAnswer ans = (GetVmVncTicketAnswer) answer;
|
||||
if (!ans.getResult()) {
|
||||
s_logger.info("VNC ticket could not be acquired correctly: " + ans.getDetails());
|
||||
}
|
||||
return ans.getTicket();
|
||||
} catch (AgentUnavailableException | OperationTimedoutException e) {
|
||||
s_logger.error("Error acquiring ticket", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String composeConsoleAccessUrl(String rootUrl, VirtualMachine vm, HostVO hostVo, InetAddress addr) {
|
||||
StringBuffer sb = new StringBuffer(rootUrl);
|
||||
String host = hostVo.getPrivateIpAddress();
|
||||
@ -477,6 +527,10 @@ public class ConsoleProxyServlet extends HttpServlet {
|
||||
param.setTicket(ticket);
|
||||
param.setSourceIP(addr != null ? addr.getHostAddress(): null);
|
||||
|
||||
if (requiresVncOverWebSocketConnection(vm, hostVo)) {
|
||||
setWebsocketUrl(vm, param);
|
||||
}
|
||||
|
||||
if (details != null) {
|
||||
param.setLocale(details.getValue());
|
||||
}
|
||||
@ -513,6 +567,14 @@ public class ConsoleProxyServlet extends HttpServlet {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Since VMware 7.0 VNC servers are deprecated, it uses a ticket to create a VNC over websocket connection
|
||||
* Check: https://docs.vmware.com/en/VMware-vSphere/7.0/rn/vsphere-esxi-vcenter-server-70-release-notes.html
|
||||
*/
|
||||
private boolean requiresVncOverWebSocketConnection(VirtualMachine vm, HostVO hostVo) {
|
||||
return vm.getHypervisorType() == Hypervisor.HypervisorType.VMware && hostVo.getHypervisorVersion().compareTo("7.0") >= 0;
|
||||
}
|
||||
|
||||
public static String genAccessTicket(String host, String port, String sid, String tag) {
|
||||
return genAccessTicket(host, port, sid, tag, new Date());
|
||||
}
|
||||
|
||||
@ -65,6 +65,11 @@
|
||||
<artifactId>websocket-server</artifactId>
|
||||
<version>${cs.jetty.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.java-websocket</groupId>
|
||||
<artifactId>Java-WebSocket</artifactId>
|
||||
<version>1.5.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<resources>
|
||||
|
||||
@ -31,6 +31,7 @@ import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import com.cloud.utils.StringUtils;
|
||||
import org.apache.log4j.xml.DOMConfigurator;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
|
||||
@ -172,6 +173,11 @@ public class ConsoleProxy {
|
||||
authResult.setHost(param.getClientHostAddress());
|
||||
authResult.setPort(param.getClientHostPort());
|
||||
|
||||
String websocketUrl = param.getWebsocketUrl();
|
||||
if (StringUtils.isNotBlank(websocketUrl)) {
|
||||
return authResult;
|
||||
}
|
||||
|
||||
if (standaloneStart) {
|
||||
return authResult;
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ public class ConsoleProxyClientParam {
|
||||
private String hypervHost;
|
||||
private String username;
|
||||
private String password;
|
||||
private String websocketUrl;
|
||||
|
||||
private String sourceIP;
|
||||
|
||||
@ -153,4 +154,12 @@ public class ConsoleProxyClientParam {
|
||||
public void setSourceIP(String sourceIP) {
|
||||
this.sourceIP = sourceIP;
|
||||
}
|
||||
|
||||
public String getWebsocketUrl() {
|
||||
return websocketUrl;
|
||||
}
|
||||
|
||||
public void setWebsocketUrl(String websocketUrl) {
|
||||
this.websocketUrl = websocketUrl;
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,6 +93,9 @@ public class ConsoleProxyHttpHandlerHelper {
|
||||
map.put("password", param.getPassword());
|
||||
if (param.getSourceIP() != null)
|
||||
map.put("sourceIP", param.getSourceIP());
|
||||
if (param.getWebsocketUrl() != null) {
|
||||
map.put("websocketUrl", param.getWebsocketUrl());
|
||||
}
|
||||
} else {
|
||||
s_logger.error("Unable to decode token");
|
||||
}
|
||||
@ -116,5 +119,6 @@ public class ConsoleProxyHttpHandlerHelper {
|
||||
map.remove("hypervHost");
|
||||
map.remove("username");
|
||||
map.remove("password");
|
||||
map.remove("websocketUrl");
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,6 +88,7 @@ public class ConsoleProxyNoVNCHandler extends WebSocketHandler {
|
||||
String username = queryMap.get("username");
|
||||
String password = queryMap.get("password");
|
||||
String sourceIP = queryMap.get("sourceIP");
|
||||
String websocketUrl = queryMap.get("websocketUrl");
|
||||
|
||||
if (tag == null)
|
||||
tag = "";
|
||||
@ -131,6 +132,7 @@ public class ConsoleProxyNoVNCHandler extends WebSocketHandler {
|
||||
param.setHypervHost(hypervHost);
|
||||
param.setUsername(username);
|
||||
param.setPassword(password);
|
||||
param.setWebsocketUrl(websocketUrl);
|
||||
viewer = ConsoleProxy.getNoVncViewer(param, ajaxSessionIdStr, session);
|
||||
} catch (Exception e) {
|
||||
s_logger.warn("Failed to create viewer due to " + e.getMessage(), e);
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
// under the License.
|
||||
package com.cloud.consoleproxy;
|
||||
|
||||
import com.cloud.utils.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.extensions.Frame;
|
||||
@ -96,47 +97,30 @@ public class ConsoleProxyNoVncClient implements ConsoleProxyClient {
|
||||
|
||||
String tunnelUrl = param.getClientTunnelUrl();
|
||||
String tunnelSession = param.getClientTunnelSession();
|
||||
String websocketUrl = param.getWebsocketUrl();
|
||||
|
||||
try {
|
||||
if (tunnelUrl != null && !tunnelUrl.isEmpty() && tunnelSession != null
|
||||
&& !tunnelSession.isEmpty()) {
|
||||
URI uri = new URI(tunnelUrl);
|
||||
s_logger.info("Connect to VNC server via tunnel. url: " + tunnelUrl + ", session: "
|
||||
+ tunnelSession);
|
||||
connectClientToVNCServer(tunnelUrl, tunnelSession, websocketUrl);
|
||||
|
||||
ConsoleProxy.ensureRoute(uri.getHost());
|
||||
client.connectTo(uri.getHost(), uri.getPort(), uri.getPath() + "?" + uri.getQuery(),
|
||||
tunnelSession, "https".equalsIgnoreCase(uri.getScheme()));
|
||||
} else {
|
||||
s_logger.info("Connect to VNC server directly. host: " + getClientHostAddress() + ", port: "
|
||||
+ getClientHostPort());
|
||||
ConsoleProxy.ensureRoute(getClientHostAddress());
|
||||
client.connectTo(getClientHostAddress(), getClientHostPort());
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
s_logger.error("Unexpected exception", e);
|
||||
} catch (IOException e) {
|
||||
s_logger.error("Unexpected exception", e);
|
||||
} catch (Throwable e) {
|
||||
s_logger.error("Unexpected exception", e);
|
||||
}
|
||||
|
||||
String ver = client.handshake();
|
||||
session.getRemote().sendBytes(ByteBuffer.wrap(ver.getBytes(), 0, ver.length()));
|
||||
|
||||
byte[] b = client.authenticate(getClientHostPassword());
|
||||
session.getRemote().sendBytes(ByteBuffer.wrap(b, 0, 4));
|
||||
authenticateToVNCServer();
|
||||
|
||||
int readBytes;
|
||||
byte[] b;
|
||||
while (connectionAlive) {
|
||||
b = new byte[100];
|
||||
readBytes = client.read(b);
|
||||
if (readBytes == -1) {
|
||||
break;
|
||||
}
|
||||
if (readBytes > 0) {
|
||||
session.getRemote().sendBytes(ByteBuffer.wrap(b, 0, readBytes));
|
||||
updateFrontEndActivityTime();
|
||||
if (client.isVncOverWebSocketConnection()) {
|
||||
if (client.isVncOverWebSocketConnectionOpen()) {
|
||||
updateFrontEndActivityTime();
|
||||
}
|
||||
connectionAlive = client.isVncOverWebSocketConnectionAlive();
|
||||
} else {
|
||||
b = new byte[100];
|
||||
readBytes = client.read(b);
|
||||
if (readBytes == -1) {
|
||||
break;
|
||||
}
|
||||
if (readBytes > 0) {
|
||||
session.getRemote().sendBytes(ByteBuffer.wrap(b, 0, readBytes));
|
||||
updateFrontEndActivityTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
connectionAlive = false;
|
||||
@ -149,6 +133,55 @@ public class ConsoleProxyNoVncClient implements ConsoleProxyClient {
|
||||
worker.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate to VNC server when not using websockets
|
||||
* @throws IOException
|
||||
*/
|
||||
private void authenticateToVNCServer() throws IOException {
|
||||
if (!client.isVncOverWebSocketConnection()) {
|
||||
String ver = client.handshake();
|
||||
session.getRemote().sendBytes(ByteBuffer.wrap(ver.getBytes(), 0, ver.length()));
|
||||
|
||||
byte[] b = client.authenticate(getClientHostPassword());
|
||||
session.getRemote().sendBytes(ByteBuffer.wrap(b, 0, 4));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a VNC server in one of three possible ways:
|
||||
* - When tunnelUrl and tunnelSession are not empty -> via tunnel
|
||||
* - When websocketUrl is not empty -> connect to websocket
|
||||
* - Otherwise -> connect to TCP port on host directly
|
||||
*/
|
||||
private void connectClientToVNCServer(String tunnelUrl, String tunnelSession, String websocketUrl) {
|
||||
try {
|
||||
if (StringUtils.isNotBlank(websocketUrl)) {
|
||||
s_logger.info("Connect to VNC over websocket URL: " + websocketUrl);
|
||||
client.connectToWebSocket(websocketUrl, session);
|
||||
} else if (tunnelUrl != null && !tunnelUrl.isEmpty() && tunnelSession != null
|
||||
&& !tunnelSession.isEmpty()) {
|
||||
URI uri = new URI(tunnelUrl);
|
||||
s_logger.info("Connect to VNC server via tunnel. url: " + tunnelUrl + ", session: "
|
||||
+ tunnelSession);
|
||||
|
||||
ConsoleProxy.ensureRoute(uri.getHost());
|
||||
client.connectTo(uri.getHost(), uri.getPort(), uri.getPath() + "?" + uri.getQuery(),
|
||||
tunnelSession, "https".equalsIgnoreCase(uri.getScheme()));
|
||||
} else {
|
||||
s_logger.info("Connect to VNC server directly. host: " + getClientHostAddress() + ", port: "
|
||||
+ getClientHostPort());
|
||||
ConsoleProxy.ensureRoute(getClientHostAddress());
|
||||
client.connectTo(getClientHostAddress(), getClientHostPort());
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
s_logger.error("Unexpected exception", e);
|
||||
} catch (IOException e) {
|
||||
s_logger.error("Unexpected exception", e);
|
||||
} catch (Throwable e) {
|
||||
s_logger.error("Unexpected exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setClientParam(ConsoleProxyClientParam param) {
|
||||
this.clientParam = param;
|
||||
}
|
||||
|
||||
@ -20,7 +20,10 @@ import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.spec.KeySpec;
|
||||
|
||||
@ -31,6 +34,8 @@ import javax.crypto.spec.DESKeySpec;
|
||||
|
||||
import com.cloud.consoleproxy.util.Logger;
|
||||
import com.cloud.consoleproxy.util.RawHTTP;
|
||||
import com.cloud.consoleproxy.websocket.WebSocketReverseProxy;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
|
||||
public class NoVncClient {
|
||||
private static final Logger s_logger = Logger.getLogger(NoVncClient.class);
|
||||
@ -39,6 +44,8 @@ public class NoVncClient {
|
||||
private DataInputStream is;
|
||||
private DataOutputStream os;
|
||||
|
||||
private WebSocketReverseProxy webSocketReverseProxy;
|
||||
|
||||
public NoVncClient() {
|
||||
}
|
||||
|
||||
@ -62,6 +69,30 @@ public class NoVncClient {
|
||||
setStreams();
|
||||
}
|
||||
|
||||
// VNC over WebSocket connection helpers
|
||||
public void connectToWebSocket(String websocketUrl, Session session) throws URISyntaxException {
|
||||
webSocketReverseProxy = new WebSocketReverseProxy(new URI(websocketUrl), session);
|
||||
webSocketReverseProxy.connect();
|
||||
}
|
||||
|
||||
public boolean isVncOverWebSocketConnection() {
|
||||
return webSocketReverseProxy != null;
|
||||
}
|
||||
|
||||
public boolean isVncOverWebSocketConnectionOpen() {
|
||||
return isVncOverWebSocketConnection() && webSocketReverseProxy.isOpen();
|
||||
}
|
||||
|
||||
public boolean isVncOverWebSocketConnectionAlive() {
|
||||
return isVncOverWebSocketConnection() && !webSocketReverseProxy.isClosing() && !webSocketReverseProxy.isClosed();
|
||||
}
|
||||
|
||||
public void proxyMsgOverWebSocketConnection(ByteBuffer msg) {
|
||||
if (isVncOverWebSocketConnection()) {
|
||||
webSocketReverseProxy.proxyMsgFromRemoteSessionToEndpoint(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void setStreams() throws IOException {
|
||||
this.is = new DataInputStream(this.socket.getInputStream());
|
||||
this.os = new DataOutputStream(this.socket.getOutputStream());
|
||||
@ -213,7 +244,11 @@ public class NoVncClient {
|
||||
}
|
||||
|
||||
public void write(byte[] b) throws IOException {
|
||||
os.write(b);
|
||||
if (isVncOverWebSocketConnection()) {
|
||||
proxyMsgOverWebSocketConnection(ByteBuffer.wrap(b));
|
||||
} else {
|
||||
os.write(b);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,118 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
package com.cloud.consoleproxy.websocket;
|
||||
|
||||
import com.cloud.consoleproxy.util.Logger;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.java_websocket.client.WebSocketClient;
|
||||
import org.java_websocket.drafts.Draft_6455;
|
||||
import org.java_websocket.extensions.DefaultExtension;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
import org.java_websocket.protocols.Protocol;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Acts as a websocket reverse proxy between the remoteSession and the connected endpoint
|
||||
* - Connects to a websocket endpoint and sends the received data to the remoteSession endpoint
|
||||
* - Receives data from the remoteSession through the receiveProxiedMsg() method and forwards it to the connected endpoint
|
||||
*
|
||||
* remoteSession WebSocketReverseProxy websocket endpoint
|
||||
* data -----------------> receiveProxiedMsg() -----------> data
|
||||
* data <----------------- onMessage() <------------------- data
|
||||
*/
|
||||
public class WebSocketReverseProxy extends WebSocketClient {
|
||||
|
||||
private static final Protocol protocol = new Protocol("binary");
|
||||
private static final DefaultExtension defaultExtension = new DefaultExtension();
|
||||
private static final Draft_6455 draft = new Draft_6455(Collections.singletonList(defaultExtension), Collections.singletonList(protocol));
|
||||
|
||||
private static final Logger logger = Logger.getLogger(WebSocketReverseProxy.class);
|
||||
private Session remoteSession;
|
||||
|
||||
private void acceptAllCerts() {
|
||||
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
|
||||
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
|
||||
return new java.security.cert.X509Certificate[]{};
|
||||
}
|
||||
public void checkClientTrusted(X509Certificate[] chain,
|
||||
String authType) throws CertificateException {
|
||||
}
|
||||
public void checkServerTrusted(X509Certificate[] chain,
|
||||
String authType) throws CertificateException {
|
||||
}
|
||||
}};
|
||||
SSLContext sc;
|
||||
try {
|
||||
sc = SSLContext.getInstance("TLS");
|
||||
sc.init(null, trustAllCerts, new java.security.SecureRandom());
|
||||
SSLSocketFactory factory = sc.getSocketFactory();
|
||||
this.setSocketFactory(factory);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public WebSocketReverseProxy(URI wsUrl, Session session) {
|
||||
super(wsUrl, draft);
|
||||
this.remoteSession = session;
|
||||
acceptAllCerts();
|
||||
setConnectionLostTimeout(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(ServerHandshake serverHandshake) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(int code, String reason, boolean remote) {
|
||||
logger.info("Closing connection to websocket: reason=" + reason + " code=" + code + " remote=" + remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception ex) {
|
||||
logger.error("Error on connection to websocket: " + ex.getLocalizedMessage());
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(ByteBuffer bytes) {
|
||||
try {
|
||||
this.remoteSession.getRemote().sendBytes(bytes);
|
||||
} catch (IOException e) {
|
||||
logger.error("Error proxing msg from websocket to client side: " + e.getLocalizedMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void proxyMsgFromRemoteSessionToEndpoint(ByteBuffer msg) {
|
||||
this.getConnection().send(msg);
|
||||
}
|
||||
}
|
||||
@ -37,6 +37,9 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.vmware.vim25.InvalidStateFaultMsg;
|
||||
import com.vmware.vim25.RuntimeFaultFaultMsg;
|
||||
import com.vmware.vim25.VirtualMachineTicket;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
@ -3534,4 +3537,13 @@ public class VirtualMachineMO extends BaseMO {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire VNC ticket for console proxy.
|
||||
* Since VMware version 7
|
||||
*/
|
||||
public String acquireVncTicket() throws InvalidStateFaultMsg, RuntimeFaultFaultMsg {
|
||||
VirtualMachineTicket ticket = _context.getService().acquireTicket(_mor, "webmks");
|
||||
return ticket.getTicket();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user