console: don't require a console proxy for direct-URL consoles
diff --git a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java
index 237135b..9014a0b 100644
--- a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java
+++ b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java
@@ -418,10 +418,6 @@
         }
 
         String rootUrl = managementServer.getConsoleAccessUrlRoot(vmId);
-        if (rootUrl == null) {
-            throw new CloudRuntimeException("Console access will be ready in a few minutes. Please try it again later.");
-        }
-
         ConsoleEndpoint consoleEndpoint = composeConsoleAccessEndpoint(rootUrl, vm, host, clientAddress, sessionUuid, extraSecurityToken);
         logger.debug("The console URL is: " + consoleEndpoint.getUrl());
         return consoleEndpoint;
@@ -522,6 +518,11 @@
             return new ConsoleEndpoint(true, result.getDirectUrl());
         }
 
+        // Only consoles served through the console proxy need its URL; direct consoles do not
+        if (rootUrl == null) {
+            throw new CloudRuntimeException("Console access will be ready in a few minutes. Please try it again later.");
+        }
+
         String ticket = genAccessTicket(result.getHost(), String.valueOf(result.getPort()), result.getSid(),
                 result.getTag(), sessionUuid);
         ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(getEncryptorPassword());
diff --git a/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java
index 97e6295..416c9f0 100644
--- a/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java
+++ b/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java
@@ -60,6 +60,7 @@
 import com.cloud.utils.Pair;
 import com.cloud.utils.Ternary;
 import com.cloud.utils.db.EntityManager;
+import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.vm.ConsoleSessionVO;
 import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VirtualMachineManager;
@@ -787,4 +788,45 @@
         Assert.assertNull(endpoint.getWebsocketExtra());
         Assert.assertNull(endpoint.getWebsocketHost());
     }
+
+    @Test
+    public void composeConsoleAccessEndpointReturnsDirectUrlWhenNoConsoleProxyIsAvailable() {
+        String url = "url";
+        long vmId = 100L;
+        long hostId = 1L;
+        String sessionUuid = UUID.randomUUID().toString();
+        String addr = "addr";
+        ConsoleConnectionDetails details = new ConsoleConnectionDetails("password", "en", "tag", null);
+        details.setDirectUrl(url);
+        details.setModeFromExternalProtocol("direct");
+        VirtualMachine vm = Mockito.mock(VirtualMachine.class);
+        Mockito.when(vm.getId()).thenReturn(vmId);
+        HostVO host = Mockito.mock(HostVO.class);
+        Mockito.when(host.getId()).thenReturn(hostId);
+        Mockito.doReturn(details).when(consoleAccessManager).getConsoleConnectionDetails(vm, host);
+        Mockito.doNothing().when(consoleAccessManager).persistConsoleSession(sessionUuid, vmId, hostId, addr);
+
+        ConsoleEndpoint endpoint = consoleAccessManager.composeConsoleAccessEndpoint(null, vm, host, addr, sessionUuid, "");
+
+        Assert.assertTrue(endpoint.isResult());
+        Assert.assertEquals(url, endpoint.getUrl());
+        Mockito.verify(consoleAccessManager).persistConsoleSession(sessionUuid, vmId, hostId, addr);
+    }
+
+    @Test
+    public void composeConsoleAccessEndpointThrowsWhenProxiedConsoleHasNoConsoleProxy() {
+        VirtualMachine vm = Mockito.mock(VirtualMachine.class);
+        HostVO host = Mockito.mock(HostVO.class);
+        ConsoleConnectionDetails details = new ConsoleConnectionDetails("password", "en", "tag", null);
+        details.setHost("192.168.1.100");
+        details.setPort(5900);
+        Mockito.doReturn(details).when(consoleAccessManager).getConsoleConnectionDetails(vm, host);
+
+        CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class,
+                () -> consoleAccessManager.composeConsoleAccessEndpoint(null, vm, host, "addr", "sessionUuid", ""));
+
+        Assert.assertEquals("Console access will be ready in a few minutes. Please try it again later.", exception.getMessage());
+        Mockito.verify(consoleAccessManager, Mockito.never()).persistConsoleSession(Mockito.anyString(), Mockito.anyLong(),
+                Mockito.anyLong(), Mockito.anyString());
+    }
 }