diff --git a/org.jdrupes.vmoperator.manager/.settings/net.sf.jautodoc.prefs b/org.jdrupes.vmoperator.manager/.settings/net.sf.jautodoc.prefs
new file mode 100644
index 0000000..6f3b6d4
--- /dev/null
+++ b/org.jdrupes.vmoperator.manager/.settings/net.sf.jautodoc.prefs
@@ -0,0 +1,8 @@
+add_header=true
+eclipse.preferences.version=1
+header_text=/*\n * VM-Operator\n * Copyright (C) 2023 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/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/CmReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/CmReconciler.java
new file mode 100644
index 0000000..ef71f48
--- /dev/null
+++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/CmReconciler.java
@@ -0,0 +1,88 @@
+/*
+ * VM-Operator
+ * 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
+ * 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.manager;
+
+import freemarker.template.Configuration;
+import freemarker.template.TemplateException;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
+import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject;
+import io.kubernetes.client.util.generic.dynamic.Dynamics;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Map;
+import org.jdrupes.vmoperator.manager.VmDefChanged.Type;
+
+/**
+ * Delegee for reconciling the config map
+ */
+@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
+/* default */ class CmReconciler {
+
+ private final Configuration fmConfig;
+
+ /**
+ * Instantiates a new config map reconciler.
+ *
+ * @param fmConfig the fm config
+ */
+ public CmReconciler(Configuration fmConfig) {
+ this.fmConfig = fmConfig;
+ }
+
+ /**
+ * Reconcile.
+ *
+ * @param event the event
+ * @param model the model
+ * @param channel the channel
+ * @return the dynamic kubernetes object
+ * @throws IOException Signals that an I/O exception has occurred.
+ * @throws TemplateException the template exception
+ * @throws ApiException the api exception
+ */
+ public DynamicKubernetesObject reconcile(VmDefChanged event,
+ Map model, WatchChannel channel)
+ throws IOException, TemplateException, ApiException {
+ // Get API and check if exists
+ DynamicKubernetesApi cmApi = new DynamicKubernetesApi("", "v1",
+ "configmaps", channel.client());
+ var existing = K8s.get(cmApi, event.metadata());
+
+ // If deleted, delete
+ if (event.type() == Type.DELETED) {
+ if (existing.isPresent()) {
+ K8s.delete(cmApi, existing.get());
+ }
+ return null;
+ }
+
+ // Combine template and data and parse result
+ var fmTemplate = fmConfig.getTemplate("runnerConfig.ftl.yaml");
+ StringWriter out = new StringWriter();
+ fmTemplate.process(model, out);
+ // Avoid Yaml.load due to
+ // https://github.com/kubernetes-client/java/issues/2741
+ var mapDef = Dynamics.newFromYaml(out.toString());
+
+ // Apply
+ return K8s.apply(cmApi, mapDef, out.toString());
+ }
+
+}
diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java
index ae16da2..85af1eb 100644
--- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java
+++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java
@@ -37,4 +37,11 @@ public class Constants {
/** The Constant APP_NAME. */
public static final String APP_NAME = "vmrunner";
+
+ /** The Constant STATE_RUNNING. */
+ public static final String STATE_RUNNING = "Running";
+
+ /** The Constant STATE_STOPPED. */
+ public static final String STATE_STOPPED = "Stopped";
+
}
diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DataReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DataReconciler.java
new file mode 100644
index 0000000..5aeef3e
--- /dev/null
+++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DataReconciler.java
@@ -0,0 +1,86 @@
+/*
+ * VM-Operator
+ * 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
+ * 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.manager;
+
+import com.google.gson.JsonObject;
+import freemarker.template.Configuration;
+import freemarker.template.TemplateException;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
+import io.kubernetes.client.util.generic.dynamic.Dynamics;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Map;
+
+/**
+ * Delegee for reconciling the data PVC
+ */
+@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
+/* default */ class DataReconciler {
+
+ private final Configuration fmConfig;
+
+ /**
+ * Instantiates a new config map reconciler.
+ *
+ * @param fmConfig the fm config
+ */
+ public DataReconciler(Configuration fmConfig) {
+ this.fmConfig = fmConfig;
+ }
+
+ /**
+ * Reconcile.
+ *
+ * @param model the model
+ * @param channel the channel
+ * @throws TemplateException the template exception
+ * @throws ApiException the api exception
+ * @throws IOException Signals that an I/O exception has occurred.
+ */
+ @SuppressWarnings("PMD.ConfusingTernary")
+ public void reconcile(Map model, WatchChannel channel)
+ throws TemplateException, ApiException, IOException {
+ // Combine template and data and parse result
+ var fmTemplate = fmConfig.getTemplate("runnerDataPvc.ftl.yaml");
+ StringWriter out = new StringWriter();
+ fmTemplate.process(model, out);
+ // Avoid Yaml.load due to
+ // https://github.com/kubernetes-client/java/issues/2741
+ var pvcDef = Dynamics.newFromYaml(out.toString());
+
+ // Get API and check if PVC exists
+ DynamicKubernetesApi pvcApi = new DynamicKubernetesApi("", "v1",
+ "persistentvolumeclaims", channel.client());
+ var existing = K8s.get(pvcApi, pvcDef.getMetadata());
+
+ // If PVC does not exist, create. Else patch (apply)
+ if (existing.isEmpty()) {
+ pvcApi.create(pvcDef);
+ } else {
+ // spec is immutable, so mix in existing spec
+ GsonPtr.to(pvcDef.getRaw()).set("spec", GsonPtr
+ .to(existing.get().getRaw()).get(JsonObject.class, "spec")
+ .get().deepCopy());
+ K8s.apply(pvcApi, existing.get(),
+ channel.client().getJSON().serialize(pvcDef));
+ }
+ }
+
+}
diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisksReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisksReconciler.java
new file mode 100644
index 0000000..32ee7d6
--- /dev/null
+++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisksReconciler.java
@@ -0,0 +1,125 @@
+/*
+ * VM-Operator
+ * 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
+ * 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.manager;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
+import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject;
+import io.kubernetes.client.util.generic.options.ListOptions;
+import java.util.Collections;
+import static org.jdrupes.vmoperator.manager.Constants.APP_NAME;
+import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
+
+/**
+ * Delegee for reconciling the PVCs for the disks
+ */
+@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
+/* default */ class DisksReconciler {
+
+ /**
+ * Reconcile disks.
+ *
+ * @param vmDef the vm def
+ * @param channel the channel
+ * @throws ApiException the api exception
+ */
+ public void reconcile(DynamicKubernetesObject vmDef,
+ WatchChannel channel) throws ApiException {
+ @SuppressWarnings("PMD.AvoidDuplicateLiterals")
+ var disks = GsonPtr.to(vmDef.getRaw())
+ .get(JsonArray.class, "spec", "vm", "disks")
+ .map(JsonArray::asList).orElse(Collections.emptyList());
+ int index = 0;
+ for (var disk : disks) {
+ reconcileDisk(vmDef, index++, (JsonObject) disk, channel);
+ }
+ }
+
+ @SuppressWarnings({ "PMD.AvoidDuplicateLiterals", "PMD.ConfusingTernary" })
+ private void reconcileDisk(DynamicKubernetesObject vmDefinition,
+ int index, JsonObject diskDef, WatchChannel channel)
+ throws ApiException {
+ var pvcObject = new DynamicKubernetesObject();
+ var pvcRaw = GsonPtr.to(pvcObject.getRaw());
+ var vmRaw = GsonPtr.to(vmDefinition.getRaw());
+ var pvcTpl = GsonPtr.to(diskDef).to("volumeClaimTemplate");
+
+ // Copy base and metadata from template and add missing/additional data.
+ pvcObject.setApiVersion(pvcTpl.getAsString("apiVersion").get());
+ pvcObject.setKind(pvcTpl.getAsString("kind").get());
+ var vmName = vmRaw.getAsString("metadata", "name").orElse("default");
+ pvcRaw.get(JsonObject.class).add("metadata",
+ pvcTpl.to("metadata").get(JsonObject.class).deepCopy());
+ var defMeta = pvcRaw.to("metadata");
+ defMeta.computeIfAbsent("namespace", () -> new JsonPrimitive(
+ vmRaw.getAsString("metadata", "namespace").orElse("default")));
+ defMeta.computeIfAbsent("name", () -> new JsonPrimitive(
+ vmName + "-disk-" + index));
+ var pvcLbls = pvcRaw.to("metadata", "labels");
+ pvcLbls.set("app.kubernetes.io/name", APP_NAME);
+ pvcLbls.set("app.kubernetes.io/instance", vmName);
+ pvcLbls.set("app.kubernetes.io/component", "disk");
+ pvcLbls.set("app.kubernetes.io/managed-by", VM_OP_NAME);
+
+ // Get API and check if PVC exists
+ DynamicKubernetesApi pvcApi = new DynamicKubernetesApi("", "v1",
+ "persistentvolumeclaims", channel.client());
+ var existing = K8s.get(pvcApi, pvcObject.getMetadata());
+
+ // If PVC does not exist, create. Else patch (apply)
+ if (existing.isEmpty()) {
+ // PVC does not exist yet, copy spec from template
+ pvcRaw.get(JsonObject.class).add("spec",
+ pvcTpl.to("spec").get(JsonObject.class).deepCopy());
+ pvcApi.create(pvcObject);
+ } else {
+ // spec is immutable, so mix in existing spec
+ pvcRaw.set("spec", GsonPtr.to(existing.get().getRaw())
+ .to("spec").get().deepCopy());
+ K8s.apply(pvcApi, existing.get(),
+ channel.client().getJSON().serialize(pvcObject));
+ }
+ }
+
+ /**
+ * Delete the PVCs generated from the defined disks.
+ *
+ * @param event the event
+ * @param channel the channel
+ * @throws ApiException the api exception
+ */
+ public void deleteDisks(VmDefChanged event, WatchChannel channel)
+ throws ApiException {
+ // Get API and check and list related
+ var pvcApi = K8s.pvcApi(channel.client());
+ var pvcs = pvcApi.list(event.metadata().getNamespace(),
+ new ListOptions().labelSelector(
+ "app.kubernetes.io/managed-by=" + VM_OP_NAME
+ + ",app.kubernetes.io/name=" + APP_NAME
+ + ",app.kubernetes.io/instance="
+ + event.metadata().getName()));
+ for (var pvc : pvcs.getObject().getItems()) {
+ K8s.delete(pvcApi, pvc);
+ }
+ }
+
+}
diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/PodReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/PodReconciler.java
new file mode 100644
index 0000000..8ccc3fe
--- /dev/null
+++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/PodReconciler.java
@@ -0,0 +1,107 @@
+/*
+ * VM-Operator
+ * 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
+ * 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.manager;
+
+import com.google.gson.JsonObject;
+import freemarker.template.Configuration;
+import freemarker.template.TemplateException;
+import io.kubernetes.client.custom.V1Patch;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
+import io.kubernetes.client.util.generic.dynamic.Dynamics;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Map;
+import static org.jdrupes.vmoperator.manager.Constants.STATE_STOPPED;
+import org.jdrupes.vmoperator.manager.VmDefChanged.Type;
+
+/**
+ * Delegee for reconciling the pod.
+ */
+@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
+/* default */ class PodReconciler {
+
+ private final Configuration fmConfig;
+
+ /**
+ * Instantiates a new config map reconciler.
+ *
+ * @param fmConfig the fm config
+ */
+ public PodReconciler(Configuration fmConfig) {
+ this.fmConfig = fmConfig;
+ }
+
+ /**
+ * Reconcile pod.
+ *
+ * @param event the event
+ * @param model the model
+ * @param channel the channel
+ * @throws IOException Signals that an I/O exception has occurred.
+ * @throws TemplateException the template exception
+ * @throws ApiException the api exception
+ */
+ public void reconcile(VmDefChanged event, Map model,
+ WatchChannel channel)
+ throws IOException, TemplateException, ApiException {
+ // Check if exists
+ DynamicKubernetesApi podApi = new DynamicKubernetesApi("", "v1",
+ "pods", channel.client());
+ var existing = K8s.get(podApi, event.metadata());
+
+ // Get state
+ var state = GsonPtr.to((JsonObject) model.get("cr")).to("spec", "vm")
+ .getAsString("state").get();
+
+ // If deleted or stopped, delete
+ if (event.type() == Type.DELETED || STATE_STOPPED.equals(state)) {
+ if (existing.isPresent()) {
+ K8s.delete(podApi, existing.get());
+ }
+ return;
+ }
+
+ // Combine template and data and parse result
+ var fmTemplate = fmConfig.getTemplate("runnerPod.ftl.yaml");
+ StringWriter out = new StringWriter();
+ fmTemplate.process(model, out);
+ // Avoid Yaml.load due to
+ // https://github.com/kubernetes-client/java/issues/2741
+ var podDef = Dynamics.newFromYaml(out.toString());
+
+ // Check if update
+ if (existing.isEmpty()) {
+ podApi.create(podDef);
+ } else {
+ // only annotations are updated
+ var metadata = new JsonObject();
+ metadata.add("annotations", GsonPtr.to(podDef.getRaw())
+ .to("metadata").get(JsonObject.class, "annotations").get());
+ var patch = new JsonObject();
+ patch.add("metadata", metadata);
+ podApi.patch(existing.get().getMetadata().getNamespace(),
+ existing.get().getMetadata().getName(),
+ V1Patch.PATCH_FORMAT_JSON_MERGE_PATCH,
+ new V1Patch(channel.client().getJSON().serialize(patch)))
+ .throwsApiException();
+ }
+ }
+
+}
diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java
index 472b994..9181ef2 100644
--- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java
+++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java
@@ -18,9 +18,6 @@
package org.jdrupes.vmoperator.manager;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonPrimitive;
import freemarker.core.ParseException;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapperBuilder;
@@ -29,20 +26,13 @@ import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateNotFoundException;
-import io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject;
-import io.kubernetes.client.util.generic.dynamic.Dynamics;
-import io.kubernetes.client.util.generic.options.ListOptions;
import java.io.IOException;
-import java.io.StringWriter;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-import static org.jdrupes.vmoperator.manager.Constants.APP_NAME;
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_GROUP;
-import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_VERSION;
import org.jdrupes.vmoperator.manager.VmDefChanged.Type;
import org.jdrupes.vmoperator.util.ExtendedObjectWrapper;
@@ -57,9 +47,12 @@ import org.jgrapes.core.annotation.Handler;
"PMD.AvoidDuplicateLiterals" })
public class Reconciler extends Component {
- private static final String STATE_RUNNING = "Running";
- private static final String STATE_STOPPED = "Stopped";
+ @SuppressWarnings("PMD.SingularField")
private final Configuration fmConfig;
+ private final CmReconciler cmReconciler;
+ private final DataReconciler dataReconciler;
+ private final DisksReconciler disksReconciler;
+ private final PodReconciler podReconciler;
/**
* Instantiates a new reconciler.
@@ -78,6 +71,11 @@ public class Reconciler extends Component {
TemplateExceptionHandler.RETHROW_HANDLER);
fmConfig.setLogTemplateExceptions(false);
fmConfig.setClassForTemplateLoading(Reconciler.class, "");
+
+ cmReconciler = new CmReconciler(fmConfig);
+ disksReconciler = new DisksReconciler();
+ dataReconciler = new DataReconciler(fmConfig);
+ podReconciler = new PodReconciler(fmConfig);
}
/**
@@ -97,7 +95,8 @@ public class Reconciler extends Component {
@SuppressWarnings("PMD.ConfusingTernary")
public void onVmDefChanged(VmDefChanged event, WatchChannel channel)
throws ApiException, TemplateException, IOException {
- DynamicKubernetesApi vmDefApi = new DynamicKubernetesApi(VM_OP_GROUP,
+ // Get complete VM (CR) definition
+ DynamicKubernetesApi vmCrApi = new DynamicKubernetesApi(VM_OP_GROUP,
VM_OP_VERSION, event.crd().getName(), channel.client());
var defMeta = event.metadata();
@@ -105,7 +104,7 @@ public class Reconciler extends Component {
DynamicKubernetesObject vmDef = null;
Map model = null;
if (event.type() != Type.DELETED) {
- vmDef = K8s.get(vmDefApi, defMeta).get();
+ vmDef = K8s.get(vmCrApi, defMeta).get();
// Prepare Freemarker model
model = new HashMap<>();
@@ -119,191 +118,15 @@ public class Reconciler extends Component {
// Reconcile
if (event.type() != Type.DELETED) {
- reconcileDataPvc(model, channel);
- reconcileDisks(vmDef, channel);
- var configMap = reconcileConfigMap(event, model, channel);
+ dataReconciler.reconcile(model, channel);
+ disksReconciler.reconcile(vmDef, channel);
+ var configMap = cmReconciler.reconcile(event, model, channel);
model.put("cm", configMap.getRaw());
- reconcilePod(event, model, channel);
+ podReconciler.reconcile(event, model, channel);
} else {
- reconcilePod(event, model, channel);
- reconcileConfigMap(event, model, channel);
- deletePvcs(event, channel);
- }
- }
-
- @SuppressWarnings("PMD.ConfusingTernary")
- private void reconcileDataPvc(Map model,
- WatchChannel channel)
- throws TemplateException, ApiException, IOException {
- // Combine template and data and parse result
- var fmTemplate = fmConfig.getTemplate("runnerDataPvc.ftl.yaml");
- StringWriter out = new StringWriter();
- fmTemplate.process(model, out);
- // Avoid Yaml.load due to
- // https://github.com/kubernetes-client/java/issues/2741
- var pvcDef = Dynamics.newFromYaml(out.toString());
-
- // Get API and check if PVC exists
- DynamicKubernetesApi pvcApi = new DynamicKubernetesApi("", "v1",
- "persistentvolumeclaims", channel.client());
- var existing = K8s.get(pvcApi, pvcDef.getMetadata());
-
- // If PVC does not exist, create. Else patch (apply)
- if (existing.isEmpty()) {
- pvcApi.create(pvcDef);
- } else {
- // spec is immutable, so mix in existing spec
- GsonPtr.to(pvcDef.getRaw()).set("spec", GsonPtr
- .to(existing.get().getRaw()).get(JsonObject.class, "spec")
- .get().deepCopy());
- K8s.apply(pvcApi, existing.get(),
- channel.client().getJSON().serialize(pvcDef));
- }
- }
-
- private void reconcileDisks(DynamicKubernetesObject vmDef,
- WatchChannel channel) throws ApiException {
- var disks = GsonPtr.to(vmDef.getRaw())
- .get(JsonArray.class, "spec", "vm", "disks")
- .map(JsonArray::asList).orElse(Collections.emptyList());
- int index = 0;
- for (var disk : disks) {
- reconcileDisk(vmDef, index++, (JsonObject) disk, channel);
- }
- }
-
- @SuppressWarnings({ "PMD.AvoidDuplicateLiterals", "PMD.ConfusingTernary" })
- private void reconcileDisk(DynamicKubernetesObject vmDefinition,
- int index, JsonObject diskDef, WatchChannel channel)
- throws ApiException {
- var pvcObject = new DynamicKubernetesObject();
- var pvcRaw = GsonPtr.to(pvcObject.getRaw());
- var vmRaw = GsonPtr.to(vmDefinition.getRaw());
- var pvcTpl = GsonPtr.to(diskDef).to("volumeClaimTemplate");
-
- // Copy base and metadata from template and add missing/additional data.
- pvcObject.setApiVersion(pvcTpl.getAsString("apiVersion").get());
- pvcObject.setKind(pvcTpl.getAsString("kind").get());
- var vmName = vmRaw.getAsString("metadata", "name").orElse("default");
- pvcRaw.get(JsonObject.class).add("metadata",
- pvcTpl.to("metadata").get(JsonObject.class).deepCopy());
- var defMeta = pvcRaw.to("metadata");
- defMeta.computeIfAbsent("namespace", () -> new JsonPrimitive(
- vmRaw.getAsString("metadata", "namespace").orElse("default")));
- defMeta.computeIfAbsent("name", () -> new JsonPrimitive(
- vmName + "-disk-" + index));
- var pvcLbls = pvcRaw.to("metadata", "labels");
- pvcLbls.set("app.kubernetes.io/name", APP_NAME);
- pvcLbls.set("app.kubernetes.io/instance", vmName);
- pvcLbls.set("app.kubernetes.io/component", "disk");
- pvcLbls.set("app.kubernetes.io/managed-by", VM_OP_NAME);
-
- // Get API and check if PVC exists
- DynamicKubernetesApi pvcApi = new DynamicKubernetesApi("", "v1",
- "persistentvolumeclaims", channel.client());
- var existing = K8s.get(pvcApi, pvcObject.getMetadata());
-
- // If PVC does not exist, create. Else patch (apply)
- if (existing.isEmpty()) {
- // PVC does not exist yet, copy spec from template
- pvcRaw.get(JsonObject.class).add("spec",
- pvcTpl.to("spec").get(JsonObject.class).deepCopy());
- pvcApi.create(pvcObject);
- } else {
- // spec is immutable, so mix in existing spec
- pvcRaw.set("spec", GsonPtr.to(existing.get().getRaw())
- .to("spec").get().deepCopy());
- K8s.apply(pvcApi, existing.get(),
- channel.client().getJSON().serialize(pvcObject));
- }
- }
-
- private void deletePvcs(VmDefChanged event, WatchChannel channel)
- throws ApiException {
- // Get API and check and list related
- var pvcApi = K8s.pvcApi(channel.client());
- var pvcs = pvcApi.list(event.metadata().getNamespace(),
- new ListOptions().labelSelector(
- "app.kubernetes.io/managed-by=" + VM_OP_NAME
- + ",app.kubernetes.io/name=" + APP_NAME
- + ",app.kubernetes.io/instance="
- + event.metadata().getName()));
- for (var pvc : pvcs.getObject().getItems()) {
- K8s.delete(pvcApi, pvc);
- }
- }
-
- private DynamicKubernetesObject reconcileConfigMap(VmDefChanged event,
- Map model, WatchChannel channel)
- throws IOException, TemplateException, ApiException {
- // Get API and check if exists
- DynamicKubernetesApi cmApi = new DynamicKubernetesApi("", "v1",
- "configmaps", channel.client());
- var existing = K8s.get(cmApi, event.metadata());
-
- // If deleted, delete
- if (event.type() == Type.DELETED) {
- if (existing.isPresent()) {
- K8s.delete(cmApi, existing.get());
- }
- return null;
- }
-
- // Combine template and data and parse result
- var fmTemplate = fmConfig.getTemplate("runnerConfig.ftl.yaml");
- StringWriter out = new StringWriter();
- fmTemplate.process(model, out);
- // Avoid Yaml.load due to
- // https://github.com/kubernetes-client/java/issues/2741
- var mapDef = Dynamics.newFromYaml(out.toString());
-
- // Apply
- return K8s.apply(cmApi, mapDef, out.toString());
- }
-
- private void reconcilePod(VmDefChanged event, Map model,
- WatchChannel channel)
- throws IOException, TemplateException, ApiException {
- // Check if exists
- DynamicKubernetesApi podApi = new DynamicKubernetesApi("", "v1",
- "pods", channel.client());
- var existing = K8s.get(podApi, event.metadata());
-
- // Get state
- var state = GsonPtr.to((JsonObject) model.get("cr")).to("spec", "vm")
- .getAsString("state").get();
-
- // If deleted or stopped, delete
- if (event.type() == Type.DELETED || STATE_STOPPED.equals(state)) {
- if (existing.isPresent()) {
- K8s.delete(podApi, existing.get());
- }
- return;
- }
-
- // Combine template and data and parse result
- var fmTemplate = fmConfig.getTemplate("runnerPod.ftl.yaml");
- StringWriter out = new StringWriter();
- fmTemplate.process(model, out);
- // Avoid Yaml.load due to
- // https://github.com/kubernetes-client/java/issues/2741
- var podDef = Dynamics.newFromYaml(out.toString());
-
- // Check if update
- if (existing.isEmpty()) {
- podApi.create(podDef);
- } else {
- // only annotations are updated
- var metadata = new JsonObject();
- metadata.add("annotations", GsonPtr.to(podDef.getRaw())
- .to("metadata").get(JsonObject.class, "annotations").get());
- var patch = new JsonObject();
- patch.add("metadata", metadata);
- podApi.patch(existing.get().getMetadata().getNamespace(),
- existing.get().getMetadata().getName(),
- V1Patch.PATCH_FORMAT_JSON_MERGE_PATCH,
- new V1Patch(channel.client().getJSON().serialize(patch)))
- .throwsApiException();
+ podReconciler.reconcile(event, model, channel);
+ cmReconciler.reconcile(event, model, channel);
+ disksReconciler.deleteDisks(event, channel);
}
}