Skip to content

Commit 5ad052f

Browse files
committed
Delay console opening for pool VMs.
1 parent c582763 commit 5ad052f

File tree

6 files changed

+191
-119
lines changed

6 files changed

+191
-119
lines changed

org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/GetDisplayPassword.java

Lines changed: 0 additions & 74 deletions
This file was deleted.
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* VM-Operator
3+
* Copyright (C) 2024 Michael N. Lipp
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as
7+
* published by the Free Software Foundation, either version 3 of the
8+
* License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
package org.jdrupes.vmoperator.manager.events;
20+
21+
import org.jdrupes.vmoperator.common.VmDefinition;
22+
import org.jgrapes.core.Event;
23+
24+
/**
25+
* Gets the current display secret and optionally updates it.
26+
*/
27+
@SuppressWarnings("PMD.DataClass")
28+
public class PrepareConsole extends Event<String> {
29+
30+
private final VmDefinition vmDef;
31+
private final String user;
32+
private final boolean loginUser;
33+
34+
/**
35+
* Instantiates a new request for the display secret.
36+
* After handling the event, a result of `null` means that
37+
* no password is needed. No result means that the console
38+
* is not accessible.
39+
*
40+
* @param vmDef the vm name
41+
* @param user the requesting user
42+
* @param loginUser login the user
43+
*/
44+
public PrepareConsole(VmDefinition vmDef, String user,
45+
boolean loginUser) {
46+
this.vmDef = vmDef;
47+
this.user = user;
48+
this.loginUser = loginUser;
49+
}
50+
51+
/**
52+
* Instantiates a new request for the display secret.
53+
* After handling the event, a result of `null` means that
54+
* no password is needed. No result means that the console
55+
* is not accessible.
56+
*
57+
* @param vmDef the vm name
58+
* @param user the requesting user
59+
*/
60+
public PrepareConsole(VmDefinition vmDef, String user) {
61+
this(vmDef, user, false);
62+
}
63+
64+
/**
65+
* Gets the vm definition.
66+
*
67+
* @return the vm definition
68+
*/
69+
public VmDefinition vmDefinition() {
70+
return vmDef;
71+
}
72+
73+
/**
74+
* Return the id of the user who has requested the password.
75+
*
76+
* @return the string
77+
*/
78+
public String user() {
79+
return user;
80+
}
81+
82+
/**
83+
* Checks if the user should be logged in before allowing access.
84+
*
85+
* @return the loginUser
86+
*/
87+
public boolean loginUser() {
88+
return loginUser;
89+
}
90+
91+
/**
92+
* Returns `true` if a password is available. May only be called
93+
* when the event is completed. Note that the password returned
94+
* by {@link #password()} may be `null`, indicating that no password
95+
* is needed.
96+
*
97+
* @return true, if successful
98+
*/
99+
public boolean passwordAvailable() {
100+
if (!isDone()) {
101+
throw new IllegalStateException("Event is not done.");
102+
}
103+
return !currentResults().isEmpty();
104+
}
105+
106+
/**
107+
* Return the password. May only be called when the event has been
108+
* completed with a valid result (see {@link #passwordAvailable()}).
109+
*
110+
* @return the password. A value of `null` means that no password
111+
* is required.
112+
*/
113+
public String password() {
114+
if (!isDone() || currentResults().isEmpty()) {
115+
throw new IllegalStateException("Event is not done.");
116+
}
117+
return currentResults().get(0);
118+
}
119+
}

