Merge branch 'prep/v4.0.0' into testing
This commit is contained in:
commit
aea8b9540e
20 changed files with 436 additions and 418 deletions
|
|
@ -102,7 +102,7 @@ public class K8sDynamicModel implements KubernetesObject {
|
||||||
*
|
*
|
||||||
* @return the JSON object describing the status
|
* @return the JSON object describing the status
|
||||||
*/
|
*/
|
||||||
public JsonObject status() {
|
public JsonObject statusJson() {
|
||||||
return data.getAsJsonObject("status");
|
return data.getAsJsonObject("status");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -193,42 +193,41 @@ public class K8sGenericStub<O extends KubernetesObject,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the object's status, retrying for the given number of times
|
* Updates the object's status.
|
||||||
* if the update fails due to a conflict.
|
|
||||||
*
|
*
|
||||||
* @param object the current state of the object (passed to `status`)
|
* @param object the current state of the object (passed to `status`)
|
||||||
* @param status function that returns the new status
|
* @param status function that returns the new status
|
||||||
* @param retries the retries
|
* @return the updated model or empty if the object was not found
|
||||||
* @return the updated model or empty if not successful
|
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.AssignmentInOperand")
|
@SuppressWarnings("PMD.AssignmentInOperand")
|
||||||
public Optional<O> updateStatus(O object,
|
public Optional<O> updateStatus(O object, Function<O, Object> status)
|
||||||
Function<O, Object> status, int retries) throws ApiException {
|
throws ApiException {
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
return K8s.optional(api.updateStatus(object, status));
|
return K8s.optional(api.updateStatus(object, status));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the object and updates the status. In case of conflict, retries
|
||||||
|
* up to `retries` times.
|
||||||
|
*
|
||||||
|
* @param status the status
|
||||||
|
* @param retries the retries in case of conflict
|
||||||
|
* @return the updated model or empty if the object was not found
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "PMD.AssignmentInOperand", "PMD.UnusedAssignment" })
|
||||||
|
public Optional<O> updateStatus(Function<O, Object> status, int retries)
|
||||||
|
throws ApiException {
|
||||||
|
try {
|
||||||
|
return updateStatus(api.get(namespace, name).throwsApiException()
|
||||||
|
.getObject(), status);
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
if (HttpURLConnection.HTTP_CONFLICT != e.getCode()
|
if (HttpURLConnection.HTTP_CONFLICT != e.getCode()
|
||||||
|| retries-- <= 0) {
|
|| retries-- <= 0) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return Optional.empty();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the object's status, retrying up to 16 times if there
|
|
||||||
* is a conflict.
|
|
||||||
*
|
|
||||||
* @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 updateStatus(object, status, 16);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -241,8 +240,7 @@ public class K8sGenericStub<O extends KubernetesObject,
|
||||||
*/
|
*/
|
||||||
public Optional<O> updateStatus(Function<O, Object> status)
|
public Optional<O> updateStatus(Function<O, Object> status)
|
||||||
throws ApiException {
|
throws ApiException {
|
||||||
return updateStatus(
|
return updateStatus(status, 16);
|
||||||
api.get(namespace, name).throwsApiException().getObject(), status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,10 @@ package org.jdrupes.vmoperator.common;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kubernetes.client.openapi.JSON;
|
||||||
import io.kubernetes.client.openapi.models.V1Condition;
|
import io.kubernetes.client.openapi.models.V1Condition;
|
||||||
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
|
||||||
import io.kubernetes.client.util.Strings;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
@ -36,9 +35,7 @@ import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.jdrupes.vmoperator.util.DataPath;
|
import org.jdrupes.vmoperator.util.DataPath;
|
||||||
|
|
@ -46,22 +43,21 @@ import org.jdrupes.vmoperator.util.DataPath;
|
||||||
/**
|
/**
|
||||||
* Represents a VM definition.
|
* Represents a VM definition.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.DataClass", "PMD.TooManyMethods" })
|
@SuppressWarnings({ "PMD.DataClass", "PMD.TooManyMethods",
|
||||||
public class VmDefinition {
|
"PMD.CouplingBetweenObjects" })
|
||||||
|
public class VmDefinition extends K8sDynamicModel {
|
||||||
|
|
||||||
@SuppressWarnings("PMD.FieldNamingConventions")
|
@SuppressWarnings({ "PMD.FieldNamingConventions", "unused" })
|
||||||
private static final Logger logger
|
private static final Logger logger
|
||||||
= Logger.getLogger(VmDefinition.class.getName());
|
= Logger.getLogger(VmDefinition.class.getName());
|
||||||
@SuppressWarnings("PMD.FieldNamingConventions")
|
@SuppressWarnings("PMD.FieldNamingConventions")
|
||||||
|
private static final Gson gson = new JSON().getGson();
|
||||||
|
@SuppressWarnings("PMD.FieldNamingConventions")
|
||||||
private static final ObjectMapper objectMapper
|
private static final ObjectMapper objectMapper
|
||||||
= new ObjectMapper().registerModule(new JavaTimeModule());
|
= new ObjectMapper().registerModule(new JavaTimeModule());
|
||||||
|
|
||||||
private String kind;
|
private final Model model;
|
||||||
private String apiVersion;
|
private VmExtraData extraData;
|
||||||
private V1ObjectMeta metadata;
|
|
||||||
private Map<String, Object> spec;
|
|
||||||
private Map<String, Object> status;
|
|
||||||
private final Map<String, Object> extra = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The VM state from the VM definition.
|
* The VM state from the VM definition.
|
||||||
|
|
@ -145,66 +141,34 @@ public class VmDefinition {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the kind.
|
* Instantiates a new vm definition.
|
||||||
*
|
*
|
||||||
* @return the kind
|
* @param delegate the delegate
|
||||||
|
* @param json the json
|
||||||
*/
|
*/
|
||||||
public String getKind() {
|
public VmDefinition(Gson delegate, JsonObject json) {
|
||||||
return kind;
|
super(delegate, json);
|
||||||
|
model = gson.fromJson(json, Model.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the kind.
|
* Gets the spec.
|
||||||
*
|
*
|
||||||
* @param kind the kind to set
|
* @return the spec
|
||||||
*/
|
*/
|
||||||
public void setKind(String kind) {
|
public Map<String, Object> spec() {
|
||||||
this.kind = kind;
|
return model.getSpec();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the api version.
|
* Get a value from the spec using {@link DataPath#get}.
|
||||||
*
|
*
|
||||||
* @return the apiVersion
|
* @param <T> the generic type
|
||||||
|
* @param selectors the selectors
|
||||||
|
* @return the value, if found
|
||||||
*/
|
*/
|
||||||
public String getApiVersion() {
|
public <T> Optional<T> fromSpec(Object... selectors) {
|
||||||
return apiVersion;
|
return DataPath.get(spec(), selectors);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the api version.
|
|
||||||
*
|
|
||||||
* @param apiVersion the apiVersion to set
|
|
||||||
*/
|
|
||||||
public void setApiVersion(String apiVersion) {
|
|
||||||
this.apiVersion = apiVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the metadata.
|
|
||||||
*
|
|
||||||
* @return the metadata
|
|
||||||
*/
|
|
||||||
public V1ObjectMeta getMetadata() {
|
|
||||||
return metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the metadata.
|
|
||||||
*
|
|
||||||
* @return the metadata
|
|
||||||
*/
|
|
||||||
public V1ObjectMeta metadata() {
|
|
||||||
return metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the metadata.
|
|
||||||
*
|
|
||||||
* @param metadata the metadata to set
|
|
||||||
*/
|
|
||||||
public void setMetadata(V1ObjectMeta metadata) {
|
|
||||||
this.metadata = metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -217,35 +181,6 @@ public class VmDefinition {
|
||||||
.orElse(Collections.emptyList());
|
.orElse(Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the spec.
|
|
||||||
*
|
|
||||||
* @return the spec
|
|
||||||
*/
|
|
||||||
public Map<String, Object> getSpec() {
|
|
||||||
return spec;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the spec.
|
|
||||||
*
|
|
||||||
* @return the spec
|
|
||||||
*/
|
|
||||||
public Map<String, Object> spec() {
|
|
||||||
return spec;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a value from the spec using {@link DataPath#get}.
|
|
||||||
*
|
|
||||||
* @param <T> the generic type
|
|
||||||
* @param selectors the selectors
|
|
||||||
* @return the value, if found
|
|
||||||
*/
|
|
||||||
public <T> Optional<T> fromSpec(Object... selectors) {
|
|
||||||
return DataPath.get(spec, selectors);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a value from the `spec().get("vm")` using {@link DataPath#get}.
|
* Get a value from the `spec().get("vm")` using {@link DataPath#get}.
|
||||||
*
|
*
|
||||||
|
|
@ -254,35 +189,17 @@ public class VmDefinition {
|
||||||
* @return the value, if found
|
* @return the value, if found
|
||||||
*/
|
*/
|
||||||
public <T> Optional<T> fromVm(Object... selectors) {
|
public <T> Optional<T> fromVm(Object... selectors) {
|
||||||
return DataPath.get(spec, "vm")
|
return DataPath.get(spec(), "vm")
|
||||||
.flatMap(vm -> DataPath.get(vm, selectors));
|
.flatMap(vm -> DataPath.get(vm, selectors));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the spec.
|
|
||||||
*
|
|
||||||
* @param spec the spec to set
|
|
||||||
*/
|
|
||||||
public void setSpec(Map<String, Object> spec) {
|
|
||||||
this.spec = spec;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the status.
|
|
||||||
*
|
|
||||||
* @return the status
|
|
||||||
*/
|
|
||||||
public Map<String, Object> getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the status.
|
* Gets the status.
|
||||||
*
|
*
|
||||||
* @return the status
|
* @return the status
|
||||||
*/
|
*/
|
||||||
public Map<String, Object> status() {
|
public Map<String, Object> status() {
|
||||||
return status;
|
return model.getStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -293,16 +210,7 @@ public class VmDefinition {
|
||||||
* @return the value, if found
|
* @return the value, if found
|
||||||
*/
|
*/
|
||||||
public <T> Optional<T> fromStatus(Object... selectors) {
|
public <T> Optional<T> fromStatus(Object... selectors) {
|
||||||
return DataPath.get(status, selectors);
|
return DataPath.get(status(), selectors);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the status.
|
|
||||||
*
|
|
||||||
* @param status the status to set
|
|
||||||
*/
|
|
||||||
public void setStatus(Map<String, Object> status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -382,27 +290,21 @@ public class VmDefinition {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set extra data (locally used, unknown to kubernetes).
|
* Set extra data (unknown to kubernetes).
|
||||||
*
|
|
||||||
* @param property the property
|
|
||||||
* @param value the value
|
|
||||||
* @return the VM definition
|
* @return the VM definition
|
||||||
*/
|
*/
|
||||||
public VmDefinition extra(String property, Object value) {
|
/* default */ VmDefinition extra(VmExtraData extraData) {
|
||||||
extra.put(property, value);
|
this.extraData = extraData;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return extra data.
|
* Return the extra data.
|
||||||
*
|
*
|
||||||
* @param <T> the generic type
|
* @return the data
|
||||||
* @param property the property
|
|
||||||
* @return the object
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
public Optional<VmExtraData> extra() {
|
||||||
public <T> T extra(String property) {
|
return Optional.ofNullable(extraData);
|
||||||
return (T) extra.get(property);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -411,7 +313,7 @@ public class VmDefinition {
|
||||||
* @return the string
|
* @return the string
|
||||||
*/
|
*/
|
||||||
public String name() {
|
public String name() {
|
||||||
return metadata.getName();
|
return metadata().getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -420,7 +322,7 @@ public class VmDefinition {
|
||||||
* @return the string
|
* @return the string
|
||||||
*/
|
*/
|
||||||
public String namespace() {
|
public String namespace() {
|
||||||
return metadata.getNamespace();
|
return metadata().getNamespace();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -490,78 +392,6 @@ public class VmDefinition {
|
||||||
.map(Number::longValue);
|
.map(Number::longValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a connection file.
|
|
||||||
*
|
|
||||||
* @param password the password
|
|
||||||
* @param preferredIpVersion the preferred IP version
|
|
||||||
* @param deleteConnectionFile the delete connection file
|
|
||||||
* @return the string
|
|
||||||
*/
|
|
||||||
public String connectionFile(String password,
|
|
||||||
Class<?> preferredIpVersion, boolean deleteConnectionFile) {
|
|
||||||
var addr = displayIp(preferredIpVersion);
|
|
||||||
if (addr.isEmpty()) {
|
|
||||||
logger.severe(() -> "Failed to find display IP for " + name());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var port = this.<Number> fromVm("display", "spice", "port")
|
|
||||||
.map(Number::longValue);
|
|
||||||
if (port.isEmpty()) {
|
|
||||||
logger.severe(() -> "No port defined for display of " + name());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
StringBuffer data = new StringBuffer(100)
|
|
||||||
.append("[virt-viewer]\ntype=spice\nhost=")
|
|
||||||
.append(addr.get().getHostAddress()).append("\nport=")
|
|
||||||
.append(port.get().toString())
|
|
||||||
.append('\n');
|
|
||||||
if (password != null) {
|
|
||||||
data.append("password=").append(password).append('\n');
|
|
||||||
}
|
|
||||||
this.<String> fromVm("display", "spice", "proxyUrl")
|
|
||||||
.ifPresent(u -> {
|
|
||||||
if (!Strings.isNullOrEmpty(u)) {
|
|
||||||
data.append("proxy=").append(u).append('\n');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (deleteConnectionFile) {
|
|
||||||
data.append("delete-this-file=1\n");
|
|
||||||
}
|
|
||||||
return data.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<InetAddress> displayIp(Class<?> preferredIpVersion) {
|
|
||||||
Optional<String> server = fromVm("display", "spice", "server");
|
|
||||||
if (server.isPresent()) {
|
|
||||||
var srv = server.get();
|
|
||||||
try {
|
|
||||||
var addr = InetAddress.getByName(srv);
|
|
||||||
logger.fine(() -> "Using IP address from CRD for "
|
|
||||||
+ getMetadata().getName() + ": " + addr);
|
|
||||||
return Optional.of(addr);
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
logger.log(Level.SEVERE, e, () -> "Invalid server address "
|
|
||||||
+ srv + ": " + e.getMessage());
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var addrs = Optional.<List<String>> ofNullable(
|
|
||||||
extra("nodeAddresses")).orElse(Collections.emptyList()).stream()
|
|
||||||
.map(a -> {
|
|
||||||
try {
|
|
||||||
return InetAddress.getByName(a);
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
logger.warning(() -> "Invalid IP address: " + a);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}).filter(a -> a != null).toList();
|
|
||||||
logger.fine(() -> "Known IP addresses for " + name() + ": " + addrs);
|
|
||||||
return addrs.stream()
|
|
||||||
.filter(a -> preferredIpVersion.isAssignableFrom(a.getClass()))
|
|
||||||
.findFirst().or(() -> addrs.stream().findFirst());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash code.
|
* Hash code.
|
||||||
*
|
*
|
||||||
|
|
@ -569,7 +399,7 @@ public class VmDefinition {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(metadata.getNamespace(), metadata.getName());
|
return Objects.hash(metadata().getNamespace(), metadata().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -590,9 +420,55 @@ public class VmDefinition {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
VmDefinition other = (VmDefinition) obj;
|
VmDefinition other = (VmDefinition) obj;
|
||||||
return Objects.equals(metadata.getNamespace(),
|
return Objects.equals(metadata().getNamespace(),
|
||||||
other.metadata.getNamespace())
|
other.metadata().getNamespace())
|
||||||
&& Objects.equals(metadata.getName(), other.metadata.getName());
|
&& Objects.equals(metadata().getName(), other.metadata().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Class Model.
|
||||||
|
*/
|
||||||
|
public static class Model {
|
||||||
|
|
||||||
|
private Map<String, Object> spec;
|
||||||
|
private Map<String, Object> status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the spec.
|
||||||
|
*
|
||||||
|
* @return the spec
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getSpec() {
|
||||||
|
return spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the spec.
|
||||||
|
*
|
||||||
|
* @param spec the spec to set
|
||||||
|
*/
|
||||||
|
public void setSpec(Map<String, Object> spec) {
|
||||||
|
this.spec = spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the status.
|
||||||
|
*
|
||||||
|
* @return the status
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the status.
|
||||||
|
*
|
||||||
|
* @param status the status to set
|
||||||
|
*/
|
||||||
|
public void setStatus(Map<String, Object> status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a VM definition.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("PMD.DataClass")
|
|
||||||
public class VmDefinitionModel extends K8sDynamicModel {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates a new model from the JSON representation.
|
|
||||||
*
|
|
||||||
* @param delegate the gson instance to use for extracting structured data
|
|
||||||
* @param json the JSON
|
|
||||||
*/
|
|
||||||
public VmDefinitionModel(Gson delegate, JsonObject json) {
|
|
||||||
super(delegate, json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -33,10 +33,10 @@ import java.util.Collection;
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
||||||
public class VmDefinitionStub
|
public class VmDefinitionStub
|
||||||
extends K8sDynamicStubBase<VmDefinitionModel, VmDefinitionModels> {
|
extends K8sDynamicStubBase<VmDefinition, VmDefinitions> {
|
||||||
|
|
||||||
private static DynamicTypeAdapterFactory<VmDefinitionModel,
|
private static DynamicTypeAdapterFactory<VmDefinition,
|
||||||
VmDefinitionModels> taf = new VmDefintionModelTypeAdapterFactory();
|
VmDefinitions> taf = new VmDefintionModelTypeAdapterFactory();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new stub for VM defintions.
|
* Instantiates a new stub for VM defintions.
|
||||||
|
|
@ -48,7 +48,7 @@ public class VmDefinitionStub
|
||||||
*/
|
*/
|
||||||
public VmDefinitionStub(K8sClient client, APIResource context,
|
public VmDefinitionStub(K8sClient client, APIResource context,
|
||||||
String namespace, String name) {
|
String namespace, String name) {
|
||||||
super(VmDefinitionModel.class, VmDefinitionModels.class, taf, client,
|
super(VmDefinition.class, VmDefinitions.class, taf, client,
|
||||||
context, namespace, name);
|
context, namespace, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,10 +101,10 @@ public class VmDefinitionStub
|
||||||
*/
|
*/
|
||||||
public static VmDefinitionStub createFromYaml(K8sClient client,
|
public static VmDefinitionStub createFromYaml(K8sClient client,
|
||||||
APIResource context, Reader yaml) throws ApiException {
|
APIResource context, Reader yaml) throws ApiException {
|
||||||
var model = new VmDefinitionModel(client.getJSON().getGson(),
|
var model = new VmDefinition(client.getJSON().getGson(),
|
||||||
K8s.yamlToJson(client, yaml));
|
K8s.yamlToJson(client, yaml));
|
||||||
return K8sGenericStub.create(VmDefinitionModel.class,
|
return K8sGenericStub.create(VmDefinition.class,
|
||||||
VmDefinitionModels.class, client, context, model,
|
VmDefinitions.class, client, context, model,
|
||||||
(c, ns, n) -> new VmDefinitionStub(c, context, ns, n));
|
(c, ns, n) -> new VmDefinitionStub(c, context, ns, n));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,8 +121,8 @@ public class VmDefinitionStub
|
||||||
public static Collection<VmDefinitionStub> list(K8sClient client,
|
public static Collection<VmDefinitionStub> list(K8sClient client,
|
||||||
APIResource context, String namespace, ListOptions options)
|
APIResource context, String namespace, ListOptions options)
|
||||||
throws ApiException {
|
throws ApiException {
|
||||||
return K8sGenericStub.list(VmDefinitionModel.class,
|
return K8sGenericStub.list(VmDefinition.class,
|
||||||
VmDefinitionModels.class, client, context, namespace, options,
|
VmDefinitions.class, client, context, namespace, options,
|
||||||
(c, ns, n) -> new VmDefinitionStub(c, context, ns, n));
|
(c, ns, n) -> new VmDefinitionStub(c, context, ns, n));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,13 +144,13 @@ public class VmDefinitionStub
|
||||||
* A factory for creating VmDefinitionModel(s) objects.
|
* A factory for creating VmDefinitionModel(s) objects.
|
||||||
*/
|
*/
|
||||||
public static class VmDefintionModelTypeAdapterFactory extends
|
public static class VmDefintionModelTypeAdapterFactory extends
|
||||||
DynamicTypeAdapterFactory<VmDefinitionModel, VmDefinitionModels> {
|
DynamicTypeAdapterFactory<VmDefinition, VmDefinitions> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new dynamic model type adapter factory.
|
* Instantiates a new dynamic model type adapter factory.
|
||||||
*/
|
*/
|
||||||
public VmDefintionModelTypeAdapterFactory() {
|
public VmDefintionModelTypeAdapterFactory() {
|
||||||
super(VmDefinitionModel.class, VmDefinitionModels.class);
|
super(VmDefinition.class, VmDefinitions.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,10 @@ import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a list of {@link VmDefinitionModel}s.
|
* Represents a list of {@link VmDefinition}s.
|
||||||
*/
|
*/
|
||||||
public class VmDefinitionModels
|
public class VmDefinitions
|
||||||
extends K8sDynamicModelsBase<VmDefinitionModel> {
|
extends K8sDynamicModelsBase<VmDefinition> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the object list using the given JSON data.
|
* Initialize the object list using the given JSON data.
|
||||||
|
|
@ -33,7 +33,7 @@ public class VmDefinitionModels
|
||||||
* @param delegate the gson instance to use for extracting structured data
|
* @param delegate the gson instance to use for extracting structured data
|
||||||
* @param data the data
|
* @param data the data
|
||||||
*/
|
*/
|
||||||
public VmDefinitionModels(Gson delegate, JsonObject data) {
|
public VmDefinitions(Gson delegate, JsonObject data) {
|
||||||
super(VmDefinitionModel.class, delegate, data);
|
super(VmDefinition.class, delegate, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2025 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.util.Strings;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents internally used dynamic data associated with a
|
||||||
|
* {@link VmDefinition}.
|
||||||
|
*/
|
||||||
|
public class VmExtraData {
|
||||||
|
|
||||||
|
@SuppressWarnings("PMD.FieldNamingConventions")
|
||||||
|
private static final Logger logger
|
||||||
|
= Logger.getLogger(VmExtraData.class.getName());
|
||||||
|
|
||||||
|
private final VmDefinition vmDef;
|
||||||
|
private String nodeName = "";
|
||||||
|
private List<String> nodeAddresses = Collections.emptyList();
|
||||||
|
private long resetCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new instance.
|
||||||
|
*
|
||||||
|
* @param vmDef the VM definition
|
||||||
|
*/
|
||||||
|
public VmExtraData(VmDefinition vmDef) {
|
||||||
|
this.vmDef = vmDef;
|
||||||
|
vmDef.extra(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the node info.
|
||||||
|
*
|
||||||
|
* @param name the name
|
||||||
|
* @param addresses the addresses
|
||||||
|
* @return the VM extra data
|
||||||
|
*/
|
||||||
|
public VmExtraData nodeInfo(String name, List<String> addresses) {
|
||||||
|
nodeName = name;
|
||||||
|
nodeAddresses = addresses;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the node name.
|
||||||
|
*
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
public String nodeName() {
|
||||||
|
return nodeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the reset count.
|
||||||
|
*
|
||||||
|
* @param resetCount the reset count
|
||||||
|
* @return the vm extra data
|
||||||
|
*/
|
||||||
|
public VmExtraData resetCount(long resetCount) {
|
||||||
|
this.resetCount = resetCount;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the reset count.
|
||||||
|
*
|
||||||
|
* @return the long
|
||||||
|
*/
|
||||||
|
public long resetCount() {
|
||||||
|
return resetCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a connection file.
|
||||||
|
*
|
||||||
|
* @param password the password
|
||||||
|
* @param preferredIpVersion the preferred IP version
|
||||||
|
* @param deleteConnectionFile the delete connection file
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
public String connectionFile(String password,
|
||||||
|
Class<?> preferredIpVersion, boolean deleteConnectionFile) {
|
||||||
|
var addr = displayIp(preferredIpVersion);
|
||||||
|
if (addr.isEmpty()) {
|
||||||
|
logger
|
||||||
|
.severe(() -> "Failed to find display IP for " + vmDef.name());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var port = vmDef.<Number> fromVm("display", "spice", "port")
|
||||||
|
.map(Number::longValue);
|
||||||
|
if (port.isEmpty()) {
|
||||||
|
logger
|
||||||
|
.severe(() -> "No port defined for display of " + vmDef.name());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
StringBuffer data = new StringBuffer(100)
|
||||||
|
.append("[virt-viewer]\ntype=spice\nhost=")
|
||||||
|
.append(addr.get().getHostAddress()).append("\nport=")
|
||||||
|
.append(port.get().toString())
|
||||||
|
.append('\n');
|
||||||
|
if (password != null) {
|
||||||
|
data.append("password=").append(password).append('\n');
|
||||||
|
}
|
||||||
|
vmDef.<String> fromVm("display", "spice", "proxyUrl")
|
||||||
|
.ifPresent(u -> {
|
||||||
|
if (!Strings.isNullOrEmpty(u)) {
|
||||||
|
data.append("proxy=").append(u).append('\n');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (deleteConnectionFile) {
|
||||||
|
data.append("delete-this-file=1\n");
|
||||||
|
}
|
||||||
|
return data.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<InetAddress> displayIp(Class<?> preferredIpVersion) {
|
||||||
|
Optional<String> server = vmDef.fromVm("display", "spice", "server");
|
||||||
|
if (server.isPresent()) {
|
||||||
|
var srv = server.get();
|
||||||
|
try {
|
||||||
|
var addr = InetAddress.getByName(srv);
|
||||||
|
logger.fine(() -> "Using IP address from CRD for "
|
||||||
|
+ vmDef.metadata().getName() + ": " + addr);
|
||||||
|
return Optional.of(addr);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
logger.log(Level.SEVERE, e, () -> "Invalid server address "
|
||||||
|
+ srv + ": " + e.getMessage());
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var addrs = nodeAddresses.stream().map(a -> {
|
||||||
|
try {
|
||||||
|
return InetAddress.getByName(a);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
logger.warning(() -> "Invalid IP address: " + a);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}).filter(Objects::nonNull).toList();
|
||||||
|
logger.fine(
|
||||||
|
() -> "Known IP addresses for " + vmDef.name() + ": " + addrs);
|
||||||
|
return addrs.stream()
|
||||||
|
.filter(a -> preferredIpVersion.isAssignableFrom(a.getClass()))
|
||||||
|
.findFirst().or(() -> addrs.stream().findFirst());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ metadata:
|
||||||
annotations:
|
annotations:
|
||||||
vmoperator.jdrupes.org/version: ${ managerVersion }
|
vmoperator.jdrupes.org/version: ${ managerVersion }
|
||||||
ownerReferences:
|
ownerReferences:
|
||||||
- apiVersion: ${ cr.apiVersion }
|
- apiVersion: ${ cr.apiVersion() }
|
||||||
kind: ${ constants.VM_OP_KIND_VM }
|
kind: ${ constants.VM_OP_KIND_VM }
|
||||||
name: ${ cr.name() }
|
name: ${ cr.name() }
|
||||||
uid: ${ cr.metadata().getUid() }
|
uid: ${ cr.metadata().getUid() }
|
||||||
|
|
@ -53,7 +53,7 @@ data:
|
||||||
# i.e. if you start the VM without a value for this property, and
|
# i.e. if you start the VM without a value for this property, and
|
||||||
# decide to trigger a reset later, you have to first set the value
|
# decide to trigger a reset later, you have to first set the value
|
||||||
# and then inrement it.
|
# and then inrement it.
|
||||||
resetCounter: ${ cr.extra("resetCount")?c }
|
resetCounter: ${ cr.extra().get().resetCount()?c }
|
||||||
|
|
||||||
# Forward the cloud-init data if provided
|
# Forward the cloud-init data if provided
|
||||||
<#if spec.cloudInit??>
|
<#if spec.cloudInit??>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ metadata:
|
||||||
annotations:
|
annotations:
|
||||||
vmoperator.jdrupes.org/version: ${ managerVersion }
|
vmoperator.jdrupes.org/version: ${ managerVersion }
|
||||||
ownerReferences:
|
ownerReferences:
|
||||||
- apiVersion: ${ cr.apiVersion }
|
- apiVersion: ${ cr.apiVersion() }
|
||||||
kind: ${ constants.VM_OP_KIND_VM }
|
kind: ${ constants.VM_OP_KIND_VM }
|
||||||
name: ${ cr.name() }
|
name: ${ cr.name() }
|
||||||
uid: ${ cr.metadata().getUid() }
|
uid: ${ cr.metadata().getUid() }
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ metadata:
|
||||||
vmrunner.jdrupes.org/cmVersion: "${ cm.metadata.resourceVersion }"
|
vmrunner.jdrupes.org/cmVersion: "${ cm.metadata.resourceVersion }"
|
||||||
vmoperator.jdrupes.org/version: ${ managerVersion }
|
vmoperator.jdrupes.org/version: ${ managerVersion }
|
||||||
ownerReferences:
|
ownerReferences:
|
||||||
- apiVersion: ${ cr.apiVersion }
|
- apiVersion: ${ cr.apiVersion() }
|
||||||
kind: ${ constants.VM_OP_KIND_VM }
|
kind: ${ constants.VM_OP_KIND_VM }
|
||||||
name: ${ cr.name() }
|
name: ${ cr.name() }
|
||||||
uid: ${ cr.metadata().getUid() }
|
uid: ${ cr.metadata().getUid() }
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ 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 java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
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;
|
||||||
|
|
@ -211,7 +212,10 @@ public class Controller extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the assignment information in the status of the VM CR.
|
* Attempt to Update the assignment information in the status of the
|
||||||
|
* VM CR. Returns true if successful. The handler does not attempt
|
||||||
|
* retries, because in case of failure it will be necessary to
|
||||||
|
* re-evaluate the chosen VM.
|
||||||
*
|
*
|
||||||
* @param event the event
|
* @param event the event
|
||||||
* @param channel the channel
|
* @param channel the channel
|
||||||
|
|
@ -220,18 +224,27 @@ public class Controller extends Component {
|
||||||
@Handler
|
@Handler
|
||||||
public void onUpdatedAssignment(UpdateAssignment event, VmChannel channel)
|
public void onUpdatedAssignment(UpdateAssignment event, VmChannel channel)
|
||||||
throws ApiException {
|
throws ApiException {
|
||||||
|
try {
|
||||||
var vmDef = channel.vmDefinition();
|
var vmDef = channel.vmDefinition();
|
||||||
var vmStub = VmDefinitionStub.get(channel.client(),
|
var vmStub = VmDefinitionStub.get(channel.client(),
|
||||||
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM),
|
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM),
|
||||||
vmDef.namespace(), vmDef.name());
|
vmDef.namespace(), vmDef.name());
|
||||||
vmStub.updateStatus(from -> {
|
if (vmStub.updateStatus(vmDef, from -> {
|
||||||
JsonObject status = from.status();
|
JsonObject status = from.statusJson();
|
||||||
var assignment = GsonPtr.to(status).to("assignment");
|
var assignment = GsonPtr.to(status).to("assignment");
|
||||||
assignment.set("pool", event.usedPool());
|
assignment.set("pool", event.usedPool());
|
||||||
assignment.set("user", event.toUser());
|
assignment.set("user", event.toUser());
|
||||||
assignment.set("lastUsed", Instant.now().toString());
|
assignment.set("lastUsed", Instant.now().toString());
|
||||||
return status;
|
return status;
|
||||||
});
|
}).isPresent()) {
|
||||||
event.setResult(true);
|
event.setResult(true);
|
||||||
}
|
}
|
||||||
|
} catch (ApiException e) {
|
||||||
|
// Log exceptions except for conflict, which can be expected
|
||||||
|
if (HttpURLConnection.HTTP_CONFLICT != e.getCode()) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event.setResult(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ public class DisplaySecretMonitor
|
||||||
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM),
|
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM),
|
||||||
event.vmDefinition().namespace(), event.vmDefinition().name());
|
event.vmDefinition().namespace(), event.vmDefinition().name());
|
||||||
vmStub.updateStatus(from -> {
|
vmStub.updateStatus(from -> {
|
||||||
JsonObject status = from.status();
|
JsonObject status = from.statusJson();
|
||||||
status.addProperty("consoleUser", event.user());
|
status.addProperty("consoleUser", event.user());
|
||||||
return status;
|
return status;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,8 @@ public class PoolMonitor extends
|
||||||
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM),
|
new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM),
|
||||||
vmDef.namespace(), vmDef.name());
|
vmDef.namespace(), vmDef.name());
|
||||||
vmStub.updateStatus(from -> {
|
vmStub.updateStatus(from -> {
|
||||||
JsonObject status = from.status();
|
// TODO
|
||||||
|
JsonObject status = from.statusJson();
|
||||||
var assignment = GsonPtr.to(status).to("assignment");
|
var assignment = GsonPtr.to(status).to("assignment");
|
||||||
assignment.set("lastUsed", ccChange.get().toString());
|
assignment.set("lastUsed", ccChange.get().toString());
|
||||||
return status;
|
return status;
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@ public class Reconciler extends Component {
|
||||||
public void onResetVm(ResetVm event, VmChannel channel)
|
public void onResetVm(ResetVm event, VmChannel channel)
|
||||||
throws ApiException, IOException, TemplateException {
|
throws ApiException, IOException, TemplateException {
|
||||||
var vmDef = channel.vmDefinition();
|
var vmDef = channel.vmDefinition();
|
||||||
vmDef.extra("resetCount", vmDef.<Long> extra("resetCount") + 1);
|
vmDef.extra().ifPresent(e -> e.resetCount(e.resetCount() + 1));
|
||||||
Map<String, Object> model
|
Map<String, Object> model
|
||||||
= prepareModel(channel.client(), channel.vmDefinition());
|
= prepareModel(channel.client(), channel.vmDefinition());
|
||||||
cmReconciler.reconcile(model, channel);
|
cmReconciler.reconcile(model, channel);
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,9 @@ import org.jdrupes.vmoperator.common.K8sV1ConfigMapStub;
|
||||||
import org.jdrupes.vmoperator.common.K8sV1PodStub;
|
import org.jdrupes.vmoperator.common.K8sV1PodStub;
|
||||||
import org.jdrupes.vmoperator.common.K8sV1StatefulSetStub;
|
import org.jdrupes.vmoperator.common.K8sV1StatefulSetStub;
|
||||||
import org.jdrupes.vmoperator.common.VmDefinition;
|
import org.jdrupes.vmoperator.common.VmDefinition;
|
||||||
import org.jdrupes.vmoperator.common.VmDefinitionModel;
|
|
||||||
import org.jdrupes.vmoperator.common.VmDefinitionModels;
|
|
||||||
import org.jdrupes.vmoperator.common.VmDefinitionStub;
|
import org.jdrupes.vmoperator.common.VmDefinitionStub;
|
||||||
|
import org.jdrupes.vmoperator.common.VmDefinitions;
|
||||||
|
import org.jdrupes.vmoperator.common.VmExtraData;
|
||||||
import org.jdrupes.vmoperator.common.VmPool;
|
import org.jdrupes.vmoperator.common.VmPool;
|
||||||
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_NAME;
|
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
|
||||||
|
|
@ -65,7 +65,7 @@ import org.jgrapes.core.annotation.Handler;
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports" })
|
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports" })
|
||||||
public class VmMonitor extends
|
public class VmMonitor extends
|
||||||
AbstractMonitor<VmDefinitionModel, VmDefinitionModels, VmChannel> {
|
AbstractMonitor<VmDefinition, VmDefinitions, VmChannel> {
|
||||||
|
|
||||||
private final ChannelManager<String, VmChannel, ?> channelManager;
|
private final ChannelManager<String, VmChannel, ?> channelManager;
|
||||||
|
|
||||||
|
|
@ -77,8 +77,8 @@ public class VmMonitor extends
|
||||||
*/
|
*/
|
||||||
public VmMonitor(Channel componentChannel,
|
public VmMonitor(Channel componentChannel,
|
||||||
ChannelManager<String, VmChannel, ?> channelManager) {
|
ChannelManager<String, VmChannel, ?> channelManager) {
|
||||||
super(componentChannel, VmDefinitionModel.class,
|
super(componentChannel, VmDefinition.class,
|
||||||
VmDefinitionModels.class);
|
VmDefinitions.class);
|
||||||
this.channelManager = channelManager;
|
this.channelManager = channelManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,7 +122,7 @@ public class VmMonitor extends
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleChange(K8sClient client,
|
protected void handleChange(K8sClient client,
|
||||||
Watch.Response<VmDefinitionModel> response) {
|
Watch.Response<VmDefinition> response) {
|
||||||
V1ObjectMeta metadata = response.object.getMetadata();
|
V1ObjectMeta metadata = response.object.getMetadata();
|
||||||
AtomicBoolean toBeAdded = new AtomicBoolean(false);
|
AtomicBoolean toBeAdded = new AtomicBoolean(false);
|
||||||
VmChannel channel = channelManager.channel(metadata.getName())
|
VmChannel channel = channelManager.channel(metadata.getName())
|
||||||
|
|
@ -132,21 +132,17 @@ public class VmMonitor extends
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get full definition and associate with channel as backup
|
// Get full definition and associate with channel as backup
|
||||||
var vmModel = response.object;
|
var vmDef = response.object;
|
||||||
if (vmModel.data() == null) {
|
if (vmDef.data() == null) {
|
||||||
// ADDED event does not provide data, see
|
// ADDED event does not provide data, see
|
||||||
// https://github.com/kubernetes-client/java/issues/3215
|
// https://github.com/kubernetes-client/java/issues/3215
|
||||||
vmModel = getModel(client, vmModel);
|
vmDef = getModel(client, vmDef);
|
||||||
}
|
}
|
||||||
VmDefinition vmDef = null;
|
if (vmDef.data() != null) {
|
||||||
if (vmModel.data() != null) {
|
|
||||||
// New data, augment and save
|
// New data, augment and save
|
||||||
vmDef = client.getJSON().getGson().fromJson(vmModel.data(),
|
addExtraData(channel.client(), vmDef, channel.vmDefinition());
|
||||||
VmDefinition.class);
|
|
||||||
addDynamicData(channel.client(), vmDef, channel.vmDefinition());
|
|
||||||
channel.setVmDefinition(vmDef);
|
channel.setVmDefinition(vmDef);
|
||||||
}
|
} else {
|
||||||
if (vmDef == null) {
|
|
||||||
// Reuse cached (e.g. if deleted)
|
// Reuse cached (e.g. if deleted)
|
||||||
vmDef = channel.vmDefinition();
|
vmDef = channel.vmDefinition();
|
||||||
}
|
}
|
||||||
|
|
@ -173,8 +169,7 @@ public class VmMonitor extends
|
||||||
channel.pipeline().fire(chgEvt, channel);
|
channel.pipeline().fire(chgEvt, channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private VmDefinitionModel getModel(K8sClient client,
|
private VmDefinition getModel(K8sClient client, VmDefinition vmDef) {
|
||||||
VmDefinitionModel vmDef) {
|
|
||||||
try {
|
try {
|
||||||
return VmDefinitionStub.get(client, context(), namespace(),
|
return VmDefinitionStub.get(client, context(), namespace(),
|
||||||
vmDef.metadata().getName()).model().orElse(null);
|
vmDef.metadata().getName()).model().orElse(null);
|
||||||
|
|
@ -184,17 +179,14 @@ public class VmMonitor extends
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
|
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
|
||||||
private void addDynamicData(K8sClient client, VmDefinition vmDef,
|
private void addExtraData(K8sClient client, VmDefinition vmDef,
|
||||||
VmDefinition prevState) {
|
VmDefinition prevState) {
|
||||||
// Maintain (or initialize) the resetCount
|
var extra = new VmExtraData(vmDef);
|
||||||
vmDef.extra("resetCount",
|
|
||||||
Optional.ofNullable(prevState).map(d -> d.extra("resetCount"))
|
|
||||||
.orElse(0L));
|
|
||||||
|
|
||||||
// Node information
|
// Maintain (or initialize) the resetCount
|
||||||
// Add defaults in case the VM is not running
|
extra.resetCount(
|
||||||
vmDef.extra("nodeName", "");
|
Optional.ofNullable(prevState).flatMap(VmDefinition::extra)
|
||||||
vmDef.extra("nodeAddress", "");
|
.map(VmExtraData::resetCount).orElse(0L));
|
||||||
|
|
||||||
// VM definition status changes before the pod terminates.
|
// VM definition status changes before the pod terminates.
|
||||||
// This results in pod information being shown for a stopped
|
// This results in pod information being shown for a stopped
|
||||||
|
|
@ -202,6 +194,8 @@ public class VmMonitor extends
|
||||||
if (!vmDef.conditionStatus("Running").orElse(false)) {
|
if (!vmDef.conditionStatus("Running").orElse(false)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get pod and extract node information.
|
||||||
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
|
||||||
|
|
@ -211,16 +205,15 @@ public class VmMonitor extends
|
||||||
= K8sV1PodStub.list(client, namespace(), podSearch);
|
= K8sV1PodStub.list(client, namespace(), podSearch);
|
||||||
for (var podStub : podList) {
|
for (var podStub : podList) {
|
||||||
var nodeName = podStub.model().get().getSpec().getNodeName();
|
var nodeName = podStub.model().get().getSpec().getNodeName();
|
||||||
vmDef.extra("nodeName", nodeName);
|
logger.fine(() -> "Adding node name " + nodeName
|
||||||
logger.fine(() -> "Added node name " + nodeName
|
|
||||||
+ " to VM info for " + vmDef.name());
|
+ " to VM info for " + vmDef.name());
|
||||||
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
|
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
|
||||||
var addrs = new ArrayList<String>();
|
var addrs = new ArrayList<String>();
|
||||||
podStub.model().get().getStatus().getPodIPs().stream()
|
podStub.model().get().getStatus().getPodIPs().stream()
|
||||||
.map(ip -> ip.getIp()).forEach(addrs::add);
|
.map(ip -> ip.getIp()).forEach(addrs::add);
|
||||||
vmDef.extra("nodeAddresses", addrs);
|
logger.fine(() -> "Adding node addresses " + addrs
|
||||||
logger.fine(() -> "Added node addresses " + addrs
|
|
||||||
+ " to VM info for " + vmDef.name());
|
+ " to VM info for " + vmDef.name());
|
||||||
|
extra.nodeInfo(nodeName, addrs);
|
||||||
}
|
}
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
logger.log(Level.WARNING, e,
|
logger.log(Level.WARNING, e,
|
||||||
|
|
@ -262,20 +255,21 @@ public class VmMonitor extends
|
||||||
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
|
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
|
||||||
public void onAssignVm(AssignVm event)
|
public void onAssignVm(AssignVm event)
|
||||||
throws ApiException, InterruptedException {
|
throws ApiException, InterruptedException {
|
||||||
|
while (true) {
|
||||||
// Search for existing assignment.
|
// Search for existing assignment.
|
||||||
var assignedVm = channelManager.channels().stream()
|
var vmQuery = channelManager.channels().stream()
|
||||||
.filter(c -> c.vmDefinition().assignedFrom()
|
.filter(c -> c.vmDefinition().assignedFrom()
|
||||||
.map(p -> p.equals(event.fromPool())).orElse(false))
|
.map(p -> p.equals(event.fromPool())).orElse(false))
|
||||||
.filter(c -> c.vmDefinition().assignedTo()
|
.filter(c -> c.vmDefinition().assignedTo()
|
||||||
.map(u -> u.equals(event.toUser())).orElse(false))
|
.map(u -> u.equals(event.toUser())).orElse(false))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
if (assignedVm.isPresent()) {
|
if (vmQuery.isPresent()) {
|
||||||
var vmDef = assignedVm.get().vmDefinition();
|
var vmDef = vmQuery.get().vmDefinition();
|
||||||
event.setResult(new VmData(vmDef, assignedVm.get()));
|
event.setResult(new VmData(vmDef, vmQuery.get()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the pool definition assignability check
|
// Get the pool definition for checking possible assignment
|
||||||
VmPool vmPool = newEventPipeline().fire(new GetPools()
|
VmPool vmPool = newEventPipeline().fire(new GetPools()
|
||||||
.withName(event.fromPool())).get().stream().findFirst()
|
.withName(event.fromPool())).get().stream().findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|
@ -284,7 +278,7 @@ public class VmMonitor extends
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find available VM.
|
// Find available VM.
|
||||||
assignedVm = channelManager.channels().stream()
|
vmQuery = channelManager.channels().stream()
|
||||||
.filter(c -> vmPool.isAssignable(c.vmDefinition()))
|
.filter(c -> vmPool.isAssignable(c.vmDefinition()))
|
||||||
.sorted(Comparator.comparing((VmChannel c) -> c.vmDefinition()
|
.sorted(Comparator.comparing((VmChannel c) -> c.vmDefinition()
|
||||||
.assignmentLastUsed().orElse(Instant.ofEpochSecond(0)))
|
.assignmentLastUsed().orElse(Instant.ofEpochSecond(0)))
|
||||||
|
|
@ -292,19 +286,25 @@ public class VmMonitor extends
|
||||||
.findFirst();
|
.findFirst();
|
||||||
|
|
||||||
// None found
|
// None found
|
||||||
if (assignedVm.isEmpty()) {
|
if (vmQuery.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign to user
|
// Assign to user
|
||||||
assignedVm.get().pipeline().fire(new UpdateAssignment(vmPool.name(),
|
var chosenVm = vmQuery.get();
|
||||||
event.toUser()), assignedVm.get()).get();
|
var vmPipeline = chosenVm.pipeline();
|
||||||
var vmDef = assignedVm.get().vmDefinition();
|
if (Optional.ofNullable(vmPipeline.fire(new UpdateAssignment(
|
||||||
event.setResult(new VmData(vmDef, assignedVm.get()));
|
vmPool.name(), event.toUser()), chosenVm).get())
|
||||||
|
.orElse(false)) {
|
||||||
|
var vmDef = chosenVm.vmDefinition();
|
||||||
|
event.setResult(new VmData(vmDef, chosenVm));
|
||||||
|
|
||||||
// Make sure that a newly assigned VM is running.
|
// Make sure that a newly assigned VM is running.
|
||||||
assignedVm.get().pipeline().fire(new ModifyVm(vmDef.name(),
|
chosenVm.pipeline().fire(new ModifyVm(vmDef.name(),
|
||||||
"state", "Running", assignedVm.get()));
|
"state", "Running", chosenVm));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Comparator<VmChannel> preferRunning
|
private static Comparator<VmChannel> preferRunning
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ public class ConsoleTracker extends VmDefUpdater {
|
||||||
mainChannelClientHost = event.clientHost();
|
mainChannelClientHost = event.clientHost();
|
||||||
mainChannelClientPort = event.clientPort();
|
mainChannelClientPort = event.clientPort();
|
||||||
vmStub.updateStatus(from -> {
|
vmStub.updateStatus(from -> {
|
||||||
JsonObject status = from.status();
|
JsonObject status = from.statusJson();
|
||||||
status.addProperty("consoleClient", event.clientHost());
|
status.addProperty("consoleClient", event.clientHost());
|
||||||
updateCondition(from, status, "ConsoleConnected", true, "Connected",
|
updateCondition(from, status, "ConsoleConnected", true, "Connected",
|
||||||
"Connection from " + event.clientHost());
|
"Connection from " + event.clientHost());
|
||||||
|
|
@ -141,7 +141,7 @@ public class ConsoleTracker extends VmDefUpdater {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
vmStub.updateStatus(from -> {
|
vmStub.updateStatus(from -> {
|
||||||
JsonObject status = from.status();
|
JsonObject status = from.statusJson();
|
||||||
status.addProperty("consoleClient", "");
|
status.addProperty("consoleClient", "");
|
||||||
updateCondition(from, status, "ConsoleConnected", false,
|
updateCondition(from, status, "ConsoleConnected", false,
|
||||||
"Disconnected", event.clientHost() + " has disconnected");
|
"Disconnected", event.clientHost() + " has disconnected");
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ 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.VmDefinitionModel;
|
import org.jdrupes.vmoperator.common.VmDefinition;
|
||||||
import org.jdrupes.vmoperator.common.VmDefinitionStub;
|
import org.jdrupes.vmoperator.common.VmDefinitionStub;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.BalloonChangeEvent;
|
import org.jdrupes.vmoperator.runner.qemu.events.BalloonChangeEvent;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
|
import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
|
||||||
|
|
@ -140,12 +140,12 @@ public class StatusUpdater extends VmDefUpdater {
|
||||||
if (vmDef.isPresent()
|
if (vmDef.isPresent()
|
||||||
&& vmDef.get().metadata().getGeneration() == observedGeneration
|
&& vmDef.get().metadata().getGeneration() == observedGeneration
|
||||||
&& (event.configuration().hasDisplayPassword
|
&& (event.configuration().hasDisplayPassword
|
||||||
|| vmDef.get().status().getAsJsonPrimitive(
|
|| vmDef.get().statusJson().getAsJsonPrimitive(
|
||||||
"displayPasswordSerial").getAsInt() == -1)) {
|
"displayPasswordSerial").getAsInt() == -1)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
vmStub.updateStatus(vmDef.get(), from -> {
|
vmStub.updateStatus(vmDef.get(), from -> {
|
||||||
JsonObject status = from.status();
|
JsonObject status = from.statusJson();
|
||||||
if (!event.configuration().hasDisplayPassword) {
|
if (!event.configuration().hasDisplayPassword) {
|
||||||
status.addProperty("displayPasswordSerial", -1);
|
status.addProperty("displayPasswordSerial", -1);
|
||||||
}
|
}
|
||||||
|
|
@ -169,14 +169,14 @@ public class StatusUpdater extends VmDefUpdater {
|
||||||
"PMD.AvoidLiteralsInIfCondition" })
|
"PMD.AvoidLiteralsInIfCondition" })
|
||||||
public void onRunnerStateChanged(RunnerStateChange event)
|
public void onRunnerStateChanged(RunnerStateChange event)
|
||||||
throws ApiException {
|
throws ApiException {
|
||||||
VmDefinitionModel vmDef;
|
VmDefinition vmDef;
|
||||||
if (vmStub == null || (vmDef = vmStub.model().orElse(null)) == null) {
|
if (vmStub == null || (vmDef = vmStub.model().orElse(null)) == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
vmStub.updateStatus(vmDef, from -> {
|
vmStub.updateStatus(vmDef, from -> {
|
||||||
JsonObject status = from.status();
|
JsonObject status = from.statusJson();
|
||||||
boolean running = RUNNING_STATES.contains(event.runState());
|
boolean running = RUNNING_STATES.contains(event.runState());
|
||||||
updateCondition(vmDef, vmDef.status(), "Running", running,
|
updateCondition(vmDef, vmDef.statusJson(), "Running", running,
|
||||||
event.reason(), event.message());
|
event.reason(), event.message());
|
||||||
if (event.runState() == RunState.STARTING) {
|
if (event.runState() == RunState.STARTING) {
|
||||||
status.addProperty("ram", GsonPtr.to(from.data())
|
status.addProperty("ram", GsonPtr.to(from.data())
|
||||||
|
|
@ -230,7 +230,7 @@ public class StatusUpdater extends VmDefUpdater {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
vmStub.updateStatus(from -> {
|
vmStub.updateStatus(from -> {
|
||||||
JsonObject status = from.status();
|
JsonObject status = from.statusJson();
|
||||||
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());
|
||||||
|
|
@ -250,7 +250,7 @@ public class StatusUpdater extends VmDefUpdater {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
vmStub.updateStatus(from -> {
|
vmStub.updateStatus(from -> {
|
||||||
JsonObject status = from.status();
|
JsonObject status = from.statusJson();
|
||||||
status.addProperty("cpus", event.usedCpus().size());
|
status.addProperty("cpus", event.usedCpus().size());
|
||||||
return status;
|
return status;
|
||||||
});
|
});
|
||||||
|
|
@ -269,7 +269,7 @@ public class StatusUpdater extends VmDefUpdater {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
vmStub.updateStatus(from -> {
|
vmStub.updateStatus(from -> {
|
||||||
JsonObject status = from.status();
|
JsonObject status = from.statusJson();
|
||||||
status.addProperty("displayPasswordSerial",
|
status.addProperty("displayPasswordSerial",
|
||||||
status.get("displayPasswordSerial").getAsLong() + 1);
|
status.get("displayPasswordSerial").getAsLong() + 1);
|
||||||
return status;
|
return status;
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import java.util.Optional;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.jdrupes.vmoperator.common.K8sClient;
|
import org.jdrupes.vmoperator.common.K8sClient;
|
||||||
import org.jdrupes.vmoperator.common.VmDefinitionModel;
|
import org.jdrupes.vmoperator.common.VmDefinition;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.Exit;
|
import org.jdrupes.vmoperator.runner.qemu.events.Exit;
|
||||||
import org.jgrapes.core.Channel;
|
import org.jgrapes.core.Channel;
|
||||||
import org.jgrapes.core.Component;
|
import org.jgrapes.core.Component;
|
||||||
|
|
@ -118,7 +118,7 @@ public class VmDefUpdater extends Component {
|
||||||
* @param reason the reason for the change
|
* @param reason the reason for the change
|
||||||
* @param message the message
|
* @param message the message
|
||||||
*/
|
*/
|
||||||
protected void updateCondition(VmDefinitionModel from, JsonObject status,
|
protected void updateCondition(VmDefinition from, JsonObject status,
|
||||||
String type, boolean state, String reason, String message) {
|
String type, boolean state, String reason, String message) {
|
||||||
// Optimize, as we can get this several times
|
// Optimize, as we can get this several times
|
||||||
var current = status.getAsJsonArray("conditions").asList().stream()
|
var current = status.getAsJsonArray("conditions").asList().stream()
|
||||||
|
|
|
||||||
|
|
@ -593,7 +593,7 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
Map.of("namespace", vmDef.namespace(),
|
Map.of("namespace", vmDef.namespace(),
|
||||||
"name", vmDef.name()),
|
"name", vmDef.name()),
|
||||||
"spec", vmDef.spec(),
|
"spec", vmDef.spec(),
|
||||||
"status", vmDef.getStatus());
|
"status", vmDef.status());
|
||||||
} catch (JsonSyntaxException e) {
|
} catch (JsonSyntaxException e) {
|
||||||
logger.log(Level.SEVERE, e,
|
logger.log(Level.SEVERE, e,
|
||||||
() -> "Failed to serialize VM definition");
|
() -> "Failed to serialize VM definition");
|
||||||
|
|
@ -810,13 +810,12 @@ public class VmAccess extends FreeMarkerConlet<VmAccess.ResourceModel> {
|
||||||
}
|
}
|
||||||
var pwQuery = Event.onCompletion(new GetDisplayPassword(vmDef, user),
|
var pwQuery = Event.onCompletion(new GetDisplayPassword(vmDef, user),
|
||||||
e -> {
|
e -> {
|
||||||
var data = vmDef.connectionFile(e.password().orElse(null),
|
vmDef.extra()
|
||||||
preferredIpVersion, deleteConnectionFile);
|
.map(xtra -> xtra.connectionFile(e.password().orElse(null),
|
||||||
if (data == null) {
|
preferredIpVersion, deleteConnectionFile))
|
||||||
return;
|
.ifPresent(
|
||||||
}
|
cf -> channel.respond(new NotifyConletView(type(),
|
||||||
channel.respond(new NotifyConletView(type(),
|
model.getConletId(), "openConsole", cf)));
|
||||||
model.getConletId(), "openConsole", data));
|
|
||||||
});
|
});
|
||||||
fire(pwQuery, vmChannel);
|
fire(pwQuery, vmChannel);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ import java.util.Set;
|
||||||
import org.jdrupes.vmoperator.common.K8sObserver;
|
import org.jdrupes.vmoperator.common.K8sObserver;
|
||||||
import org.jdrupes.vmoperator.common.VmDefinition;
|
import org.jdrupes.vmoperator.common.VmDefinition;
|
||||||
import org.jdrupes.vmoperator.common.VmDefinition.Permission;
|
import org.jdrupes.vmoperator.common.VmDefinition.Permission;
|
||||||
|
import org.jdrupes.vmoperator.common.VmExtraData;
|
||||||
import org.jdrupes.vmoperator.manager.events.ChannelTracker;
|
import org.jdrupes.vmoperator.manager.events.ChannelTracker;
|
||||||
import org.jdrupes.vmoperator.manager.events.GetDisplayPassword;
|
import org.jdrupes.vmoperator.manager.events.GetDisplayPassword;
|
||||||
import org.jdrupes.vmoperator.manager.events.ModifyVm;
|
import org.jdrupes.vmoperator.manager.events.ModifyVm;
|
||||||
|
|
@ -252,7 +253,7 @@ public class VmMgmt extends FreeMarkerConlet<VmMgmt.VmsModel> {
|
||||||
"name", vmDef.name()),
|
"name", vmDef.name()),
|
||||||
"spec", spec,
|
"spec", spec,
|
||||||
"status", status,
|
"status", status,
|
||||||
"nodeName", vmDef.extra("nodeName"),
|
"nodeName", vmDef.extra().map(VmExtraData::nodeName).orElse(""),
|
||||||
"permissions", vmDef.permissionsFor(user, roles).stream()
|
"permissions", vmDef.permissionsFor(user, roles).stream()
|
||||||
.map(VmDefinition.Permission::toString).toList());
|
.map(VmDefinition.Permission::toString).toList());
|
||||||
}
|
}
|
||||||
|
|
@ -484,13 +485,11 @@ public class VmMgmt extends FreeMarkerConlet<VmMgmt.VmsModel> {
|
||||||
}
|
}
|
||||||
var pwQuery = Event.onCompletion(new GetDisplayPassword(vmDef, user),
|
var pwQuery = Event.onCompletion(new GetDisplayPassword(vmDef, user),
|
||||||
e -> {
|
e -> {
|
||||||
var data = vmDef.connectionFile(e.password().orElse(null),
|
vmDef.extra().map(xtra -> xtra.connectionFile(
|
||||||
preferredIpVersion, deleteConnectionFile);
|
e.password().orElse(null), preferredIpVersion,
|
||||||
if (data == null) {
|
deleteConnectionFile)).ifPresent(
|
||||||
return;
|
cf -> channel.respond(new NotifyConletView(type(),
|
||||||
}
|
model.getConletId(), "openConsole", cf)));
|
||||||
channel.respond(new NotifyConletView(type(),
|
|
||||||
model.getConletId(), "openConsole", data));
|
|
||||||
});
|
});
|
||||||
fire(pwQuery, vmChannel);
|
fire(pwQuery, vmChannel);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue