mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-10-26 08:42:29 +01:00 
			
		
		
		
	Merge remote-tracking branch 'origin/4.15'
Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
		
						commit
						f42024714c
					
				| @ -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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -38,6 +38,7 @@ import com.cloud.agent.api.to.DatadiskTO; | |||||||
| import com.cloud.utils.StringUtils; | import com.cloud.utils.StringUtils; | ||||||
| import com.cloud.vm.SecondaryStorageVmVO; | import com.cloud.vm.SecondaryStorageVmVO; | ||||||
| import com.cloud.vm.UserVmDetailVO; | import com.cloud.vm.UserVmDetailVO; | ||||||
|  | import com.cloud.vm.VMInstanceVO; | ||||||
| import com.cloud.vm.VmDetailConstants; | import com.cloud.vm.VmDetailConstants; | ||||||
| import com.cloud.vm.dao.SecondaryStorageVmDao; | import com.cloud.vm.dao.SecondaryStorageVmDao; | ||||||
| import com.cloud.vm.dao.UserVmDetailsDao; | import com.cloud.vm.dao.UserVmDetailsDao; | ||||||
| @ -227,6 +228,8 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati | |||||||
|     UserVmDetailsDao userVmDetailsDao; |     UserVmDetailsDao userVmDetailsDao; | ||||||
|     @Inject |     @Inject | ||||||
|     private SecondaryStorageVmDao secondaryStorageVmDao; |     private SecondaryStorageVmDao secondaryStorageVmDao; | ||||||
|  |     @Inject | ||||||
|  |     VolumeApiService _volumeApiService; | ||||||
| 
 | 
 | ||||||
