Optimize state change handling.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled

This commit is contained in:
Michael Lipp 2025-03-29 15:09:38 +01:00
parent db49f5ba2f
commit 991763f228
18 changed files with 152 additions and 262 deletions

View file

@ -300,8 +300,8 @@ public class VmDefinition extends K8sDynamicModel {
*
* @return the data
*/
public Optional<VmExtraData> extra() {
return Optional.ofNullable(extraData);
public VmExtraData extra() {
return extraData;
}
/**

View file

@ -112,7 +112,7 @@ public class VmExtraData {
* @param deleteConnectionFile the delete connection file
* @return the string
*/
public String connectionFile(String password,
public Optional<String> connectionFile(String password,
Class<?> preferredIpVersion, boolean deleteConnectionFile) {
var addr = displayIp(preferredIpVersion);
if (addr.isEmpty()) {
@ -144,7 +144,7 @@ public class VmExtraData {
if (deleteConnectionFile) {
data.append("delete-this-file=1\n");
}
return data.toString();
return Optional.of(data.toString());
}
private Optional<InetAddress> displayIp(Class<?> preferredIpVersion) {

View file

@ -177,7 +177,7 @@ public class VmPool {
}
// Additional check in case lastUsed has not been updated
// by PoolMonitor#onVmDefChanged() yet ("race condition")
// by PoolMonitor#onVmResourceChanged() yet ("race condition")
if (vmDef.condition("ConsoleConnected")
.map(cc -> cc.getLastTransitionTime().toInstant())
.map(this::retainUntil)

View file

@ -25,31 +25,35 @@ import org.jgrapes.core.Components;
import org.jgrapes.core.Event;
/**
* Indicates a change in a VM definition. Note that the definition
* consists of the metadata (mostly immutable), the "spec" and the
* "status" parts. Consumers that are only interested in "spec"
* changes should check {@link #specChanged()} before processing
* the event any further.
* Indicates a change in a VM "resource". Note that the resource
* combines the VM CR's metadata (mostly immutable), the VM CR's
* "spec" part, the VM CR's "status" subresource and state information
* from the pod. Consumers that are only interested in "spec" changes
* should check {@link #specChanged()} before processing the event any
* further.
*/
@SuppressWarnings("PMD.DataClass")
public class VmDefChanged extends Event<Void> {
public class VmResourceChanged extends Event<Void> {
private final K8sObserver.ResponseType type;
private final boolean specChanged;
private final VmDefinition vmDefinition;
private final boolean specChanged;
private final boolean podChanged;
/**
* Instantiates a new VM changed event.
*
* @param type the type
* @param specChanged the spec part changed
* @param vmDefinition the VM definition
* @param specChanged the spec part changed
*/
public VmDefChanged(K8sObserver.ResponseType type, boolean specChanged,
VmDefinition vmDefinition) {
public VmResourceChanged(K8sObserver.ResponseType type,
VmDefinition vmDefinition, boolean specChanged,
boolean podChanged) {
this.type = type;
this.specChanged = specChanged;
this.vmDefinition = vmDefinition;
this.specChanged = specChanged;
this.podChanged = podChanged;
}
/**
@ -61,6 +65,15 @@ public class VmDefChanged extends Event<Void> {
return type;
}
/**
* Return the VM definition.
*
* @return the VM definition
*/
public VmDefinition vmDefinition() {
return vmDefinition;
}
/**
* Indicates if the "spec" part changed.
*/
@ -69,12 +82,10 @@ public class VmDefChanged extends Event<Void> {
}
/**
* Return the VM definition.
*
* @return the VM definition
* Indicates if the pod status changed.
*/
public VmDefinition vmDefinition() {
return vmDefinition;
public boolean podChanged() {
return podChanged;
}
@Override

View file

@ -53,7 +53,7 @@ data:
# i.e. if you start the VM without a value for this property, and
# decide to trigger a reset later, you have to first set the value
# and then inrement it.
resetCounter: ${ cr.extra().get().resetCount()?c }
resetCounter: ${ cr.extra().resetCount()?c }
# Forward the cloud-init data if provided
<#if spec.cloudInit??>

View file

@ -1,6 +1,6 @@
/*
* VM-Operator
* Copyright (C) 2023 Michael N. Lipp
* Copyright (C) 2023, 2025 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
@ -46,8 +46,8 @@ import org.jdrupes.vmoperator.manager.events.ModifyVm;
import org.jdrupes.vmoperator.manager.events.PodChanged;
import org.jdrupes.vmoperator.manager.events.UpdateAssignment;
import org.jdrupes.vmoperator.manager.events.VmChannel;
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
import org.jdrupes.vmoperator.manager.events.VmPoolChanged;
import org.jdrupes.vmoperator.manager.events.VmResourceChanged;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Component;
import org.jgrapes.core.annotation.Handler;
@ -61,7 +61,7 @@ import org.jgrapes.util.events.ConfigurationUpdate;
*
* The implementation splits the controller in two components. The
* {@link VmMonitor} and the {@link Reconciler}. The former watches
* the VM definitions (CRs) and generates {@link VmDefChanged} events
* the VM definitions (CRs) and generates {@link VmResourceChanged} events
* when they change. The latter handles the changes and reconciles the
* resources in the cluster.
*

View file

@ -45,7 +45,7 @@ import org.jdrupes.vmoperator.common.VmDefinition;
import org.jdrupes.vmoperator.common.VmDefinitionStub;
import org.jdrupes.vmoperator.manager.events.GetDisplaySecret;
import org.jdrupes.vmoperator.manager.events.VmChannel;
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
import org.jdrupes.vmoperator.manager.events.VmResourceChanged;
import org.jdrupes.vmoperator.util.DataPath;
import org.jgrapes.core.Channel;
import org.jgrapes.core.CompletionLock;
@ -123,13 +123,19 @@ public class DisplaySecretReconciler extends Component {
* @param vmDef the VM definition
* @param model the model
* @param channel the channel
* @param specChanged the spec changed
* @throws IOException Signals that an I/O exception has occurred.
* @throws TemplateException the template exception
* @throws ApiException the api exception
*/
public void reconcile(VmDefinition vmDef, Map<String, Object> model,
VmChannel channel)
VmChannel channel, boolean specChanged)
throws IOException, TemplateException, ApiException {
// Nothing to do unless spec changed
if (!specChanged) {
return;
}
// Secret needed at all?
var display = vmDef.fromVm("display").get();
if (!DataPath.<Boolean> get(display, "spice", "generateSecret")
@ -292,7 +298,7 @@ public class DisplaySecretReconciler extends Component {
*/
@Handler
@SuppressWarnings("PMD.AvoidSynchronizedStatement")
public void onVmDefChanged(VmDefChanged event, Channel channel) {
public void onVmResourceChanged(VmResourceChanged event, Channel channel) {
synchronized (pendingPrepares) {
String vmName = event.vmDefinition().name();
for (var pending : pendingPrepares) {

View file

@ -71,13 +71,19 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
* @param vmDef the VM definition
* @param model the model
* @param channel the channel
* @param specChanged the spec changed
* @throws IOException Signals that an I/O exception has occurred.
* @throws TemplateException the template exception
* @throws ApiException the api exception
*/
public void reconcile(VmDefinition vmDef,
Map<String, Object> model, VmChannel channel)
public void reconcile(VmDefinition vmDef, Map<String, Object> model,
VmChannel channel, boolean specChanged)
throws IOException, TemplateException, ApiException {
// Nothing to do unless spec changed
if (!specChanged) {
return;
}
// Check if to be generated
@SuppressWarnings({ "PMD.AvoidDuplicateLiterals", "unchecked" })
var lbsDef = Optional.of(model)

View file

@ -38,7 +38,7 @@ import org.jdrupes.vmoperator.common.K8sV1PodStub;
import org.jdrupes.vmoperator.manager.events.ChannelDictionary;
import org.jdrupes.vmoperator.manager.events.PodChanged;
import org.jdrupes.vmoperator.manager.events.VmChannel;
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
import org.jdrupes.vmoperator.manager.events.VmResourceChanged;
import org.jgrapes.core.Channel;
import org.jgrapes.core.annotation.Handler;
@ -118,7 +118,8 @@ public class PodMonitor extends AbstractMonitor<V1Pod, V1PodList, VmChannel> {
* @param channel the channel
*/
@Handler
public void onVmDefChanged(VmDefChanged event, VmChannel channel) {
public void onVmResourceChanged(VmResourceChanged event,
VmChannel channel) {
Optional.ofNullable(pendingChanges.remove(event.vmDefinition().name()))
.map(PendingChange::change).ifPresent(change -> {
logger.finer(() -> "Firing pending pod change for "

View file

@ -64,18 +64,14 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
* @param vmDef the vm def
* @param model the model
* @param channel the channel
* @param specChanged the spec changed
* @throws IOException Signals that an I/O exception has occurred.
* @throws TemplateException the template exception
* @throws ApiException the api exception
*/
public void reconcile(VmDefinition vmDef, Map<String, Object> model,
VmChannel channel)
VmChannel channel, boolean specChanged)
throws IOException, TemplateException, ApiException {
// Don't do anything if stateful set is still in use (pre v3.4)
if ((Boolean) model.get("usingSts")) {
return;
}
// Get pod stub.
var podStub = K8sV1PodStub.get(channel.client(), vmDef.namespace(),
vmDef.name());

View file

@ -40,8 +40,8 @@ import org.jdrupes.vmoperator.common.VmDefinition.Assignment;
import org.jdrupes.vmoperator.common.VmDefinitionStub;
import org.jdrupes.vmoperator.common.VmPool;
import org.jdrupes.vmoperator.manager.events.GetPools;
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
import org.jdrupes.vmoperator.manager.events.VmPoolChanged;
import org.jdrupes.vmoperator.manager.events.VmResourceChanged;
import org.jdrupes.vmoperator.util.GsonPtr;
import org.jgrapes.core.Channel;
import org.jgrapes.core.EventPipeline;
@ -142,7 +142,8 @@ public class PoolMonitor extends
* @throws ApiException
*/
@Handler
public void onVmDefChanged(VmDefChanged event) throws ApiException {
public void onVmResourceChanged(VmResourceChanged event)
throws ApiException {
final var vmDef = event.vmDefinition();
final String vmName = vmDef.name();
switch (event.type()) {

View file

@ -67,30 +67,35 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
/**
* Reconcile the PVCs.
*
* @param vmDef the vm def
* @param vmDef the VM definition
* @param model the model
* @param channel the channel
* @param specChanged the spec changed
* @throws IOException Signals that an I/O exception has occurred.
* @throws TemplateException the template exception
* @throws ApiException the api exception
*/
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
@SuppressWarnings({ "PMD.AvoidDuplicateLiterals", "unchecked" })
public void reconcile(VmDefinition vmDef, Map<String, Object> model,
VmChannel channel)
VmChannel channel, boolean specChanged)
throws IOException, TemplateException, ApiException {
// Existing disks
ListOptions listOpts = new ListOptions();
listOpts.setLabelSelector(
"app.kubernetes.io/managed-by=" + VM_OP_NAME + ","
+ "app.kubernetes.io/name=" + APP_NAME + ","
+ "app.kubernetes.io/instance=" + vmDef.name());
var knownDisks = K8sV1PvcStub.list(channel.client(),
vmDef.namespace(), listOpts);
var knownPvcs = knownDisks.stream().map(K8sV1PvcStub::name)
.collect(Collectors.toSet());
Set<String> knownPvcs;
if (!specChanged && channel.associated(this, Set.class).isPresent()) {
knownPvcs = (Set<String>) channel.associated(this, Set.class).get();
} else {
ListOptions listOpts = new ListOptions();
listOpts.setLabelSelector(
"app.kubernetes.io/managed-by=" + VM_OP_NAME + ","
+ "app.kubernetes.io/name=" + APP_NAME + ","
+ "app.kubernetes.io/instance=" + vmDef.name());
knownPvcs = K8sV1PvcStub.list(channel.client(),
vmDef.namespace(), listOpts).stream().map(K8sV1PvcStub::name)
.collect(Collectors.toSet());
channel.setAssociated(this, knownPvcs);
}
// Reconcile runner data pvc
reconcileRunnerDataPvc(vmDef, model, channel, knownPvcs);
reconcileRunnerDataPvc(vmDef, model, channel, knownPvcs, specChanged);
// Reconcile pvcs for defined disks
var diskDefs = vmDef.<List<Map<String, Object>>> fromVm("disks")
@ -114,15 +119,13 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
}
// Update PVC
model.put("disk", diskDef);
reconcileRunnerDiskPvc(vmDef, model, channel);
reconcileRunnerDiskPvc(vmDef, model, channel, specChanged, diskDef);
}
model.remove("disk");
}
private void reconcileRunnerDataPvc(VmDefinition vmDef,
Map<String, Object> model, VmChannel channel,
Set<String> knownPvcs)
Set<String> knownPvcs, boolean specChanged)
throws TemplateNotFoundException, MalformedTemplateNameException,
ParseException, IOException, TemplateException, ApiException {
@ -135,7 +138,12 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
}
// Generate PVC
model.put("runnerDataPvcName", vmDef.name() + "-runner-data");
var runnerDataPvcName = vmDef.name() + "-runner-data";
model.put("runnerDataPvcName", runnerDataPvcName);
if (!specChanged) {
// Augmenting the model is all we have to do
return;
}
var fmTemplate = fmConfig.getTemplate("runnerDataPvc.ftl.yaml");
StringWriter out = new StringWriter();
fmTemplate.process(model, out);
@ -159,17 +167,24 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
}
private void reconcileRunnerDiskPvc(VmDefinition vmDef,
Map<String, Object> model, VmChannel channel)
Map<String, Object> model, VmChannel channel, boolean specChanged,
Map<String, Object> diskDef)
throws TemplateNotFoundException, MalformedTemplateNameException,
ParseException, IOException, TemplateException, ApiException {
// Generate PVC
@SuppressWarnings("unchecked")
var diskDef = (Map<String, Object>) model.get("disk");
var pvcName = vmDef.name() + "-" + diskDef.get("generatedDiskName");
diskDef.put("generatedPvcName", pvcName);
if (!specChanged) {
// Augmenting the model is all we have to do
return;
}
// Generate PVC
model.put("disk", diskDef);
var fmTemplate = fmConfig.getTemplate("runnerDiskPvc.ftl.yaml");
StringWriter out = new StringWriter();
fmTemplate.process(model, out);
model.remove("disk");
// Avoid Yaml.load due to
// https://github.com/kubernetes-client/java/issues/2741
var pvcDef = Dynamics.newFromYaml(

View file

@ -44,15 +44,13 @@ import java.util.Optional;
import java.util.logging.Level;
import org.jdrupes.vmoperator.common.Convertions;
import org.jdrupes.vmoperator.common.K8sObserver;
import org.jdrupes.vmoperator.common.K8sObserver.ResponseType;
import org.jdrupes.vmoperator.common.VmDefinition;
import org.jdrupes.vmoperator.common.VmDefinition.Assignment;
import org.jdrupes.vmoperator.common.VmPool;
import org.jdrupes.vmoperator.manager.events.GetPools;
import org.jdrupes.vmoperator.manager.events.PodChanged;
import org.jdrupes.vmoperator.manager.events.ResetVm;
import org.jdrupes.vmoperator.manager.events.VmChannel;
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
import org.jdrupes.vmoperator.manager.events.VmResourceChanged;
import org.jdrupes.vmoperator.util.ExtendedObjectWrapper;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Component;
@ -151,7 +149,6 @@ public class Reconciler extends Component {
private final Configuration fmConfig;
private final ConfigMapReconciler cmReconciler;
private final DisplaySecretReconciler dsReconciler;
private final StatefulSetReconciler stsReconciler;
private final PvcReconciler pvcReconciler;
private final PodReconciler podReconciler;
private final LoadBalancerReconciler lbReconciler;
@ -179,7 +176,6 @@ public class Reconciler extends Component {
cmReconciler = new ConfigMapReconciler(fmConfig);
dsReconciler = attach(new DisplaySecretReconciler(componentChannel));
stsReconciler = new StatefulSetReconciler(fmConfig);
pvcReconciler = new PvcReconciler(fmConfig);
podReconciler = new PodReconciler(fmConfig);
lbReconciler = new LoadBalancerReconciler(fmConfig);
@ -208,64 +204,27 @@ public class Reconciler extends Component {
*/
@Handler
@SuppressWarnings("PMD.ConfusingTernary")
public void onVmDefChanged(VmDefChanged event, VmChannel channel)
public void onVmResourceChanged(VmResourceChanged event, VmChannel channel)
throws ApiException, TemplateException, IOException {
// Ownership relationships takes care of deletions
if (event.type() == K8sObserver.ResponseType.DELETED) {
return;
}
// Reconcile
reconcile(event, channel);
}
private void reconcile(VmDefChanged event, VmChannel channel)
throws TemplateModelException, ApiException, IOException,
TemplateException {
// Create model for processing templates
var vmDef = event.vmDefinition();
Map<String, Object> model = prepareModel(vmDef);
cmReconciler.reconcile(model, channel, event.specChanged());
// The remaining reconcilers depend only on changes of the spec part.
if (!event.specChanged()) {
// The remaining reconcilers depend only on changes of the spec part
// or the pod state.
if (!event.specChanged() && !event.podChanged()) {
return;
}
dsReconciler.reconcile(vmDef, model, channel);
// Manage (eventual) removal of stateful set.
stsReconciler.reconcile(vmDef, model, channel);
pvcReconciler.reconcile(vmDef, model, channel);
podReconciler.reconcile(vmDef, model, channel);
lbReconciler.reconcile(vmDef, model, channel);
}
/**
* On pod changed.
*
* @param event the event
* @param channel the channel
* @throws ApiException the api exception
* @throws IOException Signals that an I/O exception has occurred.
* @throws TemplateException the template exception
*/
@Handler
public void onPodChanged(PodChanged event, VmChannel channel)
throws ApiException, IOException, TemplateException {
if (event.type() != ResponseType.DELETED) {
// Nothing to reconcile
return;
}
// If the pod was deleted, it may be necessary to recreate it
var vmDef = channel.vmDefinition();
Map<String, Object> model = prepareModel(vmDef);
// Call all steps because they may augment the model
cmReconciler.reconcile(model, channel, false);
dsReconciler.reconcile(vmDef, model, channel);
stsReconciler.reconcile(vmDef, model, channel);
pvcReconciler.reconcile(vmDef, model, channel);
podReconciler.reconcile(vmDef, model, channel);
dsReconciler.reconcile(vmDef, model, channel, event.specChanged());
pvcReconciler.reconcile(vmDef, model, channel, event.specChanged());
podReconciler.reconcile(vmDef, model, channel, event.specChanged());
lbReconciler.reconcile(vmDef, model, channel, event.specChanged());
}
/**
@ -282,7 +241,8 @@ public class Reconciler extends Component {
public void onResetVm(ResetVm event, VmChannel channel)
throws ApiException, IOException, TemplateException {
var vmDef = channel.vmDefinition();
vmDef.extra().ifPresent(e -> e.resetCount(e.resetCount() + 1));
var extra = vmDef.extra();
extra.resetCount(extra.resetCount() + 1);
Map<String, Object> model
= prepareModel(channel.vmDefinition());
cmReconciler.reconcile(model, channel, true);

View file

@ -1,107 +0,0 @@
/*
* 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.manager;
import freemarker.template.Configuration;
import freemarker.template.TemplateException;
import io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.util.generic.options.PatchOptions;
import java.io.IOException;
import java.util.Map;
import java.util.logging.Logger;
import org.jdrupes.vmoperator.common.K8sV1StatefulSetStub;
import org.jdrupes.vmoperator.common.VmDefinition;
import org.jdrupes.vmoperator.common.VmDefinition.RequestedVmState;
import org.jdrupes.vmoperator.manager.events.VmChannel;
/**
* Before version 3.4, the pod running the VM was created by a stateful set.
* Starting with version 3.4, this reconciler simply deletes the stateful
* set, provided that the VM is not running.
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
/* default */ class StatefulSetReconciler {
protected final Logger logger = Logger.getLogger(getClass().getName());
/**
* Instantiates a new stateful set reconciler.
*
* @param fmConfig the fm config
*/
@SuppressWarnings("PMD.UnusedFormalParameter")
public StatefulSetReconciler(Configuration fmConfig) {
// Nothing to do
}
/**
* Reconcile stateful set.
*
* @param vmDef the VM definition
* @param model the model
* @param channel the channel
* @throws IOException Signals that an I/O exception has occurred.
* @throws TemplateException the template exception
* @throws ApiException the api exception
*/
@SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
public void reconcile(VmDefinition vmDef, Map<String, Object> model,
VmChannel channel)
throws IOException, TemplateException, ApiException {
model.put("usingSts", false);
// If exists, delete when not running or supposed to be not running.
var stsStub = K8sV1StatefulSetStub.get(channel.client(),
vmDef.namespace(), vmDef.name());
if (stsStub.model().isEmpty()) {
return;
}
// Stateful set still exists, check if replicas is 0 so we can
// delete it.
var stsModel = stsStub.model().get();
if (stsModel.getSpec().getReplicas() == 0) {
stsStub.delete();
return;
}
// Cannot yet delete the stateful set.
model.put("usingSts", true);
// Check if VM is supposed to be stopped. If so,
// set replicas to 0. This is the first step of the transition,
// the stateful set will be deleted when the VM is restarted.
if (vmDef.vmState() == RequestedVmState.RUNNING) {
return;
}
// Do apply changes (set replicas to 0)
PatchOptions opts = new PatchOptions();
opts.setForce(true);
opts.setFieldManager("kubernetes-java-kubectl-apply");
if (stsStub.patch(V1Patch.PATCH_FORMAT_JSON_PATCH,
new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/replicas"
+ "\", \"value\": 0}]"),
channel.client().defaultPatchOptions()).isEmpty()) {
logger.warning(
() -> "Could not patch stateful set for " + stsStub.name());
}
}
}

View file

@ -30,7 +30,6 @@ import java.net.HttpURLConnection;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
@ -54,7 +53,7 @@ import org.jdrupes.vmoperator.manager.events.ModifyVm;
import org.jdrupes.vmoperator.manager.events.PodChanged;
import org.jdrupes.vmoperator.manager.events.UpdateAssignment;
import org.jdrupes.vmoperator.manager.events.VmChannel;
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
import org.jdrupes.vmoperator.manager.events.VmResourceChanged;
import org.jdrupes.vmoperator.util.GsonPtr;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Event;
@ -157,11 +156,11 @@ public class VmMonitor extends
// Create and fire changed event. Remove channel from channel
// manager on completion.
VmDefChanged chgEvt
= new VmDefChanged(ResponseType.valueOf(response.type),
VmResourceChanged chgEvt
= new VmResourceChanged(ResponseType.valueOf(response.type), vmDef,
channel.setGeneration(response.object.getMetadata()
.getGeneration()),
vmDef);
false);
if (ResponseType.valueOf(response.type) == ResponseType.DELETED) {
chgEvt = Event.onCompletion(chgEvt,
e -> channelManager.remove(e.vmDefinition().name()));
@ -181,8 +180,7 @@ public class VmMonitor extends
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
private void addExtraData(VmDefinition vmDef, VmDefinition prevState) {
var extra = new VmExtraData(vmDef);
var prevExtra
= Optional.ofNullable(prevState).flatMap(VmDefinition::extra);
var prevExtra = Optional.ofNullable(prevState).map(VmDefinition::extra);
// Maintain (or initialize) the resetCount
extra.resetCount(prevExtra.map(VmExtraData::resetCount).orElse(0L));
@ -200,35 +198,35 @@ public class VmMonitor extends
*/
@Handler
public void onPodChanged(PodChanged event, VmChannel channel) {
if (channel.vmDefinition().extra().isEmpty()) {
return;
}
var extra = channel.vmDefinition().extra().get();
var pod = event.pod();
var vmDef = channel.vmDefinition();
updateNodeInfo(event, vmDef);
channel
.fire(new VmResourceChanged(ResponseType.MODIFIED, vmDef, false, true));
}
private void updateNodeInfo(PodChanged event, VmDefinition vmDef) {
var extra = vmDef.extra();
if (event.type() == ResponseType.DELETED) {
// The status of a deleted pod is the status before deletion,
// i.e. the node info is still there.
// i.e. the node info is still cached and must be removed.
extra.nodeInfo("", Collections.emptyList());
} else {
var nodeName = Optional
.ofNullable(pod.getSpec().getNodeName()).orElse("");
logger.finer(() -> "Adding node name " + nodeName
+ " to VM info for " + channel.vmDefinition().name());
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
var addrs = new ArrayList<String>();
Optional.ofNullable(pod.getStatus().getPodIPs())
.orElse(Collections.emptyList()).stream()
.map(ip -> ip.getIp()).forEach(addrs::add);
logger.finer(() -> "Adding node addresses " + addrs
+ " to VM info for " + channel.vmDefinition().name());
if (Objects.equals(nodeName, extra.nodeName())
&& Objects.equals(addrs, extra.nodeAddresses())) {
return;
}
extra.nodeInfo(nodeName, addrs);
return;
}
channel.fire(new VmDefChanged(ResponseType.MODIFIED, false,
channel.vmDefinition()));
// Get current node info from pod
var pod = event.pod();
var nodeName = Optional
.ofNullable(pod.getSpec().getNodeName()).orElse("");
logger.finer(() -> "Adding node name " + nodeName
+ " to VM info for " + vmDef.name());
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
var addrs = new ArrayList<String>();
Optional.ofNullable(pod.getStatus().getPodIPs())
.orElse(Collections.emptyList()).stream()
.map(ip -> ip.getIp()).forEach(addrs::add);
logger.finer(() -> "Adding node addresses " + addrs
+ " to VM info for " + vmDef.name());
extra.nodeInfo(nodeName, addrs);
}
/**

View file

@ -57,8 +57,8 @@ import org.jdrupes.vmoperator.manager.events.GetVms.VmData;
import org.jdrupes.vmoperator.manager.events.ModifyVm;
import org.jdrupes.vmoperator.manager.events.ResetVm;
import org.jdrupes.vmoperator.manager.events.VmChannel;
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
import org.jdrupes.vmoperator.manager.events.VmPoolChanged;
import org.jdrupes.vmoperator.manager.events.VmResourceChanged;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Components;
import org.jgrapes.core.Event;
@ -658,7 +658,7 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
@SuppressWarnings({ "PMD.ConfusingTernary", "PMD.CognitiveComplexity",
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.AvoidDuplicateLiterals",
"PMD.ConfusingArgumentToVarargsMethod" })
public void onVmDefChanged(VmDefChanged event, VmChannel channel)
public void onVmResourceChanged(VmResourceChanged event, VmChannel channel)
throws IOException, InterruptedException {
var vmDef = event.vmDefinition();
@ -847,8 +847,8 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
if (!event.secretAvailable()) {
return;
}
vmDef.extra().map(xtra -> xtra.connectionFile(event.secret(),
preferredIpVersion, deleteConnectionFile))
vmDef.extra().connectionFile(event.secret(),
preferredIpVersion, deleteConnectionFile)
.ifPresent(cf -> channel.respond(new NotifyConletView(type(),
model.getConletId(), "openConsole", cf)));
}

View file

@ -42,13 +42,12 @@ import org.jdrupes.vmoperator.common.Constants.Status;
import org.jdrupes.vmoperator.common.K8sObserver;
import org.jdrupes.vmoperator.common.VmDefinition;
import org.jdrupes.vmoperator.common.VmDefinition.Permission;
import org.jdrupes.vmoperator.common.VmExtraData;
import org.jdrupes.vmoperator.manager.events.ChannelTracker;
import org.jdrupes.vmoperator.manager.events.GetDisplaySecret;
import org.jdrupes.vmoperator.manager.events.ModifyVm;
import org.jdrupes.vmoperator.manager.events.ResetVm;
import org.jdrupes.vmoperator.manager.events.VmChannel;
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
import org.jdrupes.vmoperator.manager.events.VmResourceChanged;
import org.jdrupes.vmoperator.util.DataPath;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Event;
@ -258,7 +257,7 @@ public class VmMgmt extends FreeMarkerConlet<VmMgmt.VmsModel> {
"name", vmDef.name()),
"spec", spec,
"status", status,
"nodeName", vmDef.extra().map(VmExtraData::nodeName).orElse(""),
"nodeName", vmDef.extra().nodeName(),
"consoleAccessible", vmDef.consoleAccessible(user, perms),
"permissions", perms);
}
@ -274,7 +273,7 @@ public class VmMgmt extends FreeMarkerConlet<VmMgmt.VmsModel> {
@SuppressWarnings({ "PMD.ConfusingTernary", "PMD.CognitiveComplexity",
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.AvoidDuplicateLiterals",
"PMD.ConfusingArgumentToVarargsMethod" })
public void onVmDefChanged(VmDefChanged event, VmChannel channel)
public void onVmResourceChanged(VmResourceChanged event, VmChannel channel)
throws IOException {
var vmName = event.vmDefinition().name();
if (event.type() == K8sObserver.ResponseType.DELETED) {
@ -494,8 +493,8 @@ public class VmMgmt extends FreeMarkerConlet<VmMgmt.VmsModel> {
if (!event.secretAvailable()) {
return;
}
vmDef.extra().map(xtra -> xtra.connectionFile(event.secret(),
preferredIpVersion, deleteConnectionFile)).ifPresent(
vmDef.extra().connectionFile(event.secret(),
preferredIpVersion, deleteConnectionFile).ifPresent(
cf -> channel.respond(new NotifyConletView(type(),
model.getConletId(), "openConsole", cf)));
}

View file

@ -34,6 +34,10 @@ layout: vm-operator
update the template manually. If you're using your own template, you
have to add a virtual serial port (see the git history of the standard
template for the required addition).
* Stateful sets from pre 3.4.0 versions are no longer removed automatically
(see notes below). However, PVCs with the old naming scheme are still
reused.
## To version 3.4.0