Add viewer conlet (#25)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
This commit is contained in:
parent
b6f0299932
commit
a6525a2289
77 changed files with 2642 additions and 250 deletions
|
|
@ -32,6 +32,7 @@ dependencies {
|
|||
runtimeOnly 'org.apache.logging.log4j:log4j-to-jul:2.20.0'
|
||||
|
||||
runtimeOnly project(':org.jdrupes.vmoperator.vmconlet')
|
||||
runtimeOnly project(':org.jdrupes.vmoperator.vmviewer')
|
||||
}
|
||||
|
||||
application {
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ data:
|
|||
ticket: "${ cr.spec.vm.display.spice.ticket.asString }"
|
||||
</#if>
|
||||
<#if cr.spec.vm.display.spice.streamingVideo??>
|
||||
ticket: "${ cr.spec.vm.display.spice.streamingVideo.asString }"
|
||||
streaming-video: "${ cr.spec.vm.display.spice.streamingVideo.asString }"
|
||||
</#if>
|
||||
usbRedirects: ${ cr.spec.vm.display.spice.usbRedirects.asInt?c }
|
||||
</#if>
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
|||
.list(newCm.getMetadata().getNamespace(), listOpts).getObject();
|
||||
|
||||
// If the VM is being created, the pod may not exist yet.
|
||||
if (pods == null || pods.getItems().size() == 0) {
|
||||
if (pods == null || pods.getItems().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
var pod = pods.getItems().get(0);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ package org.jdrupes.vmoperator.manager;
|
|||
/**
|
||||
* Some constants.
|
||||
*/
|
||||
@SuppressWarnings("PMD.DataClass")
|
||||
public class Constants extends org.jdrupes.vmoperator.common.Constants {
|
||||
|
||||
/** The Constant COMP_DISPLAY_SECRET. */
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ public class Controller extends Component {
|
|||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
@SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
|
||||
public Controller(Channel componentChannel) {
|
||||
super(componentChannel);
|
||||
// Prepare component tree
|
||||
|
|
@ -100,8 +101,11 @@ public class Controller extends Component {
|
|||
}
|
||||
});
|
||||
attach(new VmMonitor(channel()).channelManager(chanMgr));
|
||||
attach(new DisplaySecretsMonitor(channel())
|
||||
attach(new DisplayPasswordMonitor(channel())
|
||||
.channelManager(chanMgr.fixed()));
|
||||
// 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));
|
||||
attach(new Reconciler(channel()));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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.V1Secret;
|
||||
import io.kubernetes.client.openapi.models.V1SecretList;
|
||||
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.K8sV1SecretStub;
|
||||
import static org.jdrupes.vmoperator.manager.Constants.COMP_DISPLAY_SECRET;
|
||||
import org.jdrupes.vmoperator.manager.events.DisplayPasswordChanged;
|
||||
import org.jdrupes.vmoperator.manager.events.GetDisplayPassword;
|
||||
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
||||
import org.jgrapes.core.Channel;
|
||||
import org.jgrapes.core.annotation.Handler;
|
||||
|
||||
/**
|
||||
* Watches for changes of display secrets.
|
||||
*/
|
||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
||||
public class DisplayPasswordMonitor
|
||||
extends AbstractMonitor<V1Secret, V1SecretList, VmChannel> {
|
||||
|
||||
/**
|
||||
* Instantiates a new display secrets monitor.
|
||||
*
|
||||
* @param componentChannel the component channel
|
||||
*/
|
||||
public DisplayPasswordMonitor(Channel componentChannel) {
|
||||
super(componentChannel, V1Secret.class, V1SecretList.class);
|
||||
context(K8sV1SecretStub.CONTEXT);
|
||||
ListOptions options = new ListOptions();
|
||||
options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
|
||||
+ "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET);
|
||||
options(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepareMonitoring() throws IOException, ApiException {
|
||||
client(new K8sClient());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleChange(K8sClient client, Response<V1Secret> 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 DisplayPasswordChanged(
|
||||
ResponseType.valueOf(change.type), change.object), channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* On get display secrets.
|
||||
*
|
||||
* @param event the event
|
||||
* @param channel the channel
|
||||
* @throws ApiException the api exception
|
||||
*/
|
||||
@Handler
|
||||
@SuppressWarnings("PMD.StringInstantiation")
|
||||
public void onGetDisplaySecrets(GetDisplayPassword event, VmChannel channel)
|
||||
throws ApiException {
|
||||
ListOptions options = new ListOptions();
|
||||
options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
|
||||
+ "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET + ","
|
||||
+ "app.kubernetes.io/instance=" + event.vmName());
|
||||
var stubs = K8sV1SecretStub.list(client(), namespace(), options);
|
||||
if (stubs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
stubs.iterator().next().model().map(m -> m.getData())
|
||||
.map(m -> m.get("display-password"))
|
||||
.ifPresent(p -> event.setResult(new String(p)));
|
||||
}
|
||||
}
|
||||
|
|
@ -224,6 +224,7 @@ public class Reconciler extends Component {
|
|||
return new DynamicKubernetesObject(json);
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
|
||||
private void adjustCdRomPaths(JsonObject json) {
|
||||
var disks
|
||||
= GsonPtr.to(json).to("spec", "vm", "disks").get(JsonArray.class);
|
||||
|
|
|
|||
|
|
@ -19,38 +19,36 @@
|
|||
package org.jdrupes.vmoperator.manager;
|
||||
|
||||
import io.kubernetes.client.openapi.ApiException;
|
||||
import io.kubernetes.client.openapi.models.V1Secret;
|
||||
import io.kubernetes.client.openapi.models.V1SecretList;
|
||||
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.K8sV1SecretStub;
|
||||
import static org.jdrupes.vmoperator.manager.Constants.COMP_DISPLAY_SECRET;
|
||||
import org.jdrupes.vmoperator.manager.events.DisplaySecretChanged;
|
||||
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 display secrets.
|
||||
* Watches for changes of services.
|
||||
*/
|
||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
||||
public class DisplaySecretsMonitor
|
||||
extends AbstractMonitor<V1Secret, V1SecretList, VmChannel> {
|
||||
public class ServiceMonitor
|
||||
extends AbstractMonitor<V1Service, V1ServiceList, VmChannel> {
|
||||
|
||||
/**
|
||||
* Instantiates a new display secrets monitor.
|
||||
*
|
||||
* @param componentChannel the component channel
|
||||
*/
|
||||
public DisplaySecretsMonitor(Channel componentChannel) {
|
||||
super(componentChannel, V1Secret.class, V1SecretList.class);
|
||||
context(K8sV1SecretStub.CONTEXT);
|
||||
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 + ","
|
||||
+ "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET);
|
||||
options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME);
|
||||
options(options);
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +58,7 @@ public class DisplaySecretsMonitor
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void handleChange(K8sClient client, Response<V1Secret> change) {
|
||||
protected void handleChange(K8sClient client, Response<V1Service> change) {
|
||||
String vmName = change.object.getMetadata().getLabels()
|
||||
.get("app.kubernetes.io/instance");
|
||||
if (vmName == null) {
|
||||
|
|
@ -70,8 +68,7 @@ public class DisplaySecretsMonitor
|
|||
if (channel == null || channel.vmDefinition() == null) {
|
||||
return;
|
||||
}
|
||||
channel.pipeline().fire(new DisplaySecretChanged(
|
||||
channel.pipeline().fire(new ServiceChanged(
|
||||
ResponseType.valueOf(change.type), change.object), channel);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -82,14 +82,15 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
|||
// or not running.
|
||||
var stsStub = K8sV1StatefulSetStub.get(channel.client(),
|
||||
metadata.getNamespace(), metadata.getName());
|
||||
stsStub.model().ifPresent(sts -> {
|
||||
var current = sts.getSpec().getReplicas();
|
||||
var stsModel = stsStub.model().orElse(null);
|
||||
if (stsModel != null) {
|
||||
var current = stsModel.getSpec().getReplicas();
|
||||
var desired = GsonPtr.to(stsDef.getRaw())
|
||||
.to("spec").getAsInt("replicas").orElse(1);
|
||||
if (current == 1 && desired == 1) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Do apply changes
|
||||
PatchOptions opts = new PatchOptions();
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@ public class VmMonitor
|
|||
private void addDynamicData(K8sClient client, K8sDynamicModel vmState) {
|
||||
var rootNode = GsonPtr.to(vmState.data()).get(JsonObject.class);
|
||||
rootNode.addProperty("nodeName", "");
|
||||
rootNode.addProperty("nodeAddress", "");
|
||||
|
||||
// VM definition status changes before the pod terminates.
|
||||
// This results in pod information being shown for a stopped
|
||||
|
|
@ -172,8 +173,17 @@ public class VmMonitor
|
|||
var podList
|
||||
= K8sV1PodStub.list(client, namespace(), podSearch);
|
||||
for (var podStub : podList) {
|
||||
rootNode.addProperty("nodeName",
|
||||
podStub.model().get().getSpec().getNodeName());
|
||||
var nodeName = podStub.model().get().getSpec().getNodeName();
|
||||
rootNode.addProperty("nodeName", nodeName);
|
||||
logger.fine(() -> "Added node name " + nodeName
|
||||
+ " to VM info for " + vmState.getMetadata().getName());
|
||||
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
|
||||
var addrs = new JsonArray();
|
||||
podStub.model().get().getStatus().getPodIPs().stream()
|
||||
.map(ip -> ip.getIp()).forEach(addrs::add);
|
||||
rootNode.add("nodeAddresses", addrs);
|
||||
logger.fine(() -> "Added node addresses " + addrs
|
||||
+ " to VM info for " + vmState.getMetadata().getName());
|
||||
}
|
||||
} catch (ApiException e) {
|
||||
logger.log(Level.WARNING, e,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue