Skip to content

Commit

Permalink
Report console connection events.
Browse files Browse the repository at this point in the history
  • Loading branch information
mnlipp committed Nov 10, 2024
1 parent 9019907 commit c7b65ca
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 3 deletions.
11 changes: 11 additions & 0 deletions deploy/crds/vms-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1457,6 +1457,11 @@ spec:
Amount of memory in use.
type: string
default: "0"
consoleClient:
description: >-
The hostname of the currently connected client.
type: string
default: ""
displayPasswordSerial:
description: >-
Counts changes of the display password. Set to -1
Expand All @@ -1473,6 +1478,12 @@ spec:
lastTransitionTime: "1970-01-01T00:00:00Z"
reason: Creation
message: "Creation of CR"
- type: ConsoleConnected
status: "False"
observedGeneration: 1
lastTransitionTime: "1970-01-01T00:00:00Z"
reason: Creation
message: "Creation of CR"
type: array
items:
type: object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM;
Expand All @@ -50,6 +53,8 @@
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange;
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState;
import org.jdrupes.vmoperator.runner.qemu.events.ShutdownEvent;
import org.jdrupes.vmoperator.runner.qemu.events.SpiceConnectedEvent;
import org.jdrupes.vmoperator.runner.qemu.events.SpiceDisconnectedEvent;
import org.jdrupes.vmoperator.util.GsonPtr;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Component;
Expand Down Expand Up @@ -363,4 +368,91 @@ public void onDisplayPasswordChanged(DisplayPasswordChanged event)
public void onShutdown(ShutdownEvent event) throws ApiException {
shutdownByGuest = event.byGuest();
}

/**
* On spice connected.
*
* @param event the event
* @throws ApiException the api exception
*/
@Handler
public void onSpiceConnected(SpiceConnectedEvent event)
throws ApiException {
if (vmStub == null) {
return;
}
vmStub.updateStatus(from -> {
JsonObject status = from.status();
status.addProperty("consoleClient", event.clientHost());
updateConsoleConnectedCondition(from, status, true);
return status;
});

// Log event
var evt = new EventsV1Event()
.reportingController(VM_OP_GROUP + "/" + APP_NAME)
.action("ConsoleConnectionUpdate")
.reason("Connection from " + event.clientHost());
K8s.createEvent(apiClient, vmStub.model().get(), evt);
}

/**
* On spice disconnected.
*
* @param event the event
* @throws ApiException the api exception
*/
@Handler
public void onSpiceDisconnected(SpiceDisconnectedEvent event)
throws ApiException {
if (vmStub == null) {
return;
}
vmStub.updateStatus(from -> {
JsonObject status = from.status();
status.addProperty("consoleClient", "");
updateConsoleConnectedCondition(from, status, false);
return status;
});

// Log event
var evt = new EventsV1Event()
.reportingController(VM_OP_GROUP + "/" + APP_NAME)
.action("ConsoleConnectionUpdate")
.reason("Disconnected from " + event.clientHost());
K8s.createEvent(apiClient, vmStub.model().get(), evt);
}

private void updateConsoleConnectedCondition(VmDefinitionModel from,
JsonObject status, boolean connected) {
// Optimize, as we can get this several times
var current = status.getAsJsonArray("conditions").asList().stream()
.map(cond -> (JsonObject) cond)
.filter(cond -> "ConsoleConnected"
.equals(cond.get("type").getAsString()))
.findFirst()
.map(cond -> "True".equals(cond.get("status").getAsString()));
if (current.isPresent() && current.get() == connected) {
return;
}

// Do update
final var condition = Map.of("type", "ConsoleConnected",
"status", connected ? "True" : "False",
"observedGeneration", from.getMetadata().getGeneration(),
"reason", connected ? "Connected" : "Disconnected",
"lastTransitionTime", Instant.now().toString());
List<Object> toReplace = new ArrayList<>(List.of(condition));
List<Object> newConds
= status.getAsJsonArray("conditions").asList().stream()
.map(cond -> (JsonObject) cond)
.map(cond -> "ConsoleConnected"
.equals(cond.get("type").getAsString())
? toReplace.remove(0)
: cond)
.collect(Collectors.toCollection(() -> new ArrayList<>()));
newConds.addAll(toReplace);
status.add("conditions",
apiClient.getJSON().getGson().toJsonTree(newConds));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public class MonitorEvent extends Event<Void> {
* The kind of monitor event.
*/
public enum Kind {
READY, POWERDOWN, DEVICE_TRAY_MOVED, BALLOON_CHANGE, SHUTDOWN
READY, POWERDOWN, DEVICE_TRAY_MOVED, BALLOON_CHANGE, SHUTDOWN,
SPICE_CONNECTED, SPICE_DISCONNECTED
}

private final Kind kind;
Expand All @@ -49,8 +50,7 @@ public enum Kind {
@SuppressWarnings("PMD.TooFewBranchesForASwitchStatement")
public static Optional<MonitorEvent> from(JsonNode response) {
try {
var kind = MonitorEvent.Kind
.valueOf(response.get("event").asText());
var kind = Kind.valueOf(response.get("event").asText());
switch (kind) {
case POWERDOWN:
return Optional.of(new PowerdownEvent(kind, null));
Expand All @@ -63,6 +63,14 @@ public static Optional<MonitorEvent> from(JsonNode response) {
case SHUTDOWN:
return Optional
.of(new ShutdownEvent(kind, response.get(EVENT_DATA)));
case SPICE_CONNECTED:
return Optional
.of(new SpiceConnectedEvent(kind,
response.get(EVENT_DATA)));
case SPICE_DISCONNECTED:
return Optional
.of(new SpiceDisconnectedEvent(kind,
response.get(EVENT_DATA)));
default:
return Optional
.of(new MonitorEvent(kind, response.get(EVENT_DATA)));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* VM-Operator
* Copyright (C) 2023 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package org.jdrupes.vmoperator.runner.qemu.events;

import com.fasterxml.jackson.databind.JsonNode;

/**
* Signals a connection from a client.
*/
public class SpiceConnectedEvent extends SpiceEvent {

/**
* Instantiates a new spice connected event.
*
* @param kind the kind
* @param data the data
*/
public SpiceConnectedEvent(Kind kind, JsonNode data) {
super(kind, data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* VM-Operator
* Copyright (C) 2023 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package org.jdrupes.vmoperator.runner.qemu.events;

import com.fasterxml.jackson.databind.JsonNode;

/**
* Signals a connection from a client.
*/
public class SpiceDisconnectedEvent extends SpiceEvent {

/**
* Instantiates a new spice disconnected event.
*
* @param kind the kind
* @param data the data
*/
public SpiceDisconnectedEvent(Kind kind, JsonNode data) {
super(kind, data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* VM-Operator
* Copyright (C) 2023 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package org.jdrupes.vmoperator.runner.qemu.events;

import com.fasterxml.jackson.databind.JsonNode;

/**
* Signals a connection from a client.
*/
public class SpiceEvent extends MonitorEvent {

/**
* Instantiates a new tray moved.
*
* @param kind the kind
* @param data the data
*/
public SpiceEvent(Kind kind, JsonNode data) {
super(kind, data);
}

/**
* Returns the client's host.
*
* @return the client's host address
*/
public String clientHost() {
return data().get("client").get("host").asText();
}
}

0 comments on commit c7b65ca

Please sign in to comment.