Skip to content

Commit

Permalink
Merge branch 'main' into testing
Browse files Browse the repository at this point in the history
  • Loading branch information
mnlipp committed Nov 14, 2024
2 parents 5ec7f58 + 69507b5 commit 93a1a2b
Show file tree
Hide file tree
Showing 15 changed files with 479 additions and 211 deletions.
6 changes: 6 additions & 0 deletions deploy/crds/vms-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1467,6 +1467,12 @@ spec:
The hostname of the currently connected client.
type: string
default: ""
consoleUser:
description: >-
The id of the user who has last requested a console
connection.
type: string
default: ""
displayPasswordSerial:
description: >-
Counts changes of the display password. Set to -1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,33 @@ public Optional<O> model() throws ApiException {
}

/**
* Updates the object's status.
* Updates the object's status, retrying for the given number of times
* if the update fails due to a conflict.
*
* @param object the current state of the object (passed to `status`)
* @param status function that returns the new status
* @param retries the retries
* @return the updated model or empty if not successful
* @throws ApiException the api exception
*/
@SuppressWarnings("PMD.AssignmentInOperand")
public Optional<O> updateStatus(O object,
Function<O, Object> status, int retries) throws ApiException {
while (true) {
try {
return K8s.optional(api.updateStatus(object, status));
} catch (ApiException e) {
if (HttpURLConnection.HTTP_CONFLICT != e.getCode()
|| retries-- <= 0) {
throw e;
}
}
}
}

/**
* Updates the object's status, retrying up to 16 times if there
* is a conflict.
*
* @param object the current state of the object (passed to `status`)
* @param status function that returns the new status
Expand All @@ -202,7 +228,7 @@ public Optional<O> model() throws ApiException {
*/
public Optional<O> updateStatus(O object,
Function<O, Object> status) throws ApiException {
return K8s.optional(api.updateStatus(object, status));
return updateStatus(object, status, 16);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,17 @@
public class GetDisplayPassword extends Event<String> {

private final VmDefinition vmDef;
private final String user;

/**
* Instantiates a new returns the display secret.
* Instantiates a new request for the display secret.
*
* @param vmDef the vm name
* @param user the requesting user
*/
public GetDisplayPassword(VmDefinition vmDef) {
public GetDisplayPassword(VmDefinition vmDef, String user) {
this.vmDef = vmDef;
this.user = user;
}

/**
Expand All @@ -48,6 +51,15 @@ public VmDefinition vmDefinition() {
return vmDef;
}

/**
* Return the id of the user who has requested the password.
*
* @return the string
*/
public String user() {
return user;
}

/**
* Return the password. May only be called when the event is completed.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

package org.jdrupes.vmoperator.manager;

import com.google.gson.JsonObject;
import io.kubernetes.client.apimachinery.GroupVersionKind;
import io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.V1Secret;
Expand All @@ -37,10 +39,13 @@
import java.util.Scanner;
import java.util.logging.Level;
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;
import static org.jdrupes.vmoperator.common.Constants.VM_OP_NAME;
import org.jdrupes.vmoperator.common.K8sClient;
import org.jdrupes.vmoperator.common.K8sV1PodStub;
import org.jdrupes.vmoperator.common.K8sV1SecretStub;
import org.jdrupes.vmoperator.common.VmDefinitionStub;
import static org.jdrupes.vmoperator.manager.Constants.COMP_DISPLAY_SECRET;
import static org.jdrupes.vmoperator.manager.Constants.DATA_DISPLAY_PASSWORD;
import static org.jdrupes.vmoperator.manager.Constants.DATA_PASSWORD_EXPIRY;
Expand Down Expand Up @@ -181,12 +186,22 @@ public void onGetDisplaySecrets(GetDisplayPassword event, VmChannel channel)
+ "app.kubernetes.io/instance="
+ event.vmDefinition().metadata().getName());
var stubs = K8sV1SecretStub.list(client(),
event.vmDefinition().metadata().getNamespace(), options);
event.vmDefinition().namespace(), options);
if (stubs.isEmpty()) {
return;
}
var stub = stubs.iterator().next();

// Valid request, update console user in status
var vmStub = VmDefinitionStub.get(client(),
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM),
event.vmDefinition().namespace(), event.vmDefinition().name());
vmStub.updateStatus(from -> {
JsonObject status = from.status();
status.addProperty("consoleUser", event.user());
return status;
});

// Check validity
var model = stub.model().get();
@SuppressWarnings("PMD.StringInstantiation")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* VM-Operator
* Copyright (C) 2024 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;

import com.google.gson.JsonObject;
import io.kubernetes.client.apimachinery.GroupVersionKind;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.EventsV1Event;
import java.io.IOException;
import java.util.logging.Level;
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;
import org.jdrupes.vmoperator.common.K8s;
import org.jdrupes.vmoperator.common.K8sClient;
import org.jdrupes.vmoperator.common.VmDefinitionStub;
import org.jdrupes.vmoperator.runner.qemu.events.Exit;
import org.jdrupes.vmoperator.runner.qemu.events.SpiceDisconnectedEvent;
import org.jdrupes.vmoperator.runner.qemu.events.SpiceInitializedEvent;
import org.jgrapes.core.Channel;
import org.jgrapes.core.annotation.Handler;
import org.jgrapes.core.events.Start;

/**
* A (sub)component that updates the console status in the CR status.
* Created as child of {@link StatusUpdater}.
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class ConsoleTracker extends VmDefUpdater {

private VmDefinitionStub vmStub;
private String mainChannelClientHost;
private long mainChannelClientPort;

/**
* Instantiates a new status updater.
*
* @param componentChannel the component channel
*/
@SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
public ConsoleTracker(Channel componentChannel) {
super(componentChannel);
apiClient = (K8sClient) io.kubernetes.client.openapi.Configuration
.getDefaultApiClient();
}

/**
* Handle the start event.
*
* @param event the event
* @throws IOException
* @throws ApiException
*/
@Handler
public void onStart(Start event) {
if (namespace == null) {
return;
}
try {
vmStub = VmDefinitionStub.get(apiClient,
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM),
namespace, vmName);
} catch (ApiException e) {
logger.log(Level.SEVERE, e,
() -> "Cannot access VM object, terminating.");
event.cancel(true);
fire(new Exit(1));
}
}

/**
* On spice connected.
*
* @param event the event
* @throws ApiException the api exception
*/
@Handler
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
"PMD.AvoidDuplicateLiterals" })
public void onSpiceInitialized(SpiceInitializedEvent event)
throws ApiException {
if (vmStub == null) {
return;
}

// Only process connections using main channel.
if (event.channelType() != 1) {
return;
}
mainChannelClientHost = event.clientHost();
mainChannelClientPort = event.clientPort();
vmStub.updateStatus(from -> {
JsonObject status = from.status();
status.addProperty("consoleClient", event.clientHost());
updateCondition(from, status, "ConsoleConnected", true, "Connected",
"Connection from " + event.clientHost());
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
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
public void onSpiceDisconnected(SpiceDisconnectedEvent event)
throws ApiException {
if (vmStub == null) {
return;
}

// Only process disconnects from main channel.
if (!event.clientHost().equals(mainChannelClientHost)
|| event.clientPort() != mainChannelClientPort) {
return;
}
vmStub.updateStatus(from -> {
JsonObject status = from.status();
status.addProperty("consoleClient", "");
updateCondition(from, status, "ConsoleConnected", false,
"Disconnected", event.clientHost() + " has disconnected");
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);
}
}
Loading

0 comments on commit 93a1a2b

Please sign in to comment.