Refactor internal Kubernetes API and upgrade to official v19 (#19)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled

This commit is contained in:
Michael N. Lipp 2024-03-14 20:12:37 +01:00 committed by GitHub
parent ee2de96c56
commit a2641da7f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 2343 additions and 395 deletions

View file

@ -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

View file

@ -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 {

View file

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

View file

@ -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());

View file

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

View file

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

View file

@ -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());
}
}
/**

View file

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