From f00b5fc7ac275992c494f2329136568980ae2f19 Mon Sep 17 00:00:00 2001 From: Harikrishna Date: Thu, 15 Apr 2021 12:50:53 +0530 Subject: [PATCH 1/4] server: Fix for the issue of recover VM not able to attach the data disks which are there before destroy in case of VMware (#4493) This PR fixes: #4462 Problem Statement: In case of VMware, when a VM having multiple data disk is destroyed (without expunge) and tried to recover the VM then the previous data disks are not attached to the VM like before destroy. Only root disk is attached to the VM. Root cause: All data disks were removed as part of VM destroy. Only the volumes which are selected to delete (while destroying VM) are supposed to be detached and destroyed. Solution: During VM destroy, detach and destroy only volumes which are selected during VM destroy. Detach the other volumes during expunge of VM. --- .../engine/orchestration/VolumeOrchestrator.java | 7 +++++++ server/src/main/java/com/cloud/vm/UserVmManagerImpl.java | 8 +------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index e4cb8939de3..3e86cea32e2 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -38,6 +38,7 @@ import com.cloud.storage.dao.VMTemplateDetailsDao; import com.cloud.utils.StringUtils; import com.cloud.vm.SecondaryStorageVmVO; import com.cloud.vm.UserVmDetailVO; +import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.SecondaryStorageVmDao; import com.cloud.vm.dao.UserVmDetailsDao; @@ -220,6 +221,8 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati UserVmDetailsDao userVmDetailsDao; @Inject private SecondaryStorageVmDao secondaryStorageVmDao; + @Inject + VolumeApiService _volumeApiService; private final StateMachine2 _volStateMachine; protected List _storagePoolAllocators; @@ -1038,6 +1041,10 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati if (s_logger.isDebugEnabled()) { s_logger.debug("Detaching " + vol); } + VMInstanceVO vm = _userVmDao.findById(vmId); + if (vm.getHypervisorType().equals(HypervisorType.VMware)) { + _volumeApiService.detachVolumeViaDestroyVM(vmId, vol.getId()); + } _volsDao.detachVolume(vol.getId()); } } diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 351304f6aaf..6f668516744 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -2968,13 +2968,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir stopVirtualMachine(vmId, VmDestroyForcestop.value()); - if (vm.getHypervisorType() == HypervisorType.VMware) { - List allVolumes = _volsDao.findByInstance(vm.getId()); - allVolumes.removeIf(vol -> vol.getVolumeType() == Volume.Type.ROOT); - detachVolumesFromVm(allVolumes); - } else { - detachVolumesFromVm(volumesToBeDeleted); - } + detachVolumesFromVm(volumesToBeDeleted); UserVm destroyedVm = destroyVm(vmId, expunge); if (expunge) { From 50da578fded65819ec9924c7add977e4b4e232f9 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 15 Apr 2021 12:59:04 +0530 Subject: [PATCH 2/4] test: reduce vr traceroute hops (#4913) Signed-off-by: Abhishek Kumar --- test/integration/smoke/test_diagnostics.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/integration/smoke/test_diagnostics.py b/test/integration/smoke/test_diagnostics.py index 176f74b9b33..b2de339bb5b 100644 --- a/test/integration/smoke/test_diagnostics.py +++ b/test/integration/smoke/test_diagnostics.py @@ -429,11 +429,11 @@ class TestRemoteDiagnostics(cloudstackTestCase): @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true") def test_10_traceroute_in_vr(self): ''' - Test Arping command execution in VR + Test traceroute command execution in VR ''' # Validate the following: - # 1. Arping command is executed remotely on VR + # 1. Traceroute command is executed remotely on VR list_router_response = list_routers( self.apiclient, @@ -452,13 +452,13 @@ class TestRemoteDiagnostics(cloudstackTestCase): cmd.targetid = router.id cmd.ipaddress = '8.8.4.4' cmd.type = 'traceroute' - cmd.params = "-m 10" + cmd.params = "-m 5" cmd_response = self.apiclient.runDiagnostics(cmd) self.assertEqual( '0', cmd_response.exitcode, - 'Failed to run remote Arping in VR') + 'Failed to run remote Traceroute in VR') @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true") def test_11_traceroute_in_ssvm(self): @@ -488,7 +488,7 @@ class TestRemoteDiagnostics(cloudstackTestCase): cmd.targetid = ssvm.id cmd.ipaddress = '8.8.4.4' cmd.type = 'traceroute' - cmd.params = '-m 10' + cmd.params = '-m 5' cmd_response = self.apiclient.runDiagnostics(cmd) self.assertEqual( @@ -525,7 +525,7 @@ class TestRemoteDiagnostics(cloudstackTestCase): cmd.targetid = cpvm.id cmd.ipaddress = '8.8.4.4' cmd.type = 'traceroute' - cmd.params = '-m 10' + cmd.params = '-m 5' cmd_response = self.apiclient.runDiagnostics(cmd) self.assertEqual( From 8fec222af08b6b7aea40ba984f01ab2676876e47 Mon Sep 17 00:00:00 2001 From: Hoang Nguyen Date: Thu, 15 Apr 2021 14:31:11 +0700 Subject: [PATCH 3/4] ui: fix not find tab name due to language other than English (#4920) Fixes #4919 --- ui/src/views/network/IngressEgressRuleConfigure.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/views/network/IngressEgressRuleConfigure.vue b/ui/src/views/network/IngressEgressRuleConfigure.vue index 3965e99f42f..3d7e7db892e 100644 --- a/ui/src/views/network/IngressEgressRuleConfigure.vue +++ b/ui/src/views/network/IngressEgressRuleConfigure.vue @@ -261,7 +261,7 @@ export default { }, methods: { fetchData () { - this.tabType = this.$parent.tab === 'Ingress Rule' ? 'ingress' : 'egress' + this.tabType = this.$parent.tab === this.$t('label.ingress.rule') ? 'ingress' : 'egress' this.rules = this.tabType === 'ingress' ? this.resource.ingressrule : this.resource.egressrule }, handleAddRule () { From 0302750aacb12e550fe36e154ad53ca964b3001b Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 15 Apr 2021 16:10:14 +0530 Subject: [PATCH 4/4] vmware: Add support for VMware 7 (#4300) --- .../cloud/agent/api/GetVmVncTicketAnswer.java | 34 +++++ .../agent/api/GetVmVncTicketCommand.java | 37 ++++++ .../META-INF/db/schema-41500to41510.sql | 78 +++++++++++- .../vmware/resource/VmwareResource.java | 25 ++++ pom.xml | 2 +- .../servlet/ConsoleProxyClientParam.java | 9 ++ .../cloud/servlet/ConsoleProxyServlet.java | 62 +++++++++ services/console-proxy/server/pom.xml | 5 + .../com/cloud/consoleproxy/ConsoleProxy.java | 6 + .../consoleproxy/ConsoleProxyClientParam.java | 9 ++ .../ConsoleProxyHttpHandlerHelper.java | 4 + .../ConsoleProxyNoVNCHandler.java | 2 + .../consoleproxy/ConsoleProxyNoVncClient.java | 105 ++++++++++------ .../cloud/consoleproxy/vnc/NoVncClient.java | 37 +++++- .../websocket/WebSocketReverseProxy.java | 118 ++++++++++++++++++ .../vmware/mo/VirtualMachineMO.java | 12 ++ 16 files changed, 506 insertions(+), 39 deletions(-) create mode 100644 core/src/main/java/com/cloud/agent/api/GetVmVncTicketAnswer.java create mode 100644 core/src/main/java/com/cloud/agent/api/GetVmVncTicketCommand.java create mode 100644 services/console-proxy/server/src/main/java/com/cloud/consoleproxy/websocket/WebSocketReverseProxy.java diff --git a/core/src/main/java/com/cloud/agent/api/GetVmVncTicketAnswer.java b/core/src/main/java/com/cloud/agent/api/GetVmVncTicketAnswer.java new file mode 100644 index 00000000000..9320098cd02 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/GetVmVncTicketAnswer.java @@ -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; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/GetVmVncTicketCommand.java b/core/src/main/java/com/cloud/agent/api/GetVmVncTicketCommand.java new file mode 100644 index 00000000000..bc119792762 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/GetVmVncTicketCommand.java @@ -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; + } +} diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41500to41510.sql b/engine/schema/src/main/resources/META-INF/db/schema-41500to41510.sql index 21d9dcba03b..859bbd00776 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41500to41510.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41500to41510.sql @@ -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); + diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java index 9963b7589c2..97a10e51f56 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -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()); + } + } } diff --git a/pom.xml b/pom.xml index f497032c69f..3fe33297728 100644 --- a/pom.xml +++ b/pom.xml @@ -166,7 +166,7 @@ 4.0.1 8.5.61 build-217-jenkins-27 - 6.7 + 7.0 0.5.0 6.2.0-3.1 3.1.3 diff --git a/server/src/main/java/com/cloud/servlet/ConsoleProxyClientParam.java b/server/src/main/java/com/cloud/servlet/ConsoleProxyClientParam.java index 3d587c247e8..8f9363df5ba 100644 --- a/server/src/main/java/com/cloud/servlet/ConsoleProxyClientParam.java +++ b/server/src/main/java/com/cloud/servlet/ConsoleProxyClientParam.java @@ -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; + } } diff --git a/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java b/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java index 622a4c84761..b755a84887d 100644 --- a/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java +++ b/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java @@ -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://:443/ticket/ + */ + 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()); } diff --git a/services/console-proxy/server/pom.xml b/services/console-proxy/server/pom.xml index 342bb8a28a1..09431d6e2d8 100644 --- a/services/console-proxy/server/pom.xml +++ b/services/console-proxy/server/pom.xml @@ -65,6 +65,11 @@ websocket-server ${cs.jetty.version} + + org.java-websocket + Java-WebSocket + 1.5.1 + diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java index 3c9d2721ce9..702e9a855d1 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java @@ -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; } diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientParam.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientParam.java index ad2fc25026b..c071f551da7 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientParam.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientParam.java @@ -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; + } } diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java index 4bed1506a28..b7f969a1e57 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java @@ -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"); } } diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java index 1c3b47e8288..91d8e192fd9 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java @@ -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); diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVncClient.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVncClient.java index 353c32da24b..cf0a05de622 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVncClient.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVncClient.java @@ -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; } diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/NoVncClient.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/NoVncClient.java index 9a4372544fc..7be6421714e 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/NoVncClient.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/NoVncClient.java @@ -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); + } } } \ No newline at end of file diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/websocket/WebSocketReverseProxy.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/websocket/WebSocketReverseProxy.java new file mode 100644 index 00000000000..e2f62d6ba16 --- /dev/null +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/websocket/WebSocketReverseProxy.java @@ -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); + } +} diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java index 364526d7b1c..e1ba6b0b87f 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java @@ -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(); + } }