org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretMonitor.java

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
import static org.jdrupes.vmoperator.manager.Constants.DATA_DISPLAY_PASSWORD;
5151
import static org.jdrupes.vmoperator.manager.Constants.DATA_PASSWORD_EXPIRY;
5252
import org.jdrupes.vmoperator.manager.events.ChannelDictionary;
53-
import org.jdrupes.vmoperator.manager.events.GetDisplayPassword;
53+
import org.jdrupes.vmoperator.manager.events.PrepareConsole;
5454
import org.jdrupes.vmoperator.manager.events.VmChannel;
5555
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
5656
import org.jgrapes.core.Channel;
@@ -72,7 +72,7 @@ public class DisplaySecretMonitor
7272
extends AbstractMonitor<V1Secret, V1SecretList, VmChannel> {
7373

7474
private int passwordValidity = 10;
75-
private final List<PendingGet> pendingGets
75+
private final List<PendingGet> pendingPrepares
7676
= Collections.synchronizedList(new LinkedList<>());
7777
private final ChannelDictionary<String, VmChannel, ?> channelDictionary;
7878

@@ -178,49 +178,59 @@ private void patchPod(K8sClient client, Response<V1Secret> change)
178178
*/
179179
@Handler
180180
@SuppressWarnings("PMD.StringInstantiation")
181-
public void onGetDisplaySecrets(GetDisplayPassword event, VmChannel channel)
181+
public void onPrepareConsole(PrepareConsole event, VmChannel channel)
182182
throws ApiException {
183183
// Update console user in status
184184
var vmStub = VmDefinitionStub.get(client(),
185185
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM),
186186
event.vmDefinition().namespace(), event.vmDefinition().name());
187-
vmStub.updateStatus(from -> {
187+
var optVmDef = vmStub.updateStatus(from -> {
188188
JsonObject status = from.statusJson();
189189
status.addProperty("consoleUser", event.user());
190190
return status;
191191
});
192+
if (optVmDef.isEmpty()) {
193+
return;
194+
}
195+
var vmDef = optVmDef.get();
196+
197+
// Check if access is possible
198+
if (event.loginUser()
199+
? !vmDef.conditionStatus("Booted").orElse(false)
200+
: !vmDef.conditionStatus("Running").orElse(false)) {
201+
return;
202+
}
192203

193204
// Look for secret
194205
ListOptions options = new ListOptions();
195206
options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
196207
+ "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET + ","
197-
+ "app.kubernetes.io/instance="
198-
+ event.vmDefinition().metadata().getName());
199-
var stubs = K8sV1SecretStub.list(client(),
200-
event.vmDefinition().namespace(), options);
208+
+ "app.kubernetes.io/instance=" + vmDef.name());
209+
var stubs = K8sV1SecretStub.list(client(), vmDef.namespace(), options);
201210
if (stubs.isEmpty()) {
202211
// No secret means no password for this VM wanted
212+
event.setResult(null);
203213
return;
204214
}
205215
var stub = stubs.iterator().next();
206216

207217
// Check validity
208-
var model = stub.model().get();
218+
var secret = stub.model().get();
209219
@SuppressWarnings("PMD.StringInstantiation")
210-
var expiry = Optional.ofNullable(model.getData()
220+
var expiry = Optional.ofNullable(secret.getData()
211221
.get(DATA_PASSWORD_EXPIRY)).map(b -> new String(b)).orElse(null);
212-
if (model.getData().get(DATA_DISPLAY_PASSWORD) != null
222+
if (secret.getData().get(DATA_DISPLAY_PASSWORD) != null
213223
&& stillValid(expiry)) {
214224
// Fixed secret, don't touch
215225
event.setResult(
216-
new String(model.getData().get(DATA_DISPLAY_PASSWORD)));
226+
new String(secret.getData().get(DATA_DISPLAY_PASSWORD)));
217227
return;
218228
}
219229
updatePassword(stub, event);
220230
}
221231

222232
@SuppressWarnings("PMD.StringInstantiation")
223-
private void updatePassword(K8sV1SecretStub stub, GetDisplayPassword event)
233+
private void updatePassword(K8sV1SecretStub stub, PrepareConsole event)
224234
throws ApiException {
225235
SecureRandom random = null;
226236
try {
@@ -242,9 +252,9 @@ private void updatePassword(K8sV1SecretStub stub, GetDisplayPassword event)
242252
var pending = new PendingGet(event,
243253
event.vmDefinition().displayPasswordSerial().orElse(0L) + 1,
244254
new CompletionLock(event, 1500));
245-
pendingGets.add(pending);
255+
pendingPrepares.add(pending);
246256
Event.onCompletion(event, e -> {
247-
pendingGets.remove(pending);
257+
pendingPrepares.remove(pending);
248258
});
249259

250260
// Update, will (eventually) trigger confirmation
@@ -273,9 +283,9 @@ private boolean stillValid(String expiry) {
273283
@Handler
274284
@SuppressWarnings("PMD.AvoidSynchronizedStatement")
275285
public void onVmDefChanged(VmDefChanged event, Channel channel) {
276-
synchronized (pendingGets) {
286+
synchronized (pendingPrepares) {
277287
String vmName = event.vmDefinition().name();
278-
for (var pending : pendingGets) {
288+
for (var pending : pendingPrepares) {
279289
if (pending.event.vmDefinition().name().equals(vmName)
280290
&& event.vmDefinition().displayPasswordSerial()
281291
.map(s -> s >= pending.expectedSerial).orElse(false)) {
@@ -293,7 +303,7 @@ public void onVmDefChanged(VmDefChanged event, Channel channel) {
293303
*/
294304
@SuppressWarnings("PMD.DataClass")
295305
private static class PendingGet {
296-
public final GetDisplayPassword event;
306+
public final PrepareConsole event;
297307
public final long expectedSerial;
298308
public final CompletionLock lock;
299309

@@ -303,7 +313,7 @@ private static class PendingGet {
303313
* @param event the event
304314
* @param expectedSerial the expected serial
305315
*/
306-
public PendingGet(GetDisplayPassword event, long expectedSerial,
316+
public PendingGet(PrepareConsole event, long expectedSerial,
307317
CompletionLock lock) {
308318
super();
309319
this.event = event;

org.jdrupes.vmoperator.vmaccess/src/org/jdrupes/vmoperator/vmaccess/VmAccess.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@
4949
import org.jdrupes.vmoperator.common.VmDefinition.Permission;
5050
import org.jdrupes.vmoperator.common.VmPool;
5151
import org.jdrupes.vmoperator.manager.events.AssignVm;
52-
import org.jdrupes.vmoperator.manager.events.GetDisplayPassword;
5352
import org.jdrupes.vmoperator.manager.events.GetPools;
5453
import org.jdrupes.vmoperator.manager.events.GetVms;
5554
import org.jdrupes.vmoperator.manager.events.GetVms.VmData;
5655
import org.jdrupes.vmoperator.manager.events.ModifyVm;
56+
import org.jdrupes.vmoperator.manager.events.PrepareConsole;
5757
import org.jdrupes.vmoperator.manager.events.ResetVm;
5858
import org.jdrupes.vmoperator.manager.events.VmChannel;
5959
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
@@ -808,18 +808,23 @@ private void openConsole(ConsoleConnection channel, ResourceModel model,
808808
Map.of("autoClose", 5_000, "type", "Warning")));
809809
return;
810810
}
811-
var pwQuery = Event.onCompletion(new GetDisplayPassword(vmDef, user),
812-
e -> {
813-
vmDef.extra()
814-
.map(xtra -> xtra.connectionFile(e.password().orElse(null),
815-
preferredIpVersion, deleteConnectionFile))
816-
.ifPresent(
817-
cf -> channel.respond(new NotifyConletView(type(),
818-
model.getConletId(), "openConsole", cf)));
819-
});
811+
var pwQuery = Event.onCompletion(new PrepareConsole(vmDef, user,
812+
model.mode() == ResourceModel.Mode.POOL),
813+
e -> gotPassword(channel, model, vmDef, e));
820814
fire(pwQuery, vmChannel);
821815
}
822816

817+
private void gotPassword(ConsoleConnection channel, ResourceModel model,
818+
VmDefinition vmDef, PrepareConsole event) {
819+
if (!event.passwordAvailable()) {
820+
return;
821+
}
822+
vmDef.extra().map(xtra -> xtra.connectionFile(event.password(),
823+
preferredIpVersion, deleteConnectionFile))
824+
.ifPresent(cf -> channel.respond(new NotifyConletView(type(),
825+
model.getConletId(), "openConsole", cf)));
826+
}
827+
823828
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
824829
"PMD.UseLocaleWithCaseConversions" })
825830
private void selectResource(NotifyConletModel event,

0 commit comments

Comments
 (0)