Compare commits

..

No commits in common. "main" and "wip/optimize" have entirely different histories.

101 changed files with 431 additions and 446 deletions

78
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,78 @@
stages:
- build
- test
- publish
- deploy
.any-job:
rules:
- if: $CI_SERVER_HOST == "gitlab.mnl.de"
.gradle-job:
extends: .any-job
image: registry.mnl.de/org/jgrapes/jdk21-builder:v2
cache:
- key: dependencies-${CI_COMMIT_BRANCH}
policy: pull-push
paths:
- .gradle
- node_modules
- key: "$CI_COMMIT_SHA"
policy: pull-push
paths:
- build
- "*/build"
before_script:
- echo -n $CI_REGISTRY_PASSWORD | podman login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
- git switch $(git branch -r --sort="authordate" --contains $CI_COMMIT_SHA | head -1 | sed -e 's#[^/]*/##')
- git pull
- git reset --hard $CI_COMMIT_SHA
build-jars:
stage: build
extends: .gradle-job
script:
- ./gradlew -Pdocker.registry=$CI_REGISTRY_IMAGE build apidocs
publish-images:
stage: publish
extends: .gradle-job
dependencies:
- build-jars
script:
- ./gradlew -Pdocker.registry=$CI_REGISTRY_IMAGE publishImage
.pages-job:
extends: .any-job
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/ruby:3.2
variables:
JEKYLL_ENV: production
LC_ALL: C.UTF-8
before_script:
- git fetch origin gh-pages
- git checkout gh-pages
- gem install bundler
- bundle install
test-pages:
stage: test
extends: .pages-job
rules:
- if: $CI_COMMIT_BRANCH == "gh-pages"
script:
- bundle exec jekyll build -d test
artifacts:
paths:
- test
#publish-pages:
# stage: publish
# extends: .pages-job
# rules:
# - if: $CI_COMMIT_BRANCH == "gh-pages"
# script:
# - bundle exec jekyll build -d public
# artifacts:
# paths:
# - public
# environment: production

View file

@ -1,38 +0,0 @@
when:
- event: push
evaluate: 'CI_SYSTEM_HOST == "woodpecker.mnl.de"'
clone:
- name: git
image: woodpeckerci/plugin-git
settings:
partial: false
tags: true
depth: 0
steps:
- name: prepare
image: alpine
commands:
# Because we run the next step as user 1000 to make podman work:
- mkdir /woodpecker/workflow
- chown 1000:1000 /woodpecker/workflow
- chown -R 1000:1000 $CI_WORKSPACE
- name: build-jars
image: registry.mnl.de/mnl/jdk21-builder:v4
environment:
HOME: /woodpecker/workflow
REGISTRY: registry.mnl.de
REGISTRY_USER: mnl
REGISTRY_TOKEN:
from_secret: REGISTRY_TOKEN
commands:
- echo $REGISTRY_TOKEN | podman login -u $REGISTRY_USER --password-stdin $REGISTRY
- ./gradlew -Pdocker.registry=$REGISTRY/$REGISTRY_USER build apidocs publishImage
backend_options:
kubernetes:
securityContext:
privileged: true
runAsUser: 1000
runAsGroup: 1000

View file

@ -3,9 +3,9 @@
The CRD must be deployed independently. Apart from that, the
`kustomize.yaml`
* creates a small cdrom image repository and
* creates a small cdrom image repository and
* deploys the operator in namespace `vmop-dev` with a replica of 0.
* deploys the operator in namespace `vmop-dev` with a replica of 0.
This allows you to run the manager in your IDE.

View file

@ -1,3 +0,0 @@
#!/bin/sh
sed -i '/AutomaticLogin/d' /etc/gdm/custom.conf

View file

