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