Refactor internal Kubernetes API and upgrade to official v19 (#19)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
This commit is contained in:
parent
ee2de96c56
commit
a2641da7f5
28 changed files with 2343 additions and 395 deletions
|
|
@ -1,6 +1,6 @@
|
|||
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 */
|
||||
header_text=/*\n * VM-Operator\n * Copyright (C) 2024 Michael N. Lipp\n * \n * This program is free software\: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <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
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@ dependencies {
|
|||
runtimeOnly 'org.apache.logging.log4j:log4j-to-jul:2.20.0'
|
||||
|
||||
runtimeOnly project(':org.jdrupes.vmoperator.vmconlet')
|
||||
|
||||
testImplementation 'io.fabric8:kubernetes-client:[6.8.1,6.9)'
|
||||
}
|
||||
|
||||
application {
|
||||
|
|
|
|||
|
|
@ -18,20 +18,21 @@
|
|||
|
||||
package org.jdrupes.vmoperator.manager;
|
||||
|
||||
import io.kubernetes.client.apimachinery.GroupVersionKind;
|
||||
import io.kubernetes.client.custom.V1Patch;
|
||||
import io.kubernetes.client.openapi.ApiException;
|
||||
import io.kubernetes.client.openapi.Configuration;
|
||||
import io.kubernetes.client.util.Config;
|
||||
import io.kubernetes.client.util.generic.options.PatchOptions;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.logging.Level;
|
||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
|
||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM;
|
||||
import org.jdrupes.vmoperator.common.K8s;
|
||||
import org.jdrupes.vmoperator.common.K8sClient;
|
||||
import org.jdrupes.vmoperator.common.K8sDynamicStub;
|
||||
import org.jdrupes.vmoperator.manager.events.Exit;
|
||||
import org.jdrupes.vmoperator.manager.events.ModifyVm;
|
||||
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
||||
import org.jgrapes.core.Channel;
|
||||
import org.jgrapes.core.Component;
|
||||
|
|
@ -160,35 +161,30 @@ public class Controller extends Component {
|
|||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
@Handler
|
||||
public void onModigyVm(ModifyVm event) throws ApiException, IOException {
|
||||
patchVmSpec(event.name(), event.path(), event.value());
|
||||
public void onModifyVm(ModifyVm event, VmChannel channel)
|
||||
throws ApiException, IOException {
|
||||
patchVmSpec(channel.client(), event.name(), event.path(),
|
||||
event.value());
|
||||
}
|
||||
|
||||
private void patchVmSpec(String name, String path, Object value)
|
||||
private void patchVmSpec(K8sClient client, String name, String path,
|
||||
Object value)
|
||||
throws ApiException, IOException {
|
||||
var crApi = K8s.crApi(Config.defaultClient(), VM_OP_GROUP,
|
||||
VM_OP_KIND_VM, namespace, name);
|
||||
if (crApi.isEmpty()) {
|
||||
logger.warning(() -> "Trying to patch " + namespace + "/" + name
|
||||
+ " which does not exist.");
|
||||
return;
|
||||
}
|
||||
var vmStub = K8sDynamicStub.get(client,
|
||||
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM), namespace,
|
||||
name);
|
||||
|
||||
// Patch running
|
||||
PatchOptions patchOpts = new PatchOptions();
|
||||
patchOpts.setFieldManager("kubernetes-java-kubectl-apply");
|
||||
String valueAsText = value instanceof String
|
||||
? "\"" + value + "\""
|
||||
: value.toString();
|
||||
var res = crApi.get().patch(namespace, name,
|
||||
V1Patch.PATCH_FORMAT_JSON_PATCH,
|
||||
var res = vmStub.patch(V1Patch.PATCH_FORMAT_JSON_PATCH,
|
||||
new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/vm/"
|
||||
+ path + "\", \"value\": " + valueAsText + "}]"),
|
||||
patchOpts);
|
||||
if (!res.isSuccess()) {
|
||||
client.defaultPatchOptions());
|
||||
if (!res.isPresent()) {
|
||||
logger.warning(
|
||||
() -> "Cannot patch pod annotations: " + res.getStatus());
|
||||
() -> "Cannot patch pod annotations for " + vmStub.name());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,11 +29,11 @@ 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.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Logger;
|
||||
import org.jdrupes.vmoperator.common.K8s;
|
||||
import org.jdrupes.vmoperator.common.K8sDynamicModel;
|
||||
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
||||
import org.jdrupes.vmoperator.util.GsonPtr;
|
||||
|
|
@ -79,19 +79,25 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
|||
Map<String, Object> model, VmChannel channel)
|
||||
throws IOException, TemplateException, ApiException {
|
||||
// Check if to be generated
|
||||
@SuppressWarnings({ "unchecked", "PMD.AvoidDuplicateLiterals" })
|
||||
var lbs = Optional.of(model)
|
||||
@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);
|
||||
if (lbs instanceof Boolean isOn && !isOn) {
|
||||
return;
|
||||
}
|
||||
if (!(lbs instanceof Map)) {
|
||||
if (!(lbsDef instanceof Map) && !(lbsDef instanceof Boolean)) {
|
||||
logger.warning(() -> "\"" + LOAD_BALANCER_SERVICE
|
||||
+ "\" in configuration must be boolean or mapping but is "
|
||||
+ lbs.getClass() + ".");
|
||||
+ lbsDef.getClass() + ".");
|
||||
return;
|
||||
}
|
||||
if (lbsDef instanceof Boolean isOn && !isOn) {
|
||||
return;
|
||||
}
|
||||
JsonObject cfgMeta = new JsonObject();
|
||||
if (lbsDef instanceof Map) {
|
||||
var json = channel.client().getJSON();
|
||||
cfgMeta
|
||||
= json.deserialize(json.serialize(lbsDef), JsonObject.class);
|
||||
}
|
||||
|
||||
// Combine template and data and parse result
|
||||
var fmTemplate = fmConfig.getTemplate("runnerLoadBalancer.ftl.yaml");
|
||||
|
|
@ -101,7 +107,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
|||
// https://github.com/kubernetes-client/java/issues/2741
|
||||
var svcDef = Dynamics.newFromYaml(
|
||||
new Yaml(new SafeConstructor(new LoaderOptions())), out.toString());
|
||||
mergeMetadata(svcDef, lbs, channel);
|
||||
mergeMetadata(svcDef, cfgMeta, event.vmDefinition());
|
||||
|
||||
// Apply
|
||||
DynamicKubernetesApi svcApi = new DynamicKubernetesApi("", "v1",
|
||||
|
|
@ -109,20 +115,10 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
|||
K8s.apply(svcApi, svcDef, svcDef.getRaw().toString());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void mergeMetadata(DynamicKubernetesObject svcDef,
|
||||
Object lbsConfig, VmChannel channel) {
|
||||
// Get metadata from config
|
||||
Map<String, Object> asmData = Collections.emptyMap();
|
||||
if (lbsConfig instanceof Map config) {
|
||||
asmData = (Map<String, Object>) config;
|
||||
}
|
||||
var json = channel.client().getJSON();
|
||||
JsonObject cfgMeta
|
||||
= json.deserialize(json.serialize(asmData), JsonObject.class);
|
||||
|
||||
JsonObject cfgMeta, K8sDynamicModel vmDefinition) {
|
||||
// Get metadata from VM definition
|
||||
var vmMeta = GsonPtr.to(channel.vmDefinition().getRaw()).to("spec")
|
||||
var vmMeta = GsonPtr.to(vmDefinition.data()).to("spec")
|
||||
.get(JsonObject.class, LOAD_BALANCER_SERVICE)
|
||||
.map(JsonObject::deepCopy).orElseGet(() -> new JsonObject());
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jdrupes.vmoperator.common.Convertions;
|
||||
import org.jdrupes.vmoperator.common.K8sDynamicModel;
|
||||
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged.Type;
|
||||
|
|
@ -206,8 +207,8 @@ public class Reconciler extends Component {
|
|||
lbReconciler.reconcile(event, model, channel);
|
||||
}
|
||||
|
||||
private DynamicKubernetesObject patchCr(DynamicKubernetesObject vmDef) {
|
||||
var json = vmDef.getRaw().deepCopy();
|
||||
private DynamicKubernetesObject patchCr(K8sDynamicModel vmDef) {
|
||||
var json = vmDef.data().deepCopy();
|
||||
// Adjust cdromImage path
|
||||
adjustCdRomPaths(json);
|
||||
|
||||
|
|
|
|||
|
|
@ -22,14 +22,13 @@ 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 io.kubernetes.client.util.generic.options.PatchOptions;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
import org.jdrupes.vmoperator.common.K8s;
|
||||
import org.jdrupes.vmoperator.common.K8sV1StatefulSetStub;
|
||||
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
||||
import org.jdrupes.vmoperator.util.GsonPtr;
|
||||
|
|
@ -68,8 +67,6 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
|||
public void reconcile(VmDefChanged event, Map<String, Object> model,
|
||||
VmChannel channel)
|
||||
throws IOException, TemplateException, ApiException {
|
||||
DynamicKubernetesApi stsApi = new DynamicKubernetesApi("apps", "v1",
|
||||
"statefulsets", channel.client());
|
||||
var metadata = event.vmDefinition().getMetadata();
|
||||
|
||||
// Combine template and data and parse result
|
||||
|
|
@ -83,25 +80,27 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
|||
|
||||
// If exists apply changes only when transitioning state
|
||||
// or not running.
|
||||
var existing = K8s.get(stsApi, metadata);
|
||||
if (existing.isPresent()) {
|
||||
var current = GsonPtr.to(existing.get().getRaw())
|
||||
.to("spec").getAsInt("replicas").orElse(1);
|
||||
var stsStub = K8sV1StatefulSetStub.get(channel.client(),
|
||||
metadata.getNamespace(), metadata.getName());
|
||||
stsStub.model().ifPresent(sts -> {
|
||||
var current = sts.getSpec().getReplicas();
|
||||
var desired = GsonPtr.to(stsDef.getRaw())
|
||||
.to("spec").getAsInt("replicas").orElse(1);
|
||||
if (current == 1 && desired == 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Do apply changes
|
||||
PatchOptions opts = new PatchOptions();
|
||||
opts.setForce(true);
|
||||
opts.setFieldManager("kubernetes-java-kubectl-apply");
|
||||
stsApi.patch(stsDef.getMetadata().getNamespace(),
|
||||
stsDef.getMetadata().getName(), V1Patch.PATCH_FORMAT_APPLY_YAML,
|
||||
new V1Patch(channel.client().getJSON().serialize(stsDef)),
|
||||
opts).throwsApiException();
|
||||
if (stsStub.patch(V1Patch.PATCH_FORMAT_APPLY_YAML,
|
||||
new V1Patch(channel.client().getJSON().serialize(stsDef)), opts)
|
||||
.isEmpty()) {
|
||||
logger.warning(
|
||||
() -> "Could not patch stateful set for " + stsStub.name());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* VM-Operator
|
||||
* Copyright (C) 2023 Michael N. Lipp
|
||||
* Copyright (C) 2023,2024 Michael N. Lipp
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
|
|
@ -21,6 +21,8 @@ package org.jdrupes.vmoperator.manager;
|
|||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import io.kubernetes.client.apimachinery.GroupVersion;
|
||||
import io.kubernetes.client.apimachinery.GroupVersionKind;
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.ApiException;
|
||||
import io.kubernetes.client.openapi.apis.ApisApi;
|
||||
|
|
@ -33,7 +35,6 @@ import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
|||
import io.kubernetes.client.util.Config;
|
||||
import io.kubernetes.client.util.Watch;
|
||||
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.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
|
@ -48,7 +49,10 @@ import java.util.Set;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
|
||||
import org.jdrupes.vmoperator.common.K8s;
|
||||
import org.jdrupes.vmoperator.common.K8sClient;
|
||||
import org.jdrupes.vmoperator.common.K8sDynamicModel;
|
||||
import org.jdrupes.vmoperator.common.K8sDynamicStub;
|
||||
import org.jdrupes.vmoperator.common.K8sV1PodStub;
|
||||
import static org.jdrupes.vmoperator.manager.Constants.APP_NAME;
|
||||
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_KIND_VM;
|
||||
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
|
||||
|
|
@ -68,7 +72,7 @@ import org.jgrapes.util.events.ConfigurationUpdate;
|
|||
/**
|
||||
* Watches for changes of VM definitions.
|
||||
*/
|
||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
||||
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports" })
|
||||
public class VmWatcher extends Component {
|
||||
|
||||
private String namespaceToWatch;
|
||||
|
|
@ -269,13 +273,13 @@ public class VmWatcher extends Component {
|
|||
}
|
||||
|
||||
private void handleVmDefinitionChange(V1APIResource vmsCrd,
|
||||
Watch.Response<V1Namespace> vmDefStub) {
|
||||
V1ObjectMeta metadata = vmDefStub.object.getMetadata();
|
||||
Watch.Response<V1Namespace> vmDefRef) throws ApiException {
|
||||
V1ObjectMeta metadata = vmDefRef.object.getMetadata();
|
||||
VmChannel channel = channels.computeIfAbsent(metadata.getName(),
|
||||
k -> {
|
||||
try {
|
||||
return new VmChannel(channel(), newEventPipeline(),
|
||||
Config.defaultClient());
|
||||
new K8sClient());
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, e, () -> "Failed to create client"
|
||||
+ " for handling changes: " + e.getMessage());
|
||||
|
|
@ -287,30 +291,27 @@ public class VmWatcher extends Component {
|
|||
}
|
||||
|
||||
// Get full definition and associate with channel as backup
|
||||
var apiVersion = K8s.version(vmDefStub.object.getApiVersion());
|
||||
DynamicKubernetesApi vmCrApi = new DynamicKubernetesApi(VM_OP_GROUP,
|
||||
apiVersion, vmsCrd.getName(), channel.client());
|
||||
var curVmDef = K8s.get(vmCrApi, metadata);
|
||||
curVmDef.ifPresent(def -> {
|
||||
// Augment with "dynamic" data and associate with channel
|
||||
addDynamicData(channel.client(), def);
|
||||
channel.setVmDefinition(def);
|
||||
@SuppressWarnings("PMD.ShortVariable")
|
||||
var gv = GroupVersion.parse(vmDefRef.object.getApiVersion());
|
||||
var vmStub = K8sDynamicStub.get(channel.client(),
|
||||
new GroupVersionKind(gv.getGroup(), gv.getVersion(), VM_OP_KIND_VM),
|
||||
metadata.getNamespace(), metadata.getName());
|
||||
vmStub.model().ifPresent(vmDef -> {
|
||||
addDynamicData(channel.client(), vmDef);
|
||||
channel.setVmDefinition(vmDef);
|
||||
|
||||
// Create and fire event
|
||||
channel.pipeline().fire(new VmDefChanged(VmDefChanged.Type
|
||||
.valueOf(vmDefRef.type),
|
||||
channel
|
||||
.setGeneration(
|
||||
vmDefRef.object.getMetadata().getGeneration()),
|
||||
vmsCrd, vmDef), channel);
|
||||
});
|
||||
|
||||
// Get eventual definition to use
|
||||
var vmDef = curVmDef.orElse(channel.vmDefinition());
|
||||
|
||||
// Create and fire event
|
||||
channel.pipeline().fire(new VmDefChanged(VmDefChanged.Type
|
||||
.valueOf(vmDefStub.type),
|
||||
channel
|
||||
.setGeneration(vmDefStub.object.getMetadata().getGeneration()),
|
||||
vmsCrd, vmDef), channel);
|
||||
}
|
||||
|
||||
private void addDynamicData(ApiClient client,
|
||||
DynamicKubernetesObject vmDef) {
|
||||
var rootNode = GsonPtr.to(vmDef.getRaw()).get(JsonObject.class);
|
||||
private void addDynamicData(K8sClient client, K8sDynamicModel vmState) {
|
||||
var rootNode = GsonPtr.to(vmState.data()).get(JsonObject.class);
|
||||
rootNode.addProperty("nodeName", "");
|
||||
|
||||
// VM definition status changes before the pod terminates.
|
||||
|
|
@ -329,11 +330,18 @@ public class VmWatcher extends Component {
|
|||
var podSearch = new ListOptions();
|
||||
podSearch.setLabelSelector("app.kubernetes.io/name=" + APP_NAME
|
||||
+ ",app.kubernetes.io/component=" + APP_NAME
|
||||
+ ",app.kubernetes.io/instance=" + vmDef.getMetadata().getName());
|
||||
var podList = K8s.podApi(client).list(namespaceToWatch, podSearch);
|
||||
podList.getObject().getItems().stream().forEach(pod -> {
|
||||
rootNode.addProperty("nodeName", pod.getSpec().getNodeName());
|
||||
});
|
||||
+ ",app.kubernetes.io/instance=" + vmState.getMetadata().getName());
|
||||
try {
|
||||
var podList
|
||||
= K8sV1PodStub.list(client, namespaceToWatch, podSearch);
|
||||
for (var podStub : podList) {
|
||||
rootNode.addProperty("nodeName",
|
||||
podStub.model().get().getSpec().getNodeName());
|
||||
}
|
||||
} catch (ApiException e) {
|
||||
logger.log(Level.WARNING, e,
|
||||
() -> "Cannot access node information: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
package org.jdrupes.vmoperator.manager;
|
||||
|
||||
import io.fabric8.kubernetes.client.Config;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
|
||||
import io.fabric8.kubernetes.client.dsl.base.ResourceDefinitionContext;
|
||||
import io.kubernetes.client.Discovery.APIResource;
|
||||
import io.kubernetes.client.openapi.ApiException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
|
||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM;
|
||||
import org.jdrupes.vmoperator.common.K8s;
|
||||
import org.jdrupes.vmoperator.common.K8sClient;
|
||||
import org.jdrupes.vmoperator.common.K8sDynamicStub;
|
||||
import org.jdrupes.vmoperator.common.K8sV1ConfigMapStub;
|
||||
import org.jdrupes.vmoperator.common.K8sV1DeploymentStub;
|
||||
import org.jdrupes.vmoperator.common.K8sV1StatefulSetStub;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
|
@ -18,8 +23,9 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
|||
|
||||
class BasicTests {
|
||||
|
||||
private static KubernetesClient client;
|
||||
private static ResourceDefinitionContext vmsContext;
|
||||
private static K8sClient client;
|
||||
private static APIResource vmsContext;
|
||||
private static K8sV1DeploymentStub mgrDeployment;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() throws Exception {
|
||||
|
|
@ -27,29 +33,27 @@ class BasicTests {
|
|||
assertNotNull(testCluster);
|
||||
|
||||
// Get client
|
||||
client = new KubernetesClientBuilder()
|
||||
.withConfig(Config.autoConfigure(testCluster)).build();
|
||||
client = new K8sClient();
|
||||
|
||||
// Context for working with our CR
|
||||
vmsContext = new ResourceDefinitionContext.Builder()
|
||||
.withGroup("vmoperator.jdrupes.org").withKind("VirtualMachine")
|
||||
.withPlural("vms").withNamespaced(true).withVersion("v1").build();
|
||||
var apiRes = K8s.context(client, VM_OP_GROUP, null, VM_OP_KIND_VM);
|
||||
assertTrue(apiRes.isPresent());
|
||||
vmsContext = apiRes.get();
|
||||
|
||||
// Cleanup
|
||||
var resourcesInNamespace = client.genericKubernetesResources(vmsContext)
|
||||
.inNamespace("vmop-dev");
|
||||
resourcesInNamespace.withName("unittest-vm").delete();
|
||||
// Cleanup existing VM
|
||||
K8sDynamicStub.get(client, vmsContext, "vmop-dev", "unittest-vm")
|
||||
.delete();
|
||||
|
||||
// Update manager pod by scaling deployment
|
||||
client.apps().deployments().inNamespace("vmop-dev")
|
||||
.withName("vm-operator").scale(0);
|
||||
client.apps().deployments().inNamespace("vmop-dev")
|
||||
.withName("vm-operator").scale(1);
|
||||
mgrDeployment
|
||||
= K8sV1DeploymentStub.get(client, "vmop-dev", "vm-operator");
|
||||
mgrDeployment.scale(0);
|
||||
mgrDeployment.scale(1);
|
||||
|
||||
// Wait until available
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (client.apps().deployments().inNamespace("vmop-dev")
|
||||
.withName("vm-operator").get().getStatus().getConditions()
|
||||
if (mgrDeployment.model().get().getStatus().getConditions()
|
||||
.stream().filter(c -> "Available".equals(c.getType())).findAny()
|
||||
.isPresent()) {
|
||||
return;
|
||||
|
|
@ -62,44 +66,40 @@ class BasicTests {
|
|||
@AfterAll
|
||||
static void tearDownAfterClass() throws Exception {
|
||||
// Bring down manager
|
||||
client.apps().deployments().inNamespace("vmop-dev")
|
||||
.withName("vm-operator").scale(0);
|
||||
client.close();
|
||||
mgrDeployment.scale(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() throws IOException, InterruptedException {
|
||||
void test() throws IOException, InterruptedException, ApiException {
|
||||
// Load from Yaml
|
||||
var vm = client.genericKubernetesResources(vmsContext)
|
||||
.load(Files
|
||||
.newInputStream(Path.of("test-resources/unittest-vm.yaml")));
|
||||
// Create Custom Resource
|
||||
vm.create();
|
||||
var rdr = new FileReader("test-resources/unittest-vm.yaml");
|
||||
var vmStub = K8sDynamicStub.createFromYaml(client, vmsContext, rdr);
|
||||
assertTrue(vmStub.model().isPresent());
|
||||
|
||||
// Wait for created resources
|
||||
assertTrue(waitForConfigMap());
|
||||
assertTrue(waitForStatefulSet());
|
||||
assertTrue(waitForConfigMap(client));
|
||||
assertTrue(waitForStatefulSet(client));
|
||||
|
||||
// Check config map
|
||||
var config = client.configMaps().inNamespace("vmop-dev")
|
||||
.withName("unittest-vm").get();
|
||||
var config = K8sV1ConfigMapStub.get(client, "vmop-dev", "unittest-vm")
|
||||
.model().get();
|
||||
var yaml = new Yaml(new SafeConstructor(new LoaderOptions()))
|
||||
.load((String) config.getData().get("config.yaml"));
|
||||
.load(config.getData().get("config.yaml"));
|
||||
@SuppressWarnings("unchecked")
|
||||
var currentRam = ((Map<String, Map<String, Map<String, String>>>) yaml)
|
||||
var maximumRam = ((Map<String, Map<String, Map<String, String>>>) yaml)
|
||||
.get("/Runner").get("vm").get("maximumRam");
|
||||
assertEquals("4 GiB", currentRam);
|
||||
assertEquals("4 GiB", maximumRam);
|
||||
|
||||
// Cleanup
|
||||
var resourcesInNamespace = client.genericKubernetesResources(vmsContext)
|
||||
.inNamespace("vmop-dev");
|
||||
resourcesInNamespace.withName("unittest-vm").delete();
|
||||
K8sDynamicStub.get(client, vmsContext, "vmop-dev", "unittest-vm")
|
||||
.delete();
|
||||
}
|
||||
|
||||
private boolean waitForConfigMap() throws InterruptedException {
|
||||
private boolean waitForConfigMap(K8sClient client)
|
||||
throws InterruptedException, ApiException {
|
||||
var stub = K8sV1ConfigMapStub.get(client, "vmop-dev", "unittest-vm");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (client.configMaps().inNamespace("vmop-dev")
|
||||
.withName("unittest-vm").get() != null) {
|
||||
if (stub.model().isPresent()) {
|
||||
return true;
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
|
|
@ -107,10 +107,11 @@ class BasicTests {
|
|||
return false;
|
||||
}
|
||||
|
||||
private boolean waitForStatefulSet() throws InterruptedException {
|
||||
private boolean waitForStatefulSet(K8sClient client)
|
||||
throws InterruptedException, ApiException {
|
||||
var stub = K8sV1StatefulSetStub.get(client, "vmop-dev", "unittest-vm");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (client.apps().statefulSets().inNamespace("vmop-dev")
|
||||
.withName("unittest-vm").get() != null) {
|
||||
if (stub.model().isPresent()) {
|
||||
return true;
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue