Skip to content

Commit 89bf475

Browse files
authored
Add console session cleanup task (#7132)
1 parent 9c4b3a6 commit 89bf475

File tree

11 files changed

+194
-36
lines changed

11 files changed

+194
-36
lines changed

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,30 @@
1818

1919
import com.cloud.utils.component.Manager;
2020
import org.apache.cloudstack.api.command.user.consoleproxy.ConsoleEndpoint;
21+
import org.apache.cloudstack.framework.config.ConfigKey;
22+
import org.apache.cloudstack.framework.config.Configurable;
2123

22-
public interface ConsoleAccessManager extends Manager {
24+
public interface ConsoleAccessManager extends Manager, Configurable {
25+
26+
ConfigKey<Integer> ConsoleSessionCleanupRetentionHours = new ConfigKey<>("Advanced", Integer.class,
27+
"console.session.cleanup.retention.hours",
28+
"240",
29+
"Determines the hours to keep removed console session records before expunging them",
30+
false,
31+
ConfigKey.Scope.Global);
32+
33+
ConfigKey<Integer> ConsoleSessionCleanupInterval = new ConfigKey<>("Advanced", Integer.class,
34+
"console.session.cleanup.interval",
35+
"180",
36+
"Determines the interval (in hours) to wait between the console session cleanup tasks",
37+
false,
38+
ConfigKey.Scope.Global);
2339

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

2642
boolean isSessionAllowed(String sessionUuid);
2743

2844
void removeSessions(String[] sessionUuids);
2945

30-
void removeSession(String sessionUuid);
46+
void acquireSession(String sessionUuid);
3147
}

engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ public class ConsoleSessionVO {
5454
@Column(name = "host_id")
5555
private long hostId;
5656

57+
@Column(name = "acquired")
58+
private boolean acquired;
59+
5760
@Column(name = "removed")
5861
private Date removed;
5962

@@ -120,4 +123,12 @@ public Date getRemoved() {
120123
public void setRemoved(Date removed) {
121124
this.removed = removed;
122125
}
126+
127+
public boolean isAcquired() {
128+
return acquired;
129+
}
130+
131+
public void setAcquired(boolean acquired) {
132+
this.acquired = acquired;
133+
}
123134
}

engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,15 @@
2222
import com.cloud.vm.ConsoleSessionVO;
2323
import com.cloud.utils.db.GenericDao;
2424

25+
import java.util.Date;
26+
2527
public interface ConsoleSessionDao extends GenericDao<ConsoleSessionVO, Long> {
2628

2729
void removeSession(String sessionUuid);
2830

2931
boolean isSessionAllowed(String sessionUuid);
3032

33+
int expungeSessionsOlderThanDate(Date date);
34+
35+
void acquireSession(String sessionUuid);
3136
}

engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,23 @@
1919

2020
package com.cloud.vm.dao;
2121

22+
import com.cloud.utils.db.SearchBuilder;
23+
import com.cloud.utils.db.SearchCriteria;
2224
import com.cloud.vm.ConsoleSessionVO;
2325
import com.cloud.utils.db.GenericDaoBase;
2426

27+
import java.util.Date;
28+
2529
public class ConsoleSessionDaoImpl extends GenericDaoBase<ConsoleSessionVO, Long> implements ConsoleSessionDao {
2630

31+
private final SearchBuilder<ConsoleSessionVO> searchByRemovedDate;
32+
33+
public ConsoleSessionDaoImpl() {
34+
searchByRemovedDate = createSearchBuilder();
35+
searchByRemovedDate.and("removedNotNull", searchByRemovedDate.entity().getRemoved(), SearchCriteria.Op.NNULL);
36+
searchByRemovedDate.and("removed", searchByRemovedDate.entity().getRemoved(), SearchCriteria.Op.LTEQ);
37+
}
38+
2739
@Override
2840
public void removeSession(String sessionUuid) {
2941
ConsoleSessionVO session = findByUuid(sessionUuid);
@@ -32,6 +44,26 @@ public void removeSession(String sessionUuid) {
3244

3345
@Override
3446
public boolean isSessionAllowed(String sessionUuid) {
35-
return findByUuid(sessionUuid) != null;
47+
ConsoleSessionVO consoleSessionVO = findByUuid(sessionUuid);
48+
if (consoleSessionVO == null) {
49+
return false;
50+
}
51+
return !consoleSessionVO.isAcquired();
52+
}
53+
54+
@Override
55+
public int expungeSessionsOlderThanDate(Date date) {
56+
SearchCriteria<ConsoleSessionVO> searchCriteria = searchByRemovedDate.create();
57+
searchCriteria.setParameters("removed", date);
58+
return expunge(searchCriteria);
59+
}
60+
61+
@Override
62+
public void acquireSession(String sessionUuid) {
63+
ConsoleSessionVO consoleSessionVO = findByUuid(sessionUuid);
64+
consoleSessionVO.setAcquired(true);
65+
update(consoleSessionVO.getId(), consoleSessionVO);
3666
}
67+
68+
3769
}

engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,6 +1480,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`console_session` (
14801480
`user_id` bigint(20) unsigned NOT NULL COMMENT 'User who generated the session',
14811481
`instance_id` bigint(20) unsigned NOT NULL COMMENT 'VM for which the session was generated',
14821482
`host_id` bigint(20) unsigned NOT NULL COMMENT 'Host where the VM was when the session was generated',
1483+
`acquired` int(1) NOT NULL DEFAULT 0 COMMENT 'True if the session was already used',
14831484
`removed` datetime COMMENT 'When the session was removed/used',
14841485
CONSTRAINT `fk_consolesession__account_id` FOREIGN KEY(`account_id`) REFERENCES `cloud`.`account` (`id`),
14851486
CONSTRAINT `fk_consolesession__user_id` FOREIGN KEY(`user_id`) REFERENCES `cloud`.`user`(`id`),

server/src/main/java/com/cloud/consoleproxy/AgentHookBase.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ public AgentControlAnswer onConsoleAccessAuthentication(ConsoleAccessAuthenticat
110110
return new ConsoleAccessAuthenticationAnswer(cmd, false);
111111
}
112112

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

116116
if (!ticket.equals(ticketInUrl)) {
117117
Date now = new Date();

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

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@
5959
import com.cloud.utils.Pair;
6060
import com.cloud.utils.Ternary;
6161
import com.cloud.utils.component.ManagerBase;
62+
import com.cloud.utils.concurrency.NamedThreadFactory;
6263
import com.cloud.utils.db.EntityManager;
64+
import com.cloud.utils.db.GlobalLock;
6365
import com.cloud.utils.exception.CloudRuntimeException;
6466
import com.cloud.vm.ConsoleSessionVO;
6567
import com.cloud.vm.UserVmDetailVO;
@@ -70,6 +72,13 @@
7072
import com.cloud.vm.dao.UserVmDetailsDao;
7173
import com.google.gson.Gson;
7274
import com.google.gson.GsonBuilder;
75+
import org.apache.cloudstack.framework.config.ConfigKey;
76+
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
77+
import org.joda.time.DateTime;
78+
79+
import java.util.concurrent.Executors;
80+
import java.util.concurrent.ScheduledExecutorService;
81+
import java.util.concurrent.TimeUnit;
7382

7483
public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAccessManager {
7584

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

106+
private ScheduledExecutorService executorService = null;
107+
97108
private static KeysManager secretKeysManager;
98109
private final Gson gson = new GsonBuilder().create();
99110

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

124+
@Override
125+
public boolean start() {
126+
int consoleCleanupInterval = ConsoleAccessManager.ConsoleSessionCleanupInterval.value();
127+
if (consoleCleanupInterval > 0) {
128+
s_logger.info(String.format("The ConsoleSessionCleanupTask will run every %s hours", consoleCleanupInterval));
129+
executorService.scheduleWithFixedDelay(new ConsoleSessionCleanupTask(), consoleCleanupInterval, consoleCleanupInterval, TimeUnit.HOURS);
130+
}
131+
return true;
132+
}
133+
134+
@Override
135+
public String getConfigComponentName() {
136+
return ConsoleAccessManager.class.getName();
137+
}
138+
139+
@Override
140+
public ConfigKey<?>[] getConfigKeys() {
141+
return new ConfigKey[] {
142+
ConsoleAccessManager.ConsoleSessionCleanupInterval,
143+
ConsoleAccessManager.ConsoleSessionCleanupRetentionHours
144+
};
145+
}
146+
147+
public class ConsoleSessionCleanupTask extends ManagedContextRunnable {
148+
@Override
149+
protected void runInContext() {
150+
final GlobalLock gcLock = GlobalLock.getInternLock("ConsoleSession.Cleanup.Lock");
151+
try {
152+
if (gcLock.lock(3)) {
153+
try {
154+
reallyRun();
155+
} finally {
156+
gcLock.unlock();
157+
}
158+
}
159+
} finally {
160+
gcLock.releaseRef();
161+
}
162+
}
163+
164+
private void reallyRun() {
165+
if (s_logger.isDebugEnabled()) {
166+
s_logger.debug("Starting ConsoleSessionCleanupTask...");
167+
}
168+
Integer retentionHours = ConsoleAccessManager.ConsoleSessionCleanupRetentionHours.value();
169+
Date dateBefore = DateTime.now().minusHours(retentionHours).toDate();
170+
if (s_logger.isDebugEnabled()) {
171+
s_logger.debug(String.format("Retention hours: %s, checking for removed console session " +
172+
"records to expunge older than: %s", retentionHours, dateBefore));
173+
}
174+
int sessionsExpunged = consoleSessionDao.expungeSessionsOlderThanDate(dateBefore);
175+
if (s_logger.isDebugEnabled()) {
176+
s_logger.debug(sessionsExpunged > 0 ?
177+
String.format("Expunged %s removed console session records", sessionsExpunged) :
178+
"No removed console session records expunged on this cleanup task run");
179+
}
180+
}
181+
}
182+
112183
@Override
113184
public ConsoleEndpoint generateConsoleEndpoint(Long vmId, String extraSecurityToken, String clientAddress) {
114185
try {
@@ -171,11 +242,15 @@ public void removeSessions(String[] sessionUuids) {
171242
}
172243
}
173244

174-
@Override
175-
public void removeSession(String sessionUuid) {
245+
protected void removeSession(String sessionUuid) {
176246
consoleSessionDao.removeSession(sessionUuid);
177247
}
178248

249+
@Override
250+
public void acquireSession(String sessionUuid) {
251+
consoleSessionDao.acquireSession(sessionUuid);
252+
}
253+
179254
protected boolean checkSessionPermission(VirtualMachine vm, Account account) {
180255
if (accountManager.isRootAdmin(account.getId())) {
181256
return true;

services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.Map;
3232
import java.util.Properties;
3333
import java.util.Set;
34+
import java.util.concurrent.ConcurrentHashMap;
3435
import java.util.concurrent.Executor;
3536

3637
import org.apache.commons.lang3.ArrayUtils;
@@ -68,6 +69,7 @@ public class ConsoleProxy {
6869
public static Method ensureRouteMethod;
6970

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

375-
ConsoleProxyGCThread cthread = new ConsoleProxyGCThread(connectionMap);
377+
ConsoleProxyGCThread cthread = new ConsoleProxyGCThread(connectionMap, removedSessionsSet);
376378
cthread.setName("Console Proxy GC Thread");
377379
cthread.start();
378380
}
@@ -540,6 +542,7 @@ public static void removeViewer(ConsoleProxyClient viewer) {
540542
for (Map.Entry<String, ConsoleProxyClient> entry : connectionMap.entrySet()) {
541543
if (entry.getValue() == viewer) {
542544
connectionMap.remove(entry.getKey());
545+
removedSessionsSet.add(viewer.getSessionUuid());
543546
return;
544547
}
545548
}

services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@
1818

1919
import java.io.OutputStreamWriter;
2020
import java.util.ArrayList;
21-
import java.util.Enumeration;
22-
import java.util.Hashtable;
21+
import java.util.Iterator;
2322
import java.util.List;
23+
import java.util.Map;
24+
import java.util.Set;
2425

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

45-
public ConsoleProxyClientStatsCollector(Hashtable<String, ConsoleProxyClient> connMap) {
46+
public ConsoleProxyClientStatsCollector(Map<String, ConsoleProxyClient> connMap) {
4647
setConnections(connMap);
4748
}
4849

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

59-
private void setConnections(Hashtable<String, ConsoleProxyClient> connMap) {
60+
private void setConnections(Map<String, ConsoleProxyClient> connMap) {
6061

6162
ArrayList<ConsoleProxyConnection> conns = new ArrayList<ConsoleProxyConnection>();
62-
Enumeration<String> e = connMap.keys();
63-
while (e.hasMoreElements()) {
63+
Set<String> e = connMap.keySet();
64+
Iterator<String> iterator = e.iterator();
65+
while (iterator.hasNext()) {
6466
synchronized (connMap) {
65-
String key = e.nextElement();
67+
String key = iterator.next();
6668
ConsoleProxyClient client = connMap.get(key);
6769

6870
ConsoleProxyConnection conn = new ConsoleProxyConnection();

0 commit comments

Comments
 (0)