Prepare release v3.4

This commit is contained in:
Michael Lipp 2024-10-06 10:05:09 +00:00
parent 31a3f79e2a
commit 54445ef531
12 changed files with 274 additions and 504 deletions

View file

@ -27,14 +27,11 @@ import io.kubernetes.client.util.generic.options.ListOptions;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import org.jdrupes.vmoperator.common.K8s;
import org.jdrupes.vmoperator.common.K8sClient;
import org.jdrupes.vmoperator.common.K8sObserver;
import org.jdrupes.vmoperator.common.K8sObserver.ResponseType;
import org.jdrupes.vmoperator.manager.events.ChannelManager;
import org.jdrupes.vmoperator.manager.events.Exit;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Component;
@ -45,7 +42,11 @@ import org.jgrapes.core.events.Stop;
import org.jgrapes.util.events.ConfigurationUpdate;
/**
* A base class for monitoring VM related resources.
* A base class for monitoring VM related resources. When started,
* it creates observers for all versions of the the {@link APIResource}
* configured by {@link #context(APIResource)}. The APIResource is not
* passed to the constructor because in some cases it has to be
* evaluated lazily.
*
* @param <O> the object type for the context
* @param <L> the object list type for the context
@ -61,15 +62,17 @@ public abstract class AbstractMonitor<O extends KubernetesObject,
private String namespace;
private ListOptions options = new ListOptions();
private final AtomicInteger observerCounter = new AtomicInteger(0);
private ChannelManager<String, C, ?> channelManager;
/**
* Initializes the instance.
*
* @param componentChannel the component channel
* @param objectClass the class of the Kubernetes object to watch
* @param objectListClass the class of the list of Kubernetes objects
* to watch
*/
protected AbstractMonitor(Channel componentChannel, Class<O> objectClass,
Class<L> objectListClass) {
protected AbstractMonitor(Channel componentChannel,
Class<O> objectClass, Class<L> objectListClass) {
super(componentChannel);
this.objectClass = objectClass;
this.objectListClass = objectListClass;
@ -155,27 +158,6 @@ public abstract class AbstractMonitor<O extends KubernetesObject,
return this;
}
/**
* Returns the channel manager.
*
* @return the context
*/
public ChannelManager<String, C, ?> channelManager() {
return channelManager;
}
/**
* Sets the channel manager.
*
* @param channelManager the channel manager
* @return the abstract monitor
*/
public AbstractMonitor<O, L, C>
channelManager(ChannelManager<String, C, ?> channelManager) {
this.channelManager = channelManager;
return this;
}
/**
* Looks for a key "namespace" in the configuration and, if found,
* sets the namespace to its value.
@ -193,7 +175,7 @@ public abstract class AbstractMonitor<O extends KubernetesObject,
}
/**
* Handle the start event. Configures the namespace invokes
* Handle the start event. Configures the namespace, invokes
* {@link #prepareMonitoring()} and starts the observers.
*
* @param event the event
@ -239,9 +221,6 @@ public abstract class AbstractMonitor<O extends KubernetesObject,
K8s.preferred(context, version), namespace, options)
.handler((c, r) -> {
handleChange(c, r);
if (ResponseType.valueOf(r.type) == ResponseType.DELETED) {
channelManager.remove(r.object.getMetadata().getName());
}
}).onTerminated((o, t) -> {
if (observerCounter.decrementAndGet() == 0) {
unregisterAsGenerator();
@ -255,7 +234,8 @@ public abstract class AbstractMonitor<O extends KubernetesObject,
/**
* Invoked by {@link #onStart(Start)} after the namespace has
* been configured and before starting the observer.
* been configured and before starting the observer. This is
* the last opportunity to invoke {@link #context(APIResource)}.
*
* @throws IOException Signals that an I/O exception has occurred.
* @throws ApiException the api exception
@ -272,14 +252,4 @@ public abstract class AbstractMonitor<O extends KubernetesObject,
* @param change the change
*/
protected abstract void handleChange(K8sClient client, Response<O> change);
/**
* Returns the {@link Channel} for the given name.
*
* @param name the name
* @return the channel used for events related to the specified object
*/
protected Optional<C> channel(String name) {
return channelManager.getChannel(name);
}
}

View file

@ -100,9 +100,8 @@ public class Controller extends Component {
return null;
}
});
attach(new VmMonitor(channel()).channelManager(chanMgr));
attach(new DisplaySecretMonitor(channel())
.channelManager(chanMgr.fixed()));
attach(new VmMonitor(channel(), chanMgr));
attach(new DisplaySecretMonitor(channel(), chanMgr));
// Currently, we don't use the IP assigned by the load balancer
// to access the VM's console. Might change in the future.
// attach(new ServiceMonitor(channel()).channelManager(chanMgr));

View file

