Skip to content
Merged
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,14 +18,30 @@

import com.cloud.utils.component.Manager;
import org.apache.cloudstack.api.command.user.consoleproxy.ConsoleEndpoint;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;

public interface ConsoleAccessManager extends Manager {
public interface ConsoleAccessManager extends Manager, Configurable {

ConfigKey<Integer> ConsoleSessionCleanupRetentionHours = new ConfigKey<>("Advanced", Integer.class,
"console.session.cleanup.retention.hours",
"240",
"Determines the hours to keep removed console session records before expunging them",
false,
ConfigKey.Scope.Global);

ConfigKey<Integer> ConsoleSessionCleanupInterval = new ConfigKey<>("Advanced", Integer.class,
"console.session.cleanup.interval",
"180",
"Determines the interval (in hours) to wait between the console session cleanup tasks",
false,
ConfigKey.Scope.Global);

ConsoleEndpoint generateConsoleEndpoint(Long vmId, String extraSecurityToken, String clientAddress);

boolean isSessionAllowed(String sessionUuid);

void removeSessions(String[] sessionUuids);

void removeSession(String sessionUuid);
void acquireSession(String sessionUuid);
}
11 changes: 11 additions & 0 deletions engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public class ConsoleSessionVO {
@Column(name = "host_id")
private long hostId;

@Column(name = "acquired")
private boolean acquired;

@Column(name = "removed")
private Date removed;

Expand Down Expand Up @@ -120,4 +123,12 @@ public Date getRemoved() {
public void setRemoved(Date removed) {
this.removed = removed;
}

public boolean isAcquired() {
return acquired;
}

public void setAcquired(boolean acquired) {
this.acquired = acquired;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@
import com.cloud.vm.ConsoleSessionVO;
import com.cloud.utils.db.GenericDao;

import java.util.Date;

public interface ConsoleSessionDao extends GenericDao<ConsoleSessionVO, Long> {

void removeSession(String sessionUuid);

boolean isSessionAllowed(String sessionUuid);

int expungeSessionsOlderThanDate(Date date);

void acquireSession(String sessionUuid);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,23 @@

package com.cloud.vm.dao;

import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.vm.ConsoleSessionVO;
import com.cloud.utils.db.GenericDaoBase;

import java.util.Date;

public class ConsoleSessionDaoImpl extends GenericDaoBase<ConsoleSessionVO, Long> implements ConsoleSessionDao {

private final SearchBuilder<ConsoleSessionVO> searchByRemovedDate;

public ConsoleSessionDaoImpl() {
searchByRemovedDate = createSearchBuilder();
searchByRemovedDate.and("removedNotNull", searchByRemovedDate.entity().getRemoved(), SearchCriteria.Op.NNULL);
searchByRemovedDate.and("removed", searchByRemovedDate.entity().getRemoved(), SearchCriteria.Op.LTEQ);
}

@Override
public void removeSession(String sessionUuid) {
ConsoleSessionVO session = findByUuid(sessionUuid);
Expand All @@ -32,6 +44,26 @@ public void removeSession(String sessionUuid) {

@Override
public boolean isSessionAllowed(String sessionUuid) {
return findByUuid(sessionUuid) != null;
ConsoleSessionVO consoleSessionVO = findByUuid(sessionUuid);
if (consoleSessionVO == null) {
return false;
}
return !consoleSessionVO.isAcquired();
}

@Override
public int expungeSessionsOlderThanDate(Date date) {
SearchCriteria<ConsoleSessionVO> searchCriteria = searchByRemovedDate.create();
searchCriteria.setParameters("removed", date);
return expunge(searchCriteria);
}

@Override
public void acquireSession(String sessionUuid) {
ConsoleSessionVO consoleSessionVO = findByUuid(sessionUuid);
consoleSessionVO.setAcquired(true);
update(consoleSessionVO.getId(), consoleSessionVO);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`console_session` (
`user_id` bigint(20) unsigned NOT NULL COMMENT 'User who generated the session',
`instance_id` bigint(20) unsigned NOT NULL COMMENT 'VM for which the session was generated',
`host_id` bigint(20) unsigned NOT NULL COMMENT 'Host where the VM was when the session was generated',
`acquired` int(1) NOT NULL DEFAULT 0 COMMENT 'True if the session was already used',
Copy link
Copy Markdown
Contributor

@GutoVeronezi GutoVeronezi Jan 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nvazquez, could this field be a date? This way we would know the exactly time user used the session and for how many time, as removed will be set when the user closes the console.

`removed` datetime COMMENT 'When the session was removed/used',
CONSTRAINT `fk_consolesession__account_id` FOREIGN KEY(`account_id`) REFERENCES `cloud`.`account` (`id`),
CONSTRAINT `fk_consolesession__user_id` FOREIGN KEY(`user_id`) REFERENCES `cloud`.`user`(`id`),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ public AgentControlAnswer onConsoleAccessAuthentication(ConsoleAccessAuthenticat
return new ConsoleAccessAuthenticationAnswer(cmd, false);
}

s_logger.debug(String.format("Removing session [%s] as it was just used.", sessionUuid));
consoleAccessManager.removeSession(sessionUuid);
s_logger.debug(String.format("Acquiring session [%s] as it was just used.", sessionUuid));
consoleAccessManager.acquireSession(sessionUuid);

if (!ticket.equals(ticketInUrl)) {
Date now = new Date();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.concurrency.NamedThreadFactory;
import com.cloud.utils.db.EntityManager;
import com.cloud.utils.db.GlobalLock;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.ConsoleSessionVO;
import com.cloud.vm.UserVmDetailVO;
Expand All @@ -70,6 +72,13 @@
import com.cloud.vm.dao.UserVmDetailsDao;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.joda.time.DateTime;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAccessManager {

Expand All @@ -94,6 +103,8 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce
@Inject
private ConsoleSessionDao consoleSessionDao;

private ScheduledExecutorService executorService = null;

private static KeysManager secretKeysManager;
private final Gson gson = new GsonBuilder().create();

Expand All @@ -106,9 +117,69 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
ConsoleAccessManagerImpl.secretKeysManager = keysManager;
executorService = Executors.newScheduledThreadPool(1, new NamedThreadFactory("ConsoleSession-Scavenger"));
return super.configure(name, params);
}

@Override
public boolean start() {
int consoleCleanupInterval = ConsoleAccessManager.ConsoleSessionCleanupInterval.value();
if (consoleCleanupInterval > 0) {
s_logger.info(String.format("The ConsoleSessionCleanupTask will run every %s hours", consoleCleanupInterval));
executorService.scheduleWithFixedDelay(new ConsoleSessionCleanupTask(), consoleCleanupInterval, consoleCleanupInterval, TimeUnit.HOURS);
}
return true;
}

@Override
public String getConfigComponentName() {
return ConsoleAccessManager.class.getName();
}

@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey[] {
ConsoleAccessManager.ConsoleSessionCleanupInterval,
ConsoleAccessManager.ConsoleSessionCleanupRetentionHours
};
}

public class ConsoleSessionCleanupTask extends ManagedContextRunnable {
@Override
protected void runInContext() {
final GlobalLock gcLock = GlobalLock.getInternLock("ConsoleSession.Cleanup.Lock");
try {
if (gcLock.lock(3)) {
try {
reallyRun();
} finally {
gcLock.unlock();
}
}
} finally {
gcLock.releaseRef();
}
}

private void reallyRun() {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Starting ConsoleSessionCleanupTask...");
}
Integer retentionHours = ConsoleAccessManager.ConsoleSessionCleanupRetentionHours.value();
Date dateBefore = DateTime.now().minusHours(retentionHours).toDate();
if (s_logger.isDebugEnabled()) {
s_logger.debug(String.format("Retention hours: %s, checking for removed console session " +
"records to expunge older than: %s", retentionHours, dateBefore));
}
int sessionsExpunged = consoleSessionDao.expungeSessionsOlderThanDate(dateBefore);
if (s_logger.isDebugEnabled()) {
s_logger.debug(sessionsExpunged > 0 ?
String.format("Expunged %s removed console session records", sessionsExpunged) :
"No removed console session records expunged on this cleanup task run");
}
}
}

@Override
public ConsoleEndpoint generateConsoleEndpoint(Long vmId, String extraSecurityToken, String clientAddress) {
try {
Expand Down Expand Up @@ -171,11 +242,15 @@ public void removeSessions(String[] sessionUuids) {
}
}

@Override
public void removeSession(String sessionUuid) {
protected void removeSession(String sessionUuid) {
consoleSessionDao.removeSession(sessionUuid);
}

@Override
public void acquireSession(String sessionUuid) {
consoleSessionDao.acquireSession(sessionUuid);
}

protected boolean checkSessionPermission(VirtualMachine vm, Account account) {
if (accountManager.isRootAdmin(account.getId())) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;

import org.apache.commons.lang3.ArrayUtils;
Expand Down Expand Up @@ -68,6 +69,7 @@ public class ConsoleProxy {
public static Method ensureRouteMethod;

static Hashtable<String, ConsoleProxyClient> connectionMap = new Hashtable<String, ConsoleProxyClient>();
static Set<String> removedSessionsSet = ConcurrentHashMap.newKeySet();
static int httpListenPort = 80;
static int httpCmdListenPort = 8001;
static int reconnectMaxRetry = 5;
Expand Down Expand Up @@ -372,7 +374,7 @@ public static void start(Properties conf) {
s_logger.info("HTTP command port is disabled");
}

ConsoleProxyGCThread cthread = new ConsoleProxyGCThread(connectionMap);
ConsoleProxyGCThread cthread = new ConsoleProxyGCThread(connectionMap, removedSessionsSet);
cthread.setName("Console Proxy GC Thread");
cthread.start();
}
Expand Down Expand Up @@ -540,6 +542,7 @@ public static void removeViewer(ConsoleProxyClient viewer) {
for (Map.Entry<String, ConsoleProxyClient> entry : connectionMap.entrySet()) {
if (entry.getValue() == viewer) {
connectionMap.remove(entry.getKey());
removedSessionsSet.add(viewer.getSessionUuid());
return;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@

import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
Expand All @@ -42,7 +43,7 @@ public void setRemovedSessions(List<String> removed) {
removedSessions.addAll(removed);
}

public ConsoleProxyClientStatsCollector(Hashtable<String, ConsoleProxyClient> connMap) {
public ConsoleProxyClientStatsCollector(Map<String, ConsoleProxyClient> connMap) {
setConnections(connMap);
}

Expand All @@ -56,13 +57,14 @@ public void getStatsReport(OutputStreamWriter os) {
gson.toJson(this, os);
}

private void setConnections(Hashtable<String, ConsoleProxyClient> connMap) {
private void setConnections(Map<String, ConsoleProxyClient> connMap) {

ArrayList<ConsoleProxyConnection> conns = new ArrayList<ConsoleProxyConnection>();
Enumeration<String> e = connMap.keys();
while (e.hasMoreElements()) {
Set<String> e = connMap.keySet();
Iterator<String> iterator = e.iterator();
while (iterator.hasNext()) {
synchronized (connMap) {
String key = e.nextElement();
String key = iterator.next();
ConsoleProxyClient client = connMap.get(key);

ConsoleProxyConnection conn = new ConsoleProxyConnection();
Expand Down
Loading