Refactor.

This commit is contained in:
Michael Lipp 2023-08-03 12:16:06 +02:00
parent f3e6384db6
commit fc60c3edf1
7 changed files with 441 additions and 197 deletions

View file

@ -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 <https\://www.gnu.org/licenses/>.\n */
project_specific_settings=true
replacements=<?xml version\="1.0" standalone\="yes"?>\n\n<replacements>\n<replacement key\="get" scope\="1" mode\="0">Returns the</replacement>\n<replacement key\="set" scope\="1" mode\="0">Sets the</replacement>\n<replacement key\="add" scope\="1" mode\="0">Adds the</replacement>\n<replacement key\="edit" scope\="1" mode\="0">Edits the</replacement>\n<replacement key\="remove" scope\="1" mode\="0">Removes the</replacement>\n<replacement key\="init" scope\="1" mode\="0">Inits the</replacement>\n<replacement key\="parse" scope\="1" mode\="0">Parses the</replacement>\n<replacement key\="create" scope\="1" mode\="0">Creates the</replacement>\n<replacement key\="build" scope\="1" mode\="0">Builds the</replacement>\n<replacement key\="is" scope\="1" mode\="0">Checks if is</replacement>\n<replacement key\="print" scope\="1" mode\="0">Prints the</replacement>\n<replacement key\="has" scope\="1" mode\="0">Checks for</replacement>\n</replacements>\n\n
visibility_package=false
visibility_private=false
visibility_protected=false

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, Object> 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());
}
}

View file

@ -37,4 +37,11 @@ public class Constants {
/** The Constant APP_NAME. */ /** The Constant APP_NAME. */
public static final String APP_NAME = "vmrunner"; 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";
} }

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, Object> 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));
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, Object> 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();
}
}
}

View file

@ -18,9 +18,6 @@
package org.jdrupes.vmoperator.manager; 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.core.ParseException;
import freemarker.template.Configuration; import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapperBuilder; import freemarker.template.DefaultObjectWrapperBuilder;
@ -29,20 +26,13 @@ import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler; import freemarker.template.TemplateExceptionHandler;
import freemarker.template.TemplateHashModel; import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateNotFoundException; import freemarker.template.TemplateNotFoundException;
import io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi; import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; 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.IOException;
import java.io.StringWriter;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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_GROUP;
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_VERSION; import static org.jdrupes.vmoperator.manager.Constants.VM_OP_VERSION;
import org.jdrupes.vmoperator.manager.VmDefChanged.Type; import org.jdrupes.vmoperator.manager.VmDefChanged.Type;
import org.jdrupes.vmoperator.util.ExtendedObjectWrapper; import org.jdrupes.vmoperator.util.ExtendedObjectWrapper;
@ -57,9 +47,12 @@ import org.jgrapes.core.annotation.Handler;
"PMD.AvoidDuplicateLiterals" }) "PMD.AvoidDuplicateLiterals" })
public class Reconciler extends Component { public class Reconciler extends Component {
private static final String STATE_RUNNING = "Running"; @SuppressWarnings("PMD.SingularField")
private static final String STATE_STOPPED = "Stopped";
private final Configuration fmConfig; private final Configuration fmConfig;
private final CmReconciler cmReconciler;
private final DataReconciler dataReconciler;
private final DisksReconciler disksReconciler;
private final PodReconciler podReconciler;
/** /**
* Instantiates a new reconciler. * Instantiates a new reconciler.
@ -78,6 +71,11 @@ public class Reconciler extends Component {
TemplateExceptionHandler.RETHROW_HANDLER); TemplateExceptionHandler.RETHROW_HANDLER);
fmConfig.setLogTemplateExceptions(false); fmConfig.setLogTemplateExceptions(false);
fmConfig.setClassForTemplateLoading(Reconciler.class, ""); 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") @SuppressWarnings("PMD.ConfusingTernary")
public void onVmDefChanged(VmDefChanged event, WatchChannel channel) public void onVmDefChanged(VmDefChanged event, WatchChannel channel)
throws ApiException, TemplateException, IOException { 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()); VM_OP_VERSION, event.crd().getName(), channel.client());
var defMeta = event.metadata(); var defMeta = event.metadata();
@ -105,7 +104,7 @@ public class Reconciler extends Component {
DynamicKubernetesObject vmDef = null; DynamicKubernetesObject vmDef = null;
Map<String, Object> model = null; Map<String, Object> model = null;
if (event.type() != Type.DELETED) { if (event.type() != Type.DELETED) {
vmDef = K8s.get(vmDefApi, defMeta).get(); vmDef = K8s.get(vmCrApi, defMeta).get();
// Prepare Freemarker model // Prepare Freemarker model
model = new HashMap<>(); model = new HashMap<>();
@ -119,191 +118,15 @@ public class Reconciler extends Component {
// Reconcile // Reconcile
if (event.type() != Type.DELETED) { if (event.type() != Type.DELETED) {
reconcileDataPvc(model, channel); dataReconciler.reconcile(model, channel);
reconcileDisks(vmDef, channel); disksReconciler.reconcile(vmDef, channel);
var configMap = reconcileConfigMap(event, model, channel); var configMap = cmReconciler.reconcile(event, model, channel);
model.put("cm", configMap.getRaw()); model.put("cm", configMap.getRaw());
reconcilePod(event, model, channel); podReconciler.reconcile(event, model, channel);
} else { } else {
reconcilePod(event, model, channel); podReconciler.reconcile(event, model, channel);
reconcileConfigMap(event, model, channel); cmReconciler.reconcile(event, model, channel);
deletePvcs(event, channel); disksReconciler.deleteDisks(event, channel);
}
}
@SuppressWarnings("PMD.ConfusingTernary")
private void reconcileDataPvc(Map<String, Object> 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<String, Object> 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<String, Object> 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();
} }
} }