Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.zstack.header.Component;
import org.zstack.header.console.ConsoleBackend;
import org.zstack.header.console.ConsoleConstants;
import org.zstack.header.console.ConsoleHypervisorBackend;
import org.zstack.header.console.ConsoleInventory;
import org.zstack.header.console.ConsoleProxy;
import org.zstack.header.console.ConsoleProxyInventory;
Expand All @@ -38,6 +39,7 @@
import org.zstack.header.identity.SessionInventory;
import org.zstack.header.managementnode.ManagementNodeReadyExtensionPoint;
import org.zstack.header.rest.RESTFacade;
import org.zstack.header.host.HypervisorType;
import org.zstack.header.vm.VmInstanceInventory;
import org.zstack.header.vm.VmInstanceVO;
import org.zstack.utils.Utils;
Expand Down Expand Up @@ -72,6 +74,8 @@ public abstract class AbstractConsoleProxyBackend implements ConsoleBackend, Com
protected ErrorFacade errf;
@Autowired
protected Timer timer;
@Autowired
protected ConsoleManager consoleMgr;

protected static final String ANSIBLE_PLAYBOOK_NAME = "consoleproxy.py";

Expand Down Expand Up @@ -138,19 +142,24 @@ public void grantConsoleAccess(final SessionInventory session, final VmInstanceI
return;
}

boolean recreateExpiredConsoleSession = false;
if (timer.getCurrentTimestamp().after(vo.getExpiredDate())) {
dbf.remove(vo);
ConsoleProxy proxy = getConsoleProxy(session, vm);
establishNewProxy(proxy, vm, complete);
return;
ConsoleHypervisorBackend backend = consoleMgr.getHypervisorConsoleBackend(HypervisorType.valueOf(vm.getHypervisorType()));
if (!backend.requireExclusiveConsoleSessionRenewal()) {
dbf.remove(vo);
ConsoleProxy proxy = getConsoleProxy(session, vm);
establishNewProxy(proxy, vm, complete);
return;
}
recreateExpiredConsoleSession = true;
}

String hostIp = getHostIp(vm);
if (hostIp == null) {
throw new OperationFailureException(operr(ORG_ZSTACK_CONSOLE_10012, "cannot find host IP of the vm[uuid:%s], is the vm running???", vm.getUuid()));
}

