diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index 1863afe..e4db3d5 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -1411,6 +1411,11 @@ spec: Amount of memory in use. type: string default: "0" + displayPasswordSerial: + description: >- + Counts changes of the display password. + type: integer + default: 0 conditions: description: >- List of component conditions observed diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index d416fa1..bb15639 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -43,6 +43,7 @@ import org.jdrupes.vmoperator.common.K8sDynamicModel; import org.jdrupes.vmoperator.common.K8sDynamicStub; import org.jdrupes.vmoperator.runner.qemu.events.BalloonChangeEvent; import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; +import org.jdrupes.vmoperator.runner.qemu.events.DisplayPasswordChanged; import org.jdrupes.vmoperator.runner.qemu.events.Exit; import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; @@ -321,6 +322,26 @@ public class StatusUpdater extends Component { }); } + /** + * On ballon change. + * + * @param event the event + * @throws ApiException + */ + @Handler + public void onDisplayPasswordChanged(DisplayPasswordChanged event) + throws ApiException { + if (vmStub == null) { + return; + } + vmStub.updateStatus(from -> { + JsonObject status = from.status(); + status.addProperty("displayPasswordSerial", + status.get("displayPasswordSerial").getAsLong() + 1); + return status; + }); + } + /** * On shutdown. * diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/DisplayPasswordChanged.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/DisplayPasswordChanged.java new file mode 100644 index 0000000..0814f50 --- /dev/null +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/DisplayPasswordChanged.java @@ -0,0 +1,39 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.runner.qemu.events; + +import com.fasterxml.jackson.databind.JsonNode; +import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand; + +/** + * A {@link MonitorResult} that indicates that the display password has changed. + */ +public class DisplayPasswordChanged extends MonitorResult { + + /** + * Instantiates a new display password changed. + * + * @param command the command + * @param response the response + */ + public DisplayPasswordChanged(QmpCommand command, JsonNode response) { + super(command, response); + } + +} diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java index c0f55fe..9352ab2 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java @@ -25,6 +25,7 @@ import org.jdrupes.vmoperator.runner.qemu.commands.QmpCapabilities; import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand; import org.jdrupes.vmoperator.runner.qemu.commands.QmpDelCpu; import org.jdrupes.vmoperator.runner.qemu.commands.QmpQueryHotpluggableCpus; +import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetDisplayPassword; import org.jgrapes.core.Channel; import org.jgrapes.core.Components; import org.jgrapes.core.Event; @@ -57,6 +58,9 @@ public class MonitorResult extends Event { if (command instanceof QmpDelCpu) { return new CpuDeleted(command, response); } + if (command instanceof QmpSetDisplayPassword) { + return new DisplayPasswordChanged(command, response); + } return new MonitorResult(command, response); } diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java index c7f634b..ee1667c 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java @@ -20,6 +20,7 @@ package org.jdrupes.vmoperator.vmconlet; import java.time.Duration; import java.time.Instant; +import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; @@ -30,7 +31,8 @@ import java.util.List; @SuppressWarnings("PMD.DataflowAnomalyAnalysis") public class TimeSeries { - private final List data = new LinkedList<>(); + @SuppressWarnings("PMD.LooseCoupling") + private final LinkedList data = new LinkedList<>(); private final Duration period; /** @@ -52,25 +54,26 @@ public class TimeSeries { @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") public TimeSeries add(Instant time, Number... numbers) { var newEntry = new Entry(time, numbers); - boolean adjust = false; - if (data.size() >= 2) { - var lastEntry = data.get(data.size() - 1); - var lastButOneEntry = data.get(data.size() - 2); - adjust = lastEntry.valuesEqual(lastButOneEntry) - && lastEntry.valuesEqual(newEntry); - } - if (adjust) { - data.get(data.size() - 1).adjustTime(time); - } else { + boolean nothingNew = false; + synchronized (data) { + if (data.size() >= 2) { + var lastEntry = data.get(data.size() - 1); + var lastButOneEntry = data.get(data.size() - 2); + nothingNew = lastEntry.valuesEqual(lastButOneEntry) + && lastEntry.valuesEqual(newEntry); + } + if (nothingNew) { + data.removeLast(); + } data.add(new Entry(time, numbers)); - } - // Purge - Instant limit = time.minus(period); - while (data.size() > 2 - && data.get(0).getTime().isBefore(limit) - && data.get(1).getTime().isBefore(limit)) { - data.remove(0); + // Purge + Instant limit = time.minus(period); + while (data.size() > 2 + && data.get(0).getTime().isBefore(limit) + && data.get(1).getTime().isBefore(limit)) { + data.removeFirst(); + } } return this; } @@ -81,14 +84,16 @@ public class TimeSeries { * @return the list */ public List entries() { - return data; + synchronized (data) { + return new ArrayList<>(data); + } } /** * The Class Entry. */ public static class Entry { - private Instant timestamp; + private final Instant timestamp; private final Number[] values; /** @@ -103,15 +108,6 @@ public class TimeSeries { values = numbers; } - /** - * Changes the entry's time. - * - * @param time the time - */ - public void adjustTime(Instant time) { - timestamp = time; - } - /** * Returns the entry's time. * diff --git a/settings.gradle b/settings.gradle index cb613b6..cf075af 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,3 +16,4 @@ include 'org.jdrupes.vmoperator.vmconlet' include 'org.jdrupes.vmoperator.runner.qemu' include 'org.jdrupes.vmoperator.common' include 'org.jdrupes.vmoperator.util' +include 'spice-squid' diff --git a/spice-squid/.checkstyle b/spice-squid/.checkstyle new file mode 100644 index 0000000..7f2c604 --- /dev/null +++ b/spice-squid/.checkstyle @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/spice-squid/.eclipse-pmd b/spice-squid/.eclipse-pmd new file mode 100644 index 0000000..8b394f8 --- /dev/null +++ b/spice-squid/.eclipse-pmd @@ -0,0 +1,7 @@ + + + + + + + diff --git a/spice-squid/.settings/net.sf.jautodoc.prefs b/spice-squid/.settings/net.sf.jautodoc.prefs new file mode 100644 index 0000000..03e8200 --- /dev/null +++ b/spice-squid/.settings/net.sf.jautodoc.prefs @@ -0,0 +1,8 @@ +add_header=true +eclipse.preferences.version=1 +header_text=/*\n * VM-Operator\n * Copyright (C) 2024 Michael N. Lipp\n * \n * This program is free software\: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n */ +project_specific_settings=true +replacements=\n\n\nReturns the\nSets the\nAdds the\nEdits the\nRemoves the\nInits the\nParses the\nCreates the\nBuilds the\nChecks if is\nPrints the\nChecks for\n\n\n +visibility_package=false +visibility_private=false +visibility_protected=false diff --git a/spice-squid/.settings/org.eclipse.buildship.core.prefs b/spice-squid/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..258eb47 --- /dev/null +++ b/spice-squid/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,13 @@ +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) +connection.project.dir=.. +eclipse.preferences.version=1 +gradle.user.home= +java.home= +jvm.arguments= +offline.mode=false +override.workspace.settings=false +show.console.view=false +show.executions.view=false diff --git a/spice-squid/.settings/org.eclipse.core.resources.prefs b/spice-squid/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/spice-squid/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/spice-squid/.settings/org.eclipse.core.runtime.prefs b/spice-squid/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 0000000..5a0ad22 --- /dev/null +++ b/spice-squid/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +line.separator=\n diff --git a/spice-squid/Containerfile b/spice-squid/Containerfile new file mode 100644 index 0000000..b65b631 --- /dev/null +++ b/spice-squid/Containerfile @@ -0,0 +1,11 @@ +FROM alpine:3.19 + +RUN apk update &&\ + apk add --no-cache inotify-tools &&\ + apk add --no-cache squid + +COPY run.sh /usr/local/bin/run-squid.sh + +CMD ["/usr/local/bin/run-squid.sh"] + +EXPOSE 3128 diff --git a/spice-squid/build.gradle b/spice-squid/build.gradle new file mode 100644 index 0000000..2cb7183 --- /dev/null +++ b/spice-squid/build.gradle @@ -0,0 +1,77 @@ +plugins { + id 'org.jdrupes.vmoperator.java-application-conventions' +} + +dependencies { +} + +task buildImage(type: Exec) { + inputs.files 'Containerfile' + + commandLine 'podman', 'build', '--pull', + '-t', "${project.name}:${project.version}",\ + '-f', 'Containerfile', '.' +} + +task tagLatestImage(type: Exec) { + dependsOn buildImage + + enabled = !project.version.contains("SNAPSHOT") + && !project.version.contains("alpha") \ + && !project.version.contains("beta") \ + || project.rootProject.properties['docker.testRegistry'] \ + && project.rootProject.properties['docker.registry'] \ + == project.rootProject.properties['docker.testRegistry'] + + commandLine 'podman', 'tag', "${project.name}:${project.version}",\ + "${project.name}:latest" +} + +task buildLatestImage { + dependsOn buildImage + dependsOn tagLatestImage +} + +task pushImage(type: Exec) { + dependsOn buildImage + + commandLine 'podman', 'push', '--tls-verify=false', \ + "localhost/${project.name}:${project.version}", \ + "${project.rootProject.properties['docker.registry']}" \ + + "/${project.name}:${project.version}" +} + +task pushLatestImage(type: Exec) { + dependsOn buildLatestImage + + enabled = !project.version.contains("SNAPSHOT") + && !project.version.contains("alpha") \ + && !project.version.contains("beta") \ + || project.rootProject.properties['docker.testRegistry'] \ + && project.rootProject.properties['docker.registry'] \ + == project.rootProject.properties['docker.testRegistry'] + + commandLine 'podman', 'push', '--tls-verify=false', \ + "localhost/${project.name}:${project.version}", \ + "${project.rootProject.properties['docker.registry']}" \ + + "/${project.name}:latest" +} + +task pushImages { + // Don't push without testing first + dependsOn pushImage + dependsOn pushLatestImage +} + +test { + enabled = project.hasProperty("k8s.testCluster") + + useJUnitPlatform() + + testLogging { + showStandardStreams = true + } + + systemProperty "k8s.testCluster", project.hasProperty("k8s.testCluster") + ? project.getProperty("k8s.testCluster") : null +} diff --git a/spice-squid/run.sh b/spice-squid/run.sh new file mode 100755 index 0000000..eddea39 --- /dev/null +++ b/spice-squid/run.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +CONF_OPT="-f /run/etc/squid/squid.conf" +/usr/sbin/squid $CONF_OPT + +inotifywait -m -e create -r /run/etc/squid | + while read file_path file_event file_name; do + if [ "$file_event" != "CREATE" ]; then + continue + fi + if [ -r /run/squid/squid.pid ]; then + echo "Reconfiguring squid" + /usr/sbin/squid $CONF_OPT -k reconfigure + else + echo "Restarting squid" + /usr/sbin/squid $CONF_OPT + fi + echo "Processed event" + done diff --git a/spice-squid/squid.conf b/spice-squid/squid.conf new file mode 100644 index 0000000..724b0df --- /dev/null +++ b/spice-squid/squid.conf @@ -0,0 +1,4 @@ +http_access deny all + +# Squid normally listens to port 3128 +http_port 3128