@ -72,8 +72,8 @@ doLogin() {
return
fi
# Check if this user is already logged in on tty2
curUser=$(loginctl -j | jq -r '.[] | select(.tty=="tty2") | .user')
# Check if this user is already logged in on tty1
curUser=$(loginctl -j | jq -r '.[] | select(.tty=="tty1") | .user')
if [ "$curUser" = "$user" ]; then
echo >&${con} "201 User already logged in"
return
@ -97,13 +97,16 @@ doLogin() {
fi
fi
# Configure user as auto login user
sed -i '/AutomaticLogin/d' /etc/gdm/custom.conf
sed -i '/\[daemon\]/a AutomaticLoginEnable=true\nAutomaticLogin='$user \
/etc/gdm/custom.conf
# Activate user
systemctl restart gdm
# Start the desktop for the user
systemd-run 2>$temperr \
--unit vmop-user-desktop --uid=$uid --gid=$uid \
--working-directory="/home/$user" -p TTYPath=/dev/tty1 \
-p PAMName=login -p StandardInput=tty -p StandardOutput=journal \
-p Conflicts="gdm.service getty@tty1.service" \
-E XDG_RUNTIME_DIR="/run/user/$uid" \
-E XDG_CURRENT_DESKTOP=GNOME \
-p ExecStartPre="/usr/bin/chvt 1" \
dbus-run-session -- gnome-shell --display-server --wayland
if [ $? -eq 0 ]; then
echo >&${con} "201 User logged in successfully"
else
@ -114,8 +117,14 @@ doLogin() {
# Attempt to log out a user currently using tty1. This is an intermediate
# operation that can be invoked from other operations
attemptLogout() {
sed -i '/AutomaticLogin/d' /etc/gdm/custom.conf
systemctl stop gdm
systemctl status vmop-user-desktop > /dev/null 2>&1
if [ $? = 0 ]; then
systemctl stop vmop-user-desktop
fi
loginctl -j | jq -r '.[] | select(.tty=="tty1") | .session' \
| while read sid; do
loginctl kill-session $sid
done
echo >&${con} "102 Desktop stopped"
}
@ -124,7 +133,15 @@ attemptLogout() {
# Also try to restart gdm, if it is not running.
doLogout() {
attemptLogout
systemctl restart gdm
systemctl status gdm >/dev/null 2>&1
if [ $? != 0 ]; then
systemctl restart gdm 2>$temperr
if [ $? -eq 0 ]; then
echo >&${con} "102 gdm restarted"
else
echo >&${con} "102 Restarting gdm failed: $(tr '\n' ' ' <${temperr})"
fi
fi
echo >&${con} "202 User logged out"
}
@ -136,7 +153,7 @@ while read line <&${con}; do
done
onExit() {
doLogout
attemptLogout
if [ -n "$temperr" ]; then
rm -f $temperr
fi

View file

@ -32,5 +32,4 @@
<noscript><p><img referrerpolicy="no-referrer-when-downgrade"
src="//piwik.mnl.de/matomo.php?idsite=17&amp;rec=1&amp;action_name=VM-Operator" style="border:0;" alt="" /></p></noscript>
<!-- End Matomo Code -->
<script defer src="https://gotit.mnl.de/script.js" data-website-id="14b277ad-d330-4a54-82f1-a77d111240ac"></script>
</div>

View file

@ -32,11 +32,13 @@ import java.util.regex.Pattern;
public class Convertions {
@SuppressWarnings({ "PMD.UseConcurrentHashMap",
"PMD.FieldNamingConventions" })
"PMD.FieldNamingConventions", "PMD.VariableNamingConventions" })
private static final Map<String, BigInteger> unitMap = new HashMap<>();
@SuppressWarnings({ "PMD.FieldNamingConventions" })
@SuppressWarnings({ "PMD.FieldNamingConventions",
"PMD.VariableNamingConventions" })
private static final List<Map.Entry<String, BigInteger>> unitMappings;
@SuppressWarnings({ "PMD.FieldNamingConventions" })
@SuppressWarnings({ "PMD.FieldNamingConventions",
"PMD.VariableNamingConventions" })
private static final Pattern memorySize
= Pattern.compile("^\\s*(\\d+(\\.\\d+)?)\\s*([A-Za-z]*)\\s*");
@ -67,6 +69,7 @@ public class Convertions {
* @param amount the amount
* @return the big integer
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public static BigInteger parseMemory(Object amount) {
if (amount == null) {
return (BigInteger) amount;

View file

@ -47,7 +47,8 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
/**
* Helpers for K8s API.
*/
@SuppressWarnings({ "PMD.ShortClassName", "PMD.UseUtilityClass" })
@SuppressWarnings({ "PMD.ShortClassName", "PMD.UseUtilityClass",
"PMD.DataflowAnomalyAnalysis" })
public class K8s {
/**
@ -112,6 +113,7 @@ public class K8s {
public static JsonObject yamlToJson(ApiClient client, Reader yaml) {
// Avoid Yaml.load due to
// https://github.com/kubernetes-client/java/issues/2741
@SuppressWarnings("PMD.UseConcurrentHashMap")
Map<String, Object> yamlData
= new Yaml(new SafeConstructor(new LoaderOptions())).load(yaml);

View file

@ -48,7 +48,8 @@ import okhttp3.Response;
* A client with some additional properties.
*/
@SuppressWarnings({ "PMD.ExcessivePublicCount", "PMD.TooManyMethods",
"checkstyle:LineLength", "PMD.CouplingBetweenObjects", "PMD.GodClass" })
"PMD.LinguisticNaming", "checkstyle:LineLength",
"PMD.CouplingBetweenObjects", "PMD.GodClass" })
public class K8sClient extends ApiClient {
private ApiClient apiClient;
@ -230,6 +231,7 @@ public class K8sClient extends ApiClient {
* @return the api client
* @see ApiClient#setKeyManagers(javax.net.ssl.KeyManager[])
*/
@SuppressWarnings("PMD.UseVarargs")
@Override
public ApiClient setKeyManagers(KeyManager[] managers) {
return apiClient().setKeyManagers(managers);
@ -636,6 +638,7 @@ public class K8sClient extends ApiClient {
* @return the string
* @see ApiClient#selectHeaderAccept(java.lang.String[])
*/
@SuppressWarnings("PMD.UseVarargs")
@Override
public String selectHeaderAccept(String[] accepts) {
return apiClient().selectHeaderAccept(accepts);
@ -648,6 +651,7 @@ public class K8sClient extends ApiClient {
* @return the string
* @see ApiClient#selectHeaderContentType(java.lang.String[])
*/
@SuppressWarnings("PMD.UseVarargs")
@Override
public String selectHeaderContentType(String[] contentTypes) {
return apiClient().selectHeaderContentType(contentTypes);
@ -814,7 +818,7 @@ public class K8sClient extends ApiClient {
* @throws ApiException the api exception
* @see ApiClient#buildCall(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.Object, java.util.Map, java.util.Map, java.util.Map, java.lang.String[], io.kubernetes.client.openapi.ApiCallback)
*/
@SuppressWarnings({ "rawtypes" })
@SuppressWarnings({ "rawtypes", "PMD.ExcessiveParameterList" })
@Override
public Call buildCall(String path, String method, List<Pair> queryParams,
List<Pair> collectionQueryParams, Object body,
@ -843,7 +847,7 @@ public class K8sClient extends ApiClient {
* @throws ApiException the api exception
* @see ApiClient#buildRequest(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.Object, java.util.Map, java.util.Map, java.util.Map, java.lang.String[], io.kubernetes.client.openapi.ApiCallback)
*/
@SuppressWarnings({ "rawtypes" })
@SuppressWarnings({ "rawtypes", "PMD.ExcessiveParameterList" })
@Override
public Request buildRequest(String path, String method,
List<Pair> queryParams, List<Pair> collectionQueryParams,

View file

@ -45,7 +45,8 @@ import java.util.function.Function;
* @param <O> the generic type
* @param <L> the generic type
*/
@SuppressWarnings({ "PMD.CouplingBetweenObjects" })
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis",
"PMD.CouplingBetweenObjects" })
public class K8sClusterGenericStub<O extends KubernetesObject,
L extends KubernetesListObject> {
protected final K8sClient client;
@ -239,7 +240,6 @@ public class K8sClusterGenericStub<O extends KubernetesObject,
* @param <L> the object list type
* @param <R> the result type
*/
@FunctionalInterface
public interface GenericSupplier<O extends KubernetesObject,
L extends KubernetesListObject,
R extends K8sClusterGenericStub<O, L>> {
@ -254,6 +254,7 @@ public class K8sClusterGenericStub<O extends KubernetesObject,
* @param name the name
* @return the result
*/
@SuppressWarnings("PMD.UseObjectForClearerAPI")
R get(Class<O> objectClass, Class<L> objectListClass, K8sClient client,
APIResource context, String name);
}
@ -282,6 +283,7 @@ public class K8sClusterGenericStub<O extends KubernetesObject,
* @return the stub if the object exists
* @throws ApiException the api exception
*/
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop" })
public static <O extends KubernetesObject, L extends KubernetesListObject,
R extends K8sClusterGenericStub<O, L>>
R get(Class<O> objectClass, Class<L> objectListClass,
@ -312,6 +314,8 @@ public class K8sClusterGenericStub<O extends KubernetesObject,
* @return the stub if the object exists
* @throws ApiException the api exception
*/
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
"PMD.UseObjectForClearerAPI" })
public static <O extends KubernetesObject, L extends KubernetesListObject,
R extends K8sClusterGenericStub<O, L>>
R get(Class<O> objectClass, Class<L> objectListClass,
@ -336,6 +340,8 @@ public class K8sClusterGenericStub<O extends KubernetesObject,
* @return the stub if the object exists
* @throws ApiException the api exception
*/
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
public static <O extends KubernetesObject, L extends KubernetesListObject,
R extends K8sClusterGenericStub<O, L>>
R create(Class<O> objectClass, Class<L> objectListClass,

View file

@ -29,6 +29,7 @@ import io.kubernetes.client.openapi.models.V1ObjectMeta;
* notably the metadata, is made available through the methods
* defined by {@link KubernetesObject}.
*/
@SuppressWarnings("PMD.DataClass")
public class K8sDynamicModel implements KubernetesObject {
private final V1ObjectMeta metadata;

View file

@ -62,7 +62,7 @@ public class K8sDynamicModelsBase<T extends K8sDynamicModel>
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException exc) {
throw new IllegalArgumentException(exc);
throw new IllegalArgumentException(exc); // NOPMD
}
}
}

View file

@ -31,6 +31,7 @@ import java.util.Collection;
* state and can therefore be used for any kind of object, especially
* custom objects.
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class K8sDynamicStub
extends K8sDynamicStubBase<K8sDynamicModel, K8sDynamicModels> {
@ -63,6 +64,8 @@ public class K8sDynamicStub
* @return the stub if the object exists
* @throws ApiException the api exception
*/
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
public static K8sDynamicStub get(K8sClient client,
GroupVersionKind gvk, String namespace, String name)
throws ApiException {
@ -80,6 +83,8 @@ public class K8sDynamicStub
* @return the stub if the object exists
* @throws ApiException the api exception
*/
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
public static K8sDynamicStub get(K8sClient client,
APIResource context, String namespace, String name) {
return new K8sDynamicStub(client, context, namespace, name);

View file

@ -26,6 +26,7 @@ import io.kubernetes.client.Discovery.APIResource;
* state and can therefore be used for any kind of object, especially
* custom objects.
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public abstract class K8sDynamicStubBase<O extends K8sDynamicModel,
L extends K8sDynamicModelsBase<O>> extends K8sGenericStub<O, L> {
@ -39,6 +40,7 @@ public abstract class K8sDynamicStubBase<O extends K8sDynamicModel,
* @param namespace the namespace
* @param name the name
*/
@SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
public K8sDynamicStubBase(Class<O> objectClass,
Class<L> objectListClass, DynamicTypeAdapterFactory<O, L> taf,
K8sClient client, APIResource context, String namespace,

View file

@ -48,7 +48,7 @@ import java.util.function.Function;
* @param <O> the generic type
* @param <L> the generic type
*/
@SuppressWarnings({ "PMD.TooManyMethods" })
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.TooManyMethods" })
public class K8sGenericStub<O extends KubernetesObject,
L extends KubernetesListObject> {
protected final K8sClient client;
@ -200,6 +200,7 @@ public class K8sGenericStub<O extends KubernetesObject,
* @return the updated model or empty if the object was not found
* @throws ApiException the api exception
*/
@SuppressWarnings("PMD.AssignmentInOperand")
public Optional<O> updateStatus(O object, Function<O, Object> updater)
throws ApiException {
return K8s.optional(api.updateStatus(object, updater));
@ -217,7 +218,7 @@ public class K8sGenericStub<O extends KubernetesObject,
* @return the updated model or empty if the object was not found
* @throws ApiException the api exception
*/
@SuppressWarnings({ "PMD.AssignmentInOperand" })
@SuppressWarnings({ "PMD.AssignmentInOperand", "PMD.UnusedAssignment" })
public Optional<O> updateStatus(Function<O, Object> updater, O current,
int retries) throws ApiException {
while (true) {
@ -247,6 +248,7 @@ public class K8sGenericStub<O extends KubernetesObject,
* @return the updated model or empty if the object was not found
* @throws ApiException the api exception
*/
@SuppressWarnings({ "PMD.AssignmentInOperand", "PMD.UnusedAssignment" })
public Optional<O> updateStatus(Function<O, Object> updater, int retries)
throws ApiException {
return updateStatus(updater, null, retries);
@ -357,7 +359,6 @@ public class K8sGenericStub<O extends KubernetesObject,
* @param <L> the object list type
* @param <R> the result type
*/
@FunctionalInterface
public interface GenericSupplier<O extends KubernetesObject,
L extends KubernetesListObject, R extends K8sGenericStub<O, L>> {
@ -369,6 +370,7 @@ public class K8sGenericStub<O extends KubernetesObject,
* @param name the name
* @return the result
*/
@SuppressWarnings("PMD.UseObjectForClearerAPI")
R get(K8sClient client, String namespace, String name);
}
@ -394,6 +396,8 @@ public class K8sGenericStub<O extends KubernetesObject,
* @return the stub if the object exists
* @throws ApiException the api exception
*/
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
public static <O extends KubernetesObject, L extends KubernetesListObject,
R extends K8sGenericStub<O, L>>
R create(Class<O> objectClass, Class<L> objectListClass,

View file

@ -50,6 +50,7 @@ public class K8sObserver<O extends KubernetesObject,
ADDED, MODIFIED, DELETED
}
@SuppressWarnings("PMD.FieldNamingConventions")
protected final Logger logger = Logger.getLogger(getClass().getName());
protected final K8sClient client;
@ -72,7 +73,8 @@ public class K8sObserver<O extends KubernetesObject,
* @param namespace the namespace
* @param options the options
*/
@SuppressWarnings({ "PMD.AvoidCatchingThrowable",
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
"PMD.UseObjectForClearerAPI", "PMD.AvoidCatchingThrowable",
"PMD.CognitiveComplexity", "PMD.AvoidCatchingGenericException" })
public K8sObserver(Class<O> objectClass, Class<L> objectListClass,
K8sClient client, APIResource context, String namespace,
@ -98,6 +100,7 @@ public class K8sObserver<O extends KubernetesObject,
while (!Thread.currentThread().isInterrupted()) {
Instant startedAt = Instant.now();
try {
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
var changed
= api.watch(namespace, options).iterator();
while (changed.hasNext()) {
@ -230,6 +233,7 @@ public class K8sObserver<O extends KubernetesObject,
}
@Override
@SuppressWarnings("PMD.UseLocaleWithCaseConversions")
public String toString() {
return "Observer for " + K8s.toString(context) + " " + namespace;
}

View file

@ -26,6 +26,7 @@ import java.util.List;
/**
* A stub for config maps (v1).
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class K8sV1ConfigMapStub
extends K8sGenericStub<V1ConfigMap, V1ConfigMapList> {

View file

@ -29,6 +29,7 @@ import java.util.Optional;
/**
* A stub for pods (v1).
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class K8sV1DeploymentStub
extends K8sGenericStub<V1Deployment, V1DeploymentList> {

View file

@ -29,6 +29,7 @@ import java.util.List;
/**
* A stub for nodes (v1).
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class K8sV1NodeStub extends K8sClusterGenericStub<V1Node, V1NodeList> {
public static final APIResource CONTEXT = new APIResource("", List.of("v1"),
@ -73,7 +74,8 @@ public class K8sV1NodeStub extends K8sClusterGenericStub<V1Node, V1NodeList> {
/**
* Provide {@link GenericSupplier}.
*/
@SuppressWarnings({ "PMD.UnusedFormalParameter" })
@SuppressWarnings({ "PMD.UnusedFormalParameter",
"PMD.UnusedPrivateMethod" })
private static K8sV1NodeStub getGeneric(Class<V1Node> objectClass,
Class<V1NodeList> objectListClass, K8sClient client,
APIResource context, String name) {

View file

@ -29,6 +29,7 @@ import java.util.List;
/**
* A stub for pods (v1).
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class K8sV1PodStub extends K8sGenericStub<V1Pod, V1PodList> {
/** The pods' context. */

View file

@ -29,6 +29,7 @@ import java.util.List;
/**
* A stub for pods (v1).
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class K8sV1PvcStub extends
K8sGenericStub<V1PersistentVolumeClaim, V1PersistentVolumeClaimList> {

View file

@ -29,6 +29,7 @@ import java.util.List;
/**
* A stub for secrets (v1).
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class K8sV1SecretStub extends K8sGenericStub<V1Secret, V1SecretList> {
public static final APIResource CONTEXT = new APIResource("", List.of("v1"),

View file

@ -29,6 +29,7 @@ import java.util.List;
/**
* A stub for secrets (v1).
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class K8sV1ServiceStub extends K8sGenericStub<V1Service, V1ServiceList> {
public static final APIResource CONTEXT = new APIResource("", List.of("v1"),

View file

@ -26,6 +26,7 @@ import java.util.List;
/**
* A stub for stateful sets (v1).
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class K8sV1StatefulSetStub
extends K8sGenericStub<V1StatefulSet, V1StatefulSetList> {

View file

@ -46,10 +46,11 @@ import org.jdrupes.vmoperator.util.DataPath;
/**
* Represents a VM definition.
*/
@SuppressWarnings({ "PMD.DataClass", "PMD.TooManyMethods" })
@SuppressWarnings({ "PMD.DataClass", "PMD.TooManyMethods",
"PMD.CouplingBetweenObjects" })
public class VmDefinition extends K8sDynamicModel {
@SuppressWarnings({ "unused" })
@SuppressWarnings({ "PMD.FieldNamingConventions", "unused" })
private static final Logger logger
= Logger.getLogger(VmDefinition.class.getName());
@SuppressWarnings("PMD.FieldNamingConventions")

View file

@ -31,6 +31,7 @@ import java.util.Collection;
* state and can therefore be used for any kind of object, especially
* custom objects.
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class VmDefinitionStub
extends K8sDynamicStubBase<VmDefinition, VmDefinitions> {
@ -63,6 +64,8 @@ public class VmDefinitionStub
* @return the stub if the object exists
* @throws ApiException the api exception
*/
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
public static VmDefinitionStub get(K8sClient client,
GroupVersionKind gvk, String namespace, String name)
throws ApiException {
@ -80,6 +83,8 @@ public class VmDefinitionStub
* @return the stub if the object exists
* @throws ApiException the api exception
*/
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
public static VmDefinitionStub get(K8sClient client,
APIResource context, String namespace, String name) {
return new VmDefinitionStub(client, context, namespace, name);

View file

@ -34,6 +34,7 @@ import java.util.logging.Logger;
*/
public class VmExtraData {
@SuppressWarnings("PMD.FieldNamingConventions")
private static final Logger logger
= Logger.getLogger(VmExtraData.class.getName());
@ -117,14 +118,14 @@ public class VmExtraData {
if (addr.isEmpty()) {
logger
.severe(() -> "Failed to find display IP for " + vmDef.name());
return Optional.empty();
return null;
}
var port = vmDef.<Number> fromVm("display", "spice", "port")
.map(Number::longValue);
if (port.isEmpty()) {
logger
.severe(() -> "No port defined for display of " + vmDef.name());
return Optional.empty();
return null;
}
StringBuffer data = new StringBuffer(100)
.append("[virt-viewer]\ntype=spice\nhost=")

View file

@ -35,6 +35,7 @@ import org.jdrupes.vmoperator.util.DataPath;
/**
* Represents a VM pool.
*/
@SuppressWarnings({ "PMD.DataClass" })
public class VmPool {
private final String name;

View file

@ -24,6 +24,7 @@ import org.jgrapes.core.Event;
/**
* Assign a VM from a pool to a user.
*/
@SuppressWarnings("PMD.DataClass")
public class AssignVm extends Event<VmData> {
private final String fromPool;

View file

@ -43,6 +43,7 @@ public interface ChannelDictionary<K, C extends Channel, A> {
* @param channel the channel
* @param associated the associated
*/
@SuppressWarnings("PMD.ShortClassName")
public record Value<C extends Channel, A>(C channel, A associated) {
}

View file

@ -149,6 +149,8 @@ public class ChannelManager<K, C extends Channel, A>
* @param supplier the supplier
* @return the channel
*/
@SuppressWarnings({ "PMD.AssignmentInOperand",
"PMD.DataflowAnomalyAnalysis" })
public C computeIfAbsent(K key, Function<K, C> supplier) {
return entries.computeIfAbsent(key,
k -> new Value<>(supplier.apply(k), null)).channel();

View file

@ -24,6 +24,7 @@ import org.jgrapes.core.Event;
/**
* Gets the current display secret and optionally updates it.
*/
@SuppressWarnings("PMD.DataClass")
public class GetDisplaySecret extends Event<String> {
private final VmDefinition vmDef;

View file

@ -27,6 +27,7 @@ import org.jgrapes.core.Event;
/**
* Gets the known pools' definitions.
*/
@SuppressWarnings("PMD.DataClass")
public class GetPools extends Event<List<VmPool>> {
private String name;

View file

@ -27,6 +27,7 @@ import org.jgrapes.core.Event;
/**
* Gets the known VMs' definitions and channels.
*/
@SuppressWarnings("PMD.DataClass")
public class GetVms extends Event<List<GetVms.VmData>> {
private String name;

View file

@ -24,6 +24,7 @@ import org.jgrapes.core.Event;
/**
* Modifies a VM.
*/
@SuppressWarnings("PMD.DataClass")
public class ModifyVm extends Event<Void> {
private final String name;

View file

@ -23,6 +23,7 @@ import org.jgrapes.core.Event;
/**
* Triggers a reset of the VM.
*/
@SuppressWarnings("PMD.DataClass")
public class ResetVm extends Event<String> {
private final String vmName;

View file

@ -24,6 +24,7 @@ import org.jgrapes.core.Event;
/**
* Note the assignment to a user in the VM status.
*/
@SuppressWarnings("PMD.DataClass")
public class UpdateAssignment extends Event<Boolean> {
private final VmPool fromPool;

View file

@ -28,6 +28,7 @@ import org.jgrapes.core.Subchannel.DefaultSubchannel;
/**
* A subchannel used to send the events related to a specific VM.
*/
@SuppressWarnings("PMD.DataClass")
public class VmChannel extends DefaultSubchannel {
private final EventPipeline pipeline;
@ -55,6 +56,7 @@ public class VmChannel extends DefaultSubchannel {
* @param definition the definition
* @return the watch channel
*/
@SuppressWarnings("PMD.LinguisticNaming")
public VmChannel setVmDefinition(VmDefinition definition) {
this.definition = definition;
return this;
@ -85,6 +87,7 @@ public class VmChannel extends DefaultSubchannel {
* @param generation the generation to set
* @return true if value has changed
*/
@SuppressWarnings("PMD.LinguisticNaming")
public boolean setGeneration(long generation) {
if (this.generation == generation) {
return false;

View file

@ -26,6 +26,7 @@ import org.jgrapes.core.Event;
/**
* Indicates a change in a pool configuration.
*/
@SuppressWarnings("PMD.DataClass")
public class VmPoolChanged extends Event<Void> {
private final VmPool vmPool;

View file

@ -17,7 +17,7 @@ dependencies {
implementation 'org.jgrapes:org.jgrapes.io:[2.12.1,3)'
implementation 'org.jgrapes:org.jgrapes.http:[3.5.0,4)'
implementation 'org.jgrapes:org.jgrapes.webconsole.base:[2.3.0,3)'
implementation 'org.jgrapes:org.jgrapes.webconsole.base:[2.2.0,3)'
implementation 'org.jgrapes:org.jgrapes.webconsole.vuejs:[1.8.0,2)'
implementation 'org.jgrapes:org.jgrapes.webconsole.rbac:[1.4.0,2)'
implementation 'org.jgrapes:org.jgrapes.webconlet.oidclogin:[1.7.0,2)'

View file

@ -37,7 +37,7 @@ data:
# The template to use. Resolved relative to /usr/share/vmrunner/templates.
# template: "Standard-VM-latest.ftl.yaml"
<#if spec.runnerTemplate?? && spec.runnerTemplate.source?? >
template: ${ spec.runnerTemplate.source }
template: ${ cm.spec().runnerTemplate.source }
</#if>
# The template is copied to the data diretory when the VM starts for

View file

@ -51,6 +51,7 @@ import org.jgrapes.util.events.ConfigurationUpdate;
* @param <O> the object type for the context
* @param <L> the object list type for the context
*/
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis" })
public abstract class AbstractMonitor<O extends KubernetesObject,
L extends KubernetesListObject, C extends Channel> extends Component {
@ -180,6 +181,7 @@ public abstract class AbstractMonitor<O extends KubernetesObject,
* @param event the event
*/
@Handler(priority = 10)
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
public void onStart(Start event) {
try {
// Get namespace

View file

@ -56,6 +56,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
/**
* Delegee for reconciling the config map
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
/* default */ class ConfigMapReconciler {
protected final Logger logger = Logger.getLogger(getClass().getName());
@ -80,6 +81,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
* @throws TemplateException the template exception
* @throws ApiException the API exception
*/
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
public void reconcile(Map<String, Object> model, VmChannel channel,
boolean modelChanged)
throws IOException, TemplateException, ApiException {
@ -96,8 +98,6 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
channel.setAssociated(PrevData.class, prevData);
// Combine template and data and parse result
logger.fine(() -> "Create/update configmap "
+ DataPath.<String> get(model, "cr", "name").orElse("unknown"));
model.put("adjustCloudInitMeta", adjustCloudInitMetaModel);
prevData.added.put("adjustCloudInitMeta", adjustCloudInitMetaModel);
var fmTemplate = fmConfig.getTemplate("runnerConfig.ftl.yaml");
@ -187,6 +187,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
private final TemplateMethodModelEx adjustCloudInitMetaModel
= new TemplateMethodModelEx() {
@Override
@SuppressWarnings("PMD.PreserveStackTrace")
public Object exec(@SuppressWarnings("rawtypes") List arguments)
throws TemplateModelException {
@SuppressWarnings("unchecked")

View file

@ -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 STATE_RUNNING. */

View file

@ -50,7 +50,6 @@ 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.EventPipeline;
import org.jgrapes.core.annotation.Handler;
import org.jgrapes.core.events.HandlingError;
import org.jgrapes.core.events.Start;
@ -95,7 +94,7 @@ import org.jgrapes.util.events.ConfigurationUpdate;
public class Controller extends Component {
private String namespace;
private final ChannelManager<String, VmChannel, EventPipeline> chanMgr;
private final ChannelManager<String, VmChannel, ?> chanMgr;
/**
* Creates a new instance.
@ -183,7 +182,7 @@ public class Controller extends Component {
fire(new Exit(2));
return;
}
logger.config(() -> "Controlling namespace \"" + namespace + "\".");
logger.fine(() -> "Controlling namespace \"" + namespace + "\".");
}
/**

View file

@ -42,6 +42,7 @@ import org.jgrapes.core.Channel;
* of the pod running the VM in response to force an update of the files
* in the pod that reflect the information from the secret.
*/
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.TooManyStaticImports" })
public class DisplaySecretMonitor
extends AbstractMonitor<V1Secret, V1SecretList, VmChannel> {

View file

@ -66,6 +66,7 @@ import org.jose4j.base64url.Base64;
* * `passwordValidity`: the validity of the random password in seconds.
* Used to calculate the password expiry time in the generated secret.
*/
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.TooManyStaticImports" })
public class DisplaySecretReconciler extends Component {
protected final Logger logger = Logger.getLogger(getClass().getName());
@ -103,15 +104,12 @@ public class DisplaySecretReconciler extends Component {
return oldConfig;
}).ifPresent(c -> {
try {
Optional.ofNullable(c.get("passwordValidity"))
.map(p -> p instanceof Integer ? (Integer) p
: Integer.valueOf((String) p))
.ifPresent(p -> {
passwordValidity = p;
});
} catch (NumberFormatException e) {
logger.warning(
() -> "Malformed configuration: " + e.getMessage());
if (c.containsKey("passwordValidity")) {
passwordValidity = Integer
.parseInt((String) c.get("passwordValidity"));
}
} catch (ClassCastException e) {
logger.config("Malformed configuration: " + e.getMessage());
}
});
}
@ -157,11 +155,9 @@ public class DisplaySecretReconciler extends Component {
}
// Create secret
var secretName = vmDef.name() + "-" + DisplaySecret.NAME;
logger.fine(() -> "Create/update secret " + secretName);
var secret = new V1Secret();
secret.setMetadata(new V1ObjectMeta().namespace(vmDef.namespace())
.name(secretName)
.name(vmDef.name() + "-" + DisplaySecret.NAME)
.putLabelsItem("app.kubernetes.io/name", APP_NAME)
.putLabelsItem("app.kubernetes.io/component", DisplaySecret.NAME)
.putLabelsItem("app.kubernetes.io/instance", vmDef.name()));
@ -192,6 +188,7 @@ public class DisplaySecretReconciler extends Component {
* @throws ApiException the api exception
*/
@Handler
@SuppressWarnings("PMD.StringInstantiation")
public void onGetDisplaySecret(GetDisplaySecret event, VmChannel channel)
throws ApiException {
// Get VM definition and check if running
@ -320,6 +317,7 @@ public class DisplaySecretReconciler extends Component {
/**
* The Class PendingGet.
*/
@SuppressWarnings("PMD.DataClass")
private static class PendingRequest {
public final GetDisplaySecret event;
public final long expectedSerial;

View file

@ -36,7 +36,6 @@ import java.util.logging.Logger;
import org.jdrupes.vmoperator.common.K8sV1ServiceStub;
import org.jdrupes.vmoperator.common.VmDefinition;
import org.jdrupes.vmoperator.manager.events.VmChannel;
import org.jdrupes.vmoperator.util.DataPath;
import org.jdrupes.vmoperator.util.GsonPtr;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
@ -45,6 +44,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
/**
* Delegee for reconciling the service
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
/* default */ class LoadBalancerReconciler {
private static final String LOAD_BALANCER_SERVICE = "loadBalancerService";
@ -85,7 +85,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
}
// Check if to be generated
@SuppressWarnings({ "unchecked" })
@SuppressWarnings({ "PMD.AvoidDuplicateLiterals", "unchecked" })
var lbsDef = Optional.of(model)
.map(m -> (Map<String, Object>) m.get("reconciler"))
.map(c -> c.get(LOAD_BALANCER_SERVICE)).orElse(Boolean.FALSE);
@ -107,8 +107,6 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
}
// Combine template and data and parse result
logger.fine(() -> "Create/update load balancer service for "
+ DataPath.<String> get(model, "cr", "name").orElse("unknown"));
var fmTemplate = fmConfig.getTemplate("runnerLoadBalancer.ftl.yaml");
StringWriter out = new StringWriter();
fmTemplate.process(model, out);

View file

@ -81,7 +81,7 @@ import org.jgrapes.webconsole.vuejs.VueJsConsoleWeblet;
/**
* The application class.
*/
@SuppressWarnings({ "PMD.ExcessiveImports" })
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports" })
public class Manager extends Component {
private static String version;
@ -97,8 +97,8 @@ public class Manager extends Component {
* @throws IOException Signals that an I/O exception has occurred.
* @throws URISyntaxException
*/
@SuppressWarnings({ "PMD.NcssCount",
"PMD.ConstructorCallsOverridableMethod" })
@SuppressWarnings({ "PMD.TooFewBranchesForASwitchStatement",
"PMD.NcssCount", "PMD.ConstructorCallsOverridableMethod" })
public Manager(CommandLine cmdLine) throws IOException, URISyntaxException {
super(new NamedChannel("manager"));
// Prepare component tree
@ -217,6 +217,7 @@ public class Manager extends Component {
* @param event the event
*/
@Handler
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public void onConfigurationUpdate(ConfigurationUpdate event) {
event.structured(componentPath()).ifPresent(c -> {
if (c.containsKey("clusterName")) {
@ -263,7 +264,7 @@ public class Manager extends Component {
*/
@Handler(priority = -1000)
public void onStop(Stop event) {
logger.info(() -> "Application stopped.");
logger.fine(() -> "Application stopped.");
}
static {
@ -290,6 +291,7 @@ public class Manager extends Component {
* @param args the arguments
* @throws Exception the exception
*/
@SuppressWarnings("PMD.SignatureDeclareThrowsException")
public static void main(String[] args) {
try {
// Instance logger is not available yet.

View file

@ -43,6 +43,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
/**
* Delegee for reconciling the pod.
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
/* default */ class PodReconciler {
protected final Logger logger = Logger.getLogger(getClass().getName());

View file

@ -53,6 +53,7 @@ import org.jgrapes.core.events.Attached;
* {@link VmPoolChanged} events fired on a special pipeline to
* avoid concurrent change informations.
*/
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports" })
public class PoolMonitor extends
AbstractMonitor<K8sDynamicModel, K8sDynamicModels, Channel> {

View file

@ -49,6 +49,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
/**
* Delegee for reconciling the stateful set (effectively the pod).
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
/* default */ class PvcReconciler {
protected final Logger logger = Logger.getLogger(getClass().getName());
@ -74,7 +75,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
* @throws TemplateException the template exception
* @throws ApiException the api exception
*/
@SuppressWarnings({ "unchecked" })
@SuppressWarnings({ "PMD.AvoidDuplicateLiterals", "unchecked" })
public void reconcile(VmDefinition vmDef, Map<String, Object> model,
VmChannel channel, boolean specChanged)
throws IOException, TemplateException, ApiException {
@ -138,7 +139,6 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
// Generate PVC
var runnerDataPvcName = vmDef.name() + "-runner-data";
logger.fine(() -> "Create/update pvc " + runnerDataPvcName);
model.put("runnerDataPvcName", runnerDataPvcName);
if (!specChanged) {
// Augmenting the model is all we have to do
@ -180,7 +180,6 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
}
// Generate PVC
logger.fine(() -> "Create/update pvc " + pvcName);
model.put("disk", diskDef);
var fmTemplate = fmConfig.getTemplate("runnerDiskPvc.ftl.yaml");
StringWriter out = new StringWriter();

View file

@ -137,13 +137,15 @@ import org.jgrapes.util.events.ConfigurationUpdate;
*
* @see org.jdrupes.vmoperator.manager.DisplaySecretReconciler
*/
@SuppressWarnings({ "PMD.AvoidDuplicateLiterals" })
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis",
"PMD.AvoidDuplicateLiterals" })
public class Reconciler extends Component {
/** The Constant mapper. */
@SuppressWarnings("PMD.FieldNamingConventions")
protected static final ObjectMapper mapper = new ObjectMapper();
@SuppressWarnings("PMD.SingularField")
private final Configuration fmConfig;
private final ConfigMapReconciler cmReconciler;
private final DisplaySecretReconciler dsReconciler;
@ -201,6 +203,7 @@ public class Reconciler extends Component {
* @throws IOException Signals that an I/O exception has occurred.
*/
@Handler
@SuppressWarnings("PMD.ConfusingTernary")
public void onVmResourceChanged(VmResourceChanged event, VmChannel channel)
throws ApiException, TemplateException, IOException {
// Ownership relationships takes care of deletions
@ -335,6 +338,7 @@ public class Reconciler extends Component {
private final TemplateMethodModelEx formatMemoryModel
= new TemplateMethodModelEx() {
@Override
@SuppressWarnings("PMD.PreserveStackTrace")
public Object exec(@SuppressWarnings("rawtypes") List arguments)
throws TemplateModelException {
var arg = arguments.get(0);
@ -364,7 +368,8 @@ public class Reconciler extends Component {
private final TemplateMethodModelEx imgageLocationModel
= new TemplateMethodModelEx() {
@Override
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition" })
@SuppressWarnings({ "PMD.PreserveStackTrace",
"PMD.AvoidLiteralsInIfCondition" })
public Object exec(@SuppressWarnings("rawtypes") List arguments)
throws TemplateModelException {
var image = ((SimpleScalar) arguments.get(0)).getAsString();
@ -389,6 +394,7 @@ public class Reconciler extends Component {
private final TemplateMethodModelEx toJsonModel
= new TemplateMethodModelEx() {
@Override
@SuppressWarnings("PMD.PreserveStackTrace")
public Object exec(@SuppressWarnings("rawtypes") List arguments)
throws TemplateModelException {
try {

View file

@ -22,6 +22,7 @@ import com.google.gson.JsonObject;
import io.kubernetes.client.apimachinery.GroupVersionKind;
import io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.util.Watch;
import io.kubernetes.client.util.generic.options.ListOptions;
import java.io.IOException;
@ -31,6 +32,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import org.jdrupes.vmoperator.common.Constants.Crd;
import org.jdrupes.vmoperator.common.Constants.Status;
@ -55,27 +57,16 @@ import org.jdrupes.vmoperator.manager.events.VmResourceChanged;
import org.jdrupes.vmoperator.util.GsonPtr;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Event;
import org.jgrapes.core.EventPipeline;
import org.jgrapes.core.annotation.Handler;
/**
* Watches for changes of VM definitions. When a VM definition (CR)
* becomes known, is is registered with a {@link ChannelManager} and thus
* gets an associated {@link VmChannel} and an associated
* {@link EventPipeline}.
*
* The {@link EventPipeline} is used for submitting an action that processes
* the change data from kubernetes, eventually transforming it to a
* {@link VmResourceChanged} event that is handled by another
* {@link EventPipeline} associated with the {@link VmChannel}. This
* event pipeline should be used for all events related to changes of
* a particular VM.
* Watches for changes of VM definitions.
*/
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports" })
public class VmMonitor extends
AbstractMonitor<VmDefinition, VmDefinitions, VmChannel> {
private final ChannelManager<String, VmChannel,
EventPipeline> channelManager;
private final ChannelManager<String, VmChannel, ?> channelManager;
/**
* Instantiates a new VM definition watcher.
@ -84,7 +75,7 @@ public class VmMonitor extends
* @param channelManager the channel manager
*/
public VmMonitor(Channel componentChannel,
ChannelManager<String, VmChannel, EventPipeline> channelManager) {
ChannelManager<String, VmChannel, ?> channelManager) {
super(componentChannel, VmDefinition.class,
VmDefinitions.class);
this.channelManager = channelManager;
@ -106,6 +97,7 @@ public class VmMonitor extends
purge();
}
@SuppressWarnings("PMD.CognitiveComplexity")
private void purge() throws ApiException {
// Get existing CRs (VMs)
var known = K8sDynamicStub.list(client(), context(), namespace())
@ -130,18 +122,14 @@ public class VmMonitor extends
@Override
protected void handleChange(K8sClient client,
Watch.Response<VmDefinition> response) {
var name = response.object.getMetadata().getName();
V1ObjectMeta metadata = response.object.getMetadata();
AtomicBoolean toBeAdded = new AtomicBoolean(false);
VmChannel channel = channelManager.channel(metadata.getName())
.orElseGet(() -> {
toBeAdded.set(true);
return channelManager.createChannel(metadata.getName());
});
// Process the response data on a VM specific pipeline to
// increase concurrency when e.g. starting many VMs.
var preparing = channelManager.associated(name)
.orElseGet(() -> newEventPipeline());
preparing.submit("VmChange[" + name + "]",
() -> processChange(client, response, preparing));
}
private void processChange(K8sClient client,
Watch.Response<VmDefinition> response, EventPipeline preparing) {
// Get full definition and associate with channel as backup
var vmDef = response.object;
if (vmDef.data() == null) {
@ -149,9 +137,6 @@ public class VmMonitor extends
// https://github.com/kubernetes-client/java/issues/3215
vmDef = getModel(client, vmDef);
}
var name = response.object.getMetadata().getName();
var channel = channelManager.channel(name)
.orElseGet(() -> channelManager.createChannel(name));
if (vmDef.data() != null) {
// New data, augment and save
addExtraData(vmDef, channel.vmDefinition());
@ -165,7 +150,9 @@ public class VmMonitor extends
+ response.object.getMetadata());
return;
}
channelManager.put(name, channel, preparing);
if (toBeAdded.get()) {
channelManager.put(vmDef.name(), channel);
}
// Create and fire changed event. Remove channel from channel
// manager on completion.
@ -190,6 +177,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).map(VmDefinition::extra);
@ -211,16 +199,9 @@ public class VmMonitor extends
@Handler
public void onPodChanged(PodChanged event, VmChannel channel) {
var vmDef = channel.vmDefinition();
// Make sure that this is properly sync'd with VM CR changes.
channelManager.associated(vmDef.name())
.orElseGet(() -> activeEventPipeline())
.submit("NodeInfo[" + vmDef.name() + "]",
() -> {
updateNodeInfo(event, vmDef);
channel.fire(new VmResourceChanged(ResponseType.MODIFIED,
vmDef, false, true));
});
updateNodeInfo(event, vmDef);
channel
.fire(new VmResourceChanged(ResponseType.MODIFIED, vmDef, false, true));
}
private void updateNodeInfo(PodChanged event, VmDefinition vmDef) {
@ -238,6 +219,7 @@ public class VmMonitor extends
.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()

View file

@ -1,6 +1,6 @@
/*
* VM-Operator
* Copyright (C) 2023,2025 Michael N. Lipp
* 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
@ -83,18 +83,8 @@
* [YamlConfigurationStore] *-right[hidden]- [Controller]
*
* [Manager] *-- [Controller]
* Component VmMonitor as VmMonitor <<internal>>
* [Controller] *-- [VmMonitor]
* [VmMonitor] -right[hidden]- [PoolMonitor]
* Component PoolMonitor as PoolMonitor <<internal>>
* [Controller] *-- [PoolMonitor]
* Component PodMonitor as PodMonitor <<internal>>
* [Controller] *-- [PodMonitor]
* [PodMonitor] -up[hidden]- VmMonitor
* Component DisplaySecretMonitor as DisplaySecretMonitor <<internal>>
* [Controller] *-- [DisplaySecretMonitor]
* [DisplaySecretMonitor] -up[hidden]- VmMonitor
* [Controller] *-left- [Reconciler]
* [Controller] *-- [VmWatcher]
* [Controller] *-- [Reconciler]
* [Controller] -right[hidden]- [GuiHttpServer]
*
* [Manager] *-down- [GuiSocketServer:8080]

View file

@ -1,8 +1,8 @@
apiVersion: "vmoperator.jdrupes.org/v1"
kind: VirtualMachine
metadata:
namespace: vmop-test
name: test-vm
namespace: vmop-dev
name: unittest-vm
spec:
image:
repository: docker-registry.lan.mnl.de

View file

@ -1,111 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../deploy
namespace: vmop-test
patches:
- patch: |-
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: vmop-image-repository
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: local-path
- patch: |-
kind: ConfigMap
apiVersion: v1
metadata:
name: vm-operator
data:
# Keep in sync with config.yaml
config.yaml: |
"/Manager":
# clusterName: "test"
"/Controller":
"/Reconciler":
runnerData:
storageClassName: null
loadBalancerService:
labels:
label1: label1
label2: toBeReplaced
annotations:
metallb.universe.tf/loadBalancerIPs: 192.168.168.1
metallb.universe.tf/ip-allocated-from-pool: single-common
metallb.universe.tf/allow-shared-ip: single-common
"/GuiSocketServer":
port: 8888
"/GuiHttpServer":
# This configures the GUI
"/ConsoleWeblet":
"/WebConsole":
"/LoginConlet":
users:
- name: admin
fullName: Administrator
password: "$2b$05$NiBd74ZGdplLC63ePZf1f.UtjMKkbQ23cQoO2OKOFalDBHWAOy21."
- name: test1
fullName: Test Account
password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2"
- name: test2
fullName: Test Account
password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2"
- name: test3
fullName: Test Account
password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2"
"/RoleConfigurator":
rolesByUser:
# User admin has role admin
admin:
- admin
test1:
- user
test2:
- user
test3:
- user
# All users have role other
"*":
- other
replace: false
"/RoleConletFilter":
conletTypesByRole:
# Admins can use all conlets
admin:
- "*"
user:
- org.jdrupes.vmoperator.vmviewer.VmViewer
# Others cannot use any conlet (except login conlet to log out)
other:
- org.jgrapes.webconlet.locallogin.LoginConlet
"/ComponentCollector":
"/VmAccess":
displayResource:
preferredIpVersion: ipv4
syncPreviewsFor:
- role: user
- target:
group: apps
version: v1
kind: Deployment
name: vm-operator
patch: |-
- op: replace
path: /spec/template/spec/containers/0/image
value: docker-registry.lan.mnl.de/vmoperator/org.jdrupes.vmoperator.manager:test
- op: replace
path: /spec/template/spec/containers/0/imagePullPolicy
value: Always
- op: replace
path: /spec/replicas
value: 0

View file

@ -41,7 +41,7 @@ class BasicTests {
private static APIResource vmsContext;
private static K8sV1DeploymentStub mgrDeployment;
private static K8sDynamicStub vmStub;
private static final String VM_NAME = "test-vm";
private static final String VM_NAME = "unittest-vm";
private static final Object EXISTS = new Object();
@BeforeAll
@ -54,7 +54,7 @@ class BasicTests {
// Update manager pod by scaling deployment
mgrDeployment
= K8sV1DeploymentStub.get(client, "vmop-test", "vm-operator");
= K8sV1DeploymentStub.get(client, "vmop-dev", "vm-operator");
mgrDeployment.scale(0);
mgrDeployment.scale(1);
waitForManager();
@ -65,13 +65,13 @@ class BasicTests {
vmsContext = apiRes.get();
// Cleanup existing VM
K8sDynamicStub.get(client, vmsContext, "vmop-test", VM_NAME)
K8sDynamicStub.get(client, vmsContext, "vmop-dev", VM_NAME)
.delete();
ListOptions listOpts = new ListOptions();
listOpts.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
+ "app.kubernetes.io/instance=" + VM_NAME + ","
+ "app.kubernetes.io/component=" + DisplaySecret.NAME);
var secrets = K8sV1SecretStub.list(client, "vmop-test", listOpts);
var secrets = K8sV1SecretStub.list(client, "vmop-dev", listOpts);
for (var secret : secrets) {
secret.delete();
}
@ -103,7 +103,7 @@ class BasicTests {
"app.kubernetes.io/managed-by=" + VM_OP_NAME + ","
+ "app.kubernetes.io/name=" + APP_NAME + ","
+ "app.kubernetes.io/instance=" + VM_NAME);
var knownPvcs = K8sV1PvcStub.list(client, "vmop-test", listOpts);
var knownPvcs = K8sV1PvcStub.list(client, "vmop-dev", listOpts);
for (var pvc : knownPvcs) {
pvc.delete();
}
@ -112,7 +112,7 @@ class BasicTests {
@AfterAll
static void tearDownAfterClass() throws Exception {
// Cleanup
K8sDynamicStub.get(client, vmsContext, "vmop-test", VM_NAME)
K8sDynamicStub.get(client, vmsContext, "vmop-dev", VM_NAME)
.delete();
deletePvcs();
@ -124,7 +124,7 @@ class BasicTests {
void testConfigMap()
throws IOException, InterruptedException, ApiException {
K8sV1ConfigMapStub stub
= K8sV1ConfigMapStub.get(client, "vmop-test", VM_NAME);
= K8sV1ConfigMapStub.get(client, "vmop-dev", VM_NAME);
for (int i = 0; i < 10; i++) {
if (stub.model().isPresent()) {
break;
@ -134,7 +134,7 @@ class BasicTests {
// Check config map
var config = stub.model().get();
Map<List<? extends Object>, Object> toCheck = Map.of(
List.of("namespace"), "vmop-test",
List.of("namespace"), "vmop-dev",
List.of("name"), VM_NAME,
List.of("labels", "app.kubernetes.io/name"), Constants.APP_NAME,
List.of("labels", "app.kubernetes.io/instance"), VM_NAME,
@ -191,7 +191,7 @@ class BasicTests {
+ "app.kubernetes.io/component=" + DisplaySecret.NAME);
Collection<K8sV1SecretStub> secrets = null;
for (int i = 0; i < 10; i++) {
secrets = K8sV1SecretStub.list(client, "vmop-test", listOpts);
secrets = K8sV1SecretStub.list(client, "vmop-dev", listOpts);
if (secrets.size() > 0) {
break;
}
@ -207,7 +207,7 @@ class BasicTests {
@Test
void testRunnerPvc() throws ApiException, InterruptedException {
var stub
= K8sV1PvcStub.get(client, "vmop-test", VM_NAME + "-runner-data");
= K8sV1PvcStub.get(client, "vmop-dev", VM_NAME + "-runner-data");
for (int i = 0; i < 10; i++) {
if (stub.model().isPresent()) {
break;
@ -227,7 +227,7 @@ class BasicTests {
@Test
void testSystemDiskPvc() throws ApiException, InterruptedException {
var stub
= K8sV1PvcStub.get(client, "vmop-test", VM_NAME + "-system-disk");
= K8sV1PvcStub.get(client, "vmop-dev", VM_NAME + "-system-disk");
for (int i = 0; i < 10; i++) {
if (stub.model().isPresent()) {
break;
@ -248,7 +248,7 @@ class BasicTests {
@Test
void testDisk1Pvc() throws ApiException, InterruptedException {
var stub
= K8sV1PvcStub.get(client, "vmop-test", VM_NAME + "-disk-1");
= K8sV1PvcStub.get(client, "vmop-dev", VM_NAME + "-disk-1");
for (int i = 0; i < 10; i++) {
if (stub.model().isPresent()) {
break;
@ -274,7 +274,7 @@ class BasicTests {
new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/vm/state"
+ "\", \"value\": \"Running\"}]"),
client.defaultPatchOptions()).isPresent());
var stub = K8sV1PodStub.get(client, "vmop-test", VM_NAME);
var stub = K8sV1PodStub.get(client, "vmop-dev", VM_NAME);
for (int i = 0; i < 20; i++) {
if (stub.model().isPresent()) {
break;
@ -303,7 +303,7 @@ class BasicTests {
@Test
public void testLoadBalancer() throws ApiException, InterruptedException {
var stub = K8sV1ServiceStub.get(client, "vmop-test", VM_NAME);
var stub = K8sV1ServiceStub.get(client, "vmop-dev", VM_NAME);
for (int i = 0; i < 10; i++) {
if (stub.model().isPresent()) {
break;

View file

@ -36,6 +36,7 @@ import org.jgrapes.core.annotation.Handler;
/**
* The Class CdMediaController.
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class CdMediaController extends Component {
/**
@ -54,6 +55,7 @@ public class CdMediaController extends Component {
*
* @param componentChannel the component channel
*/
@SuppressWarnings("PMD.AssignmentToNonFinalStatic")
public CdMediaController(Channel componentChannel) {
super(componentChannel);
}
@ -64,7 +66,8 @@ public class CdMediaController extends Component {
* @param event the event
*/
@Handler
@SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops" })
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
"PMD.AvoidInstantiatingObjectsInLoops" })
public void onConfigureQemu(ConfigureQemu event) {
if (event.runState() == RunState.TERMINATING) {
return;

View file

@ -39,9 +39,11 @@ import org.jdrupes.vmoperator.util.FsdUtils;
/**
* The configuration information from the configuration file.
*/
@SuppressWarnings({ "PMD.ExcessivePublicCount", "PMD.TooManyFields" })
public class Configuration implements Dto {
private static final String CI_INSTANCE_ID = "instance-id";
@SuppressWarnings("PMD.FieldNamingConventions")
protected final Logger logger = Logger.getLogger(getClass().getName());
/** Configuration timestamp. */
@ -93,12 +95,15 @@ public class Configuration implements Dto {
public static class CloudInit implements Dto {
/** The meta data. */
@SuppressWarnings("PMD.UseConcurrentHashMap")
public Map<String, Object> metaData;
/** The user data. */
@SuppressWarnings("PMD.UseConcurrentHashMap")
public Map<String, Object> userData;
/** The network config. */
@SuppressWarnings("PMD.UseConcurrentHashMap")
public Map<String, Object> networkConfig;
}
@ -294,6 +299,7 @@ public class Configuration implements Dto {
return true;
}
@SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
private void checkDrives() {
for (Drive drive : vm.drives) {
if (drive.file != null || drive.device != null
@ -313,6 +319,7 @@ public class Configuration implements Dto {
}
}
@SuppressWarnings("PMD.AvoidDeeplyNestedIfStmts")
private boolean checkRuntimeDir() {
// Runtime directory (sockets etc.)
if (runtimeDir == null) {
@ -348,6 +355,7 @@ public class Configuration implements Dto {
return true;
}
@SuppressWarnings("PMD.AvoidDeeplyNestedIfStmts")
private boolean checkDataDir() {
// Data directory
if (dataDir == null) {

View file

@ -41,6 +41,7 @@ import org.jgrapes.core.events.Start;
* A (sub)component that updates the console status in the CR status.
* Created as child of {@link StatusUpdater}.
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class ConsoleTracker extends VmDefUpdater {
private VmDefinitionStub vmStub;
@ -52,6 +53,7 @@ public class ConsoleTracker extends VmDefUpdater {
*
* @param componentChannel the component channel
*/
@SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
public ConsoleTracker(Channel componentChannel) {
super(componentChannel);
apiClient = (K8sClient) io.kubernetes.client.openapi.Configuration
@ -89,7 +91,8 @@ public class ConsoleTracker extends VmDefUpdater {
* @throws ApiException the api exception
*/
@Handler
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition" })
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
"PMD.AvoidDuplicateLiterals" })
public void onSpiceInitialized(SpiceInitializedEvent event)
throws ApiException {
if (vmStub == null) {
@ -124,6 +127,7 @@ public class ConsoleTracker extends VmDefUpdater {
* @throws ApiException the api exception
*/
@Handler
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
public void onSpiceDisconnected(SpiceDisconnectedEvent event)
throws ApiException {
if (vmStub == null) {

View file

@ -21,6 +21,7 @@ package org.jdrupes.vmoperator.runner.qemu;
/**
* Some constants.
*/
@SuppressWarnings("PMD.DataClass")
public class Constants extends org.jdrupes.vmoperator.common.Constants {
/**

View file

@ -41,6 +41,7 @@ import org.jgrapes.core.annotation.Handler;
/**
* The Class CpuController.
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class CpuController extends Component {
private Integer currentCpus;

View file

@ -43,6 +43,7 @@ import org.jgrapes.util.events.WatchFile;
/**
* The Class DisplayController.
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class DisplayController extends Component {
private String currentPassword;
@ -58,7 +59,8 @@ public class DisplayController extends Component {
* @param componentChannel the component channel
* @param configDir
*/
@SuppressWarnings({ "PMD.ConstructorCallsOverridableMethod" })
@SuppressWarnings({ "PMD.AssignmentToNonFinalStatic",
"PMD.ConstructorCallsOverridableMethod" })
public DisplayController(Channel componentChannel, Path configDir) {
super(componentChannel);
this.configDir = configDir;
@ -112,6 +114,7 @@ public class DisplayController extends Component {
* @param event the event
*/
@Handler
@SuppressWarnings("PMD.EmptyCatchBlock")
public void onFileChanged(FileChanged event) {
if (event.path().equals(configDir.resolve(DisplaySecret.PASSWORD))) {
logger.fine(() -> "Display password updated");

View file

@ -69,14 +69,14 @@ public class GuestAgentClient extends AgentConnector {
*/
@Override
protected void agentConnected() {
logger.fine(() -> "Guest agent connected");
logger.fine(() -> "guest agent connected");
connected = true;
rep().fire(new GuestAgentCommand(new QmpGuestGetOsinfo()));
}
@Override
protected void agentDisconnected() {
logger.fine(() -> "Guest agent disconnected");
logger.fine(() -> "guest agent disconnected");
connected = false;
}
@ -88,16 +88,15 @@ public class GuestAgentClient extends AgentConnector {
*/
@Override
protected void processInput(String line) throws IOException {
logger.finer(() -> "guest agent(in): " + line);
logger.fine(() -> "guest agent(in): " + line);
try {
var response = mapper.readValue(line, ObjectNode.class);
if (response.has("return") || response.has("error")) {
QmpCommand executed = executing.poll();
logger.finer(() -> String.format("(Previous \"guest agent(in)\""
logger.fine(() -> String.format("(Previous \"guest agent(in)\""
+ " is result from executing %s)", executed));
if (executed instanceof QmpGuestGetOsinfo) {
var osInfo = new OsinfoEvent(response.get("return"));
logger.fine(() -> "Guest agent triggers: " + osInfo);
rep().fire(osInfo);
}
}
@ -121,11 +120,10 @@ public class GuestAgentClient extends AgentConnector {
return;
}
var command = event.command();
logger.fine(() -> "Guest handles: " + event);
logger.fine(() -> "guest agent(out): " + command.toString());
String asText;
try {
asText = command.asText();
logger.finer(() -> "guest agent(out): " + asText);
} catch (JsonProcessingException e) {
logger.log(Level.SEVERE, e,
() -> "Cannot serialize Json: " + e.getMessage());
@ -165,8 +163,8 @@ public class GuestAgentClient extends AgentConnector {
}
event.suspendHandling();
suspendedStop = event;
logger.fine(() -> "Attempting shutdown through guest agent,"
+ " waiting for termination until " + waitUntil);
logger.fine(() -> "Sending powerdown command, waiting for"
+ " termination until " + waitUntil);
powerdownTimer = Components.schedule(t -> {
logger.fine(() -> "Powerdown timeout reached.");
synchronized (this) {

View file

@ -54,6 +54,7 @@ import org.jgrapes.util.events.ConfigurationUpdate;
* If the log level for this class is set to fine, the messages
* exchanged on the monitor socket are logged.
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class QemuMonitor extends QemuConnector {
private int powerdownTimeout;
@ -71,6 +72,8 @@ public class QemuMonitor extends QemuConnector {
* @param configDir the config dir
* @throws IOException Signals that an I/O exception has occurred.
*/
@SuppressWarnings({ "PMD.AssignmentToNonFinalStatic",
"PMD.ConstructorCallsOverridableMethod" })
public QemuMonitor(Channel componentChannel, Path configDir)
throws IOException {
super(componentChannel);
@ -105,30 +108,24 @@ public class QemuMonitor extends QemuConnector {
@Override
protected void processInput(String line)
throws IOException {
logger.finer(() -> "monitor(in): " + line);
logger.fine(() -> "monitor(in): " + line);
try {
var response = mapper.readValue(line, ObjectNode.class);
if (response.has("QMP")) {
monitorReady = true;
logger.fine(() -> "QMP connection ready");
rep().fire(new MonitorReady());
return;
}
if (response.has("return") || response.has("error")) {
QmpCommand executed = executing.poll();
logger.finer(
logger.fine(
() -> String.format("(Previous \"monitor(in)\" is result "
+ "from executing %s)", executed));
var monRes = MonitorResult.from(executed, response);
logger.fine(() -> "QMP triggers: " + monRes);
rep().fire(monRes);
rep().fire(MonitorResult.from(executed, response));
return;
}
if (response.has("event")) {
MonitorEvent.from(response).ifPresent(me -> {
logger.fine(() -> "QMP triggers: " + me);
rep().fire(me);
});
MonitorEvent.from(response).ifPresent(rep()::fire);
}
} catch (JsonProcessingException e) {
throw new IOException(e);
@ -144,7 +141,7 @@ public class QemuMonitor extends QemuConnector {
public void onClosed(Closed<?> event, SocketIOChannel channel) {
channel.associated(this, getClass()).ifPresent(qm -> {
super.onClosed(event, channel);
logger.fine(() -> "QMP connection closed.");
logger.finer(() -> "QMP socket closed.");
monitorReady = false;
});
}
@ -161,7 +158,7 @@ public class QemuMonitor extends QemuConnector {
public void onMonitorCommand(MonitorCommand event) throws IOException {
// Check prerequisites
if (!monitorReady && !(event.command() instanceof QmpCapabilities)) {
logger.severe(() -> "Premature QMP command (not ready): "
logger.severe(() -> "Premature monitor command (not ready): "
+ event.command());
rep().fire(new Stop());
return;
@ -169,11 +166,10 @@ public class QemuMonitor extends QemuConnector {
// Send the command
var command = event.command();
logger.fine(() -> "QMP handles: " + event.toString());
logger.fine(() -> "monitor(out): " + command.toString());
String asText;
try {
asText = command.asText();
logger.finer(() -> "monitor(out): " + asText);
} catch (JsonProcessingException e) {
logger.log(Level.SEVERE, e,
() -> "Cannot serialize Json: " + e.getMessage());
@ -196,8 +192,8 @@ public class QemuMonitor extends QemuConnector {
@SuppressWarnings("PMD.AvoidSynchronizedStatement")
public void onStop(Stop event) {
if (!monitorReady) {
logger.fine(() -> "Not sending QMP powerdown command"
+ " because QMP connection is closed");
logger.fine(() -> "No QMP connection,"
+ " cannot send powerdown command");
return;
}

View file

@ -39,6 +39,7 @@ public class RamController extends Component {
*
* @param componentChannel the component channel
*/
@SuppressWarnings("PMD.AssignmentToNonFinalStatic")
public RamController(Channel componentChannel) {
super(componentChannel);
}

View file

@ -157,15 +157,6 @@ import org.jgrapes.util.events.WatchFile;
*
* success --> Running
*
* state Running {
* state Booting
* state Booted
*
* [*] -right-> Booting
* Booting -down-> Booting: VserportChanged[guest agent connected]/fire GetOsinfo
* Booting --> Booted: Osinfo
* }
*
* state Terminating {
* state terminate <<entryPoint>>
* state qemuRunning <<choice>>
@ -201,7 +192,8 @@ import org.jgrapes.util.events.WatchFile;
*
*/
@SuppressWarnings({ "PMD.ExcessiveImports", "PMD.AvoidPrintStackTrace",
"PMD.TooManyMethods", "PMD.CouplingBetweenObjects" })
"PMD.DataflowAnomalyAnalysis", "PMD.TooManyMethods",
"PMD.CouplingBetweenObjects", "PMD.TooManyFields" })
public class Runner extends Component {
private static final String TEMPLATE_DIR
@ -217,6 +209,7 @@ public class Runner extends Component {
.builder().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)
.build());
private final JsonNode defaults;
@SuppressWarnings("PMD.UseConcurrentHashMap")
private final File configFile;
private final Path configDir;
private Configuration initialConfig;
@ -248,7 +241,8 @@ public class Runner extends Component {
* @param cmdLine the cmd line
* @throws IOException Signals that an I/O exception has occurred.
*/
@SuppressWarnings({ "PMD.ConstructorCallsOverridableMethod" })
@SuppressWarnings({ "PMD.SystemPrintln",
"PMD.ConstructorCallsOverridableMethod" })
public Runner(CommandLine cmdLine) throws IOException {
yamlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
false);
@ -384,6 +378,8 @@ public class Runner extends Component {
}
}
@SuppressWarnings({ "PMD.CognitiveComplexity",
"PMD.DataflowAnomalyAnalysis" })
private void setFirmwarePaths(Configuration config) throws IOException {
JsonNode firmware = defaults.path("firmware").path(config.vm.firmware);
// Get file for firmware ROM
@ -615,6 +611,8 @@ public class Runner extends Component {
* @throws InterruptedException the interrupted exception
*/
@Handler
@SuppressWarnings({ "PMD.SwitchStmtsShouldHaveDefault",
"PMD.TooFewBranchesForASwitchStatement" })
public void onProcessStarted(ProcessStarted event, ProcessChannel channel)
throws InterruptedException {
event.startEvent().associated(CommandDefinition.class)
@ -771,6 +769,7 @@ public class Runner extends Component {
"The VM has been shut down"));
}
@SuppressWarnings("PMD.ConfusingArgumentToVarargsMethod")
private void shutdown() {
if (!Set.of(RunState.TERMINATING, RunState.STOPPED).contains(state)) {
fire(new Stop());

View file

@ -66,7 +66,8 @@ import org.jgrapes.core.events.Start;
/**
* Updates the CR status.
*/
@SuppressWarnings({ "PMD.CouplingBetweenObjects" })
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis",
"PMD.CouplingBetweenObjects" })
public class StatusUpdater extends VmDefUpdater {
@SuppressWarnings("PMD.FieldNamingConventions")
@ -89,6 +90,7 @@ public class StatusUpdater extends VmDefUpdater {
*
* @param componentChannel the component channel
*/
@SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
public StatusUpdater(Channel componentChannel) {
super(componentChannel);
attach(new ConsoleTracker(componentChannel));
@ -151,6 +153,7 @@ public class StatusUpdater extends VmDefUpdater {
* @throws ApiException
*/
@Handler
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
public void onConfigureQemu(ConfigureQemu event)
throws ApiException {
guestShutdownStops = event.configuration().guestShutdownStops;
@ -184,7 +187,8 @@ public class StatusUpdater extends VmDefUpdater {
* @throws ApiException
*/
@Handler
@SuppressWarnings({ "PMD.AssignmentInOperand" })
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
"PMD.AssignmentInOperand", "PMD.AvoidDuplicateLiterals" })
public void onRunnerStateChanged(RunnerStateChange event)
throws ApiException {
VmDefinition vmDef;
@ -421,6 +425,7 @@ public class StatusUpdater extends VmDefUpdater {
* @throws ApiException
*/
@Handler
@SuppressWarnings("PMD.AssignmentInOperand")
public void onVmopAgentLoggedIn(VmopAgentLoggedIn event)
throws ApiException {
vmStub.updateStatus(from -> {
@ -437,6 +442,7 @@ public class StatusUpdater extends VmDefUpdater {
* @throws ApiException
*/
@Handler
@SuppressWarnings("PMD.AssignmentInOperand")
public void onVmopAgentLoggedOut(VmopAgentLoggedOut event)
throws ApiException {
vmStub.updateStatus(from -> {

View file

@ -43,6 +43,7 @@ import org.jgrapes.util.events.InitialConfiguration;
/**
* Updates the CR status.
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class VmDefUpdater extends Component {
protected String namespace;

View file

@ -59,14 +59,10 @@ public class VmopAgentClient extends AgentConnector {
*/
@Handler
public void onVmopAgentLogIn(VmopAgentLogIn event) throws IOException {
logger.fine(() -> "vmop agent(out): login " + event.user());
if (writer().isPresent()) {
logger.fine(() -> "Vmop agent handles:" + event);
executing.add(event);
logger.finer(() -> "vmop agent(out): login " + event.user());
sendCommand("login " + event.user());
} else {
logger
.warning(() -> "No vmop agent connection for sending " + event);
}
}
@ -78,38 +74,34 @@ public class VmopAgentClient extends AgentConnector {
*/
@Handler
public void onVmopAgentLogout(VmopAgentLogOut event) throws IOException {
logger.fine(() -> "vmop agent(out): logout");
if (writer().isPresent()) {
logger.fine(() -> "Vmop agent handles:" + event);
executing.add(event);
logger.finer(() -> "vmop agent(out): logout");
sendCommand("logout");
}
}
@Override
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition" })
@SuppressWarnings({ "PMD.UnnecessaryReturn",
"PMD.AvoidLiteralsInIfCondition" })
protected void processInput(String line) throws IOException {
logger.finer(() -> "vmop agent(in): " + line);
logger.fine(() -> "vmop agent(in): " + line);
// Check validity
if (line.isEmpty() || !Character.isDigit(line.charAt(0))) {
logger.warning(() -> "Illegal vmop agent response: " + line);
logger.warning(() -> "Illegal response: " + line);
return;
}
// Check positive responses
if (line.startsWith("220 ")) {
var evt = new VmopAgentConnected();
logger.fine(() -> "Vmop agent triggers " + evt);
rep().fire(evt);
rep().fire(new VmopAgentConnected());
return;
}
if (line.startsWith("201 ")) {
Event<?> cmd = executing.pop();
if (cmd instanceof VmopAgentLogIn login) {
var evt = new VmopAgentLoggedIn(login);
logger.fine(() -> "Vmop agent triggers " + evt);
rep().fire(evt);
rep().fire(new VmopAgentLoggedIn(login));
} else {
logger.severe(() -> "Response " + line
+ " does not match executing command " + cmd);
@ -119,9 +111,7 @@ public class VmopAgentClient extends AgentConnector {
if (line.startsWith("202 ")) {
Event<?> cmd = executing.pop();
if (cmd instanceof VmopAgentLogOut logout) {
var evt = new VmopAgentLoggedOut(logout);
logger.fine(() -> "Vmop agent triggers " + evt);
rep().fire(evt);
rep().fire(new VmopAgentLoggedOut(logout));
} else {
logger.severe(() -> "Response " + line
+ "does not match executing command " + cmd);
@ -135,7 +125,7 @@ public class VmopAgentClient extends AgentConnector {
}
// Error
logger.warning(() -> "Error response from vmop agent: " + line);
logger.warning(() -> "Error response: " + line);
executing.pop();
}

View file

@ -25,7 +25,8 @@ import com.fasterxml.jackson.databind.JsonNode;
*/
public class QmpCapabilities extends QmpCommand {
@SuppressWarnings({ "PMD.FieldNamingConventions" })
@SuppressWarnings({ "PMD.FieldNamingConventions",
"PMD.VariableNamingConventions" })
private static final JsonNode jsonTemplate
= parseJson("{ \"execute\": \"qmp_capabilities\" }");

View file

@ -27,7 +27,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
*/
public class QmpChangeMedium extends QmpCommand {
@SuppressWarnings({ "PMD.FieldNamingConventions" })
@SuppressWarnings({ "PMD.FieldNamingConventions",
"PMD.VariableNamingConventions" })
private static final JsonNode jsonTemplate
= parseJson("{ \"execute\": \"blockdev-change-medium\",\"arguments\": {"
+ "\"id\": \"\",\"filename\": \"\",\"format\": \"raw\","

View file

@ -30,7 +30,8 @@ import java.util.logging.Logger;
*/
public abstract class QmpCommand {
@SuppressWarnings({ "PMD.FieldNamingConventions" })
@SuppressWarnings({ "PMD.FieldNamingConventions",
"PMD.VariableNamingConventions" })
protected static final ObjectMapper mapper = new ObjectMapper();
/**

View file

@ -25,7 +25,8 @@ import com.fasterxml.jackson.databind.JsonNode;
*/
public class QmpCont extends QmpCommand {
@SuppressWarnings({ "PMD.FieldNamingConventions" })
@SuppressWarnings({ "PMD.FieldNamingConventions",
"PMD.VariableNamingConventions" })
private static final JsonNode jsonTemplate
= parseJson("{ \"execute\": \"cont\" }");

View file

@ -27,7 +27,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
*/
public class QmpDelCpu extends QmpCommand {
@SuppressWarnings({ "PMD.FieldNamingConventions" })
@SuppressWarnings({ "PMD.FieldNamingConventions",
"PMD.VariableNamingConventions" })
private static final JsonNode jsonTemplate
= parseJson("{ \"execute\": \"device_del\", "
+ "\"arguments\": " + "{ \"id\": 0 } }");

View file

@ -27,7 +27,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
*/
public class QmpOpenTray extends QmpCommand {
@SuppressWarnings({ "PMD.FieldNamingConventions" })
@SuppressWarnings({ "PMD.FieldNamingConventions",
"PMD.VariableNamingConventions" })
private static final JsonNode jsonTemplate
= parseJson("{ \"execute\": \"blockdev-open-tray\",\"arguments\": {"
+ "\"id\": \"\" } }");

View file

@ -25,7 +25,8 @@ import com.fasterxml.jackson.databind.JsonNode;
*/
public class QmpPowerdown extends QmpCommand {
@SuppressWarnings({ "PMD.FieldNamingConventions" })
@SuppressWarnings({ "PMD.FieldNamingConventions",
"PMD.VariableNamingConventions" })
private static final JsonNode jsonTemplate
= parseJson("{ \"execute\": \"system_powerdown\" }");

View file

@ -25,7 +25,8 @@ import com.fasterxml.jackson.databind.JsonNode;
*/
public class QmpQueryHotpluggableCpus extends QmpCommand {
@SuppressWarnings({ "PMD.FieldNamingConventions" })
@SuppressWarnings({ "PMD.FieldNamingConventions",
"PMD.VariableNamingConventions" })
private static final JsonNode jsonTemplate = parseJson(
"{\"execute\":\"query-hotpluggable-cpus\",\"arguments\":{}}");

View file

@ -27,7 +27,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
*/
public class QmpRemoveMedium extends QmpCommand {
@SuppressWarnings({ "PMD.FieldNamingConventions" })
@SuppressWarnings({ "PMD.FieldNamingConventions",
"PMD.VariableNamingConventions" })
private static final JsonNode jsonTemplate
= parseJson("{ \"execute\": \"blockdev-remove-medium\",\"arguments\": {"
+ "\"id\": \"\" } }");

View file

@ -25,7 +25,8 @@ import com.fasterxml.jackson.databind.JsonNode;
*/
public class QmpReset extends QmpCommand {
@SuppressWarnings({ "PMD.FieldNamingConventions" })
@SuppressWarnings({ "PMD.FieldNamingConventions",
"PMD.VariableNamingConventions" })
private static final JsonNode jsonTemplate
= parseJson("{ \"execute\": \"system_reset\" }");

View file

@ -28,7 +28,8 @@ import java.math.BigInteger;
*/
public class QmpSetBalloon extends QmpCommand {
@SuppressWarnings({ "PMD.FieldNamingConventions" })
@SuppressWarnings({ "PMD.FieldNamingConventions",
"PMD.VariableNamingConventions" })
private static final JsonNode jsonTemplate
= parseJson("{ \"execute\": \"balloon\", "
+ "\"arguments\": " + "{ \"value\": 0 } }");

View file

@ -20,8 +20,6 @@ package org.jdrupes.vmoperator.runner.qemu.events;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.Optional;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Components;
import org.jgrapes.core.Event;
/**
@ -49,6 +47,7 @@ public class MonitorEvent extends Event<Void> {
* @param response the response
* @return the optional
*/
@SuppressWarnings("PMD.TooFewBranchesForASwitchStatement")
public static Optional<MonitorEvent> from(JsonNode response) {
try {
var kind = Kind.valueOf(response.get("event").asText());
@ -113,20 +112,4 @@ public class MonitorEvent extends Event<Void> {
public JsonNode data() {
return data;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(Components.objectName(this)).append(" [").append(data);
if (channels() != null) {
builder.append(", channels=").append(Channel.toString(channels()));
}
builder.append(']');
return builder.toString();
}
}

View file

@ -19,8 +19,6 @@
package org.jdrupes.vmoperator.runner.qemu.events;
import com.fasterxml.jackson.databind.JsonNode;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Components;
import org.jgrapes.core.Event;
/**
@ -42,21 +40,4 @@ public class OsinfoEvent extends Event<Void> {
public JsonNode osinfo() {
return osinfo;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(Components.objectName(this)).append(" [")
.append(osinfo);
if (channels() != null) {
builder.append(", channels=").append(Channel.toString(channels()));
}
builder.append(']');
return builder.toString();
}
}

View file

@ -26,6 +26,7 @@ import org.jgrapes.core.Event;
/**
* The Class RunnerStateChange.
*/
@SuppressWarnings("PMD.DataClass")
public class RunnerStateChange extends Event<Void> {
/**

View file

@ -32,6 +32,7 @@ import java.util.logging.Logger;
*/
public final class DataPath {
@SuppressWarnings("PMD.FieldNamingConventions")
private static final Logger logger
= Logger.getLogger(DataPath.class.getName());
@ -55,6 +56,7 @@ public final class DataPath {
* @param selectors the selectors
* @return the result
*/
@SuppressWarnings("PMD.UseLocaleWithCaseConversions")
public static <T> Optional<T> get(Object from, Object... selectors) {
Object cur = from;
for (var selector : selectors) {
@ -130,6 +132,7 @@ public final class DataPath {
@SuppressWarnings({ "PMD.CognitiveComplexity", "unchecked" })
public static <T> T deepCopy(T object) {
if (object instanceof Map map) {
@SuppressWarnings("PMD.UseConcurrentHashMap")
Map<Object, Object> copy;
try {
copy = (Map<Object, Object>) object.getClass().getConstructor()

View file

@ -32,7 +32,8 @@ import java.util.function.Supplier;
/**
* Utility class for pointing to elements on a Gson (Json) tree.
*/
@SuppressWarnings({ "PMD.ClassWithOnlyPrivateConstructorsShouldBeFinal" })
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis",
"PMD.ClassWithOnlyPrivateConstructorsShouldBeFinal", "PMD.GodClass" })
public class GsonPtr {
private final JsonElement position;
@ -101,7 +102,7 @@ public class GsonPtr {
* @param selectors the selectors
* @return the Gson pointer
*/
@SuppressWarnings({ "PMD.PreserveStackTrace" })
@SuppressWarnings({ "PMD.ShortMethodName", "PMD.PreserveStackTrace" })
public Optional<GsonPtr> get(Object... selectors) {
JsonElement element = position;
for (Object sel : selectors) {
@ -145,6 +146,7 @@ public class GsonPtr {
* @param cls the cls
* @return the result
*/
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop" })
public <T extends JsonElement> T getAs(Class<T> cls) {
if (cls.isAssignableFrom(position.getClass())) {
return cls.cast(position);

View file

@ -111,8 +111,9 @@ import org.jgrapes.webconsole.base.freemarker.FreeMarkerConlet;
* users and roles.
*
*/
@SuppressWarnings({ "PMD.ExcessiveImports", "PMD.CouplingBetweenObjects",
"PMD.GodClass", "PMD.TooManyMethods", "PMD.CyclomaticComplexity" })
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports",
"PMD.CouplingBetweenObjects", "PMD.GodClass", "PMD.TooManyMethods",
"PMD.CyclomaticComplexity" })
public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
private static final String VM_NAME_PROPERTY = "vmName";
@ -166,7 +167,7 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
*
* @param event the event
*/
@SuppressWarnings({ "unchecked" })
@SuppressWarnings({ "unchecked", "PMD.AvoidDuplicateLiterals" })
@Handler
public void onConfigurationUpdate(ConfigurationUpdate event) {
event.structured(componentPath())
@ -266,7 +267,7 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
public void onConsoleConfigured(ConsoleConfigured event,
ConsoleConnection connection) throws InterruptedException,
IOException {
@SuppressWarnings({ "unchecked" })
@SuppressWarnings({ "unchecked", "PMD.PrematureDeclaration" })
final var rendered
= (Set<ResourceModel>) connection.session().get(RENDERED);
connection.session().remove(RENDERED);
@ -276,7 +277,8 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
addMissingConlets(event, connection, rendered);
}
@SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops" })
@SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops",
"PMD.AvoidDuplicateLiterals" })
private void addMissingConlets(ConsoleConfigured event,
ConsoleConnection connection, final Set<ResourceModel> rendered)
throws InterruptedException {
@ -404,6 +406,7 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
}
@Override
@SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops" })
protected Set<RenderMode> doRenderConlet(RenderConletRequestBase<?> event,
ConsoleConnection channel, String conletId, ResourceModel model)
throws Exception {
@ -652,8 +655,9 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
* @throws InterruptedException
*/
@Handler(namedChannels = "manager")
@SuppressWarnings({ "PMD.CognitiveComplexity",
"PMD.AvoidInstantiatingObjectsInLoops" })
@SuppressWarnings({ "PMD.ConfusingTernary", "PMD.CognitiveComplexity",
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.AvoidDuplicateLiterals",
"PMD.ConfusingArgumentToVarargsMethod" })
public void onVmResourceChanged(VmResourceChanged event, VmChannel channel)
throws IOException, InterruptedException {
var vmDef = event.vmDefinition();
@ -849,7 +853,8 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
model.getConletId(), "openConsole", cf)));
}
@SuppressWarnings({ "PMD.UseLocaleWithCaseConversions" })
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
"PMD.UseLocaleWithCaseConversions" })
private void selectResource(NotifyConletModel event,
ConsoleConnection channel, ResourceModel model)
throws JsonProcessingException, InterruptedException {
@ -876,6 +881,7 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
/**
* The Class AccessModel.
*/
@SuppressWarnings("PMD.DataClass")
public static class ResourceModel extends ConletBaseModel {
/**

View file

@ -30,7 +30,7 @@
</tr>
</thead>
<tbody>
<template v-for="(entry, rowIndex) in filteredData" :key="entry.name">
<template v-for="(entry, rowIndex) in filteredData">
<tr :class="[(rowIndex % 2) ? 'odd' : 'even']"
:aria-expanded="(entry.name in detailsByName) ? 'true' : 'false'">
<td v-for="key in controller.keys"

View file

@ -28,6 +28,7 @@ import java.util.List;
/**
* The Class TimeSeries.
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class TimeSeries {
@SuppressWarnings("PMD.LooseCoupling")

View file

@ -75,7 +75,8 @@ import org.jgrapes.webconsole.base.freemarker.FreeMarkerConlet;
/**
* The Class {@link VmMgmt}.
*/
@SuppressWarnings({ "PMD.CouplingBetweenObjects", "PMD.ExcessiveImports" })
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.CouplingBetweenObjects",
"PMD.ExcessiveImports" })
public class VmMgmt extends FreeMarkerConlet<VmMgmt.VmsModel> {
private Class<?> preferredIpVersion = Inet4Address.class;
@ -111,7 +112,7 @@ public class VmMgmt extends FreeMarkerConlet<VmMgmt.VmsModel> {
*
* @param event the event
*/
@SuppressWarnings({ "unchecked" })
@SuppressWarnings({ "unchecked", "PMD.AvoidDuplicateLiterals" })
@Handler
public void onConfigurationUpdate(ConfigurationUpdate event) {
event.structured("/Manager/GuiHttpServer"
@ -176,6 +177,7 @@ public class VmMgmt extends FreeMarkerConlet<VmMgmt.VmsModel> {
}
@Override
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
protected Set<RenderMode> doRenderConlet(RenderConletRequestBase<?> event,
ConsoleConnection channel, String conletId, VmsModel conletState)
throws Exception {
@ -228,6 +230,7 @@ public class VmMgmt extends FreeMarkerConlet<VmMgmt.VmsModel> {
simplifiedVmDefinition(vmDef, user, roles)));
}
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
private Map<String, Object> simplifiedVmDefinition(VmDefinition vmDef,
String user, List<String> roles) {
// Convert RAM sizes to unitless numbers
@ -267,8 +270,9 @@ public class VmMgmt extends FreeMarkerConlet<VmMgmt.VmsModel> {
* @throws IOException
*/
@Handler(namedChannels = "manager")
@SuppressWarnings({ "PMD.CognitiveComplexity",
"PMD.AvoidInstantiatingObjectsInLoops" })
@SuppressWarnings({ "PMD.ConfusingTernary", "PMD.CognitiveComplexity",
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.AvoidDuplicateLiterals",
"PMD.ConfusingArgumentToVarargsMethod" })
public void onVmResourceChanged(VmResourceChanged event, VmChannel channel)
throws IOException {
var vmName = event.vmDefinition().name();
@ -374,6 +378,8 @@ public class VmMgmt extends FreeMarkerConlet<VmMgmt.VmsModel> {
}
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
"PMD.LambdaCanBeMethodReference" })
private Summary evaluateSummary(boolean force) {
if (!force && cachedSummary != null) {
return cachedSummary;
@ -396,7 +402,8 @@ public class VmMgmt extends FreeMarkerConlet<VmMgmt.VmsModel> {
}
@Override
@SuppressWarnings({ "PMD.NcssCount" })
@SuppressWarnings({ "PMD.AvoidDecimalLiteralsInBigDecimalConstructor",
"PMD.NcssCount" })
protected void doUpdateConletState(NotifyConletModel event,
ConsoleConnection channel, VmsModel model) throws Exception {
event.stop();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

@ -1 +0,0 @@
<script defer src="https://gotit.mnl.de/script.js" data-website-id="14b277ad-d330-4a54-82f1-a77d111240ac"></script>

View file

@ -8,7 +8,6 @@
<link rel="stylesheet" href="stylesheets/styles.css">
<link rel="stylesheet" href="stylesheets/pygment_trac.css">
<meta name="viewport" content="width=device-width">
{% include umami.html %}
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->

View file

@ -12,10 +12,10 @@ layout: vm-operator
An overview display shows the current CPU and RAM usage and a graph
with recent changes.
![VM-Operator admin GUI preview](VM-Operator-GUI-preview.png)
![VM-Operator GUI](VM-Operator-GUI-preview.png)
The detail display lists all VMs. From here you can start and stop
the VMs and adjust the CPU and RAM usages (modifies the definition
in kubernetes).
![VM-Operator admin GUI view](VM-Operator-GUI-view.png)
![VM-Operator GUI](VM-Operator-GUI-view.png)

View file

@ -9,7 +9,7 @@ layout: vm-operator
When users log into the web GUI, they have already authenticated with the
VM-Operator. In some environments, requiring an additional login on the
guest OS can be annoying. To enhance the user experience, the VM-Operator
guest OS can be cumbersome. To enhance the user experience, the VM-Operator
supports automatic login on the guest operating system, thus eliminating
the need for multiple logins. However, this feature requires specific
support from the guest OS.
@ -18,9 +18,9 @@ support from the guest OS.
Automatic login requires an agent running inside the guest OS. Similar
to QEMU's standard guest agent, the VM-Operator agent communicates with
the host via a tty device (provided in the guest as
`/dev/virtio-ports/org.jdrupes.vmop_agent.0`). On modern Linux systems, `udev` can
detect this device and trigger the start of an associated systemd service.
the host via a tty device (`/dev/virtio-ports/org.jdrupes.vmop_agent.0`). On
modern Linux systems, `udev` can detect this device and trigger the start
of an associated systemd service.
Sample configuration files for a VM-Operator agent are available
[here](https://github.com/mnlipp/VM-Operator/tree/main/dev-example/vmop-agent).

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 591 KiB

After

Width:  |  Height:  |  Size: 588 KiB

Before After
Before After

View file

@ -9,7 +9,7 @@ layout: vm-operator
# Welcome to VM-Operator
![VM-Operator summary picture](index-pic.svg)
![Overview picture](index-pic.svg)
This project provides an easy to use and flexible solution for
running QEMU/KVM based virtual machines (VMs) in Kubernetes pods.

View file

@ -46,20 +46,16 @@ spec:
The `retention` specifies how long the assignment of a VM from the pool to
a user remains valid after the user closes the console. This ensures that
a user can resume work within this timeframe without the risk of another
user taking over the VM. The time is specified as an
user taking over the VM. The time is specified as
[ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations).
Specifying an ISO 8601 time is also supported, but if you consider
using an absolute time, check again whether a dedicated VM for the user
isn't the more appropriate choice.
Setting `loginOnAssignment` to `true` (defaults to `false`) triggers automatic
login of the user (as described in [section auto login](auto-login.html))
when the VM is assigned. The `permissions` property specifies the actions
that users or roles can perform on assigned VMs. The `may` property defaults
to `[accessConsole]` if not specified.
Setting `loginOnAssignment` to `true` triggers automatic login of the
user (as described in [section auto login](auto-login.html)) when
the VM is assigned. The `permissions` property specifies the actions
that users or roles can perform on assigned VMs.
VMs become members of one (or more) pools by adding the pool name to
the `spec.pools` array in the VM definition, as shown below:
the `spec.pools` array, as shown below:
```yaml
apiVersion: "vmoperator.jdrupes.org/v1"