if (vo.getTargetHostname().equals(hostIp)) {
if (vo.getTargetHostname().equals(hostIp) && !recreateExpiredConsoleSession) {
// vm is on the same host
updateConsoleProxy(vm, vo, complete);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.Message;
import org.zstack.header.message.MessageReply;
import org.zstack.header.host.HypervisorType;
import org.zstack.header.vm.VmInstanceInventory;
import org.zstack.utils.*;
import org.zstack.utils.function.Function;
Expand Down Expand Up @@ -199,7 +200,11 @@ protected ConsoleProxy getConsoleProxy(SessionInventory session, VmInstanceInven
inv.setProxyHostname(mgmtIp);
inv.setAgentIp("127.0.0.1");
inv.setAgentType(getConsoleBackendType());
inv.setToken(session.getUuid() + "_" + vm.getUuid());
String token = session.getUuid() + "_" + vm.getUuid();
if (consoleMgr.getHypervisorConsoleBackend(HypervisorType.valueOf(vm.getHypervisorType())).requireExclusiveConsoleSessionRenewal()) {
token = token + "_" + Platform.getUuid();
}
inv.setToken(token);
inv.setVmInstanceUuid(vm.getUuid());
return new ConsoleProxyBase(inv, getAgentPort());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ public interface ConsoleHypervisorBackend {
HypervisorType getConsoleBackendHypervisorType();

void generateConsoleUrl(VmInstanceInventory vm, ReturnValueCompletion<ConsoleUrl> complete);

default boolean requireExclusiveConsoleSessionRenewal() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ import org.zstack.core.db.DatabaseFacade
import org.zstack.core.db.Q
import org.zstack.core.gc.GCStatus
import org.zstack.header.console.ConsoleConstants
import org.zstack.header.console.ConsoleHypervisorBackend
import org.zstack.header.console.ConsoleProxyAgentVO
import org.zstack.header.console.ConsoleProxyCommands
import org.zstack.header.console.ConsoleProxyVO
import org.zstack.header.console.ConsoleProxyVO_
import org.zstack.header.console.ConsoleUrl
import org.zstack.header.core.ReturnValueCompletion
import org.zstack.header.host.HypervisorType
import org.zstack.header.vm.KvmReportVmShutdownFromGuestEventMsg
import org.zstack.sdk.ConsoleInventory
import org.zstack.sdk.ConsoleProxyAgentInventory
Expand All @@ -26,6 +30,9 @@ import org.zstack.testlib.EnvSpec
import org.zstack.testlib.HttpError
import org.zstack.testlib.SubCase
import org.zstack.utils.data.SizeUnit
import org.zstack.utils.gson.JSONObjectUtil

import java.sql.Timestamp

class ConsoleProxyCase extends SubCase {
EnvSpec env
Expand Down Expand Up @@ -127,12 +134,154 @@ class ConsoleProxyCase extends SubCase {
void test() {
env.create {
testSelectConsoleProxyByClientIpVersion()
testExpiredConsoleProxyUsesLegacyRenewal()
testExpiredConsoleProxyUsesExclusiveRenewal()
testConsoleProxyCleanupOnGuestShutdown()
testUpdateConsoleProxyAgent()
testConsoleProxyGC()
}
}

void testExpiredConsoleProxyUsesLegacyRenewal() {
VmInstanceInventory vm = env.inventoryByName("vm")
int deleteProxyCount = 0
int establishProxyCount = 0
ConsoleManagerImpl consoleMgr = bean(ConsoleManagerImpl.class)
def backendsField = ConsoleManagerImpl.class.getDeclaredField("consoleHypervisorBackends")
backendsField.accessible = true
Map<String, ConsoleHypervisorBackend> backends = backendsField.get(consoleMgr)
assert !backends.get(vm.hypervisorType).requireExclusiveConsoleSessionRenewal()

env.afterSimulator(ConsoleConstants.CONSOLE_PROXY_ESTABLISH_PROXY_PATH) { rsp, HttpEntity<String> e ->
ConsoleProxyCommands.EstablishProxyCmd cmd = JSONObjectUtil.toObject(e.body, ConsoleProxyCommands.EstablishProxyCmd.class)
if (cmd.vmUuid == vm.uuid) {
establishProxyCount++
}
return rsp
}
env.afterSimulator(ConsoleConstants.CONSOLE_PROXY_DELETE_PROXY_PATH) { rsp, HttpEntity<String> e ->
ConsoleProxyCommands.DeleteProxyCmd cmd = JSONObjectUtil.toObject(e.body, ConsoleProxyCommands.DeleteProxyCmd.class)
if (cmd.vmUuid == vm.uuid) {
deleteProxyCount++
}
return rsp
}

ConsoleInventory console = requestConsoleAccess {
vmInstanceUuid = vm.uuid
} as ConsoleInventory

ConsoleProxyVO vo = Q.New(ConsoleProxyVO.class)
.eq(ConsoleProxyVO_.vmInstanceUuid, vm.uuid)
.find()
String firstToken = console.token
console = requestConsoleAccess {
vmInstanceUuid = vm.uuid
} as ConsoleInventory
assert establishProxyCount == 2
assert deleteProxyCount == 0
assert console.token == firstToken

vo.expiredDate = new Timestamp(System.currentTimeMillis() - 1000)
dbf.update(vo)

console = requestConsoleAccess {
vmInstanceUuid = vm.uuid
} as ConsoleInventory

assert establishProxyCount == 3
assert deleteProxyCount == 0
assert console.token == "${env.session.uuid}_${vm.uuid}"
}

void testExpiredConsoleProxyUsesExclusiveRenewal() {
VmInstanceInventory vm = env.inventoryByName("vm")
ConsoleProxyVO current = Q.New(ConsoleProxyVO.class)
.eq(ConsoleProxyVO_.vmInstanceUuid, vm.uuid)
.find()
dbf.remove(current)

ConsoleManagerImpl consoleMgr = bean(ConsoleManagerImpl.class)
def backendsField = ConsoleManagerImpl.class.getDeclaredField("consoleHypervisorBackends")
backendsField.accessible = true
Map<String, ConsoleHypervisorBackend> backends = backendsField.get(consoleMgr)
ConsoleHypervisorBackend originalBackend = backends.get(vm.hypervisorType)
backends.put(vm.hypervisorType, new ConsoleHypervisorBackend() {
@Override
HypervisorType getConsoleBackendHypervisorType() {
return originalBackend.getConsoleBackendHypervisorType()
}

@Override
void generateConsoleUrl(org.zstack.header.vm.VmInstanceInventory inventory, ReturnValueCompletion<ConsoleUrl> completion) {
originalBackend.generateConsoleUrl(inventory, completion)
}

@Override
boolean requireExclusiveConsoleSessionRenewal() {
return true
}
})

int deleteProxyCount = 0
int establishProxyCount = 0
env.afterSimulator(ConsoleConstants.CONSOLE_PROXY_ESTABLISH_PROXY_PATH) { rsp, HttpEntity<String> e ->
ConsoleProxyCommands.EstablishProxyCmd cmd = JSONObjectUtil.toObject(e.body, ConsoleProxyCommands.EstablishProxyCmd.class)
if (cmd.vmUuid == vm.uuid) {
establishProxyCount++
}
return rsp
}
env.afterSimulator(ConsoleConstants.CONSOLE_PROXY_DELETE_PROXY_PATH) { rsp, HttpEntity<String> e ->
ConsoleProxyCommands.DeleteProxyCmd cmd = JSONObjectUtil.toObject(e.body, ConsoleProxyCommands.DeleteProxyCmd.class)
if (cmd.vmUuid == vm.uuid) {
deleteProxyCount++
}
return rsp
}

try {
ConsoleInventory console = requestConsoleAccess {
vmInstanceUuid = vm.uuid
} as ConsoleInventory
String firstToken = console.token
String tokenPrefix = "${env.session.uuid}_${vm.uuid}_"
assert firstToken.startsWith(tokenPrefix)
assert firstToken.substring(tokenPrefix.length()) ==~ /[0-9a-f]{32}/

console = requestConsoleAccess {
vmInstanceUuid = vm.uuid
} as ConsoleInventory
assert establishProxyCount == 2
assert deleteProxyCount == 0
assert console.token == firstToken

ConsoleProxyVO vo = Q.New(ConsoleProxyVO.class)
.eq(ConsoleProxyVO_.vmInstanceUuid, vm.uuid)
.find()
vo.expiredDate = new Timestamp(System.currentTimeMillis() - 1000)
dbf.update(vo)

console = requestConsoleAccess {
vmInstanceUuid = vm.uuid
} as ConsoleInventory

assert establishProxyCount == 3
assert deleteProxyCount == 1
assert console.token.startsWith(tokenPrefix)
assert console.token.substring(tokenPrefix.length()) ==~ /[0-9a-f]{32}/
assert console.token != firstToken
} finally {
backends.put(vm.hypervisorType, originalBackend)
ConsoleProxyVO vo = Q.New(ConsoleProxyVO.class)
.eq(ConsoleProxyVO_.vmInstanceUuid, vm.uuid)
.find()
if (vo != null) {
dbf.remove(vo)
}
}
}

void testSelectConsoleProxyByClientIpVersion() {
def selectConsoleProxyHostname = ConsoleManagerImpl.class.getDeclaredMethod("selectConsoleProxyHostname",
String.class, String.class, String.class, String.class, Boolean.TYPE, String.class, String.class, String.class)
Expand Down