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
|
|
@ -30,8 +30,11 @@
|
||||||
<property name="severity" value="warning"/>
|
<property name="severity" value="warning"/>
|
||||||
<property name="charset" value="UTF-8"/>
|
<property name="charset" value="UTF-8"/>
|
||||||
<property name="fileExtensions" value="java, properties, xml"/>
|
<property name="fileExtensions" value="java, properties, xml"/>
|
||||||
|
<module name="SuppressWarningsFilter"/>
|
||||||
<module name="TreeWalker">
|
<module name="TreeWalker">
|
||||||
<property name="tabWidth" value="4"/>
|
<property name="tabWidth" value="4"/>
|
||||||
|
|
||||||
|
<module name="SuppressWarningsHolder"/>
|
||||||
<module name="OuterTypeFilename"/>
|
<module name="OuterTypeFilename"/>
|
||||||
<module name="IllegalTokenText">
|
<module name="IllegalTokenText">
|
||||||
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
|
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ spec:
|
||||||
requests:
|
requests:
|
||||||
cpu: 1
|
cpu: 1
|
||||||
memory: 2Gi
|
memory: 2Gi
|
||||||
|
|
||||||
guestShutdownStops: true
|
guestShutdownStops: true
|
||||||
|
|
||||||
cloudInit: {}
|
cloudInit: {}
|
||||||
|
|
||||||
vm:
|
vm:
|
||||||
# state: Running
|
# state: Running
|
||||||
bootMenu: yes
|
bootMenu: yes
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
add_header=true
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
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
|
||||||
|
visibility_package=false
|
||||||
|
visibility_private=false
|
||||||
|
visibility_protected=false
|
||||||
|
|
@ -10,5 +10,6 @@ plugins {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(':org.jdrupes.vmoperator.util')
|
api project(':org.jdrupes.vmoperator.util')
|
||||||
api 'io.kubernetes:client-java:[18.0.0,19)'
|
api 'io.kubernetes:client-java:[19.0.0,20.0.0)'
|
||||||
|
api 'org.yaml:snakeyaml'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* VM-Operator
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -18,29 +18,31 @@
|
||||||
|
|
||||||
package org.jdrupes.vmoperator.common;
|
package org.jdrupes.vmoperator.common;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kubernetes.client.Discovery;
|
||||||
|
import io.kubernetes.client.Discovery.APIResource;
|
||||||
import io.kubernetes.client.common.KubernetesListObject;
|
import io.kubernetes.client.common.KubernetesListObject;
|
||||||
import io.kubernetes.client.common.KubernetesObject;
|
import io.kubernetes.client.common.KubernetesObject;
|
||||||
|
import io.kubernetes.client.common.KubernetesType;
|
||||||
import io.kubernetes.client.custom.V1Patch;
|
import io.kubernetes.client.custom.V1Patch;
|
||||||
import io.kubernetes.client.openapi.ApiClient;
|
import io.kubernetes.client.openapi.ApiClient;
|
||||||
import io.kubernetes.client.openapi.ApiException;
|
import io.kubernetes.client.openapi.ApiException;
|
||||||
import io.kubernetes.client.openapi.apis.ApisApi;
|
import io.kubernetes.client.openapi.apis.EventsV1Api;
|
||||||
import io.kubernetes.client.openapi.apis.CustomObjectsApi;
|
import io.kubernetes.client.openapi.models.EventsV1Event;
|
||||||
import io.kubernetes.client.openapi.models.V1APIGroup;
|
|
||||||
import io.kubernetes.client.openapi.models.V1ConfigMap;
|
|
||||||
import io.kubernetes.client.openapi.models.V1ConfigMapList;
|
|
||||||
import io.kubernetes.client.openapi.models.V1GroupVersionForDiscovery;
|
|
||||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||||
import io.kubernetes.client.openapi.models.V1ObjectReference;
|
import io.kubernetes.client.openapi.models.V1ObjectReference;
|
||||||
import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim;
|
import io.kubernetes.client.util.Strings;
|
||||||
import io.kubernetes.client.openapi.models.V1PersistentVolumeClaimList;
|
|
||||||
import io.kubernetes.client.openapi.models.V1Pod;
|
|
||||||
import io.kubernetes.client.openapi.models.V1PodList;
|
|
||||||
import io.kubernetes.client.util.generic.GenericKubernetesApi;
|
import io.kubernetes.client.util.generic.GenericKubernetesApi;
|
||||||
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
|
import io.kubernetes.client.util.generic.KubernetesApiResponse;
|
||||||
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject;
|
|
||||||
import io.kubernetes.client.util.generic.options.DeleteOptions;
|
|
||||||
import io.kubernetes.client.util.generic.options.PatchOptions;
|
import io.kubernetes.client.util.generic.options.PatchOptions;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import org.yaml.snakeyaml.LoaderOptions;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helpers for K8s API.
|
* Helpers for K8s API.
|
||||||
|
|
@ -50,89 +52,80 @@ import java.util.Optional;
|
||||||
public class K8s {
|
public class K8s {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a groupVersion, returns only the version.
|
* Returns the result from an API call as {@link Optional} if the
|
||||||
|
* call was successful. Returns an empty `Optional` if the status
|
||||||
|
* code is 404 (not found). Else throws an exception.
|
||||||
*
|
*
|
||||||
* @param groupVersion the group version
|
* @param <T> the generic type
|
||||||
* @return the string
|
* @param response the response
|
||||||
|
* @return the optional
|
||||||
|
* @throws ApiException the API exception
|
||||||
*/
|
*/
|
||||||
public static String version(String groupVersion) {
|
public static <T extends KubernetesType> Optional<T>
|
||||||
return groupVersion.substring(groupVersion.lastIndexOf('/') + 1);
|
optional(KubernetesApiResponse<T> response) throws ApiException {
|
||||||
|
if (response.isSuccess()) {
|
||||||
|
return Optional.of(response.getObject());
|
||||||
|
}
|
||||||
|
if (response.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
response.throwsApiException();
|
||||||
|
// Never reached
|
||||||
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get PVC API.
|
* Convert Yaml to Json.
|
||||||
*
|
*
|
||||||
* @param client the client
|
* @param client the client
|
||||||
* @return the generic kubernetes api
|
* @param yaml the yaml
|
||||||
|
* @return the json element
|
||||||
*/
|
*/
|
||||||
public static GenericKubernetesApi<V1PersistentVolumeClaim,
|
public static JsonObject yamlToJson(ApiClient client, Reader yaml) {
|
||||||
V1PersistentVolumeClaimList> pvcApi(ApiClient client) {
|
// Avoid Yaml.load due to
|
||||||
return new GenericKubernetesApi<>(V1PersistentVolumeClaim.class,
|
// https://github.com/kubernetes-client/java/issues/2741
|
||||||
V1PersistentVolumeClaimList.class, "", "v1",
|
@SuppressWarnings("PMD.UseConcurrentHashMap")
|
||||||
"persistentvolumeclaims", client);
|
Map<String, Object> yamlData
|
||||||
|
= new Yaml(new SafeConstructor(new LoaderOptions())).load(yaml);
|
||||||
|
|
||||||
|
// There's no short-cut from Java (collections) to Gson
|
||||||
|
var gson = client.getJSON().getGson();
|
||||||
|
var jsonText = gson.toJson(yamlData);
|
||||||
|
return gson.fromJson(jsonText, JsonObject.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get config map API.
|
* Lookup the specified API resource. If the version is `null` or
|
||||||
*
|
* empty, the preferred version in the result is the default
|
||||||
* @param client the client
|
* returned from the server.
|
||||||
* @return the generic kubernetes api
|
|
||||||
*/
|
|
||||||
public static GenericKubernetesApi<V1ConfigMap,
|
|
||||||
V1ConfigMapList> cmApi(ApiClient client) {
|
|
||||||
return new GenericKubernetesApi<>(V1ConfigMap.class,
|
|
||||||
V1ConfigMapList.class, "", "v1", "configmaps", client);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get pod API.
|
|
||||||
*
|
|
||||||
* @param client the client
|
|
||||||
* @return the generic kubernetes api
|
|
||||||
*/
|
|
||||||
public static GenericKubernetesApi<V1Pod, V1PodList>
|
|
||||||
podApi(ApiClient client) {
|
|
||||||
return new GenericKubernetesApi<>(V1Pod.class, V1PodList.class, "",
|
|
||||||
"v1", "pods", client);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the API for a custom resource.
|
|
||||||
*
|
*
|
||||||
* @param client the client
|
* @param client the client
|
||||||
* @param group the group
|
* @param group the group
|
||||||
|
* @param version the version
|
||||||
* @param kind the kind
|
* @param kind the kind
|
||||||
* @param namespace the namespace
|
* @return the optional
|
||||||
* @param name the name
|
|
||||||
* @return the dynamic kubernetes api
|
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.UseObjectForClearerAPI")
|
public static Optional<APIResource> context(ApiClient client,
|
||||||
public static Optional<DynamicKubernetesApi> crApi(ApiClient client,
|
String group, String version, String kind) throws ApiException {
|
||||||
String group, String kind, String namespace, String name)
|
var apiMatch = new Discovery(client).findAll().stream()
|
||||||
throws ApiException {
|
.filter(r -> r.getGroup().equals(group) && r.getKind().equals(kind)
|
||||||
var apis = new ApisApi(client).getAPIVersions();
|
&& (Strings.isNullOrEmpty(version)
|
||||||
var crdVersions = apis.getGroups().stream()
|
|| r.getVersions().contains(version)))
|
||||||
.filter(g -> g.getName().equals(group)).findFirst()
|
.findFirst();
|
||||||
.map(V1APIGroup::getVersions).stream().flatMap(l -> l.stream())
|
if (apiMatch.isEmpty()) {
|
||||||
.map(V1GroupVersionForDiscovery::getVersion).toList();
|
return Optional.empty();
|
||||||
var coa = new CustomObjectsApi(client);
|
|
||||||
for (var crdVersion : crdVersions) {
|
|
||||||
var crdApiRes = coa.getAPIResources(group, crdVersion)
|
|
||||||
.getResources().stream().filter(r -> kind.equals(r.getKind()))
|
|
||||||
.findFirst();
|
|
||||||
if (crdApiRes.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
|
|
||||||
var crApi = new DynamicKubernetesApi(group,
|
|
||||||
crdVersion, crdApiRes.get().getName(), client);
|
|
||||||
var customResource = crApi.get(namespace, name);
|
|
||||||
if (customResource.isSuccess()) {
|
|
||||||
return Optional.of(crApi);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
var apiRes = apiMatch.get();
|
||||||
|
if (!Strings.isNullOrEmpty(version)) {
|
||||||
|
if (!apiRes.getVersions().contains(version)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
apiRes = new APIResource(apiRes.getGroup(), apiRes.getVersions(),
|
||||||
|
version, apiRes.getKind(), apiRes.getNamespaced(),
|
||||||
|
apiRes.getResourcePlural(), apiRes.getResourceSingular());
|
||||||
|
}
|
||||||
|
return Optional.of(apiRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -144,6 +137,7 @@ public class K8s {
|
||||||
* @param meta the meta
|
* @param meta the meta
|
||||||
* @return the object
|
* @return the object
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static <T extends KubernetesObject, LT extends KubernetesListObject>
|
public static <T extends KubernetesObject, LT extends KubernetesListObject>
|
||||||
Optional<T>
|
Optional<T>
|
||||||
get(GenericKubernetesApi<T, LT> api, V1ObjectMeta meta) {
|
get(GenericKubernetesApi<T, LT> api, V1ObjectMeta meta) {
|
||||||
|
|
@ -154,36 +148,6 @@ public class K8s {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete an object.
|
|
||||||
*
|
|
||||||
* @param <T> the generic type
|
|
||||||
* @param <LT> the generic type
|
|
||||||
* @param api the api
|
|
||||||
* @param object the object
|
|
||||||
*/
|
|
||||||
public static <T extends KubernetesObject, LT extends KubernetesListObject>
|
|
||||||
void delete(GenericKubernetesApi<T, LT> api, T object)
|
|
||||||
throws ApiException {
|
|
||||||
api.delete(object.getMetadata().getNamespace(),
|
|
||||||
object.getMetadata().getName()).throwsApiException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete an object.
|
|
||||||
*
|
|
||||||
* @param <T> the generic type
|
|
||||||
* @param <LT> the generic type
|
|
||||||
* @param api the api
|
|
||||||
* @param object the object
|
|
||||||
*/
|
|
||||||
public static <T extends KubernetesObject, LT extends KubernetesListObject>
|
|
||||||
void delete(GenericKubernetesApi<T, LT> api, T object,
|
|
||||||
DeleteOptions options) throws ApiException {
|
|
||||||
api.delete(object.getMetadata().getNamespace(),
|
|
||||||
object.getMetadata().getName(), options).throwsApiException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply the given patch data.
|
* Apply the given patch data.
|
||||||
*
|
*
|
||||||
|
|
@ -213,7 +177,7 @@ public class K8s {
|
||||||
* @return the v 1 object reference
|
* @return the v 1 object reference
|
||||||
*/
|
*/
|
||||||
public static V1ObjectReference
|
public static V1ObjectReference
|
||||||
objectReference(DynamicKubernetesObject object) {
|
objectReference(KubernetesObject object) {
|
||||||
return new V1ObjectReference().apiVersion(object.getApiVersion())
|
return new V1ObjectReference().apiVersion(object.getApiVersion())
|
||||||
.kind(object.getKind())
|
.kind(object.getKind())
|
||||||
.namespace(object.getMetadata().getNamespace())
|
.namespace(object.getMetadata().getNamespace())
|
||||||
|
|
@ -221,4 +185,54 @@ public class K8s {
|
||||||
.resourceVersion(object.getMetadata().getResourceVersion())
|
.resourceVersion(object.getMetadata().getResourceVersion())
|
||||||
.uid(object.getMetadata().getUid());
|
.uid(object.getMetadata().getUid());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an event related to the object, adding reasonable defaults.
|
||||||
|
*
|
||||||
|
* * If `kind` is not set, it is set to "Event".
|
||||||
|
* * If `metadata.namespace` is not set, it is set
|
||||||
|
* to the object's namespace.
|
||||||
|
* * If neither `metadata.name` nor `matadata.generateName` are set,
|
||||||
|
* set `generateName` to the object's name with a dash appended.
|
||||||
|
* * If `reportingInstance` is not set, set it to the object's name.
|
||||||
|
* * If `eventTime` is not set, set it to now.
|
||||||
|
* * If `type` is not set, set it to "Normal"
|
||||||
|
* * If `regarding` is not set, set it to the given object.
|
||||||
|
*
|
||||||
|
* @param event the event
|
||||||
|
* @throws ApiException
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.NPathComplexity")
|
||||||
|
public static void createEvent(ApiClient client,
|
||||||
|
KubernetesObject object, EventsV1Event event)
|
||||||
|
throws ApiException {
|
||||||
|
if (Strings.isNullOrEmpty(event.getKind())) {
|
||||||
|
event.kind("Event");
|
||||||
|
}
|
||||||
|
if (event.getMetadata() == null) {
|
||||||
|
event.metadata(new V1ObjectMeta());
|
||||||
|
}
|
||||||
|
if (Strings.isNullOrEmpty(event.getMetadata().getNamespace())) {
|
||||||
|
event.getMetadata().namespace(object.getMetadata().getNamespace());
|
||||||
|
}
|
||||||
|
if (Strings.isNullOrEmpty(event.getMetadata().getName())
|
||||||
|
&& Strings.isNullOrEmpty(event.getMetadata().getGenerateName())) {
|
||||||
|
event.getMetadata()
|
||||||
|
.generateName(object.getMetadata().getName() + "-");
|
||||||
|
}
|
||||||
|
if (Strings.isNullOrEmpty(event.getReportingInstance())) {
|
||||||
|
event.reportingInstance(object.getMetadata().getName());
|
||||||
|
}
|
||||||
|
if (event.getEventTime() == null) {
|
||||||
|
event.eventTime(OffsetDateTime.now());
|
||||||
|
}
|
||||||
|
if (Strings.isNullOrEmpty(event.getType())) {
|
||||||
|
event.type("Normal");
|
||||||
|
}
|
||||||
|
if (event.getRegarding() == null) {
|
||||||
|
event.regarding(objectReference(object));
|
||||||
|
}
|
||||||
|
new EventsV1Api(client).createNamespacedEvent(
|
||||||
|
object.getMetadata().getNamespace(), event, null, null, null, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,759 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 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
|
||||||
|
* 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.common;
|
||||||
|
|
||||||
|
import io.kubernetes.client.openapi.ApiCallback;
|
||||||
|
import io.kubernetes.client.openapi.ApiClient;
|
||||||
|
import io.kubernetes.client.openapi.ApiException;
|
||||||
|
import io.kubernetes.client.openapi.ApiResponse;
|
||||||
|
import io.kubernetes.client.openapi.JSON;
|
||||||
|
import io.kubernetes.client.openapi.Pair;
|
||||||
|
import io.kubernetes.client.openapi.auth.Authentication;
|
||||||
|
import io.kubernetes.client.util.ClientBuilder;
|
||||||
|
import io.kubernetes.client.util.generic.options.PatchOptions;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Request.Builder;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A client with some additional properties.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "PMD.ExcessivePublicCount", "PMD.TooManyMethods",
|
||||||
|
"PMD.LinguisticNaming", "checkstyle:LineLength" })
|
||||||
|
public class K8sClient extends ApiClient {
|
||||||
|
|
||||||
|
private ApiClient apiClient;
|
||||||
|
private PatchOptions defaultPatchOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new client.
|
||||||
|
*
|
||||||
|
* @throws IOException Signals that an I/O exception has occurred.
|
||||||
|
*/
|
||||||
|
public K8sClient() throws IOException {
|
||||||
|
defaultPatchOptions = new PatchOptions();
|
||||||
|
defaultPatchOptions.setFieldManager("kubernetes-java-kubectl-apply");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiClient apiClient() {
|
||||||
|
if (apiClient == null) {
|
||||||
|
try {
|
||||||
|
apiClient = ClientBuilder.standard().build();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the default patch options.
|
||||||
|
*
|
||||||
|
* @return the defaultPatchOptions
|
||||||
|
*/
|
||||||
|
public PatchOptions defaultPatchOptions() {
|
||||||
|
return defaultPatchOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the default patch options.
|
||||||
|
*
|
||||||
|
* @param patchOptions the patch options
|
||||||
|
* @return the client
|
||||||
|
*/
|
||||||
|
public K8sClient with(PatchOptions patchOptions) {
|
||||||
|
defaultPatchOptions = patchOptions;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#getBasePath()
|
||||||
|
*/
|
||||||
|
public String getBasePath() {
|
||||||
|
return apiClient().getBasePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param basePath
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setBasePath(java.lang.String)
|
||||||
|
*/
|
||||||
|
public ApiClient setBasePath(String basePath) {
|
||||||
|
return apiClient().setBasePath(basePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#getHttpClient()
|
||||||
|
*/
|
||||||
|
public OkHttpClient getHttpClient() {
|
||||||
|
return apiClient().getHttpClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param newHttpClient
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setHttpClient(okhttp3.OkHttpClient)
|
||||||
|
*/
|
||||||
|
public ApiClient setHttpClient(OkHttpClient newHttpClient) {
|
||||||
|
return apiClient().setHttpClient(newHttpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#getJSON()
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("abbreviationAsWordInName")
|
||||||
|
public JSON getJSON() {
|
||||||
|
return apiClient().getJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param json
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setJSON(io.kubernetes.client.openapi.JSON)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("abbreviationAsWordInName")
|
||||||
|
public ApiClient setJSON(JSON json) {
|
||||||
|
return apiClient().setJSON(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#isVerifyingSsl()
|
||||||
|
*/
|
||||||
|
public boolean isVerifyingSsl() {
|
||||||
|
return apiClient().isVerifyingSsl();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param verifyingSsl
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setVerifyingSsl(boolean)
|
||||||
|
*/
|
||||||
|
public ApiClient setVerifyingSsl(boolean verifyingSsl) {
|
||||||
|
return apiClient().setVerifyingSsl(verifyingSsl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#getSslCaCert()
|
||||||
|
*/
|
||||||
|
public InputStream getSslCaCert() {
|
||||||
|
return apiClient().getSslCaCert();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param sslCaCert
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setSslCaCert(java.io.InputStream)
|
||||||
|
*/
|
||||||
|
public ApiClient setSslCaCert(InputStream sslCaCert) {
|
||||||
|
return apiClient().setSslCaCert(sslCaCert);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#getKeyManagers()
|
||||||
|
*/
|
||||||
|
public KeyManager[] getKeyManagers() {
|
||||||
|
return apiClient().getKeyManagers();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param managers
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setKeyManagers(javax.net.ssl.KeyManager[])
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.UseVarargs")
|
||||||
|
public ApiClient setKeyManagers(KeyManager[] managers) {
|
||||||
|
return apiClient().setKeyManagers(managers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#getDateFormat()
|
||||||
|
*/
|
||||||
|
public DateFormat getDateFormat() {
|
||||||
|
return apiClient().getDateFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dateFormat
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setDateFormat(java.text.DateFormat)
|
||||||
|
*/
|
||||||
|
public ApiClient setDateFormat(DateFormat dateFormat) {
|
||||||
|
return apiClient().setDateFormat(dateFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dateFormat
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setSqlDateFormat(java.text.DateFormat)
|
||||||
|
*/
|
||||||
|
public ApiClient setSqlDateFormat(DateFormat dateFormat) {
|
||||||
|
return apiClient().setSqlDateFormat(dateFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dateFormat
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setOffsetDateTimeFormat(java.time.format.DateTimeFormatter)
|
||||||
|
*/
|
||||||
|
public ApiClient setOffsetDateTimeFormat(DateTimeFormatter dateFormat) {
|
||||||
|
return apiClient().setOffsetDateTimeFormat(dateFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dateFormat
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setLocalDateFormat(java.time.format.DateTimeFormatter)
|
||||||
|
*/
|
||||||
|
public ApiClient setLocalDateFormat(DateTimeFormatter dateFormat) {
|
||||||
|
return apiClient().setLocalDateFormat(dateFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param lenientOnJson
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setLenientOnJson(boolean)
|
||||||
|
*/
|
||||||
|
public ApiClient setLenientOnJson(boolean lenientOnJson) {
|
||||||
|
return apiClient().setLenientOnJson(lenientOnJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#getAuthentications()
|
||||||
|
*/
|
||||||
|
public Map<String, Authentication> getAuthentications() {
|
||||||
|
return apiClient().getAuthentications();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param authName
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#getAuthentication(java.lang.String)
|
||||||
|
*/
|
||||||
|
public Authentication getAuthentication(String authName) {
|
||||||
|
return apiClient().getAuthentication(authName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param username
|
||||||
|
* @see ApiClient#setUsername(java.lang.String)
|
||||||
|
*/
|
||||||
|
public void setUsername(String username) {
|
||||||
|
apiClient().setUsername(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param password
|
||||||
|
* @see ApiClient#setPassword(java.lang.String)
|
||||||
|
*/
|
||||||
|
public void setPassword(String password) {
|
||||||
|
apiClient().setPassword(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param apiKey
|
||||||
|
* @see ApiClient#setApiKey(java.lang.String)
|
||||||
|
*/
|
||||||
|
public void setApiKey(String apiKey) {
|
||||||
|
apiClient().setApiKey(apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param apiKeyPrefix
|
||||||
|
* @see ApiClient#setApiKeyPrefix(java.lang.String)
|
||||||
|
*/
|
||||||
|
public void setApiKeyPrefix(String apiKeyPrefix) {
|
||||||
|
apiClient().setApiKeyPrefix(apiKeyPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param accessToken
|
||||||
|
* @see ApiClient#setAccessToken(java.lang.String)
|
||||||
|
*/
|
||||||
|
public void setAccessToken(String accessToken) {
|
||||||
|
apiClient().setAccessToken(accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param userAgent
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setUserAgent(java.lang.String)
|
||||||
|
*/
|
||||||
|
public ApiClient setUserAgent(String userAgent) {
|
||||||
|
return apiClient().setUserAgent(userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
* @see java.lang.Object#toString()
|
||||||
|
*/
|
||||||
|
public String toString() {
|
||||||
|
return apiClient().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#addDefaultHeader(java.lang.String, java.lang.String)
|
||||||
|
*/
|
||||||
|
public ApiClient addDefaultHeader(String key, String value) {
|
||||||
|
return apiClient().addDefaultHeader(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#addDefaultCookie(java.lang.String, java.lang.String)
|
||||||
|
*/
|
||||||
|
public ApiClient addDefaultCookie(String key, String value) {
|
||||||
|
return apiClient().addDefaultCookie(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#isDebugging()
|
||||||
|
*/
|
||||||
|
public boolean isDebugging() {
|
||||||
|
return apiClient().isDebugging();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param debugging
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setDebugging(boolean)
|
||||||
|
*/
|
||||||
|
public ApiClient setDebugging(boolean debugging) {
|
||||||
|
return apiClient().setDebugging(debugging);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#getTempFolderPath()
|
||||||
|
*/
|
||||||
|
public String getTempFolderPath() {
|
||||||
|
return apiClient().getTempFolderPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param tempFolderPath
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setTempFolderPath(java.lang.String)
|
||||||
|
*/
|
||||||
|
public ApiClient setTempFolderPath(String tempFolderPath) {
|
||||||
|
return apiClient().setTempFolderPath(tempFolderPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#getConnectTimeout()
|
||||||
|
*/
|
||||||
|
public int getConnectTimeout() {
|
||||||
|
return apiClient().getConnectTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param connectionTimeout
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setConnectTimeout(int)
|
||||||
|
*/
|
||||||
|
public ApiClient setConnectTimeout(int connectionTimeout) {
|
||||||
|
return apiClient().setConnectTimeout(connectionTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#getReadTimeout()
|
||||||
|
*/
|
||||||
|
public int getReadTimeout() {
|
||||||
|
return apiClient().getReadTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param readTimeout
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setReadTimeout(int)
|
||||||
|
*/
|
||||||
|
public ApiClient setReadTimeout(int readTimeout) {
|
||||||
|
return apiClient().setReadTimeout(readTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#getWriteTimeout()
|
||||||
|
*/
|
||||||
|
public int getWriteTimeout() {
|
||||||
|
return apiClient().getWriteTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param writeTimeout
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#setWriteTimeout(int)
|
||||||
|
*/
|
||||||
|
public ApiClient setWriteTimeout(int writeTimeout) {
|
||||||
|
return apiClient().setWriteTimeout(writeTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param param
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#parameterToString(java.lang.Object)
|
||||||
|
*/
|
||||||
|
public String parameterToString(Object param) {
|
||||||
|
return apiClient().parameterToString(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param name
|
||||||
|
* @param value
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#parameterToPair(java.lang.String, java.lang.Object)
|
||||||
|
*/
|
||||||
|
public List<Pair> parameterToPair(String name, Object value) {
|
||||||
|
return apiClient().parameterToPair(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param collectionFormat
|
||||||
|
* @param name
|
||||||
|
* @param value
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#parameterToPairs(java.lang.String, java.lang.String, java.util.Collection)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "rawtypes", "PMD.AvoidDuplicateLiterals" })
|
||||||
|
public List<Pair> parameterToPairs(String collectionFormat, String name,
|
||||||
|
Collection value) {
|
||||||
|
return apiClient().parameterToPairs(collectionFormat, name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param collectionFormat
|
||||||
|
* @param value
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#collectionPathParameterToString(java.lang.String, java.util.Collection)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public String collectionPathParameterToString(String collectionFormat,
|
||||||
|
Collection value) {
|
||||||
|
return apiClient().collectionPathParameterToString(collectionFormat,
|
||||||
|
value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param filename
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#sanitizeFilename(java.lang.String)
|
||||||
|
*/
|
||||||
|
public String sanitizeFilename(String filename) {
|
||||||
|
return apiClient().sanitizeFilename(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mime
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#isJsonMime(java.lang.String)
|
||||||
|
*/
|
||||||
|
public boolean isJsonMime(String mime) {
|
||||||
|
return apiClient().isJsonMime(mime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param accepts
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#selectHeaderAccept(java.lang.String[])
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.UseVarargs")
|
||||||
|
public String selectHeaderAccept(String[] accepts) {
|
||||||
|
return apiClient().selectHeaderAccept(accepts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param contentTypes
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#selectHeaderContentType(java.lang.String[])
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.UseVarargs")
|
||||||
|
public String selectHeaderContentType(String[] contentTypes) {
|
||||||
|
return apiClient().selectHeaderContentType(contentTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param str
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#escapeString(java.lang.String)
|
||||||
|
*/
|
||||||
|
public String escapeString(String str) {
|
||||||
|
return apiClient().escapeString(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param <T>
|
||||||
|
* @param response
|
||||||
|
* @param returnType
|
||||||
|
* @return
|
||||||
|
* @throws ApiException
|
||||||
|
* @see ApiClient#deserialize(okhttp3.Response, java.lang.reflect.Type)
|
||||||
|
*/
|
||||||
|
public <T> T deserialize(Response response, Type returnType)
|
||||||
|
throws ApiException {
|
||||||
|
return apiClient().deserialize(response, returnType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param obj
|
||||||
|
* @param contentType
|
||||||
|
* @return
|
||||||
|
* @throws ApiException
|
||||||
|
* @see ApiClient#serialize(java.lang.Object, java.lang.String)
|
||||||
|
*/
|
||||||
|
public RequestBody serialize(Object obj, String contentType)
|
||||||
|
throws ApiException {
|
||||||
|
return apiClient().serialize(obj, contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param response
|
||||||
|
* @return
|
||||||
|
* @throws ApiException
|
||||||
|
* @see ApiClient#downloadFileFromResponse(okhttp3.Response)
|
||||||
|
*/
|
||||||
|
public File downloadFileFromResponse(Response response)
|
||||||
|
throws ApiException {
|
||||||
|
return apiClient().downloadFileFromResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param response
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
* @see ApiClient#prepareDownloadFile(okhttp3.Response)
|
||||||
|
*/
|
||||||
|
public File prepareDownloadFile(Response response) throws IOException {
|
||||||
|
return apiClient().prepareDownloadFile(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param <T>
|
||||||
|
* @param call
|
||||||
|
* @return
|
||||||
|
* @throws ApiException
|
||||||
|
* @see ApiClient#execute(okhttp3.Call)
|
||||||
|
*/
|
||||||
|
public <T> ApiResponse<T> execute(Call call) throws ApiException {
|
||||||
|
return apiClient().execute(call);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param <T>
|
||||||
|
* @param call
|
||||||
|
* @param returnType
|
||||||
|
* @return
|
||||||
|
* @throws ApiException
|
||||||
|
* @see ApiClient#execute(okhttp3.Call, java.lang.reflect.Type)
|
||||||
|
*/
|
||||||
|
public <T> ApiResponse<T> execute(Call call, Type returnType)
|
||||||
|
throws ApiException {
|
||||||
|
return apiClient().execute(call, returnType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param <T>
|
||||||
|
* @param call
|
||||||
|
* @param callback
|
||||||
|
* @see ApiClient#executeAsync(okhttp3.Call, io.kubernetes.client.openapi.ApiCallback)
|
||||||
|
*/
|
||||||
|
public <T> void executeAsync(Call call, ApiCallback<T> callback) {
|
||||||
|
apiClient().executeAsync(call, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param <T>
|
||||||
|
* @param call
|
||||||
|
* @param returnType
|
||||||
|
* @param callback
|
||||||
|
* @see ApiClient#executeAsync(okhttp3.Call, java.lang.reflect.Type, io.kubernetes.client.openapi.ApiCallback)
|
||||||
|
*/
|
||||||
|
public <T> void executeAsync(Call call, Type returnType,
|
||||||
|
ApiCallback<T> callback) {
|
||||||
|
apiClient().executeAsync(call, returnType, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param <T>
|
||||||
|
* @param response
|
||||||
|
* @param returnType
|
||||||
|
* @return
|
||||||
|
* @throws ApiException
|
||||||
|
* @see ApiClient#handleResponse(okhttp3.Response, java.lang.reflect.Type)
|
||||||
|
*/
|
||||||
|
public <T> T handleResponse(Response response, Type returnType)
|
||||||
|
throws ApiException {
|
||||||
|
return apiClient().handleResponse(response, returnType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param path
|
||||||
|
* @param method
|
||||||
|
* @param queryParams
|
||||||
|
* @param collectionQueryParams
|
||||||
|
* @param body
|
||||||
|
* @param headerParams
|
||||||
|
* @param cookieParams
|
||||||
|
* @param formParams
|
||||||
|
* @param authNames
|
||||||
|
* @param callback
|
||||||
|
* @return
|
||||||
|
* @throws ApiException
|
||||||
|
* @see ApiClient#buildCall(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.Object, java.util.Map, java.util.Map, java.util.Map, java.lang.String[], io.kubernetes.client.openapi.ApiCallback)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "rawtypes", "PMD.ExcessiveParameterList" })
|
||||||
|
public Call buildCall(String path, String method, List<Pair> queryParams,
|
||||||
|
List<Pair> collectionQueryParams, Object body,
|
||||||
|
Map<String, String> headerParams, Map<String, String> cookieParams,
|
||||||
|
Map<String, Object> formParams, String[] authNames,
|
||||||
|
ApiCallback callback) throws ApiException {
|
||||||
|
return apiClient().buildCall(path, method, queryParams,
|
||||||
|
collectionQueryParams, body, headerParams, cookieParams, formParams,
|
||||||
|
authNames, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param path
|
||||||
|
* @param method
|
||||||
|
* @param queryParams
|
||||||
|
* @param collectionQueryParams
|
||||||
|
* @param body
|
||||||
|
* @param headerParams
|
||||||
|
* @param cookieParams
|
||||||
|
* @param formParams
|
||||||
|
* @param authNames
|
||||||
|
* @param callback
|
||||||
|
* @return
|
||||||
|
* @throws ApiException
|
||||||
|
* @see ApiClient#buildRequest(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.Object, java.util.Map, java.util.Map, java.util.Map, java.lang.String[], io.kubernetes.client.openapi.ApiCallback)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "rawtypes", "PMD.ExcessiveParameterList" })
|
||||||
|
public Request buildRequest(String path, String method,
|
||||||
|
List<Pair> queryParams, List<Pair> collectionQueryParams,
|
||||||
|
Object body, Map<String, String> headerParams,
|
||||||
|
Map<String, String> cookieParams, Map<String, Object> formParams,
|
||||||
|
String[] authNames, ApiCallback callback) throws ApiException {
|
||||||
|
return apiClient().buildRequest(path, method, queryParams,
|
||||||
|
collectionQueryParams, body, headerParams, cookieParams, formParams,
|
||||||
|
authNames, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param path
|
||||||
|
* @param queryParams
|
||||||
|
* @param collectionQueryParams
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#buildUrl(java.lang.String, java.util.List, java.util.List)
|
||||||
|
*/
|
||||||
|
public String buildUrl(String path, List<Pair> queryParams,
|
||||||
|
List<Pair> collectionQueryParams) {
|
||||||
|
return apiClient().buildUrl(path, queryParams, collectionQueryParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param headerParams
|
||||||
|
* @param reqBuilder
|
||||||
|
* @see ApiClient#processHeaderParams(java.util.Map, okhttp3.Request.Builder)
|
||||||
|
*/
|
||||||
|
public void processHeaderParams(Map<String, String> headerParams,
|
||||||
|
Builder reqBuilder) {
|
||||||
|
apiClient().processHeaderParams(headerParams, reqBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cookieParams
|
||||||
|
* @param reqBuilder
|
||||||
|
* @see ApiClient#processCookieParams(java.util.Map, okhttp3.Request.Builder)
|
||||||
|
*/
|
||||||
|
public void processCookieParams(Map<String, String> cookieParams,
|
||||||
|
Builder reqBuilder) {
|
||||||
|
apiClient().processCookieParams(cookieParams, reqBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param authNames
|
||||||
|
* @param queryParams
|
||||||
|
* @param headerParams
|
||||||
|
* @param cookieParams
|
||||||
|
* @see ApiClient#updateParamsForAuth(java.lang.String[], java.util.List, java.util.Map, java.util.Map)
|
||||||
|
*/
|
||||||
|
public void updateParamsForAuth(String[] authNames, List<Pair> queryParams,
|
||||||
|
Map<String, String> headerParams,
|
||||||
|
Map<String, String> cookieParams) {
|
||||||
|
apiClient().updateParamsForAuth(authNames, queryParams, headerParams,
|
||||||
|
cookieParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param formParams
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#buildRequestBodyFormEncoding(java.util.Map)
|
||||||
|
*/
|
||||||
|
public RequestBody
|
||||||
|
buildRequestBodyFormEncoding(Map<String, Object> formParams) {
|
||||||
|
return apiClient().buildRequestBodyFormEncoding(formParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param formParams
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#buildRequestBodyMultipart(java.util.Map)
|
||||||
|
*/
|
||||||
|
public RequestBody
|
||||||
|
buildRequestBodyMultipart(Map<String, Object> formParams) {
|
||||||
|
return apiClient().buildRequestBodyMultipart(formParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param file
|
||||||
|
* @return
|
||||||
|
* @see ApiClient#guessContentTypeFromFile(java.io.File)
|
||||||
|
*/
|
||||||
|
public String guessContentTypeFromFile(File file) {
|
||||||
|
return apiClient().guessContentTypeFromFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 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
|
||||||
|
* 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.common;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kubernetes.client.common.KubernetesObject;
|
||||||
|
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a Kubernetes object using a JSON data structure.
|
||||||
|
* Some information that is common to all Kubernetes objects,
|
||||||
|
* notably the metadata, is made available through the methods
|
||||||
|
* defined by {@link KubernetesObject}.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.DataClass")
|
||||||
|
public class K8sDynamicModel implements KubernetesObject {
|
||||||
|
|
||||||
|
private final V1ObjectMeta metadata;
|
||||||
|
private final JsonObject data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new model from the JSON representation.
|
||||||
|
*
|
||||||
|
* @param delegate the gson instance to use for extracting structured data
|
||||||
|
* @param json the JSON
|
||||||
|
*/
|
||||||
|
public K8sDynamicModel(Gson delegate, JsonObject json) {
|
||||||
|
this.data = json;
|
||||||
|
metadata = delegate.fromJson(data.get("metadata"), V1ObjectMeta.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getApiVersion() {
|
||||||
|
return apiVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the API version. (Abbreviated method name for convenience.)
|
||||||
|
*
|
||||||
|
* @return the API version
|
||||||
|
*/
|
||||||
|
public String apiVersion() {
|
||||||
|
return data.get("apiVersion").getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKind() {
|
||||||
|
return kind();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the kind. (Abbreviated method name for convenience.)
|
||||||
|
*
|
||||||
|
* @return the kind
|
||||||
|
*/
|
||||||
|
public String kind() {
|
||||||
|
return data.get("kind").getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V1ObjectMeta getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the metadata. (Abbreviated method name for convenience.)
|
||||||
|
*
|
||||||
|
* @return the metadata
|
||||||
|
*/
|
||||||
|
public V1ObjectMeta metadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the data.
|
||||||
|
*
|
||||||
|
* @return the data
|
||||||
|
*/
|
||||||
|
public JsonObject data() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method for getting the status.
|
||||||
|
*
|
||||||
|
* @return the JSON object describing the status
|
||||||
|
*/
|
||||||
|
public JsonObject status() {
|
||||||
|
return data.getAsJsonObject("status");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return data.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 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
|
||||||
|
* 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.common;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.InstanceCreator;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.TypeAdapter;
|
||||||
|
import com.google.gson.TypeAdapterFactory;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import com.google.gson.stream.JsonReader;
|
||||||
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory for creating K8sDynamicModel(s) objects.
|
||||||
|
*/
|
||||||
|
public class K8sDynamicModelTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a type adapter for the given type.
|
||||||
|
*
|
||||||
|
* @param <T> the generic type
|
||||||
|
* @param gson the gson
|
||||||
|
* @param typeToken the type token
|
||||||
|
* @return the type adapter or null if the type is not handles by
|
||||||
|
* this factory
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
||||||
|
if (TypeToken.get(K8sDynamicModel.class).equals(typeToken)) {
|
||||||
|
return (TypeAdapter<T>) (new K8sDynamicModelCreator(gson));
|
||||||
|
}
|
||||||
|
if (TypeToken.get(K8sDynamicModels.class).equals(typeToken)) {
|
||||||
|
return (TypeAdapter<T>) (new K8sDynamicModelsCreator(gson));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Class K8sDynamicModelCreator.
|
||||||
|
*/
|
||||||
|
/* default */ class K8sDynamicModelCreator
|
||||||
|
extends TypeAdapter<K8sDynamicModel>
|
||||||
|
implements InstanceCreator<K8sDynamicModel> {
|
||||||
|
private final Gson delegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new object state creator.
|
||||||
|
*
|
||||||
|
* @param delegate the delegate
|
||||||
|
*/
|
||||||
|
public K8sDynamicModelCreator(Gson delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public K8sDynamicModel createInstance(Type type) {
|
||||||
|
return new K8sDynamicModel(delegate, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(JsonWriter jsonWriter, K8sDynamicModel state)
|
||||||
|
throws IOException {
|
||||||
|
jsonWriter.jsonValue(delegate.toJson(state.data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public K8sDynamicModel read(JsonReader jsonReader)
|
||||||
|
throws IOException {
|
||||||
|
return new K8sDynamicModel(delegate,
|
||||||
|
delegate.fromJson(jsonReader, JsonObject.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Class K8sDynamicModelsCreator.
|
||||||
|
*/
|
||||||
|
/* default */class K8sDynamicModelsCreator
|
||||||
|
extends TypeAdapter<K8sDynamicModels>
|
||||||
|
implements InstanceCreator<K8sDynamicModels> {
|
||||||
|
|
||||||
|
private final Gson delegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new object states creator.
|
||||||
|
*
|
||||||
|
* @param delegate the delegate
|
||||||
|
*/
|
||||||
|
public K8sDynamicModelsCreator(Gson delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public K8sDynamicModels createInstance(Type type) {
|
||||||
|
return new K8sDynamicModels(delegate, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(JsonWriter jsonWriter, K8sDynamicModels states)
|
||||||
|
throws IOException {
|
||||||
|
jsonWriter.jsonValue(delegate.toJson(states.data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public K8sDynamicModels read(JsonReader jsonReader)
|
||||||
|
throws IOException {
|
||||||
|
return new K8sDynamicModels(delegate,
|
||||||
|
delegate.fromJson(jsonReader, JsonObject.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 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
|
||||||
|
* 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.common;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kubernetes.client.common.KubernetesListObject;
|
||||||
|
import io.kubernetes.client.openapi.Configuration;
|
||||||
|
import io.kubernetes.client.openapi.models.V1ListMeta;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a list of Kubernetes objects each of which is
|
||||||
|
* represented using a JSON data structure.
|
||||||
|
* Some information that is common to all Kubernetes objects,
|
||||||
|
* notably the metadata, is made available through the methods
|
||||||
|
* defined by {@link KubernetesListObject}.
|
||||||
|
*/
|
||||||
|
public class K8sDynamicModels implements KubernetesListObject {
|
||||||
|
|
||||||
|
private final JsonObject data;
|
||||||
|
private final V1ListMeta metadata;
|
||||||
|
private final List<K8sDynamicModel> items;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the object list using the given JSON data.
|
||||||
|
*
|
||||||
|
* @param delegate the gson instance to use for extracting structured data
|
||||||
|
* @param data the data
|
||||||
|
*/
|
||||||
|
public K8sDynamicModels(Gson delegate, JsonObject data) {
|
||||||
|
this.data = data;
|
||||||
|
metadata = delegate.fromJson(data.get("metadata"), V1ListMeta.class);
|
||||||
|
items = new ArrayList<>();
|
||||||
|
for (JsonElement e : data.get("items").getAsJsonArray()) {
|
||||||
|
items.add(new K8sDynamicModel(delegate, e.getAsJsonObject()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getApiVersion() {
|
||||||
|
return apiVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the API version. (Abbreviated method name for convenience.)
|
||||||
|
*
|
||||||
|
* @return the API version
|
||||||
|
*/
|
||||||
|
public String apiVersion() {
|
||||||
|
return data.get("apiVersion").getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKind() {
|
||||||
|
return kind();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the kind. (Abbreviated method name for convenience.)
|
||||||
|
*
|
||||||
|
* @return the kind
|
||||||
|
*/
|
||||||
|
public String kind() {
|
||||||
|
return data.get("kind").getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V1ListMeta getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the metadata. (Abbreviated method name for convenience.)
|
||||||
|
*
|
||||||
|
* @return the metadata
|
||||||
|
*/
|
||||||
|
public V1ListMeta metadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the JSON representation of this object.
|
||||||
|
*
|
||||||
|
* @return the JOSN representation
|
||||||
|
*/
|
||||||
|
public JsonObject data() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<K8sDynamicModel> getItems() {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the api version.
|
||||||
|
*
|
||||||
|
* @param apiVersion the new api version
|
||||||
|
*/
|
||||||
|
public void setApiVersion(String apiVersion) {
|
||||||
|
data.addProperty("apiVersion", apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the kind.
|
||||||
|
*
|
||||||
|
* @param kind the new kind
|
||||||
|
*/
|
||||||
|
public void setKind(String kind) {
|
||||||
|
data.addProperty("kind", kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the metadata.
|
||||||
|
*
|
||||||
|
* @param objectMeta the new metadata
|
||||||
|
*/
|
||||||
|
public void setMetadata(V1ListMeta objectMeta) {
|
||||||
|
data.add("metadata",
|
||||||
|
Configuration.getDefaultApiClient().getJSON().getGson()
|
||||||
|
.toJsonTree(objectMeta));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
K8sDynamicModels other = (K8sDynamicModels) obj;
|
||||||
|
return Objects.equals(data, other.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 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
|
||||||
|
* 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.common;
|
||||||
|
|
||||||
|
import io.kubernetes.client.Discovery.APIResource;
|
||||||
|
import io.kubernetes.client.apimachinery.GroupVersionKind;
|
||||||
|
import io.kubernetes.client.openapi.ApiException;
|
||||||
|
import java.io.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A stub for namespaced custom objects. It uses a dynamic model
|
||||||
|
* (see {@link K8sDynamicModel}) for representing the object's
|
||||||
|
* state and can therefore be used for any kind of object, especially
|
||||||
|
* custom objects.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
||||||
|
public class K8sDynamicStub
|
||||||
|
extends K8sGenericStub<K8sDynamicModel, K8sDynamicModels> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new dynamic stub.
|
||||||
|
*
|
||||||
|
* @param objectClass the object class
|
||||||
|
* @param objectListClass the object list class
|
||||||
|
* @param client the client
|
||||||
|
* @param context the context
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
*/
|
||||||
|
public K8sDynamicStub(Class<K8sDynamicModel> objectClass,
|
||||||
|
Class<K8sDynamicModels> objectListClass, K8sClient client,
|
||||||
|
APIResource context, String namespace, String name) {
|
||||||
|
super(objectClass, objectListClass, client, context, namespace, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a dynamic object stub. If the version in parameter
|
||||||
|
* `gvk` is an empty string, the stub refers to the first object with
|
||||||
|
* matching group and kind.
|
||||||
|
*
|
||||||
|
* @param client the client
|
||||||
|
* @param gvk the group, version and kind
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
* @return the stub if the object exists
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
|
||||||
|
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
|
||||||
|
public static K8sDynamicStub get(K8sClient client,
|
||||||
|
GroupVersionKind gvk, String namespace, String name)
|
||||||
|
throws ApiException {
|
||||||
|
return K8sGenericStub.get(K8sDynamicModel.class, K8sDynamicModels.class,
|
||||||
|
client, gvk, namespace, name, K8sDynamicStub::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a dynamic object stub.
|
||||||
|
*
|
||||||
|
* @param client the client
|
||||||
|
* @param context the context
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
* @return the stub if the object exists
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
|
||||||
|
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
|
||||||
|
public static K8sDynamicStub get(K8sClient client,
|
||||||
|
APIResource context, String namespace, String name)
|
||||||
|
throws ApiException {
|
||||||
|
return K8sGenericStub.get(K8sDynamicModel.class, K8sDynamicModels.class,
|
||||||
|
client, context, namespace, name, K8sDynamicStub::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a stub from yaml.
|
||||||
|
*
|
||||||
|
* @param client the client
|
||||||
|
* @param context the context
|
||||||
|
* @param yaml the yaml
|
||||||
|
* @return the k 8 s dynamic stub
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
public static K8sDynamicStub createFromYaml(K8sClient client,
|
||||||
|
APIResource context, Reader yaml) throws ApiException {
|
||||||
|
var model = new K8sDynamicModel(client.getJSON().getGson(),
|
||||||
|
K8s.yamlToJson(client, yaml));
|
||||||
|
return K8sGenericStub.create(K8sDynamicModel.class,
|
||||||
|
K8sDynamicModels.class, client, context, model,
|
||||||
|
K8sDynamicStub::new);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,418 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 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
|
||||||
|
* 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.common;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import io.kubernetes.client.Discovery.APIResource;
|
||||||
|
import io.kubernetes.client.apimachinery.GroupVersionKind;
|
||||||
|
import io.kubernetes.client.common.KubernetesListObject;
|
||||||
|
import io.kubernetes.client.common.KubernetesObject;
|
||||||
|
import io.kubernetes.client.custom.V1Patch;
|
||||||
|
import io.kubernetes.client.openapi.ApiClient;
|
||||||
|
import io.kubernetes.client.openapi.ApiException;
|
||||||
|
import io.kubernetes.client.util.Strings;
|
||||||
|
import io.kubernetes.client.util.generic.GenericKubernetesApi;
|
||||||
|
import io.kubernetes.client.util.generic.options.ListOptions;
|
||||||
|
import io.kubernetes.client.util.generic.options.PatchOptions;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A stub for namespaced custom objects. This stub provides the
|
||||||
|
* functions common to all Kubernetes objects, but uses variables
|
||||||
|
* for all types. This class should be used as base class only.
|
||||||
|
*
|
||||||
|
* @param <O> the generic type
|
||||||
|
* @param <L> the generic type
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
||||||
|
public class K8sGenericStub<O extends KubernetesObject,
|
||||||
|
L extends KubernetesListObject> {
|
||||||
|
protected final K8sClient client;
|
||||||
|
private final GenericKubernetesApi<O, L> api;
|
||||||
|
protected final String group;
|
||||||
|
protected final String version;
|
||||||
|
protected final String kind;
|
||||||
|
protected final String plural;
|
||||||
|
protected final String namespace;
|
||||||
|
protected final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a namespaced object stub. If the version in parameter
|
||||||
|
* `gvk` is an empty string, the stub refers to the first object
|
||||||
|
* found with matching group and kind.
|
||||||
|
*
|
||||||
|
* @param <O> the object type
|
||||||
|
* @param <L> the object list type
|
||||||
|
* @param <R> the stub type
|
||||||
|
* @param objectClass the object class
|
||||||
|
* @param objectListClass the object list class
|
||||||
|
* @param client the client
|
||||||
|
* @param gvk the group, version and kind
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
* @param provider the provider
|
||||||
|
* @return the stub if the object exists
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
|
||||||
|
"PMD.AvoidInstantiatingObjectsInLoops" })
|
||||||
|
public static <O extends KubernetesObject, L extends KubernetesListObject,
|
||||||
|
R extends K8sGenericStub<O, L>>
|
||||||
|
R get(Class<O> objectClass, Class<L> objectListClass,
|
||||||
|
K8sClient client, GroupVersionKind gvk, String namespace,
|
||||||
|
String name, GenericSupplier<O, L, R> provider)
|
||||||
|
throws ApiException {
|
||||||
|
var context = K8s.context(client, gvk.getGroup(), gvk.getVersion(),
|
||||||
|
gvk.getKind());
|
||||||
|
if (context.isEmpty()) {
|
||||||
|
throw new ApiException("No known API for " + gvk.getGroup()
|
||||||
|
+ "/" + gvk.getVersion() + " " + gvk.getKind());
|
||||||
|
}
|
||||||
|
return provider.get(objectClass, objectListClass, client, context.get(),
|
||||||
|
namespace, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a namespaced object stub.
|
||||||
|
*
|
||||||
|
* @param <O> the object type
|
||||||
|
* @param <L> the object list type
|
||||||
|
* @param <R> the stub type
|
||||||
|
* @param objectClass the object class
|
||||||
|
* @param objectListClass the object list class
|
||||||
|
* @param client the client
|
||||||
|
* @param context the context
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
* @param provider the provider
|
||||||
|
* @return the stub if the object exists
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
|
||||||
|
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
|
||||||
|
public static <O extends KubernetesObject, L extends KubernetesListObject,
|
||||||
|
R extends K8sGenericStub<O, L>>
|
||||||
|
R get(Class<O> objectClass, Class<L> objectListClass,
|
||||||
|
K8sClient client, APIResource context, String namespace,
|
||||||
|
String name, GenericSupplier<O, L, R> provider)
|
||||||
|
throws ApiException {
|
||||||
|
return provider.get(objectClass, objectListClass, client,
|
||||||
|
context, namespace, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a namespaced object stub for a newly created object.
|
||||||
|
*
|
||||||
|
* @param <O> the object type
|
||||||
|
* @param <L> the object list type
|
||||||
|
* @param <R> the stub type
|
||||||
|
* @param objectClass the object class
|
||||||
|
* @param objectListClass the object list class
|
||||||
|
* @param client the client
|
||||||
|
* @param context the context
|
||||||
|
* @param model the model
|
||||||
|
* @param provider the provider
|
||||||
|
* @return the stub if the object exists
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
|
||||||
|
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
|
||||||
|
public static <O extends KubernetesObject, L extends KubernetesListObject,
|
||||||
|
R extends K8sGenericStub<O, L>>
|
||||||
|
R create(Class<O> objectClass, Class<L> objectListClass,
|
||||||
|
K8sClient client, APIResource context, O model,
|
||||||
|
GenericSupplier<O, L, R> provider) throws ApiException {
|
||||||
|
var api = new GenericKubernetesApi<>(objectClass, objectListClass,
|
||||||
|
context.getGroup(), context.getPreferredVersion(),
|
||||||
|
context.getResourcePlural(), client);
|
||||||
|
api.create(model).throwsApiException();
|
||||||
|
return provider.get(objectClass, objectListClass, client,
|
||||||
|
context, model.getMetadata().getNamespace(),
|
||||||
|
model.getMetadata().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the stubs for the objects in the given namespace that match
|
||||||
|
* the criteria from the given options.
|
||||||
|
*
|
||||||
|
* @param <O> the object type
|
||||||
|
* @param <L> the object list type
|
||||||
|
* @param <R> the stub type
|
||||||
|
* @param objectClass the object class
|
||||||
|
* @param objectListClass the object list class
|
||||||
|
* @param client the client
|
||||||
|
* @param context the context
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param options the options
|
||||||
|
* @param provider the provider
|
||||||
|
* @return the collection
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
public static <O extends KubernetesObject, L extends KubernetesListObject,
|
||||||
|
R extends K8sGenericStub<O, L>>
|
||||||
|
Collection<R> list(Class<O> objectClass, Class<L> objectListClass,
|
||||||
|
K8sClient client, APIResource context, String namespace,
|
||||||
|
ListOptions options, SpecificSupplier<O, L, R> provider)
|
||||||
|
throws ApiException {
|
||||||
|
var api = new GenericKubernetesApi<>(objectClass, objectListClass,
|
||||||
|
context.getGroup(), context.getPreferredVersion(),
|
||||||
|
context.getResourcePlural(), client);
|
||||||
|
var objs = api.list(namespace, options).throwsApiException();
|
||||||
|
var result = new ArrayList<R>();
|
||||||
|
for (var item : objs.getObject().getItems()) {
|
||||||
|
result.add(
|
||||||
|
provider.get(client, namespace, item.getMetadata().getName()));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new namespaced custom object stub.
|
||||||
|
*
|
||||||
|
* @param objectClass the object class
|
||||||
|
* @param objectListClass the object list class
|
||||||
|
* @param client the client
|
||||||
|
* @param context the context
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
*/
|
||||||
|
protected K8sGenericStub(Class<O> objectClass, Class<L> objectListClass,
|
||||||
|
K8sClient client, APIResource context, String namespace,
|
||||||
|
String name) {
|
||||||
|
this.client = client;
|
||||||
|
group = context.getGroup();
|
||||||
|
version = context.getPreferredVersion();
|
||||||
|
kind = context.getKind();
|
||||||
|
plural = context.getResourcePlural();
|
||||||
|
this.namespace = namespace;
|
||||||
|
this.name = name;
|
||||||
|
|
||||||
|
Gson gson = client.getJSON().getGson();
|
||||||
|
if (!checkAdapters(client)) {
|
||||||
|
client.getJSON().setGson(gson.newBuilder()
|
||||||
|
.registerTypeAdapterFactory(
|
||||||
|
new K8sDynamicModelTypeAdapterFactory())
|
||||||
|
.create());
|
||||||
|
}
|
||||||
|
api = new GenericKubernetesApi<>(objectClass,
|
||||||
|
objectListClass, group, version, plural, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkAdapters(ApiClient client) {
|
||||||
|
return K8sDynamicModelTypeAdapterFactory.K8sDynamicModelCreator.class
|
||||||
|
.equals(client.getJSON().getGson().getAdapter(K8sDynamicModel.class)
|
||||||
|
.getClass())
|
||||||
|
&& K8sDynamicModelTypeAdapterFactory.K8sDynamicModelsCreator.class
|
||||||
|
.equals(client.getJSON().getGson()
|
||||||
|
.getAdapter(K8sDynamicModels.class).getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the group.
|
||||||
|
*
|
||||||
|
* @return the group
|
||||||
|
*/
|
||||||
|
public String group() {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the version.
|
||||||
|
*
|
||||||
|
* @return the version
|
||||||
|
*/
|
||||||
|
public String version() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the kind.
|
||||||
|
*
|
||||||
|
* @return the kind
|
||||||
|
*/
|
||||||
|
public String kind() {
|
||||||
|
return kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the plural.
|
||||||
|
*
|
||||||
|
* @return the plural
|
||||||
|
*/
|
||||||
|
public String plural() {
|
||||||
|
return plural;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the namespace.
|
||||||
|
*
|
||||||
|
* @return the namespace
|
||||||
|
*/
|
||||||
|
public String namespace() {
|
||||||
|
return namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the name.
|
||||||
|
*
|
||||||
|
* @return the name
|
||||||
|
*/
|
||||||
|
public String name() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the Kubernetes object.
|
||||||
|
*
|
||||||
|
* @throws ApiException the API exception
|
||||||
|
*/
|
||||||
|
public void delete() throws ApiException {
|
||||||
|
var result = api.delete(namespace, name);
|
||||||
|
if (result.isSuccess()
|
||||||
|
|| result.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result.throwsApiException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves and returns the current state of the object.
|
||||||
|
*
|
||||||
|
* @return the object's state
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
public Optional<O> model() throws ApiException {
|
||||||
|
return K8s.optional(api.get(namespace, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the object's status.
|
||||||
|
*
|
||||||
|
* @param object the current state of the object (passed to `status`)
|
||||||
|
* @param status function that returns the new status
|
||||||
|
* @return the updated model or empty if not successful
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
public Optional<O> updateStatus(O object,
|
||||||
|
Function<O, Object> status) throws ApiException {
|
||||||
|
return K8s.optional(api.updateStatus(object, status));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the status.
|
||||||
|
*
|
||||||
|
* @param status the status
|
||||||
|
* @return the kubernetes api response
|
||||||
|
* the updated model or empty if not successful
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
public Optional<O> updateStatus(Function<O, Object> status)
|
||||||
|
throws ApiException {
|
||||||
|
return updateStatus(
|
||||||
|
api.get(namespace, name).throwsApiException().getObject(), status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch the object.
|
||||||
|
*
|
||||||
|
* @param patchType the patch type
|
||||||
|
* @param patch the patch
|
||||||
|
* @param options the options
|
||||||
|
* @return the kubernetes api response
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
public Optional<O> patch(String patchType, V1Patch patch,
|
||||||
|
PatchOptions options) throws ApiException {
|
||||||
|
return K8s
|
||||||
|
.optional(api.patch(namespace, name, patchType, patch, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch the object using default options.
|
||||||
|
*
|
||||||
|
* @param patchType the patch type
|
||||||
|
* @param patch the patch
|
||||||
|
* @return the kubernetes api response
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
public Optional<O>
|
||||||
|
patch(String patchType, V1Patch patch) throws ApiException {
|
||||||
|
PatchOptions opts = new PatchOptions();
|
||||||
|
return patch(patchType, patch, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A supplier for generic stubs.
|
||||||
|
*
|
||||||
|
* @param <O> the object type
|
||||||
|
* @param <L> the object list type
|
||||||
|
* @param <R> the result type
|
||||||
|
*/
|
||||||
|
public interface GenericSupplier<O extends KubernetesObject,
|
||||||
|
L extends KubernetesListObject, R extends K8sGenericStub<O, L>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a new stub.
|
||||||
|
*
|
||||||
|
* @param objectClass the object class
|
||||||
|
* @param objectListClass the object list class
|
||||||
|
* @param client the client
|
||||||
|
* @param context the API resource
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
* @return the result
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.UseObjectForClearerAPI")
|
||||||
|
R get(Class<O> objectClass, Class<L> objectListClass, K8sClient client,
|
||||||
|
APIResource context, String namespace, String name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A supplier for specific stubs.
|
||||||
|
*
|
||||||
|
* @param <O> the object type
|
||||||
|
* @param <L> the object list type
|
||||||
|
* @param <R> the result type
|
||||||
|
*/
|
||||||
|
public interface SpecificSupplier<O extends KubernetesObject,
|
||||||
|
L extends KubernetesListObject, R extends K8sGenericStub<O, L>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a new stub.
|
||||||
|
*
|
||||||
|
* @param client the client
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
* @return the result
|
||||||
|
*/
|
||||||
|
R get(K8sClient client, String namespace, String name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("PMD.UseLocaleWithCaseConversions")
|
||||||
|
public String toString() {
|
||||||
|
return (Strings.isNullOrEmpty(group) ? "" : group + "/")
|
||||||
|
+ version.toUpperCase() + kind + " " + namespace + ":" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 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
|
||||||
|
* 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.common;
|
||||||
|
|
||||||
|
import io.kubernetes.client.Discovery.APIResource;
|
||||||
|
import io.kubernetes.client.openapi.models.V1ConfigMap;
|
||||||
|
import io.kubernetes.client.openapi.models.V1ConfigMapList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A stub for config maps (v1).
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
||||||
|
public class K8sV1ConfigMapStub
|
||||||
|
extends K8sGenericStub<V1ConfigMap, V1ConfigMapList> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new stub.
|
||||||
|
*
|
||||||
|
* @param client the client
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
*/
|
||||||
|
protected K8sV1ConfigMapStub(K8sClient client, String namespace,
|
||||||
|
String name) {
|
||||||
|
super(V1ConfigMap.class, V1ConfigMapList.class, client,
|
||||||
|
new APIResource("", List.of("v1"), "v1", "ConfigMap", true,
|
||||||
|
"configmaps", "configmap"),
|
||||||
|
namespace, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the stub for the given namespace and name.
|
||||||
|
*
|
||||||
|
* @param client the client
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
* @return the config map stub
|
||||||
|
*/
|
||||||
|
public static K8sV1ConfigMapStub get(K8sClient client, String namespace,
|
||||||
|
String name) {
|
||||||
|
return new K8sV1ConfigMapStub(client, namespace, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 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
|
||||||
|
* 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.common;
|
||||||
|
|
||||||
|
import io.kubernetes.client.Discovery.APIResource;
|
||||||
|
import io.kubernetes.client.custom.V1Patch;
|
||||||
|
import io.kubernetes.client.openapi.ApiException;
|
||||||
|
import io.kubernetes.client.openapi.models.V1Deployment;
|
||||||
|
import io.kubernetes.client.openapi.models.V1DeploymentList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A stub for pods (v1).
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
||||||
|
public class K8sV1DeploymentStub
|
||||||
|
extends K8sGenericStub<V1Deployment, V1DeploymentList> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new stub.
|
||||||
|
*
|
||||||
|
* @param client the client
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
*/
|
||||||
|
protected K8sV1DeploymentStub(K8sClient client, String namespace,
|
||||||
|
String name) {
|
||||||
|
super(V1Deployment.class, V1DeploymentList.class, client,
|
||||||
|
new APIResource("apps", List.of("v1"), "v1", "Pod", true,
|
||||||
|
"deployments", "deployment"),
|
||||||
|
namespace, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the stub for the given namespace and name.
|
||||||
|
*
|
||||||
|
* @param client the client
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
* @return the deployment stub
|
||||||
|
*/
|
||||||
|
public static K8sV1DeploymentStub get(K8sClient client, String namespace,
|
||||||
|
String name) {
|
||||||
|
return new K8sV1DeploymentStub(client, namespace, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scales the deployment.
|
||||||
|
*
|
||||||
|
* @param replicas the replicas
|
||||||
|
* @return the new model or empty if not successful
|
||||||
|
* @throws ApiException the API exception
|
||||||
|
*/
|
||||||
|
public Optional<V1Deployment> scale(int replicas) throws ApiException {
|
||||||
|
return patch(V1Patch.PATCH_FORMAT_JSON_PATCH,
|
||||||
|
new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/replicas"
|
||||||
|
+ "\", \"value\": " + replicas + "}]"),
|
||||||
|
client.defaultPatchOptions());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 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
|
||||||
|
* 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.common;
|
||||||
|
|
||||||
|
import io.kubernetes.client.Discovery.APIResource;
|
||||||
|
import io.kubernetes.client.openapi.ApiException;
|
||||||
|
import io.kubernetes.client.openapi.models.V1Pod;
|
||||||
|
import io.kubernetes.client.openapi.models.V1PodList;
|
||||||
|
import io.kubernetes.client.util.generic.options.ListOptions;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A stub for pods (v1).
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
||||||
|
public class K8sV1PodStub extends K8sGenericStub<V1Pod, V1PodList> {
|
||||||
|
|
||||||
|
public static final APIResource CONTEXT
|
||||||
|
= new APIResource("", List.of("v1"), "v1", "Pod", true, "pods", "pod");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new stub.
|
||||||
|
*
|
||||||
|
* @param client the client
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
*/
|
||||||
|
protected K8sV1PodStub(K8sClient client, String namespace, String name) {
|
||||||
|
super(V1Pod.class, V1PodList.class, client, CONTEXT, namespace, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the stub for the given namespace and name.
|
||||||
|
*
|
||||||
|
* @param client the client
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
* @return the kpod stub
|
||||||
|
*/
|
||||||
|
public static K8sV1PodStub get(K8sClient client, String namespace,
|
||||||
|
String name) {
|
||||||
|
return new K8sV1PodStub(client, namespace, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the stubs for the objects in the given namespace that match
|
||||||
|
* the criteria from the given options.
|
||||||
|
*
|
||||||
|
* @param client the client
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param options the options
|
||||||
|
* @return the collection
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
public static Collection<K8sV1PodStub> list(K8sClient client,
|
||||||
|
String namespace, ListOptions options) throws ApiException {
|
||||||
|
return K8sGenericStub.list(V1Pod.class, V1PodList.class, client,
|
||||||
|
CONTEXT, namespace, options, K8sV1PodStub::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 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
|
||||||
|
* 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.common;
|
||||||
|
|
||||||
|
import io.kubernetes.client.Discovery.APIResource;
|
||||||
|
import io.kubernetes.client.openapi.models.V1StatefulSet;
|
||||||
|
import io.kubernetes.client.openapi.models.V1StatefulSetList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A stub for stateful sets (v1).
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
||||||
|
public class K8sV1StatefulSetStub
|
||||||
|
extends K8sGenericStub<V1StatefulSet, V1StatefulSetList> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new stub.
|
||||||
|
*
|
||||||
|
* @param client the client
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
*/
|
||||||
|
protected K8sV1StatefulSetStub(K8sClient client, String namespace,
|
||||||
|
String name) {
|
||||||
|
super(V1StatefulSet.class, V1StatefulSetList.class, client,
|
||||||
|
new APIResource("apps", List.of("v1"), "v1", "StatefulSet", true,
|
||||||
|
"statefulsets", "statefulset"),
|
||||||
|
namespace, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the stub for the given namespace and name.
|
||||||
|
*
|
||||||
|
* @param client the client
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
* @return the stateful set stub
|
||||||
|
*/
|
||||||
|
public static K8sV1StatefulSetStub get(K8sClient client, String namespace,
|
||||||
|
String name) {
|
||||||
|
return new K8sV1StatefulSetStub(client, namespace, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,8 +18,8 @@
|
||||||
|
|
||||||
package org.jdrupes.vmoperator.manager.events;
|
package org.jdrupes.vmoperator.manager.events;
|
||||||
|
|
||||||
import io.kubernetes.client.openapi.ApiClient;
|
import org.jdrupes.vmoperator.common.K8sClient;
|
||||||
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject;
|
import org.jdrupes.vmoperator.common.K8sDynamicModel;
|
||||||
import org.jgrapes.core.Channel;
|
import org.jgrapes.core.Channel;
|
||||||
import org.jgrapes.core.EventPipeline;
|
import org.jgrapes.core.EventPipeline;
|
||||||
import org.jgrapes.core.Subchannel.DefaultSubchannel;
|
import org.jgrapes.core.Subchannel.DefaultSubchannel;
|
||||||
|
|
@ -31,8 +31,8 @@ import org.jgrapes.core.Subchannel.DefaultSubchannel;
|
||||||
public class VmChannel extends DefaultSubchannel {
|
public class VmChannel extends DefaultSubchannel {
|
||||||
|
|
||||||
private final EventPipeline pipeline;
|
private final EventPipeline pipeline;
|
||||||
private final ApiClient client;
|
private final K8sClient client;
|
||||||
private DynamicKubernetesObject vmDefinition;
|
private K8sDynamicModel vmDefinition;
|
||||||
private long generation = -1;
|
private long generation = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -43,7 +43,7 @@ public class VmChannel extends DefaultSubchannel {
|
||||||
* @param client the client
|
* @param client the client
|
||||||
*/
|
*/
|
||||||
public VmChannel(Channel mainChannel, EventPipeline pipeline,
|
public VmChannel(Channel mainChannel, EventPipeline pipeline,
|
||||||
ApiClient client) {
|
K8sClient client) {
|
||||||
super(mainChannel);
|
super(mainChannel);
|
||||||
this.pipeline = pipeline;
|
this.pipeline = pipeline;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
|
@ -56,7 +56,7 @@ public class VmChannel extends DefaultSubchannel {
|
||||||
* @return the watch channel
|
* @return the watch channel
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.LinguisticNaming")
|
@SuppressWarnings("PMD.LinguisticNaming")
|
||||||
public VmChannel setVmDefinition(DynamicKubernetesObject definition) {
|
public VmChannel setVmDefinition(K8sDynamicModel definition) {
|
||||||
this.vmDefinition = definition;
|
this.vmDefinition = definition;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
@ -66,7 +66,7 @@ public class VmChannel extends DefaultSubchannel {
|
||||||
*
|
*
|
||||||
* @return the json object
|
* @return the json object
|
||||||
*/
|
*/
|
||||||
public DynamicKubernetesObject vmDefinition() {
|
public K8sDynamicModel vmDefinition() {
|
||||||
return vmDefinition;
|
return vmDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,7 +109,7 @@ public class VmChannel extends DefaultSubchannel {
|
||||||
*
|
*
|
||||||
* @return the API client
|
* @return the API client
|
||||||
*/
|
*/
|
||||||
public ApiClient client() {
|
public K8sClient client() {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
package org.jdrupes.vmoperator.manager.events;
|
package org.jdrupes.vmoperator.manager.events;
|
||||||
|
|
||||||
import io.kubernetes.client.openapi.models.V1APIResource;
|
import io.kubernetes.client.openapi.models.V1APIResource;
|
||||||
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject;
|
import org.jdrupes.vmoperator.common.K8sDynamicModel;
|
||||||
import org.jgrapes.core.Channel;
|
import org.jgrapes.core.Channel;
|
||||||
import org.jgrapes.core.Components;
|
import org.jgrapes.core.Components;
|
||||||
import org.jgrapes.core.Event;
|
import org.jgrapes.core.Event;
|
||||||
|
|
@ -44,7 +44,7 @@ public class VmDefChanged extends Event<Void> {
|
||||||
private final Type type;
|
private final Type type;
|
||||||
private final boolean specChanged;
|
private final boolean specChanged;
|
||||||
private final V1APIResource crd;
|
private final V1APIResource crd;
|
||||||
private final DynamicKubernetesObject vmDef;
|
private final K8sDynamicModel vmDef;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new VM changed event.
|
* Instantiates a new VM changed event.
|
||||||
|
|
@ -55,7 +55,7 @@ public class VmDefChanged extends Event<Void> {
|
||||||
* @param vmDefinition the VM definition
|
* @param vmDefinition the VM definition
|
||||||
*/
|
*/
|
||||||
public VmDefChanged(Type type, boolean specChanged, V1APIResource crd,
|
public VmDefChanged(Type type, boolean specChanged, V1APIResource crd,
|
||||||
DynamicKubernetesObject vmDefinition) {
|
K8sDynamicModel vmDefinition) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.specChanged = specChanged;
|
this.specChanged = specChanged;
|
||||||
this.crd = crd;
|
this.crd = crd;
|
||||||
|
|
@ -92,7 +92,7 @@ public class VmDefChanged extends Event<Void> {
|
||||||
*
|
*
|
||||||
* @return the object.
|
* @return the object.
|
||||||
*/
|
*/
|
||||||
public DynamicKubernetesObject vmDefinition() {
|
public K8sDynamicModel vmDefinition() {
|
||||||
return vmDef;
|
return vmDef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
add_header=true
|
add_header=true
|
||||||
eclipse.preferences.version=1
|
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
|
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
|
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_package=false
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,6 @@ dependencies {
|
||||||
runtimeOnly 'org.apache.logging.log4j:log4j-to-jul:2.20.0'
|
runtimeOnly 'org.apache.logging.log4j:log4j-to-jul:2.20.0'
|
||||||
|
|
||||||
runtimeOnly project(':org.jdrupes.vmoperator.vmconlet')
|
runtimeOnly project(':org.jdrupes.vmoperator.vmconlet')
|
||||||
|
|
||||||
testImplementation 'io.fabric8:kubernetes-client:[6.8.1,6.9)'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
|
|
|
||||||
|
|
@ -18,20 +18,21 @@
|
||||||
|
|
||||||
package org.jdrupes.vmoperator.manager;
|
package org.jdrupes.vmoperator.manager;
|
||||||
|
|
||||||
|
import io.kubernetes.client.apimachinery.GroupVersionKind;
|
||||||
import io.kubernetes.client.custom.V1Patch;
|
import io.kubernetes.client.custom.V1Patch;
|
||||||
import io.kubernetes.client.openapi.ApiException;
|
import io.kubernetes.client.openapi.ApiException;
|
||||||
import io.kubernetes.client.openapi.Configuration;
|
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.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
|
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
|
||||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM;
|
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.Exit;
|
||||||
import org.jdrupes.vmoperator.manager.events.ModifyVm;
|
import org.jdrupes.vmoperator.manager.events.ModifyVm;
|
||||||
|
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
||||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
||||||
import org.jgrapes.core.Channel;
|
import org.jgrapes.core.Channel;
|
||||||
import org.jgrapes.core.Component;
|
import org.jgrapes.core.Component;
|
||||||
|
|
@ -160,35 +161,30 @@ public class Controller extends Component {
|
||||||
* @throws IOException Signals that an I/O exception has occurred.
|
* @throws IOException Signals that an I/O exception has occurred.
|
||||||
*/
|
*/
|
||||||
@Handler
|
@Handler
|
||||||
public void onModigyVm(ModifyVm event) throws ApiException, IOException {
|
public void onModifyVm(ModifyVm event, VmChannel channel)
|
||||||
patchVmSpec(event.name(), event.path(), event.value());
|
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 {
|
throws ApiException, IOException {
|
||||||
var crApi = K8s.crApi(Config.defaultClient(), VM_OP_GROUP,
|
var vmStub = K8sDynamicStub.get(client,
|
||||||
VM_OP_KIND_VM, namespace, name);
|
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM), namespace,
|
||||||
if (crApi.isEmpty()) {
|
name);
|
||||||
logger.warning(() -> "Trying to patch " + namespace + "/" + name
|
|
||||||
+ " which does not exist.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Patch running
|
// Patch running
|
||||||
PatchOptions patchOpts = new PatchOptions();
|
|
||||||
patchOpts.setFieldManager("kubernetes-java-kubectl-apply");
|
|
||||||
String valueAsText = value instanceof String
|
String valueAsText = value instanceof String
|
||||||
? "\"" + value + "\""
|
? "\"" + value + "\""
|
||||||
: value.toString();
|
: value.toString();
|
||||||
var res = crApi.get().patch(namespace, name,
|
var res = vmStub.patch(V1Patch.PATCH_FORMAT_JSON_PATCH,
|
||||||
V1Patch.PATCH_FORMAT_JSON_PATCH,
|
|
||||||
new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/vm/"
|
new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/vm/"
|
||||||
+ path + "\", \"value\": " + valueAsText + "}]"),
|
+ path + "\", \"value\": " + valueAsText + "}]"),
|
||||||
patchOpts);
|
client.defaultPatchOptions());
|
||||||
if (!res.isSuccess()) {
|
if (!res.isPresent()) {
|
||||||
logger.warning(
|
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 io.kubernetes.client.util.generic.dynamic.Dynamics;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import org.jdrupes.vmoperator.common.K8s;
|
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.VmChannel;
|
||||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
||||||
import org.jdrupes.vmoperator.util.GsonPtr;
|
import org.jdrupes.vmoperator.util.GsonPtr;
|
||||||
|
|
@ -79,19 +79,25 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||||
Map<String, Object> model, VmChannel channel)
|
Map<String, Object> model, VmChannel channel)
|
||||||
throws IOException, TemplateException, ApiException {
|
throws IOException, TemplateException, ApiException {
|
||||||
// Check if to be generated
|
// Check if to be generated
|
||||||
@SuppressWarnings({ "unchecked", "PMD.AvoidDuplicateLiterals" })
|
@SuppressWarnings({ "PMD.AvoidDuplicateLiterals", "unchecked" })
|
||||||
var lbs = Optional.of(model)
|
var lbsDef = Optional.of(model)
|
||||||
.map(m -> (Map<String, Object>) m.get("reconciler"))
|
.map(m -> (Map<String, Object>) m.get("reconciler"))
|
||||||
.map(c -> c.get(LOAD_BALANCER_SERVICE)).orElse(Boolean.FALSE);
|
.map(c -> c.get(LOAD_BALANCER_SERVICE)).orElse(Boolean.FALSE);
|
||||||
if (lbs instanceof Boolean isOn && !isOn) {
|
if (!(lbsDef instanceof Map) && !(lbsDef instanceof Boolean)) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!(lbs instanceof Map)) {
|
|
||||||
logger.warning(() -> "\"" + LOAD_BALANCER_SERVICE
|
logger.warning(() -> "\"" + LOAD_BALANCER_SERVICE
|
||||||
+ "\" in configuration must be boolean or mapping but is "
|
+ "\" in configuration must be boolean or mapping but is "
|
||||||
+ lbs.getClass() + ".");
|
+ lbsDef.getClass() + ".");
|
||||||
return;
|
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
|
// Combine template and data and parse result
|
||||||
var fmTemplate = fmConfig.getTemplate("runnerLoadBalancer.ftl.yaml");
|
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
|
// https://github.com/kubernetes-client/java/issues/2741
|
||||||
var svcDef = Dynamics.newFromYaml(
|
var svcDef = Dynamics.newFromYaml(
|
||||||
new Yaml(new SafeConstructor(new LoaderOptions())), out.toString());
|
new Yaml(new SafeConstructor(new LoaderOptions())), out.toString());
|
||||||
mergeMetadata(svcDef, lbs, channel);
|
mergeMetadata(svcDef, cfgMeta, event.vmDefinition());
|
||||||
|
|
||||||
// Apply
|
// Apply
|
||||||
DynamicKubernetesApi svcApi = new DynamicKubernetesApi("", "v1",
|
DynamicKubernetesApi svcApi = new DynamicKubernetesApi("", "v1",
|
||||||
|
|
@ -109,20 +115,10 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||||
K8s.apply(svcApi, svcDef, svcDef.getRaw().toString());
|
K8s.apply(svcApi, svcDef, svcDef.getRaw().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private void mergeMetadata(DynamicKubernetesObject svcDef,
|
private void mergeMetadata(DynamicKubernetesObject svcDef,
|
||||||
Object lbsConfig, VmChannel channel) {
|
JsonObject cfgMeta, K8sDynamicModel vmDefinition) {
|
||||||
// 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);
|
|
||||||
|
|
||||||
// Get metadata from VM definition
|
// 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)
|
.get(JsonObject.class, LOAD_BALANCER_SERVICE)
|
||||||
.map(JsonObject::deepCopy).orElseGet(() -> new JsonObject());
|
.map(JsonObject::deepCopy).orElseGet(() -> new JsonObject());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.jdrupes.vmoperator.common.Convertions;
|
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.VmChannel;
|
||||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
||||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged.Type;
|
import org.jdrupes.vmoperator.manager.events.VmDefChanged.Type;
|
||||||
|
|
@ -206,8 +207,8 @@ public class Reconciler extends Component {
|
||||||
lbReconciler.reconcile(event, model, channel);
|
lbReconciler.reconcile(event, model, channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DynamicKubernetesObject patchCr(DynamicKubernetesObject vmDef) {
|
private DynamicKubernetesObject patchCr(K8sDynamicModel vmDef) {
|
||||||
var json = vmDef.getRaw().deepCopy();
|
var json = vmDef.data().deepCopy();
|
||||||
// Adjust cdromImage path
|
// Adjust cdromImage path
|
||||||
adjustCdRomPaths(json);
|
adjustCdRomPaths(json);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,13 @@ import freemarker.template.Configuration;
|
||||||
import freemarker.template.TemplateException;
|
import freemarker.template.TemplateException;
|
||||||
import io.kubernetes.client.custom.V1Patch;
|
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.Dynamics;
|
import io.kubernetes.client.util.generic.dynamic.Dynamics;
|
||||||
import io.kubernetes.client.util.generic.options.PatchOptions;
|
import io.kubernetes.client.util.generic.options.PatchOptions;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Logger;
|
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.VmChannel;
|
||||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
||||||
import org.jdrupes.vmoperator.util.GsonPtr;
|
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,
|
public void reconcile(VmDefChanged event, Map<String, Object> model,
|
||||||
VmChannel channel)
|
VmChannel channel)
|
||||||
throws IOException, TemplateException, ApiException {
|
throws IOException, TemplateException, ApiException {
|
||||||
DynamicKubernetesApi stsApi = new DynamicKubernetesApi("apps", "v1",
|
|
||||||
"statefulsets", channel.client());
|
|
||||||
var metadata = event.vmDefinition().getMetadata();
|
var metadata = event.vmDefinition().getMetadata();
|
||||||
|
|
||||||
// Combine template and data and parse result
|
// 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
|
// If exists apply changes only when transitioning state
|
||||||
// or not running.
|
// or not running.
|
||||||
var existing = K8s.get(stsApi, metadata);
|
var stsStub = K8sV1StatefulSetStub.get(channel.client(),
|
||||||
if (existing.isPresent()) {
|
metadata.getNamespace(), metadata.getName());
|
||||||
var current = GsonPtr.to(existing.get().getRaw())
|
stsStub.model().ifPresent(sts -> {
|
||||||
.to("spec").getAsInt("replicas").orElse(1);
|
var current = sts.getSpec().getReplicas();
|
||||||
var desired = GsonPtr.to(stsDef.getRaw())
|
var desired = GsonPtr.to(stsDef.getRaw())
|
||||||
.to("spec").getAsInt("replicas").orElse(1);
|
.to("spec").getAsInt("replicas").orElse(1);
|
||||||
if (current == 1 && desired == 1) {
|
if (current == 1 && desired == 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
// Do apply changes
|
// Do apply changes
|
||||||
PatchOptions opts = new PatchOptions();
|
PatchOptions opts = new PatchOptions();
|
||||||
opts.setForce(true);
|
opts.setForce(true);
|
||||||
opts.setFieldManager("kubernetes-java-kubectl-apply");
|
opts.setFieldManager("kubernetes-java-kubectl-apply");
|
||||||
stsApi.patch(stsDef.getMetadata().getNamespace(),
|
if (stsStub.patch(V1Patch.PATCH_FORMAT_APPLY_YAML,
|
||||||
stsDef.getMetadata().getName(), V1Patch.PATCH_FORMAT_APPLY_YAML,
|
new V1Patch(channel.client().getJSON().serialize(stsDef)), opts)
|
||||||
new V1Patch(channel.client().getJSON().serialize(stsDef)),
|
.isEmpty()) {
|
||||||
opts).throwsApiException();
|
logger.warning(
|
||||||
|
() -> "Could not patch stateful set for " + stsStub.name());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* VM-Operator
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* 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.JsonArray;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.reflect.TypeToken;
|
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.ApiClient;
|
||||||
import io.kubernetes.client.openapi.ApiException;
|
import io.kubernetes.client.openapi.ApiException;
|
||||||
import io.kubernetes.client.openapi.apis.ApisApi;
|
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.Config;
|
||||||
import io.kubernetes.client.util.Watch;
|
import io.kubernetes.client.util.Watch;
|
||||||
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.options.ListOptions;
|
import io.kubernetes.client.util.generic.options.ListOptions;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
|
@ -48,7 +49,10 @@ import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
|
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.APP_NAME;
|
||||||
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_KIND_VM;
|
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_KIND_VM;
|
||||||
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
|
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.
|
* Watches for changes of VM definitions.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports" })
|
||||||
public class VmWatcher extends Component {
|
public class VmWatcher extends Component {
|
||||||
|
|
||||||
private String namespaceToWatch;
|
private String namespaceToWatch;
|
||||||
|
|
@ -269,13 +273,13 @@ public class VmWatcher extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleVmDefinitionChange(V1APIResource vmsCrd,
|
private void handleVmDefinitionChange(V1APIResource vmsCrd,
|
||||||
Watch.Response<V1Namespace> vmDefStub) {
|
Watch.Response<V1Namespace> vmDefRef) throws ApiException {
|
||||||
V1ObjectMeta metadata = vmDefStub.object.getMetadata();
|
V1ObjectMeta metadata = vmDefRef.object.getMetadata();
|
||||||
VmChannel channel = channels.computeIfAbsent(metadata.getName(),
|
VmChannel channel = channels.computeIfAbsent(metadata.getName(),
|
||||||
k -> {
|
k -> {
|
||||||
try {
|
try {
|
||||||
return new VmChannel(channel(), newEventPipeline(),
|
return new VmChannel(channel(), newEventPipeline(),
|
||||||
Config.defaultClient());
|
new K8sClient());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.log(Level.SEVERE, e, () -> "Failed to create client"
|
logger.log(Level.SEVERE, e, () -> "Failed to create client"
|
||||||
+ " for handling changes: " + e.getMessage());
|
+ " for handling changes: " + e.getMessage());
|
||||||
|
|
@ -287,30 +291,27 @@ public class VmWatcher extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get full definition and associate with channel as backup
|
// Get full definition and associate with channel as backup
|
||||||
var apiVersion = K8s.version(vmDefStub.object.getApiVersion());
|
@SuppressWarnings("PMD.ShortVariable")
|
||||||
DynamicKubernetesApi vmCrApi = new DynamicKubernetesApi(VM_OP_GROUP,
|
var gv = GroupVersion.parse(vmDefRef.object.getApiVersion());
|
||||||
apiVersion, vmsCrd.getName(), channel.client());
|
var vmStub = K8sDynamicStub.get(channel.client(),
|
||||||
var curVmDef = K8s.get(vmCrApi, metadata);
|
new GroupVersionKind(gv.getGroup(), gv.getVersion(), VM_OP_KIND_VM),
|
||||||
curVmDef.ifPresent(def -> {
|
metadata.getNamespace(), metadata.getName());
|
||||||
// Augment with "dynamic" data and associate with channel
|
vmStub.model().ifPresent(vmDef -> {
|
||||||
addDynamicData(channel.client(), def);
|
addDynamicData(channel.client(), vmDef);
|
||||||
channel.setVmDefinition(def);
|
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,
|
private void addDynamicData(K8sClient client, K8sDynamicModel vmState) {
|
||||||
DynamicKubernetesObject vmDef) {
|
var rootNode = GsonPtr.to(vmState.data()).get(JsonObject.class);
|
||||||
var rootNode = GsonPtr.to(vmDef.getRaw()).get(JsonObject.class);
|
|
||||||
rootNode.addProperty("nodeName", "");
|
rootNode.addProperty("nodeName", "");
|
||||||
|
|
||||||
// VM definition status changes before the pod terminates.
|
// VM definition status changes before the pod terminates.
|
||||||
|
|
@ -329,11 +330,18 @@ public class VmWatcher extends Component {
|
||||||
var podSearch = new ListOptions();
|
var podSearch = new ListOptions();
|
||||||
podSearch.setLabelSelector("app.kubernetes.io/name=" + APP_NAME
|
podSearch.setLabelSelector("app.kubernetes.io/name=" + APP_NAME
|
||||||
+ ",app.kubernetes.io/component=" + APP_NAME
|
+ ",app.kubernetes.io/component=" + APP_NAME
|
||||||
+ ",app.kubernetes.io/instance=" + vmDef.getMetadata().getName());
|
+ ",app.kubernetes.io/instance=" + vmState.getMetadata().getName());
|
||||||
var podList = K8s.podApi(client).list(namespaceToWatch, podSearch);
|
try {
|
||||||
podList.getObject().getItems().stream().forEach(pod -> {
|
var podList
|
||||||
rootNode.addProperty("nodeName", pod.getSpec().getNodeName());
|
= 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;
|
package org.jdrupes.vmoperator.manager;
|
||||||
|
|
||||||
import io.fabric8.kubernetes.client.Config;
|
import io.kubernetes.client.Discovery.APIResource;
|
||||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
import io.kubernetes.client.openapi.ApiException;
|
||||||
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
|
import java.io.FileReader;
|
||||||
import io.fabric8.kubernetes.client.dsl.base.ResourceDefinitionContext;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Map;
|
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 org.junit.jupiter.api.AfterAll;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
|
@ -18,8 +23,9 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||||
|
|
||||||
class BasicTests {
|
class BasicTests {
|
||||||
|
|
||||||
private static KubernetesClient client;
|
private static K8sClient client;
|
||||||
private static ResourceDefinitionContext vmsContext;
|
private static APIResource vmsContext;
|
||||||
|
private static K8sV1DeploymentStub mgrDeployment;
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
static void setUpBeforeClass() throws Exception {
|
static void setUpBeforeClass() throws Exception {
|
||||||
|
|
@ -27,29 +33,27 @@ class BasicTests {
|
||||||
assertNotNull(testCluster);
|
assertNotNull(testCluster);
|
||||||
|
|
||||||
// Get client
|
// Get client
|
||||||
client = new KubernetesClientBuilder()
|
client = new K8sClient();
|
||||||
.withConfig(Config.autoConfigure(testCluster)).build();
|
|
||||||
|
|
||||||
// Context for working with our CR
|
// Context for working with our CR
|
||||||
vmsContext = new ResourceDefinitionContext.Builder()
|
var apiRes = K8s.context(client, VM_OP_GROUP, null, VM_OP_KIND_VM);
|
||||||
.withGroup("vmoperator.jdrupes.org").withKind("VirtualMachine")
|
assertTrue(apiRes.isPresent());
|
||||||
.withPlural("vms").withNamespaced(true).withVersion("v1").build();
|
vmsContext = apiRes.get();
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup existing VM
|
||||||
var resourcesInNamespace = client.genericKubernetesResources(vmsContext)
|
K8sDynamicStub.get(client, vmsContext, "vmop-dev", "unittest-vm")
|
||||||
.inNamespace("vmop-dev");
|
.delete();
|
||||||
resourcesInNamespace.withName("unittest-vm").delete();
|
|
||||||
|
|
||||||
// Update manager pod by scaling deployment
|
// Update manager pod by scaling deployment
|
||||||
client.apps().deployments().inNamespace("vmop-dev")
|
mgrDeployment
|
||||||
.withName("vm-operator").scale(0);
|
= K8sV1DeploymentStub.get(client, "vmop-dev", "vm-operator");
|
||||||
client.apps().deployments().inNamespace("vmop-dev")
|
mgrDeployment.scale(0);
|
||||||
.withName("vm-operator").scale(1);
|
mgrDeployment.scale(1);
|
||||||
|
|
||||||
// Wait until available
|
// Wait until available
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
if (client.apps().deployments().inNamespace("vmop-dev")
|
if (mgrDeployment.model().get().getStatus().getConditions()
|
||||||
.withName("vm-operator").get().getStatus().getConditions()
|
|
||||||
.stream().filter(c -> "Available".equals(c.getType())).findAny()
|
.stream().filter(c -> "Available".equals(c.getType())).findAny()
|
||||||
.isPresent()) {
|
.isPresent()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -62,44 +66,40 @@ class BasicTests {
|
||||||
@AfterAll
|
@AfterAll
|
||||||
static void tearDownAfterClass() throws Exception {
|
static void tearDownAfterClass() throws Exception {
|
||||||
// Bring down manager
|
// Bring down manager
|
||||||
client.apps().deployments().inNamespace("vmop-dev")
|
mgrDeployment.scale(0);
|
||||||
.withName("vm-operator").scale(0);
|
|
||||||
client.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void test() throws IOException, InterruptedException {
|
void test() throws IOException, InterruptedException, ApiException {
|
||||||
// Load from Yaml
|
// Load from Yaml
|
||||||
var vm = client.genericKubernetesResources(vmsContext)
|
var rdr = new FileReader("test-resources/unittest-vm.yaml");
|
||||||
.load(Files
|
var vmStub = K8sDynamicStub.createFromYaml(client, vmsContext, rdr);
|
||||||
.newInputStream(Path.of("test-resources/unittest-vm.yaml")));
|
assertTrue(vmStub.model().isPresent());
|
||||||
// Create Custom Resource
|
|
||||||
vm.create();
|
|
||||||
|
|
||||||
// Wait for created resources
|
// Wait for created resources
|
||||||
assertTrue(waitForConfigMap());
|
assertTrue(waitForConfigMap(client));
|
||||||
assertTrue(waitForStatefulSet());
|
assertTrue(waitForStatefulSet(client));
|
||||||
|
|
||||||
// Check config map
|
// Check config map
|
||||||
var config = client.configMaps().inNamespace("vmop-dev")
|
var config = K8sV1ConfigMapStub.get(client, "vmop-dev", "unittest-vm")
|
||||||
.withName("unittest-vm").get();
|
.model().get();
|
||||||
var yaml = new Yaml(new SafeConstructor(new LoaderOptions()))
|
var yaml = new Yaml(new SafeConstructor(new LoaderOptions()))
|
||||||
.load((String) config.getData().get("config.yaml"));
|
.load(config.getData().get("config.yaml"));
|
||||||
@SuppressWarnings("unchecked")
|
@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");
|
.get("/Runner").get("vm").get("maximumRam");
|
||||||
assertEquals("4 GiB", currentRam);
|
assertEquals("4 GiB", maximumRam);
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
var resourcesInNamespace = client.genericKubernetesResources(vmsContext)
|
K8sDynamicStub.get(client, vmsContext, "vmop-dev", "unittest-vm")
|
||||||
.inNamespace("vmop-dev");
|
.delete();
|
||||||
resourcesInNamespace.withName("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++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
if (client.configMaps().inNamespace("vmop-dev")
|
if (stub.model().isPresent()) {
|
||||||
.withName("unittest-vm").get() != null) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
|
|
@ -107,10 +107,11 @@ class BasicTests {
|
||||||
return false;
|
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++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
if (client.apps().statefulSets().inNamespace("vmop-dev")
|
if (stub.model().isPresent()) {
|
||||||
.withName("unittest-vm").get() != null) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ dependencies {
|
||||||
implementation project(':org.jdrupes.vmoperator.common')
|
implementation project(':org.jdrupes.vmoperator.common')
|
||||||
|
|
||||||
implementation 'commons-cli:commons-cli:1.5.0'
|
implementation 'commons-cli:commons-cli:1.5.0'
|
||||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:[2.15.1,3]'
|
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:[2.16.1]'
|
||||||
|
|
||||||
runtimeOnly 'org.slf4j:slf4j-jdk14:[2.0.7,3)'
|
runtimeOnly 'org.slf4j:slf4j-jdk14:[2.0.7,3)'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* VM-Operator
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -19,27 +19,17 @@
|
||||||
package org.jdrupes.vmoperator.runner.qemu;
|
package org.jdrupes.vmoperator.runner.qemu;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kubernetes.client.apimachinery.GroupVersionKind;
|
||||||
import io.kubernetes.client.custom.Quantity;
|
import io.kubernetes.client.custom.Quantity;
|
||||||
import io.kubernetes.client.custom.Quantity.Format;
|
import io.kubernetes.client.custom.Quantity.Format;
|
||||||
import io.kubernetes.client.custom.V1Patch;
|
import io.kubernetes.client.custom.V1Patch;
|
||||||
import io.kubernetes.client.openapi.ApiException;
|
import io.kubernetes.client.openapi.ApiException;
|
||||||
import io.kubernetes.client.openapi.apis.ApisApi;
|
|
||||||
import io.kubernetes.client.openapi.apis.CustomObjectsApi;
|
|
||||||
import io.kubernetes.client.openapi.apis.EventsV1Api;
|
|
||||||
import io.kubernetes.client.openapi.models.EventsV1Event;
|
import io.kubernetes.client.openapi.models.EventsV1Event;
|
||||||
import io.kubernetes.client.openapi.models.V1APIGroup;
|
|
||||||
import io.kubernetes.client.openapi.models.V1GroupVersionForDiscovery;
|
|
||||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
|
||||||
import io.kubernetes.client.util.Config;
|
|
||||||
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
|
|
||||||
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject;
|
|
||||||
import io.kubernetes.client.util.generic.options.PatchOptions;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
@ -48,6 +38,9 @@ import static org.jdrupes.vmoperator.common.Constants.APP_NAME;
|
||||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
|
import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP;
|
||||||
import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM;
|
import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM;
|
||||||
import org.jdrupes.vmoperator.common.K8s;
|
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.runner.qemu.events.BalloonChangeEvent;
|
import org.jdrupes.vmoperator.runner.qemu.events.BalloonChangeEvent;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.Exit;
|
import org.jdrupes.vmoperator.runner.qemu.events.Exit;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus;
|
import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus;
|
||||||
|
|
@ -75,11 +68,11 @@ public class StatusUpdater extends Component {
|
||||||
|
|
||||||
private String namespace;
|
private String namespace;
|
||||||
private String vmName;
|
private String vmName;
|
||||||
private DynamicKubernetesApi vmCrApi;
|
private K8sClient apiClient;
|
||||||
private EventsV1Api evtsApi;
|
|
||||||
private long observedGeneration;
|
private long observedGeneration;
|
||||||
private boolean guestShutdownStops;
|
private boolean guestShutdownStops;
|
||||||
private boolean shutdownByGuest;
|
private boolean shutdownByGuest;
|
||||||
|
private K8sDynamicStub vmStub;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new status updater.
|
* Instantiates a new status updater.
|
||||||
|
|
@ -88,6 +81,16 @@ public class StatusUpdater extends Component {
|
||||||
*/
|
*/
|
||||||
public StatusUpdater(Channel componentChannel) {
|
public StatusUpdater(Channel componentChannel) {
|
||||||
super(componentChannel);
|
super(componentChannel);
|
||||||
|
try {
|
||||||
|
apiClient = new K8sClient();
|
||||||
|
io.kubernetes.client.openapi.Configuration
|
||||||
|
.setDefaultApiClient(apiClient);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.log(Level.SEVERE, e,
|
||||||
|
() -> "Cannot access events API, terminating.");
|
||||||
|
fire(new Exit(1));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -154,59 +157,18 @@ public class StatusUpdater extends Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
initVmCrApi(event);
|
vmStub = K8sDynamicStub.get(apiClient,
|
||||||
} catch (IOException | ApiException e) {
|
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM),
|
||||||
|
namespace, vmName);
|
||||||
|
vmStub.model().ifPresent(model -> {
|
||||||
|
observedGeneration = model.getMetadata().getGeneration();
|
||||||
|
});
|
||||||
|
} catch (ApiException e) {
|
||||||
logger.log(Level.SEVERE, e,
|
logger.log(Level.SEVERE, e,
|
||||||
() -> "Cannot access VM's CR, terminating.");
|
() -> "Cannot access VM object, terminating.");
|
||||||
event.cancel(true);
|
event.cancel(true);
|
||||||
fire(new Exit(1));
|
fire(new Exit(1));
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
evtsApi = new EventsV1Api(Config.defaultClient());
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.log(Level.SEVERE, e,
|
|
||||||
() -> "Cannot access events API, terminating.");
|
|
||||||
event.cancel(true);
|
|
||||||
fire(new Exit(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initVmCrApi(Start event) throws IOException, ApiException {
|
|
||||||
var client = Config.defaultClient();
|
|
||||||
var apis = new ApisApi(client).getAPIVersions();
|
|
||||||
var crdVersions = apis.getGroups().stream()
|
|
||||||
.filter(g -> g.getName().equals(VM_OP_GROUP)).findFirst()
|
|
||||||
.map(V1APIGroup::getVersions).stream().flatMap(l -> l.stream())
|
|
||||||
.map(V1GroupVersionForDiscovery::getVersion).toList();
|
|
||||||
var coa = new CustomObjectsApi(client);
|
|
||||||
for (var crdVersion : crdVersions) {
|
|
||||||
var crdApiRes = coa.getAPIResources(VM_OP_GROUP,
|
|
||||||
crdVersion).getResources().stream()
|
|
||||||
.filter(r -> VM_OP_KIND_VM.equals(r.getKind())).findFirst();
|
|
||||||
if (crdApiRes.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
|
|
||||||
var crApi = new DynamicKubernetesApi(VM_OP_GROUP,
|
|
||||||
crdVersion, crdApiRes.get().getName(), client);
|
|
||||||
var vmCr = crApi.get(namespace, vmName);
|
|
||||||
if (vmCr.isSuccess()) {
|
|
||||||
vmCrApi = crApi;
|
|
||||||
observedGeneration
|
|
||||||
= vmCr.getObject().getMetadata().getGeneration();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (vmCrApi == null) {
|
|
||||||
logger.severe(() -> "Cannot find VM's CR, terminating.");
|
|
||||||
event.cancel(true);
|
|
||||||
fire(new Exit(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
|
|
||||||
private JsonObject currentStatus(DynamicKubernetesObject vmCr) {
|
|
||||||
return vmCr.getRaw().getAsJsonObject("status").deepCopy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -221,18 +183,19 @@ public class StatusUpdater extends Component {
|
||||||
guestShutdownStops = event.configuration().guestShutdownStops;
|
guestShutdownStops = event.configuration().guestShutdownStops;
|
||||||
|
|
||||||
// Remainder applies only if we have a connection to k8s.
|
// Remainder applies only if we have a connection to k8s.
|
||||||
if (vmCrApi == null) {
|
if (vmStub == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A change of the runner configuration is typically caused
|
// A change of the runner configuration is typically caused
|
||||||
// by a new version of the CR. So we observe the new CR.
|
// by a new version of the CR. So we observe the new CR.
|
||||||
var vmCr = vmCrApi.get(namespace, vmName).throwsApiException()
|
var vmDef = vmStub.model();
|
||||||
.getObject();
|
if (vmDef.isPresent()
|
||||||
if (vmCr.getMetadata().getGeneration() == observedGeneration) {
|
&& vmDef.get().metadata().getGeneration() == observedGeneration) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
vmCrApi.updateStatus(vmCr, from -> {
|
vmStub.updateStatus(vmDef.get(), from -> {
|
||||||
JsonObject status = currentStatus(from);
|
JsonObject status = from.status();
|
||||||
status.getAsJsonArray("conditions").asList().stream()
|
status.getAsJsonArray("conditions").asList().stream()
|
||||||
.map(cond -> (JsonObject) cond).filter(cond -> "Running"
|
.map(cond -> (JsonObject) cond).filter(cond -> "Running"
|
||||||
.equals(cond.get("type").getAsString()))
|
.equals(cond.get("type").getAsString()))
|
||||||
|
|
@ -249,15 +212,15 @@ public class StatusUpdater extends Component {
|
||||||
* @throws ApiException
|
* @throws ApiException
|
||||||
*/
|
*/
|
||||||
@Handler
|
@Handler
|
||||||
|
@SuppressWarnings("PMD.AssignmentInOperand")
|
||||||
public void onRunnerStateChanged(RunnerStateChange event)
|
public void onRunnerStateChanged(RunnerStateChange event)
|
||||||
throws ApiException {
|
throws ApiException {
|
||||||
if (vmCrApi == null) {
|
K8sDynamicModel vmDef;
|
||||||
|
if (vmStub == null || (vmDef = vmStub.model().orElse(null)) == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var vmCr = vmCrApi.get(namespace, vmName).throwsApiException()
|
vmStub.updateStatus(vmDef, from -> {
|
||||||
.getObject();
|
JsonObject status = from.status();
|
||||||
vmCrApi.updateStatus(vmCr, from -> {
|
|
||||||
JsonObject status = currentStatus(from);
|
|
||||||
status.getAsJsonArray("conditions").asList().stream()
|
status.getAsJsonArray("conditions").asList().stream()
|
||||||
.map(cond -> (JsonObject) cond)
|
.map(cond -> (JsonObject) cond)
|
||||||
.forEach(cond -> {
|
.forEach(cond -> {
|
||||||
|
|
@ -266,7 +229,7 @@ public class StatusUpdater extends Component {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (event.state() == State.STARTING) {
|
if (event.state() == State.STARTING) {
|
||||||
status.addProperty("ram", GsonPtr.to(from.getRaw())
|
status.addProperty("ram", GsonPtr.to(from.data())
|
||||||
.getAsString("spec", "vm", "maximumRam").orElse("0"));
|
.getAsString("spec", "vm", "maximumRam").orElse("0"));
|
||||||
status.addProperty("cpus", 1);
|
status.addProperty("cpus", 1);
|
||||||
} else if (event.state() == State.STOPPED) {
|
} else if (event.state() == State.STOPPED) {
|
||||||
|
|
@ -274,40 +237,32 @@ public class StatusUpdater extends Component {
|
||||||
status.addProperty("cpus", 0);
|
status.addProperty("cpus", 0);
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}).throwsApiException();
|
});
|
||||||
|
|
||||||
// Maybe stop VM
|
// Maybe stop VM
|
||||||
if (event.state() == State.TERMINATING && !event.failed()
|
if (event.state() == State.TERMINATING && !event.failed()
|
||||||
&& guestShutdownStops && shutdownByGuest) {
|
&& guestShutdownStops && shutdownByGuest) {
|
||||||
logger.info(() -> "Stopping VM because of shutdown by guest.");
|
logger.info(() -> "Stopping VM because of shutdown by guest.");
|
||||||
PatchOptions patchOpts = new PatchOptions();
|
var res = vmStub.patch(V1Patch.PATCH_FORMAT_JSON_PATCH,
|
||||||
patchOpts.setFieldManager("kubernetes-java-kubectl-apply");
|
|
||||||
var res = vmCrApi.patch(namespace, vmName,
|
|
||||||
V1Patch.PATCH_FORMAT_JSON_PATCH,
|
|
||||||
new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/vm/state"
|
new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/vm/state"
|
||||||
+ "\", \"value\": \"Stopped\"}]"),
|
+ "\", \"value\": \"Stopped\"}]"),
|
||||||
patchOpts);
|
apiClient.defaultPatchOptions());
|
||||||
if (!res.isSuccess()) {
|
if (!res.isPresent()) {
|
||||||
logger.warning(
|
logger.warning(
|
||||||
() -> "Cannot patch pod annotations: " + res.getStatus());
|
() -> "Cannot patch pod annotations for: " + vmStub.name());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log event
|
// Log event
|
||||||
var evt = new EventsV1Event().kind("Event")
|
var evt = new EventsV1Event()
|
||||||
.metadata(new V1ObjectMeta().namespace(namespace)
|
|
||||||
.generateName("vmrunner-"))
|
|
||||||
.reportingController(VM_OP_GROUP + "/" + APP_NAME)
|
.reportingController(VM_OP_GROUP + "/" + APP_NAME)
|
||||||
.reportingInstance(vmCr.getMetadata().getName())
|
|
||||||
.eventTime(OffsetDateTime.now()).type("Normal")
|
|
||||||
.regarding(K8s.objectReference(vmCr))
|
|
||||||
.action("StatusUpdate").reason(event.reason())
|
.action("StatusUpdate").reason(event.reason())
|
||||||
.note(event.message());
|
.note(event.message());
|
||||||
evtsApi.createNamespacedEvent(namespace, evt, null, null, null, null);
|
K8s.createEvent(apiClient, vmDef, evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRunningCondition(RunnerStateChange event,
|
private void updateRunningCondition(RunnerStateChange event,
|
||||||
DynamicKubernetesObject from, JsonObject cond) {
|
K8sDynamicModel from, JsonObject cond) {
|
||||||
boolean reportedRunning
|
boolean reportedRunning
|
||||||
= "True".equals(cond.get("status").getAsString());
|
= "True".equals(cond.get("status").getAsString());
|
||||||
if (RUNNING_STATES.contains(event.state())
|
if (RUNNING_STATES.contains(event.state())
|
||||||
|
|
@ -336,18 +291,16 @@ public class StatusUpdater extends Component {
|
||||||
*/
|
*/
|
||||||
@Handler
|
@Handler
|
||||||
public void onBallonChange(BalloonChangeEvent event) throws ApiException {
|
public void onBallonChange(BalloonChangeEvent event) throws ApiException {
|
||||||
if (vmCrApi == null) {
|
if (vmStub == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var vmCr = vmCrApi.get(namespace, vmName).throwsApiException()
|
vmStub.updateStatus(from -> {
|
||||||
.getObject();
|
JsonObject status = from.status();
|
||||||
vmCrApi.updateStatus(vmCr, from -> {
|
|
||||||
JsonObject status = currentStatus(from);
|
|
||||||
status.addProperty("ram",
|
status.addProperty("ram",
|
||||||
new Quantity(new BigDecimal(event.size()), Format.BINARY_SI)
|
new Quantity(new BigDecimal(event.size()), Format.BINARY_SI)
|
||||||
.toSuffixedString());
|
.toSuffixedString());
|
||||||
return status;
|
return status;
|
||||||
}).throwsApiException();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -358,16 +311,14 @@ public class StatusUpdater extends Component {
|
||||||
*/
|
*/
|
||||||
@Handler
|
@Handler
|
||||||
public void onCpuChange(HotpluggableCpuStatus event) throws ApiException {
|
public void onCpuChange(HotpluggableCpuStatus event) throws ApiException {
|
||||||
if (vmCrApi == null) {
|
if (vmStub == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var vmCr = vmCrApi.get(namespace, vmName).throwsApiException()
|
vmStub.updateStatus(from -> {
|
||||||
.getObject();
|
JsonObject status = from.status();
|
||||||
vmCrApi.updateStatus(vmCr, from -> {
|
|
||||||
JsonObject status = currentStatus(from);
|
|
||||||
status.addProperty("cpus", event.usedCpus().size());
|
status.addProperty("cpus", event.usedCpus().size());
|
||||||
return status;
|
return status;
|
||||||
}).throwsApiException();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import freemarker.template.Template;
|
||||||
import freemarker.template.TemplateNotFoundException;
|
import freemarker.template.TemplateNotFoundException;
|
||||||
import io.kubernetes.client.custom.Quantity;
|
import io.kubernetes.client.custom.Quantity;
|
||||||
import io.kubernetes.client.custom.Quantity.Format;
|
import io.kubernetes.client.custom.Quantity.Format;
|
||||||
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
|
@ -38,6 +37,7 @@ import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import org.jdrupes.json.JsonBeanDecoder;
|
import org.jdrupes.json.JsonBeanDecoder;
|
||||||
import org.jdrupes.json.JsonDecodeException;
|
import org.jdrupes.json.JsonDecodeException;
|
||||||
|
import org.jdrupes.vmoperator.common.K8sDynamicModel;
|
||||||
import org.jdrupes.vmoperator.manager.events.ModifyVm;
|
import org.jdrupes.vmoperator.manager.events.ModifyVm;
|
||||||
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
||||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
||||||
|
|
@ -46,7 +46,6 @@ import org.jdrupes.vmoperator.util.GsonPtr;
|
||||||
import org.jgrapes.core.Channel;
|
import org.jgrapes.core.Channel;
|
||||||
import org.jgrapes.core.Event;
|
import org.jgrapes.core.Event;
|
||||||
import org.jgrapes.core.Manager;
|
import org.jgrapes.core.Manager;
|
||||||
import org.jgrapes.core.NamedChannel;
|
|
||||||
import org.jgrapes.core.annotation.Handler;
|
import org.jgrapes.core.annotation.Handler;
|
||||||
import org.jgrapes.webconsole.base.Conlet.RenderMode;
|
import org.jgrapes.webconsole.base.Conlet.RenderMode;
|
||||||
import org.jgrapes.webconsole.base.ConletBaseModel;
|
import org.jgrapes.webconsole.base.ConletBaseModel;
|
||||||
|
|
@ -70,7 +69,9 @@ public class VmConlet extends FreeMarkerConlet<VmConlet.VmsModel> {
|
||||||
|
|
||||||
private static final Set<RenderMode> MODES = RenderMode.asSet(
|
private static final Set<RenderMode> MODES = RenderMode.asSet(
|
||||||
RenderMode.Preview, RenderMode.View);
|
RenderMode.Preview, RenderMode.View);
|
||||||
private final Map<String, DynamicKubernetesObject> vmInfos
|
private final Map<String, K8sDynamicModel> vmInfos
|
||||||
|
= new ConcurrentHashMap<>();
|
||||||
|
private final Map<String, VmChannel> vmChannels
|
||||||
= new ConcurrentHashMap<>();
|
= new ConcurrentHashMap<>();
|
||||||
private final TimeSeries summarySeries = new TimeSeries(Duration.ofDays(1));
|
private final TimeSeries summarySeries = new TimeSeries(Duration.ofDays(1));
|
||||||
private Summary cachedSummary;
|
private Summary cachedSummary;
|
||||||
|
|
@ -162,7 +163,7 @@ public class VmConlet extends FreeMarkerConlet<VmConlet.VmsModel> {
|
||||||
}
|
}
|
||||||
if (sendVmInfos) {
|
if (sendVmInfos) {
|
||||||
for (var vmInfo : vmInfos.values()) {
|
for (var vmInfo : vmInfos.values()) {
|
||||||
var def = JsonBeanDecoder.create(vmInfo.getRaw().toString())
|
var def = JsonBeanDecoder.create(vmInfo.data().toString())
|
||||||
.readObject();
|
.readObject();
|
||||||
channel.respond(new NotifyConletView(type(),
|
channel.respond(new NotifyConletView(type(),
|
||||||
conletId, "updateVm", def));
|
conletId, "updateVm", def));
|
||||||
|
|
@ -185,9 +186,10 @@ public class VmConlet extends FreeMarkerConlet<VmConlet.VmsModel> {
|
||||||
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.AvoidDuplicateLiterals" })
|
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.AvoidDuplicateLiterals" })
|
||||||
public void onVmDefChanged(VmDefChanged event, VmChannel channel)
|
public void onVmDefChanged(VmDefChanged event, VmChannel channel)
|
||||||
throws JsonDecodeException, IOException {
|
throws JsonDecodeException, IOException {
|
||||||
|
var vmName = event.vmDefinition().getMetadata().getName();
|
||||||
if (event.type() == Type.DELETED) {
|
if (event.type() == Type.DELETED) {
|
||||||
var vmName = event.vmDefinition().getMetadata().getName();
|
|
||||||
vmInfos.remove(vmName);
|
vmInfos.remove(vmName);
|
||||||
|
vmChannels.remove(vmName);
|
||||||
for (var entry : conletIdsByConsoleConnection().entrySet()) {
|
for (var entry : conletIdsByConsoleConnection().entrySet()) {
|
||||||
for (String conletId : entry.getValue()) {
|
for (String conletId : entry.getValue()) {
|
||||||
entry.getKey().respond(new NotifyConletView(type(),
|
entry.getKey().respond(new NotifyConletView(type(),
|
||||||
|
|
@ -195,8 +197,11 @@ public class VmConlet extends FreeMarkerConlet<VmConlet.VmsModel> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var vmDef = convertQuantities(event);
|
var vmDef = new K8sDynamicModel(channel.client().getJSON()
|
||||||
var def = JsonBeanDecoder.create(vmDef.getRaw().toString())
|
.getGson(), convertQuantities(event.vmDefinition().data()));
|
||||||
|
vmInfos.put(vmName, vmDef);
|
||||||
|
vmChannels.put(vmName, channel);
|
||||||
|
var def = JsonBeanDecoder.create(vmDef.data().toString())
|
||||||
.readObject();
|
.readObject();
|
||||||
for (var entry : conletIdsByConsoleConnection().entrySet()) {
|
for (var entry : conletIdsByConsoleConnection().entrySet()) {
|
||||||
for (String conletId : entry.getValue()) {
|
for (String conletId : entry.getValue()) {
|
||||||
|
|
@ -217,28 +222,25 @@ public class VmConlet extends FreeMarkerConlet<VmConlet.VmsModel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
|
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
|
||||||
private DynamicKubernetesObject convertQuantities(VmDefChanged event) {
|
private JsonObject convertQuantities(JsonObject vmDef) {
|
||||||
// Clone and remove managed fields
|
// Clone and remove managed fields
|
||||||
var vmDef = new DynamicKubernetesObject(
|
var json = vmDef.deepCopy();
|
||||||
event.vmDefinition().getRaw().deepCopy());
|
GsonPtr.to(json).to("metadata").get(JsonObject.class)
|
||||||
GsonPtr.to(vmDef.getRaw()).to("metadata").get(JsonObject.class)
|
|
||||||
.remove("managedFields");
|
.remove("managedFields");
|
||||||
|
|
||||||
// Convert RAM sizes to unitless numbers
|
// Convert RAM sizes to unitless numbers
|
||||||
var vmSpec = GsonPtr.to(vmDef.getRaw()).to("spec", "vm");
|
var vmSpec = GsonPtr.to(json).to("spec", "vm");
|
||||||
vmSpec.set("maximumRam", Quantity.fromString(
|
vmSpec.set("maximumRam", Quantity.fromString(
|
||||||
vmSpec.getAsString("maximumRam").orElse("0")).getNumber()
|
vmSpec.getAsString("maximumRam").orElse("0")).getNumber()
|
||||||
.toBigInteger());
|
.toBigInteger());
|
||||||
vmSpec.set("currentRam", Quantity.fromString(
|
vmSpec.set("currentRam", Quantity.fromString(
|
||||||
vmSpec.getAsString("currentRam").orElse("0")).getNumber()
|
vmSpec.getAsString("currentRam").orElse("0")).getNumber()
|
||||||
.toBigInteger());
|
.toBigInteger());
|
||||||
var status = GsonPtr.to(vmDef.getRaw()).to("status");
|
var status = GsonPtr.to(json).to("status");
|
||||||
status.set("ram", Quantity.fromString(
|
status.set("ram", Quantity.fromString(
|
||||||
status.getAsString("ram").orElse("0")).getNumber()
|
status.getAsString("ram").orElse("0")).getNumber()
|
||||||
.toBigInteger());
|
.toBigInteger());
|
||||||
String vmName = event.vmDefinition().getMetadata().getName();
|
return json;
|
||||||
vmInfos.put(vmName, vmDef);
|
|
||||||
return vmDef;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -323,7 +325,7 @@ public class VmConlet extends FreeMarkerConlet<VmConlet.VmsModel> {
|
||||||
Summary summary = new Summary();
|
Summary summary = new Summary();
|
||||||
for (var vmDef : vmInfos.values()) {
|
for (var vmDef : vmInfos.values()) {
|
||||||
summary.totalVms += 1;
|
summary.totalVms += 1;
|
||||||
var status = GsonPtr.to(vmDef.getRaw()).to("status");
|
var status = GsonPtr.to(vmDef.data()).to("status");
|
||||||
summary.usedCpus += status.getAsInt("cpus").orElse(0);
|
summary.usedCpus += status.getAsInt("cpus").orElse(0);
|
||||||
summary.usedRam = summary.usedRam.add(status.getAsString("ram")
|
summary.usedRam = summary.usedRam.add(status.getAsString("ram")
|
||||||
.map(BigInteger::new).orElse(BigInteger.ZERO));
|
.map(BigInteger::new).orElse(BigInteger.ZERO));
|
||||||
|
|
@ -346,25 +348,28 @@ public class VmConlet extends FreeMarkerConlet<VmConlet.VmsModel> {
|
||||||
ConsoleConnection channel, VmsModel conletState)
|
ConsoleConnection channel, VmsModel conletState)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
event.stop();
|
event.stop();
|
||||||
|
var vmName = event.params().asString(0);
|
||||||
|
var vmChannel = vmChannels.get(vmName);
|
||||||
|
if (vmChannel == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (event.method()) {
|
switch (event.method()) {
|
||||||
case "start":
|
case "start":
|
||||||
fire(new ModifyVm(event.params().asString(0), "state", "Running",
|
fire(new ModifyVm(vmName, "state", "Running", vmChannel));
|
||||||
new NamedChannel("manager")));
|
|
||||||
break;
|
break;
|
||||||
case "stop":
|
case "stop":
|
||||||
fire(new ModifyVm(event.params().asString(0), "state", "Stopped",
|
fire(new ModifyVm(vmName, "state", "Stopped", vmChannel));
|
||||||
new NamedChannel("manager")));
|
|
||||||
break;
|
break;
|
||||||
case "cpus":
|
case "cpus":
|
||||||
fire(new ModifyVm(event.params().asString(0), "currentCpus",
|
fire(new ModifyVm(vmName, "currentCpus",
|
||||||
new BigDecimal(event.params().asDouble(1)).toBigInteger(),
|
new BigDecimal(event.params().asDouble(1)).toBigInteger(),
|
||||||
new NamedChannel("manager")));
|
vmChannel));
|
||||||
break;
|
break;
|
||||||
case "ram":
|
case "ram":
|
||||||
fire(new ModifyVm(event.params().asString(0), "currentRam",
|
fire(new ModifyVm(vmName, "currentRam",
|
||||||
new Quantity(new BigDecimal(event.params().asDouble(1)),
|
new Quantity(new BigDecimal(event.params().asDouble(1)),
|
||||||
Format.BINARY_SI).toSuffixedString(),
|
Format.BINARY_SI).toSuffixedString(),
|
||||||
new NamedChannel("manager")));
|
vmChannel));
|
||||||
break;
|
break;
|
||||||
default:// ignore
|
default:// ignore
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue