Skip to content

Commit 7abc435

Browse files
committed
Allow multiple console viewers for KVM user VMs
1 parent 659f675 commit 7abc435

4 files changed

Lines changed: 99 additions & 1 deletion

File tree

PendingReleaseNotes

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,8 @@ example.ver.1 > example.ver.2:
3939
which can now be attached to Instances. This is to prevent the Secondary
4040
Storage to grow to enormous sizes as Linux Distributions keep growing in
4141
size while a stripped down Linux should fit on a 2.88MB floppy.
42+
43+
KVM console:
44+
* Added the global setting `consoleproxy.kvm.multiple.viewers.enabled` to allow
45+
multiple users to simultaneously access the console of the same KVM user VM.
46+
The setting defaults to false to preserve the existing single-viewer behavior.

api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManager.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ public interface ConsoleAccessManager extends Manager, Configurable {
4141
false,
4242
ConfigKey.Scope.Global);
4343

44+
ConfigKey<Boolean> KvmMultipleConsoleViewersEnabled = new ConfigKey<>("Advanced", Boolean.class,
45+
"consoleproxy.kvm.multiple.viewers.enabled",
46+
"false",
47+
"Determines whether multiple simultaneous console viewers are allowed for the same KVM user VM",
48+
true,
49+
ConfigKey.Scope.Global);
50+
4451
ConsoleEndpoint generateConsoleEndpoint(Long vmId, String extraSecurityToken, String clientAddress);
4552

4653
boolean isSessionAllowed(String sessionUuid);

server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ public String getConfigComponentName() {
160160
public ConfigKey<?>[] getConfigKeys() {
161161
return new ConfigKey[] {
162162
ConsoleAccessManager.ConsoleSessionCleanupInterval,
163-
ConsoleAccessManager.ConsoleSessionCleanupRetentionHours
163+
ConsoleAccessManager.ConsoleSessionCleanupRetentionHours,
164+
ConsoleAccessManager.KvmMultipleConsoleViewersEnabled
164165
};
165166
}
166167

@@ -474,6 +475,10 @@ protected Pair<String, Integer> getHostAndPortForKVMMaintenanceHostIfNeeded(Host
474475
return null;
475476
}
476477

478+
protected boolean isKvmMultipleConsoleViewersEnabled() {
479+
return ConsoleAccessManager.KvmMultipleConsoleViewersEnabled.value();
480+
}
481+
477482
protected ConsoleConnectionDetails getConsoleConnectionDetails(VirtualMachine vm, HostVO host) {
478483
String locale = null;
479484
String tag = vm.getUuid();
@@ -507,6 +512,13 @@ protected ConsoleConnectionDetails getConsoleConnectionDetails(VirtualMachine vm
507512
.getValue()));
508513
logger.debug("HyperV RDP port for {} on {} is: {}", vm, host, details.getPort());
509514
}
515+
516+
if (Hypervisor.HypervisorType.KVM.equals(host.getHypervisorType()) &&
517+
VirtualMachine.Type.User.equals(vm.getType()) &&
518+
isKvmMultipleConsoleViewersEnabled()) {
519+
details.setSessionRequiresNewViewer(true);
520+
}
521+
510522
return details;
511523
}
512524

server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,80 @@ public void getConsoleConnectionDetailsReturnsDetailsForKVMHypervisor() {
545545
Assert.assertEquals(port, result.getPort());
546546
}
547547

548+
@Test
549+
public void getConsoleConnectionDetailsRequiresNewViewerForKVMUserVmWhenMultipleViewersEnabled() {
550+
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
551+
HostVO host = Mockito.mock(HostVO.class);
552+
String hostAddress = "192.168.1.100";
553+
int port = 5900;
554+
Pair<String, Integer> hostPortInfo = new Pair<>(hostAddress, port);
555+
556+
Mockito.when(vm.getUuid()).thenReturn("vm-uuid");
557+
Mockito.when(vm.getHostName()).thenReturn("vm-hostname");
558+
Mockito.when(vm.getVncPassword()).thenReturn("vnc-password");
559+
Mockito.when(vm.getType()).thenReturn(VirtualMachine.Type.User);
560+
Mockito.when(host.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
561+
Mockito.when(vmInstanceDetailsDao.listDetailsKeyPairs(Mockito.anyLong(), Mockito.anyList())).thenReturn(Map.of());
562+
Mockito.when(managementServer.getVncPort(vm)).thenReturn(hostPortInfo);
563+
Mockito.doReturn(new Ternary<>(hostAddress, null, null))
564+
.when(consoleAccessManager).parseHostInfo(Mockito.anyString());
565+
Mockito.doReturn(true).when(consoleAccessManager).isKvmMultipleConsoleViewersEnabled();
566+
567+
ConsoleConnectionDetails result = consoleAccessManager.getConsoleConnectionDetails(vm, host);
568+
569+
Assert.assertNotNull(result);
570+
Assert.assertTrue(result.isSessionRequiresNewViewer());
571+
}
572+
573+
@Test
574+
public void getConsoleConnectionDetailsDoesNotRequireNewViewerForKVMUserVmWhenMultipleViewersDisabled() {
575+
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
576+
HostVO host = Mockito.mock(HostVO.class);
577+
String hostAddress = "192.168.1.100";
578+
int port = 5900;
579+
Pair<String, Integer> hostPortInfo = new Pair<>(hostAddress, port);
580+
581+
Mockito.when(vm.getUuid()).thenReturn("vm-uuid");
582+
Mockito.when(vm.getHostName()).thenReturn("vm-hostname");
583+
Mockito.when(vm.getVncPassword()).thenReturn("vnc-password");
584+
Mockito.when(vm.getType()).thenReturn(VirtualMachine.Type.User);
585+
Mockito.when(host.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
586+
Mockito.when(vmInstanceDetailsDao.listDetailsKeyPairs(Mockito.anyLong(), Mockito.anyList())).thenReturn(Map.of());
587+
Mockito.when(managementServer.getVncPort(vm)).thenReturn(hostPortInfo);
588+
Mockito.doReturn(new Ternary<>(hostAddress, null, null))
589+
.when(consoleAccessManager).parseHostInfo(Mockito.anyString());
590+
Mockito.doReturn(false).when(consoleAccessManager).isKvmMultipleConsoleViewersEnabled();
591+
592+
ConsoleConnectionDetails result = consoleAccessManager.getConsoleConnectionDetails(vm, host);
593+
594+
Assert.assertNotNull(result);
595+
Assert.assertFalse(result.isSessionRequiresNewViewer());
596+
}
597+
598+
@Test
599+
public void getConsoleConnectionDetailsDoesNotRequireNewViewerForKVMSystemVmWhenMultipleViewersEnabled() {
600+
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
601+
HostVO host = Mockito.mock(HostVO.class);
602+
String hostAddress = "192.168.1.100";
603+
int port = 5900;
604+
Pair<String, Integer> hostPortInfo = new Pair<>(hostAddress, port);
605+
606+
Mockito.when(vm.getUuid()).thenReturn("vm-uuid");
607+
Mockito.when(vm.getHostName()).thenReturn("vm-hostname");
608+
Mockito.when(vm.getVncPassword()).thenReturn("vnc-password");
609+
Mockito.when(vm.getType()).thenReturn(VirtualMachine.Type.DomainRouter);
610+
Mockito.when(host.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
611+
Mockito.when(vmInstanceDetailsDao.listDetailsKeyPairs(Mockito.anyLong(), Mockito.anyList())).thenReturn(Map.of());
612+
Mockito.when(managementServer.getVncPort(vm)).thenReturn(hostPortInfo);
613+
Mockito.doReturn(new Ternary<>(hostAddress, null, null))
614+
.when(consoleAccessManager).parseHostInfo(Mockito.anyString());
615+
616+
ConsoleConnectionDetails result = consoleAccessManager.getConsoleConnectionDetails(vm, host);
617+
618+
Assert.assertNotNull(result);
619+
Assert.assertFalse(result.isSessionRequiresNewViewer());
620+
}
621+
548622
@Test
549623
public void getConsoleConnectionDetailsReturnsDetailsWithRDPForHyperV() {
550624
VirtualMachine vm = Mockito.mock(VirtualMachine.class);

0 commit comments

Comments
 (0)