@ -44,6 +44,7 @@ import org.jdrupes.vmoperator.common.K8sV1SecretStub;
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;
import org.jdrupes.vmoperator.manager.events.ChannelDictionary;
import org.jdrupes.vmoperator.manager.events.GetDisplayPassword;
import org.jdrupes.vmoperator.manager.events.VmChannel;
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
@ -68,14 +69,18 @@ public class DisplaySecretMonitor
private int passwordValidity = 10;
private final List<PendingGet> pendingGets
= Collections.synchronizedList(new LinkedList<>());
private final ChannelDictionary<String, VmChannel, ?> channelDictionary;
/**
* Instantiates a new display secrets monitor.
*
* @param componentChannel the component channel
* @param channelDictionary the channel dictionary
*/
public DisplaySecretMonitor(Channel componentChannel) {
public DisplaySecretMonitor(Channel componentChannel,
ChannelDictionary<String, VmChannel, ?> channelDictionary) {
super(componentChannel, V1Secret.class, V1SecretList.class);
this.channelDictionary = channelDictionary;
context(K8sV1SecretStub.CONTEXT);
ListOptions options = new ListOptions();
options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
@ -116,7 +121,7 @@ public class DisplaySecretMonitor
if (vmName == null) {
return;
}
var channel = channel(vmName).orElse(null);
var channel = channelDictionary.channel(vmName).orElse(null);
if (channel == null || channel.vmDefinition() == null) {
return;
}
@ -248,6 +253,7 @@ public class DisplaySecretMonitor
* @param channel the channel
*/
@Handler
@SuppressWarnings("PMD.AvoidSynchronizedStatement")
public void onVmDefChanged(VmDefChanged event, Channel channel) {
synchronized (pendingGets) {
String vmName = event.vmDefinition().metadata().getName();

View file

@ -1,74 +0,0 @@
/*
* 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.manager;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.V1Service;
import io.kubernetes.client.openapi.models.V1ServiceList;
import io.kubernetes.client.util.Watch.Response;
import io.kubernetes.client.util.generic.options.ListOptions;
import java.io.IOException;
import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
import org.jdrupes.vmoperator.common.K8sClient;
import org.jdrupes.vmoperator.common.K8sObserver.ResponseType;
import org.jdrupes.vmoperator.common.K8sV1ServiceStub;
import org.jdrupes.vmoperator.manager.events.ServiceChanged;
import org.jdrupes.vmoperator.manager.events.VmChannel;
import org.jgrapes.core.Channel;
/**
* Watches for changes of services.
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class ServiceMonitor
extends AbstractMonitor<V1Service, V1ServiceList, VmChannel> {
/**
* Instantiates a new display secrets monitor.
*
* @param componentChannel the component channel
*/
public ServiceMonitor(Channel componentChannel) {
super(componentChannel, V1Service.class, V1ServiceList.class);
context(K8sV1ServiceStub.CONTEXT);
ListOptions options = new ListOptions();
options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME);
options(options);
}
@Override
protected void prepareMonitoring() throws IOException, ApiException {
client(new K8sClient());
}
@Override
protected void handleChange(K8sClient client, Response<V1Service> change) {
String vmName = change.object.getMetadata().getLabels()
.get("app.kubernetes.io/instance");
if (vmName == null) {
return;
}
var channel = channel(vmName).orElse(null);
if (channel == null || channel.vmDefinition() == null) {
return;
}
channel.pipeline().fire(new ServiceChanged(
ResponseType.valueOf(change.type), change.object), channel);
}
}

View file

@ -43,10 +43,12 @@ import org.jdrupes.vmoperator.common.VmDefinitionStub;
import static org.jdrupes.vmoperator.manager.Constants.APP_NAME;
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_KIND_VM;
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
import org.jdrupes.vmoperator.manager.events.ChannelManager;
import org.jdrupes.vmoperator.manager.events.VmChannel;
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
import org.jdrupes.vmoperator.util.GsonPtr;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Event;
/**
* Watches for changes of VM definitions.
@ -55,14 +57,19 @@ import org.jgrapes.core.Channel;
public class VmMonitor extends
AbstractMonitor<VmDefinitionModel, VmDefinitionModels, VmChannel> {
private final ChannelManager<String, VmChannel, ?> channelManager;
/**
* Instantiates a new VM definition watcher.
*
* @param componentChannel the component channel
* @param channelManager the channel manager
*/
public VmMonitor(Channel componentChannel) {
public VmMonitor(Channel componentChannel,
ChannelManager<String, VmChannel, ?> channelManager) {
super(componentChannel, VmDefinitionModel.class,
VmDefinitionModels.class);
this.channelManager = channelManager;
}
@Override
@ -107,10 +114,7 @@ public class VmMonitor extends
protected void handleChange(K8sClient client,
Watch.Response<VmDefinitionModel> response) {
V1ObjectMeta metadata = response.object.getMetadata();
VmChannel channel = channel(metadata.getName()).orElse(null);
if (channel == null) {
return;
}
VmChannel channel = channelManager.channelGet(metadata.getName());
// Get full definition and associate with channel as backup
var vmDef = response.object;
@ -132,13 +136,24 @@ public class VmMonitor extends
() -> "Cannot get model for " + response.object.getMetadata());
return;
}
if (ResponseType.valueOf(response.type) == ResponseType.DELETED) {
channelManager.remove(metadata.getName());
}
// Create and fire event
// Create and fire changed event. Remove channel from channel
// manager on completion.
channel.pipeline()
.fire(new VmDefChanged(ResponseType.valueOf(response.type),
channel.setGeneration(
response.object.getMetadata().getGeneration()),
vmDef), channel);
.fire(Event.onCompletion(
new VmDefChanged(ResponseType.valueOf(response.type),
channel.setGeneration(
response.object.getMetadata().getGeneration()),
vmDef),
e -> {
if (e.type() == ResponseType.DELETED) {
channelManager
.remove(e.vmDefinition().metadata().getName());
}
}), channel);
}
private VmDefinitionModel getModel(K8sClient client,