Feature/web gui (#12)

Basic GUI functions (start/stop).
This commit is contained in:
Michael N. Lipp 2023-10-21 22:16:10 +02:00 committed by GitHub
parent 6491742eb0
commit ae3941707a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
86 changed files with 12225 additions and 514 deletions

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<fileset-config file-format-version="1.2.0" simple-config="false" sync-formatter="false">
<local-check-config name="Project Checks" location="/VM-Operator/checkstyle.xml" type="project" description="">
<additional-data name="protect-config-file" value="false"/>
</local-check-config>
<fileset name="all" enabled="true" check-config-name="Project Checks" local="true">
<file-match-pattern match-pattern="." include-pattern="true"/>
</fileset>
</fileset-config>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<eclipse-pmd xmlns="http://acanda.ch/eclipse-pmd/0.8" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://acanda.ch/eclipse-pmd/0.8 http://acanda.ch/eclipse-pmd/eclipse-pmd-0.8.xsd">
<analysis enabled="true" />
<rulesets>
<ruleset name="Custom Rules" ref="moodle-tools-console/ruleset.xml" refcontext="workspace" />
</rulesets>
</eclipse-pmd>

View file

@ -0,0 +1,13 @@
arguments=
auto.sync=false
build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=..
eclipse.preferences.version=1
gradle.user.home=
java.home=
jvm.arguments=
offline.mode=false
override.workspace.settings=false
show.console.view=false
show.executions.view=false

View file

@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8

View file

@ -0,0 +1,2 @@
eclipse.preferences.version=1
line.separator=\n

View file

@ -0,0 +1,14 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This project uses @Incubating APIs which are subject to change.
*/
plugins {
id 'org.jdrupes.vmoperator.java-library-conventions'
}
dependencies {
api project(':org.jdrupes.vmoperator.util')
api 'io.kubernetes:client-java:[18.0.0,19)'
}

View file

@ -0,0 +1,34 @@
/*
* VM-Operator
* Copyright (C) 2023 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jdrupes.vmoperator.common;
/**
* Some constants.
*/
public class Constants {
/** The Constant VM_OP_NAME. */
public static final String VM_OP_NAME = "vm-operator";
/** The Constant VM_OP_GROUP. */
public static final String VM_OP_GROUP = "vmoperator.jdrupes.org";
/** The Constant VM_OP_KIND_VM. */
public static final String VM_OP_KIND_VM = "VirtualMachine";
}

View file

@ -0,0 +1,116 @@
/*
* VM-Operator
* Copyright (C) 2023 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jdrupes.vmoperator.common;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Provides methods for parsing "official" memory sizes..
*/
@SuppressWarnings("PMD.UseUtilityClass")
public class Convertions {
@SuppressWarnings({ "PMD.UseConcurrentHashMap",
"PMD.FieldNamingConventions", "PMD.VariableNamingConventions" })
private static final Map<String, BigInteger> unitMap = new HashMap<>();
@SuppressWarnings({ "PMD.FieldNamingConventions",
"PMD.VariableNamingConventions" })
private static final List<Map.Entry<String, BigInteger>> unitMappings;
@SuppressWarnings({ "PMD.FieldNamingConventions",
"PMD.VariableNamingConventions" })
private static final Pattern memorySize
= Pattern.compile("^\\s*(\\d+(\\.\\d+)?)\\s*([A-Za-z]*)\\s*");
static {
// SI units and common abbreviations
BigInteger factor = BigInteger.ONE;
unitMap.put("", factor);
BigInteger scale = BigInteger.valueOf(1000);
for (var unit : List.of("B", "kB", "MB", "GB", "TB", "PB", "EB")) {
unitMap.put(unit, factor);
factor = factor.multiply(scale);
}
// Binary units
factor = BigInteger.valueOf(1024);
scale = BigInteger.valueOf(1024);
for (var unit : List.of("KiB", "MiB", "GiB", "TiB", "PiB", "EiB")) {
unitMap.put(unit, factor);
factor = factor.multiply(scale);
}
unitMappings = unitMap.entrySet().stream()
.sorted((a, b) -> -1 * a.getValue().compareTo(b.getValue()))
.toList();
}
/**
* Parses a memory size specification.
*
* @param amount the amount
* @return the big integer
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public static BigInteger parseMemory(Object amount) {
if (amount == null) {
return (BigInteger) amount;
}
if (amount instanceof BigInteger number) {
return number;
}
if (amount instanceof Number number) {
return BigInteger.valueOf(number.longValue());
}
var matcher = memorySize.matcher(amount.toString());
if (!matcher.matches()) {
throw new NumberFormatException(amount.toString());
}
var unit = BigInteger.ONE;
if (matcher.group(3) != null) {
unit = unitMap.get(matcher.group(3));
if (unit == null) {
throw new NumberFormatException("Illegal unit \""
+ matcher.group(3) + "\" in \"" + amount.toString() + "\"");
}
}
var number = matcher.group(1);
return new BigDecimal(number).multiply(new BigDecimal(unit))
.toBigInteger();
}
/**
* Format memory size for humans.
*
* @param size the size
* @return the string
*/
public static String formatMemory(BigInteger size) {
for (var mapping : unitMappings) {
if (size.compareTo(mapping.getValue()) >= 0
&& size.mod(mapping.getValue()).equals(BigInteger.ZERO)) {
return (size.divide(mapping.getValue()).toString()
+ " " + mapping.getKey()).trim();
}
}
return size.toString();
}
}

View file

@ -0,0 +1,207 @@
/*
* VM-Operator
* Copyright (C) 2023 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jdrupes.vmoperator.common;
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.openapi.apis.ApisApi;
import io.kubernetes.client.openapi.apis.CustomObjectsApi;
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.V1PersistentVolumeClaim;
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.dynamic.DynamicKubernetesApi;
import io.kubernetes.client.util.generic.options.DeleteOptions;
import io.kubernetes.client.util.generic.options.PatchOptions;
import java.util.Optional;
/**
* Helpers for K8s API.
*/
@SuppressWarnings({ "PMD.ShortClassName", "PMD.UseUtilityClass",
"PMD.DataflowAnomalyAnalysis" })
public class K8s {
/**
* Given a groupVersion, returns only the version.
*
* @param groupVersion the group version
* @return the string
*/
public static String version(String groupVersion) {
return groupVersion.substring(groupVersion.lastIndexOf('/') + 1);
}
/**
* Get PVC API.
*
* @param client the client
* @return the generic kubernetes api
*/
public static GenericKubernetesApi<V1PersistentVolumeClaim,
V1PersistentVolumeClaimList> pvcApi(ApiClient client) {
return new GenericKubernetesApi<>(V1PersistentVolumeClaim.class,
V1PersistentVolumeClaimList.class, "", "v1",
"persistentvolumeclaims", client);
}
/**
* Get config map API.
*
* @param client the client
* @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 group the group
* @param kind the kind
* @param namespace the namespace
* @param name the name
* @return the dynamic kubernetes api
* @throws ApiException the api exception
*/
@SuppressWarnings("PMD.UseObjectForClearerAPI")
public static Optional<DynamicKubernetesApi> crApi(ApiClient client,
String group, String kind, String namespace, String name)
throws ApiException {
var apis = new ApisApi(client).getAPIVersions();
var crdVersions = apis.getGroups().stream()
.filter(g -> g.getName().equals(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(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();
}
/**
* Get an object from its metadata.
*
* @param <T> the generic type
* @param <LT> the generic type
* @param api the api
* @param meta the meta
* @return the object
*/
public static <T extends KubernetesObject, LT extends KubernetesListObject>
Optional<T>
get(GenericKubernetesApi<T, LT> api, V1ObjectMeta meta) {
var response = api.get(meta.getNamespace(), meta.getName());
if (response.isSuccess()) {
return Optional.of(response.getObject());
}
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.
*
* @param <T> the generic type
* @param <LT> the generic type
* @param api the api
* @param existing the existing
* @param update the update
* @throws ApiException the api exception
*/
public static <T extends KubernetesObject, LT extends KubernetesListObject>
T apply(GenericKubernetesApi<T, LT> api, T existing, String update)
throws ApiException {
PatchOptions opts = new PatchOptions();
opts.setForce(true);
opts.setFieldManager("kubernetes-java-kubectl-apply");
var response = api.patch(existing.getMetadata().getNamespace(),
existing.getMetadata().getName(), V1Patch.PATCH_FORMAT_APPLY_YAML,
new V1Patch(update), opts).throwsApiException();
return response.getObject();
}
}

View file

@ -0,0 +1,22 @@
/*
* VM-Operator
* Copyright (C) 2023 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* Classes and methods shared among the VM operator modules.
*/
package org.jdrupes.vmoperator.common;