|     private final StateMachine2<Volume.State, Volume.Event, Volume> _volStateMachine; |     private final StateMachine2<Volume.State, Volume.Event, Volume> _volStateMachine; | ||||||
|     protected List<StoragePoolAllocator> _storagePoolAllocators; |     protected List<StoragePoolAllocator> _storagePoolAllocators; | ||||||
| @ -1102,6 +1105,10 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati | |||||||
|                         if (s_logger.isDebugEnabled()) { |                         if (s_logger.isDebugEnabled()) { | ||||||
|                             s_logger.debug("Detaching " + vol); |                             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()); |                         _volsDao.detachVolume(vol.getId()); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -18,7 +18,6 @@ | |||||||
| --; | --; | ||||||
| -- Schema upgrade from 4.15.0.0 to 4.15.1.0 | -- Schema upgrade from 4.15.0.0 to 4.15.1.0 | ||||||
| --; | --; | ||||||
| 
 |  | ||||||
| -- Correct guest OS names | -- 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='Fedora Linux (32 bit)' WHERE id=320; | ||||||
| UPDATE `cloud`.`guest_os` SET display_name='Mandriva Linux (32 bit)' WHERE id=323; | 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 | -- 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` (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); | 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.GetVmDiskStatsAnswer; | ||||||
| import com.cloud.agent.api.GetVmDiskStatsCommand; | import com.cloud.agent.api.GetVmDiskStatsCommand; | ||||||
| import com.cloud.agent.api.GetVmIpAddressCommand; | 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.GetVmNetworkStatsAnswer; | ||||||
| import com.cloud.agent.api.GetVmNetworkStatsCommand; | import com.cloud.agent.api.GetVmNetworkStatsCommand; | ||||||
| import com.cloud.agent.api.GetVmStatsAnswer; | import com.cloud.agent.api.GetVmStatsAnswer; | ||||||
| @ -582,6 +584,8 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa | |||||||
|                 answer = execute((ValidateVcenterDetailsCommand) cmd); |                 answer = execute((ValidateVcenterDetailsCommand) cmd); | ||||||
|             } else if (clz == SetupPersistentNetworkCommand.class) { |             } else if (clz == SetupPersistentNetworkCommand.class) { | ||||||
|                 answer = execute((SetupPersistentNetworkCommand) cmd); |                 answer = execute((SetupPersistentNetworkCommand) cmd); | ||||||
|  |             } else if (clz == GetVmVncTicketCommand.class) { | ||||||
|  |                 answer = execute((GetVmVncTicketCommand) cmd); | ||||||
|             } else { |             } else { | ||||||
|                 answer = Answer.createUnsupportedCommandAnswer(cmd); |                 answer = Answer.createUnsupportedCommandAnswer(cmd); | ||||||
|             } |             } | ||||||
| @ -7602,4 +7606,25 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa | |||||||
|             return new Answer(cmd, false, "Provided vCenter server address is invalid"); |             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.servlet.version>4.0.1</cs.servlet.version> | ||||||
|         <cs.tomcat-embed-core.version>8.5.61</cs.tomcat-embed-core.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.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.winrm4j.version>0.5.0</cs.winrm4j.version> | ||||||
|         <cs.xapi.version>6.2.0-3.1</cs.xapi.version> |         <cs.xapi.version>6.2.0-3.1</cs.xapi.version> | ||||||
|         <cs.xmlrpc.version>3.1.3</cs.xmlrpc.version> |         <cs.xmlrpc.version>3.1.3</cs.xmlrpc.version> | ||||||
|  | |||||||
| @ -34,6 +34,7 @@ public class ConsoleProxyClientParam { | |||||||
|     private String password; |     private String password; | ||||||
| 
 | 
 | ||||||
|     private String sourceIP; |     private String sourceIP; | ||||||
|  |     private String websocketUrl; | ||||||
| 
 | 
 | ||||||
|     public ConsoleProxyClientParam() { |     public ConsoleProxyClientParam() { | ||||||
|         clientHostPort = 0; |         clientHostPort = 0; | ||||||
| @ -150,4 +151,12 @@ public class ConsoleProxyClientParam { | |||||||
|     public void setSourceIP(String sourceIP) { |     public void setSourceIP(String sourceIP) { | ||||||
|         this.sourceIP = 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.HttpServletResponse; | ||||||
| import javax.servlet.http.HttpSession; | 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.cloudstack.framework.security.keys.KeysManager; | ||||||
| import org.apache.commons.codec.binary.Base64; | import org.apache.commons.codec.binary.Base64; | ||||||
| import org.apache.log4j.Logger; | import org.apache.log4j.Logger; | ||||||
| @ -94,6 +101,8 @@ public class ConsoleProxyServlet extends HttpServlet { | |||||||
|     UserVmDetailsDao _userVmDetailsDao; |     UserVmDetailsDao _userVmDetailsDao; | ||||||
|     @Inject |     @Inject | ||||||
|     KeysManager _keysMgr; |     KeysManager _keysMgr; | ||||||
|  |     @Inject | ||||||
|  |     AgentManager agentManager; | ||||||
| 
 | 
 | ||||||
|     static KeysManager s_keysMgr; |     static KeysManager s_keysMgr; | ||||||
| 
 | 
 | ||||||
| @ -427,6 +436,47 @@ public class ConsoleProxyServlet extends HttpServlet { | |||||||
|         return sb.toString(); |         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) { |     private String composeConsoleAccessUrl(String rootUrl, VirtualMachine vm, HostVO hostVo, InetAddress addr) { | ||||||
|         StringBuffer sb = new StringBuffer(rootUrl); |         StringBuffer sb = new StringBuffer(rootUrl); | ||||||
|         String host = hostVo.getPrivateIpAddress(); |         String host = hostVo.getPrivateIpAddress(); | ||||||
| @ -477,6 +527,10 @@ public class ConsoleProxyServlet extends HttpServlet { | |||||||
|         param.setTicket(ticket); |         param.setTicket(ticket); | ||||||
|         param.setSourceIP(addr != null ? addr.getHostAddress(): null); |         param.setSourceIP(addr != null ? addr.getHostAddress(): null); | ||||||
| 
 | 
 | ||||||
|  |         if (requiresVncOverWebSocketConnection(vm, hostVo)) { | ||||||
|  |             setWebsocketUrl(vm, param); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         if (details != null) { |         if (details != null) { | ||||||
|             param.setLocale(details.getValue()); |             param.setLocale(details.getValue()); | ||||||
|         } |         } | ||||||
| @ -513,6 +567,14 @@ public class ConsoleProxyServlet extends HttpServlet { | |||||||
|         return sb.toString(); |         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) { |     public static String genAccessTicket(String host, String port, String sid, String tag) { | ||||||
|         return genAccessTicket(host, port, sid, tag, new Date()); |         return genAccessTicket(host, port, sid, tag, new Date()); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -3002,13 +3002,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir | |||||||
| 
 | 
 | ||||||
|         stopVirtualMachine(vmId, VmDestroyForcestop.value()); |         stopVirtualMachine(vmId, VmDestroyForcestop.value()); | ||||||
| 
 | 
 | ||||||
|         if (vm.getHypervisorType() == HypervisorType.VMware) { |  | ||||||
|             List<VolumeVO> 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); |         UserVm destroyedVm = destroyVm(vmId, expunge); | ||||||
|         if (expunge) { |         if (expunge) { | ||||||
|  | |||||||
| @ -65,6 +65,11 @@ | |||||||
|             <artifactId>websocket-server</artifactId> |             <artifactId>websocket-server</artifactId> | ||||||
|             <version>${cs.jetty.version}</version> |             <version>${cs.jetty.version}</version> | ||||||
|         </dependency> |         </dependency> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.java-websocket</groupId> | ||||||
|  |             <artifactId>Java-WebSocket</artifactId> | ||||||
|  |             <version>1.5.1</version> | ||||||
|  |         </dependency> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|     <build> |     <build> | ||||||
|         <resources> |         <resources> | ||||||
|  | |||||||
| @ -31,6 +31,7 @@ import java.util.Map; | |||||||
| import java.util.Properties; | import java.util.Properties; | ||||||
| import java.util.concurrent.Executor; | import java.util.concurrent.Executor; | ||||||
| 
 | 
 | ||||||
|  | import com.cloud.utils.StringUtils; | ||||||
| import org.apache.log4j.xml.DOMConfigurator; | import org.apache.log4j.xml.DOMConfigurator; | ||||||
| import org.eclipse.jetty.websocket.api.Session; | import org.eclipse.jetty.websocket.api.Session; | ||||||
| 
 | 
 | ||||||
| @ -172,6 +173,11 @@ public class ConsoleProxy { | |||||||
|         authResult.setHost(param.getClientHostAddress()); |         authResult.setHost(param.getClientHostAddress()); | ||||||
|         authResult.setPort(param.getClientHostPort()); |         authResult.setPort(param.getClientHostPort()); | ||||||
| 
 | 
 | ||||||
|  |         String websocketUrl = param.getWebsocketUrl(); | ||||||
|  |         if (StringUtils.isNotBlank(websocketUrl)) { | ||||||
|  |             return authResult; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         if (standaloneStart) { |         if (standaloneStart) { | ||||||
|             return authResult; |             return authResult; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -36,6 +36,7 @@ public class ConsoleProxyClientParam { | |||||||
|     private String hypervHost; |     private String hypervHost; | ||||||
|     private String username; |     private String username; | ||||||
|     private String password; |     private String password; | ||||||
|  |     private String websocketUrl; | ||||||
| 
 | 
 | ||||||
|     private String sourceIP; |     private String sourceIP; | ||||||
| 
 | 
 | ||||||
| @ -153,4 +154,12 @@ public class ConsoleProxyClientParam { | |||||||
|     public void setSourceIP(String sourceIP) { |     public void setSourceIP(String sourceIP) { | ||||||
|         this.sourceIP = 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()); |                     map.put("password", param.getPassword()); | ||||||
|                 if (param.getSourceIP() != null) |                 if (param.getSourceIP() != null) | ||||||
|                     map.put("sourceIP", param.getSourceIP()); |                     map.put("sourceIP", param.getSourceIP()); | ||||||
|  |                 if (param.getWebsocketUrl() != null) { | ||||||
|  |                     map.put("websocketUrl", param.getWebsocketUrl()); | ||||||
|  |                 } | ||||||
|             } else { |             } else { | ||||||
|                 s_logger.error("Unable to decode token"); |                 s_logger.error("Unable to decode token"); | ||||||
|             } |             } | ||||||
| @ -116,5 +119,6 @@ public class ConsoleProxyHttpHandlerHelper { | |||||||
|         map.remove("hypervHost"); |         map.remove("hypervHost"); | ||||||
|         map.remove("username"); |         map.remove("username"); | ||||||
|         map.remove("password"); |         map.remove("password"); | ||||||
|  |         map.remove("websocketUrl"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -88,6 +88,7 @@ public class ConsoleProxyNoVNCHandler extends WebSocketHandler { | |||||||
|         String username = queryMap.get("username"); |         String username = queryMap.get("username"); | ||||||
|         String password = queryMap.get("password"); |         String password = queryMap.get("password"); | ||||||
|         String sourceIP = queryMap.get("sourceIP"); |         String sourceIP = queryMap.get("sourceIP"); | ||||||
|  |         String websocketUrl = queryMap.get("websocketUrl"); | ||||||
| 
 | 
 | ||||||
|         if (tag == null) |         if (tag == null) | ||||||
|             tag = ""; |             tag = ""; | ||||||
| @ -131,6 +132,7 @@ public class ConsoleProxyNoVNCHandler extends WebSocketHandler { | |||||||
|             param.setHypervHost(hypervHost); |             param.setHypervHost(hypervHost); | ||||||
|             param.setUsername(username); |             param.setUsername(username); | ||||||
|             param.setPassword(password); |             param.setPassword(password); | ||||||
|  |             param.setWebsocketUrl(websocketUrl); | ||||||
|             viewer = ConsoleProxy.getNoVncViewer(param, ajaxSessionIdStr, session); |             viewer = ConsoleProxy.getNoVncViewer(param, ajaxSessionIdStr, session); | ||||||
|         } catch (Exception e) { |         } catch (Exception e) { | ||||||
|             s_logger.warn("Failed to create viewer due to " + e.getMessage(), e); |             s_logger.warn("Failed to create viewer due to " + e.getMessage(), e); | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ | |||||||
| // under the License. | // under the License. | ||||||
| package com.cloud.consoleproxy; | package com.cloud.consoleproxy; | ||||||
| 
 | 
 | ||||||
|  | import com.cloud.utils.StringUtils; | ||||||
| import org.apache.log4j.Logger; | import org.apache.log4j.Logger; | ||||||
| import org.eclipse.jetty.websocket.api.Session; | import org.eclipse.jetty.websocket.api.Session; | ||||||
| import org.eclipse.jetty.websocket.api.extensions.Frame; | import org.eclipse.jetty.websocket.api.extensions.Frame; | ||||||
| @ -96,9 +97,68 @@ public class ConsoleProxyNoVncClient implements ConsoleProxyClient { | |||||||
| 
 | 
 | ||||||
|                     String tunnelUrl = param.getClientTunnelUrl(); |                     String tunnelUrl = param.getClientTunnelUrl(); | ||||||
|                     String tunnelSession = param.getClientTunnelSession(); |                     String tunnelSession = param.getClientTunnelSession(); | ||||||
|  |                     String websocketUrl = param.getWebsocketUrl(); | ||||||
| 
 | 
 | ||||||
|  |                     connectClientToVNCServer(tunnelUrl, tunnelSession, websocketUrl); | ||||||
|  | 
 | ||||||
|  |                     authenticateToVNCServer(); | ||||||
|  | 
 | ||||||
|  |                     int readBytes; | ||||||
|  |                     byte[] b; | ||||||
|  |                     while (connectionAlive) { | ||||||
|  |                         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; | ||||||
|  |                 } catch (IOException e) { | ||||||
|  |                     e.printStackTrace(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         }); | ||||||
|  |         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 { |         try { | ||||||
|                         if (tunnelUrl != null && !tunnelUrl.isEmpty() && tunnelSession != null |             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()) { |                     && !tunnelSession.isEmpty()) { | ||||||
|                 URI uri = new URI(tunnelUrl); |                 URI uri = new URI(tunnelUrl); | ||||||
|                 s_logger.info("Connect to VNC server via tunnel. url: " + tunnelUrl + ", session: " |                 s_logger.info("Connect to VNC server via tunnel. url: " + tunnelUrl + ", session: " | ||||||
| @ -120,33 +180,6 @@ public class ConsoleProxyNoVncClient implements ConsoleProxyClient { | |||||||
|         } catch (Throwable e) { |         } catch (Throwable e) { | ||||||
|             s_logger.error("Unexpected exception", 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)); |  | ||||||
| 
 |  | ||||||
|                     int readBytes; |  | ||||||
|                     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(); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     connectionAlive = false; |  | ||||||
|                 } catch (IOException e) { |  | ||||||
|                     e.printStackTrace(); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|         }); |  | ||||||
|         worker.start(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void setClientParam(ConsoleProxyClientParam param) { |     private void setClientParam(ConsoleProxyClientParam param) { | ||||||
|  | |||||||
| @ -20,7 +20,10 @@ import java.io.DataInputStream; | |||||||
| import java.io.DataOutputStream; | import java.io.DataOutputStream; | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.net.Socket; | import java.net.Socket; | ||||||
|  | import java.net.URI; | ||||||
|  | import java.net.URISyntaxException; | ||||||
| import java.net.UnknownHostException; | import java.net.UnknownHostException; | ||||||
|  | import java.nio.ByteBuffer; | ||||||
| import java.nio.charset.Charset; | import java.nio.charset.Charset; | ||||||
| import java.security.spec.KeySpec; | 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.Logger; | ||||||
| import com.cloud.consoleproxy.util.RawHTTP; | import com.cloud.consoleproxy.util.RawHTTP; | ||||||
|  | import com.cloud.consoleproxy.websocket.WebSocketReverseProxy; | ||||||
|  | import org.eclipse.jetty.websocket.api.Session; | ||||||
| 
 | 
 | ||||||
| public class NoVncClient { | public class NoVncClient { | ||||||
|     private static final Logger s_logger = Logger.getLogger(NoVncClient.class); |     private static final Logger s_logger = Logger.getLogger(NoVncClient.class); | ||||||
| @ -39,6 +44,8 @@ public class NoVncClient { | |||||||
|     private DataInputStream is; |     private DataInputStream is; | ||||||
|     private DataOutputStream os; |     private DataOutputStream os; | ||||||
| 
 | 
 | ||||||
|  |     private WebSocketReverseProxy webSocketReverseProxy; | ||||||
|  | 
 | ||||||
|     public NoVncClient() { |     public NoVncClient() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -62,6 +69,30 @@ public class NoVncClient { | |||||||
|         setStreams(); |         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 { |     private void setStreams() throws IOException { | ||||||
|         this.is = new DataInputStream(this.socket.getInputStream()); |         this.is = new DataInputStream(this.socket.getInputStream()); | ||||||
|         this.os = new DataOutputStream(this.socket.getOutputStream()); |         this.os = new DataOutputStream(this.socket.getOutputStream()); | ||||||
| @ -213,7 +244,11 @@ public class NoVncClient { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void write(byte[] b) throws IOException { |     public void write(byte[] b) throws IOException { | ||||||
|  |         if (isVncOverWebSocketConnection()) { | ||||||
|  |             proxyMsgOverWebSocketConnection(ByteBuffer.wrap(b)); | ||||||
|  |         } else { | ||||||
|             os.write(b); |             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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -429,11 +429,11 @@ class TestRemoteDiagnostics(cloudstackTestCase): | |||||||
|     @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true") |     @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true") | ||||||
|     def test_10_traceroute_in_vr(self): |     def test_10_traceroute_in_vr(self): | ||||||
|         ''' |         ''' | ||||||
|         Test Arping command execution in VR |         Test traceroute command execution in VR | ||||||
|         ''' |         ''' | ||||||
| 
 | 
 | ||||||
|         # Validate the following: |         # Validate the following: | ||||||
|         # 1. Arping command is executed remotely on VR |         # 1. Traceroute command is executed remotely on VR | ||||||
| 
 | 
 | ||||||
|         list_router_response = list_routers( |         list_router_response = list_routers( | ||||||
|             self.apiclient, |             self.apiclient, | ||||||
| @ -452,13 +452,13 @@ class TestRemoteDiagnostics(cloudstackTestCase): | |||||||
|         cmd.targetid = router.id |         cmd.targetid = router.id | ||||||
|         cmd.ipaddress = '8.8.4.4' |         cmd.ipaddress = '8.8.4.4' | ||||||
|         cmd.type = 'traceroute' |         cmd.type = 'traceroute' | ||||||
|         cmd.params = "-m 10" |         cmd.params = "-m 5" | ||||||
|         cmd_response = self.apiclient.runDiagnostics(cmd) |         cmd_response = self.apiclient.runDiagnostics(cmd) | ||||||
| 
 | 
 | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|             '0', |             '0', | ||||||
|             cmd_response.exitcode, |             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") |     @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="true") | ||||||
|     def test_11_traceroute_in_ssvm(self): |     def test_11_traceroute_in_ssvm(self): | ||||||
| @ -488,7 +488,7 @@ class TestRemoteDiagnostics(cloudstackTestCase): | |||||||
|         cmd.targetid = ssvm.id |         cmd.targetid = ssvm.id | ||||||
|         cmd.ipaddress = '8.8.4.4' |         cmd.ipaddress = '8.8.4.4' | ||||||
|         cmd.type = 'traceroute' |         cmd.type = 'traceroute' | ||||||
|         cmd.params = '-m 10' |         cmd.params = '-m 5' | ||||||
|         cmd_response = self.apiclient.runDiagnostics(cmd) |         cmd_response = self.apiclient.runDiagnostics(cmd) | ||||||
| 
 | 
 | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
| @ -525,7 +525,7 @@ class TestRemoteDiagnostics(cloudstackTestCase): | |||||||
|         cmd.targetid = cpvm.id |         cmd.targetid = cpvm.id | ||||||
|         cmd.ipaddress = '8.8.4.4' |         cmd.ipaddress = '8.8.4.4' | ||||||
|         cmd.type = 'traceroute' |         cmd.type = 'traceroute' | ||||||
|         cmd.params = '-m 10' |         cmd.params = '-m 5' | ||||||
|         cmd_response = self.apiclient.runDiagnostics(cmd) |         cmd_response = self.apiclient.runDiagnostics(cmd) | ||||||
| 
 | 
 | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|  | |||||||
| @ -261,7 +261,7 @@ export default { | |||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     fetchData () { |     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 |       this.rules = this.tabType === 'ingress' ? this.resource.ingressrule : this.resource.egressrule | ||||||
|     }, |     }, | ||||||
|     handleAddRule () { |     handleAddRule () { | ||||||
|  | |||||||
| @ -37,6 +37,9 @@ import java.util.concurrent.Executors; | |||||||
| import java.util.concurrent.Future; | import java.util.concurrent.Future; | ||||||
| 
 | 
 | ||||||
| import com.cloud.utils.exception.CloudRuntimeException; | 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.collections.CollectionUtils; | ||||||
| import org.apache.commons.lang.StringUtils; | import org.apache.commons.lang.StringUtils; | ||||||
| import org.apache.log4j.Logger; | import org.apache.log4j.Logger; | ||||||
| @ -3534,4 +3537,13 @@ public class VirtualMachineMO extends BaseMO { | |||||||
|             return false; |             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