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); } }