diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index d5bd1c5..b151a4a 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -924,9 +924,9 @@ spec: update: type: boolean default: true - additionalServiceMetadata: + loadBalancerService: description: >- - Data to be merged with the additionalServiceMetadata + Data to be merged with the loadBalancerService defined in the manager's configuration. Values specified here override values from the manager's configuration. If the value of a label or an annotation diff --git a/dev-example/test-vm.yaml b/dev-example/test-vm.yaml index 352ae7b..0c1716b 100644 --- a/dev-example/test-vm.yaml +++ b/dev-example/test-vm.yaml @@ -9,7 +9,7 @@ spec: path: vmoperator/org.jdrupes.vmoperator.runner.qemu-arch pullPolicy: Always - additionalServiceMetadata: + loadBalancerService: labels: test2: null test3: added diff --git a/org.jdrupes.vmoperator.manager/config-sample.yaml b/org.jdrupes.vmoperator.manager/config-sample.yaml index 19e0f05..68f28f5 100644 --- a/org.jdrupes.vmoperator.manager/config-sample.yaml +++ b/org.jdrupes.vmoperator.manager/config-sample.yaml @@ -14,11 +14,13 @@ # the resource properties. ramOvercommit: 1.5 - # Additional metadata (labels and annotations) to be merged - # into the service. Must be provided as nested YAML - # additionalServiceMetadata: | + # If defined, causes a load balancer service to be created. + # May be a boolean or a string with nested yaml that + # defines additional labels or annotations to be merged + # into the service. + # loadBalancerService: | # labels: {} # annotations: {} - # Only for development: + # Explicitly specify the namespace to be managed (only for development). # namespace: vmop-dev diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerLoadBalancer.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerLoadBalancer.ftl.yaml index e103cdb..2c32aa6 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerLoadBalancer.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerLoadBalancer.ftl.yaml @@ -17,10 +17,10 @@ metadata: controller: false spec: + type: LoadBalancer ports: - name: spice port: ${ cr.spec.vm.display.spice.port.asInt?c } - clusterIP: None selector: - app.kubernetes.io/name: ${ cr.metadata.name.asString } + app.kubernetes.io/name: ${ constants.APP_NAME } app.kubernetes.io/instance: ${ cr.metadata.name.asString } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java index 008f676..f0dd304 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java @@ -29,7 +29,7 @@ import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; import io.kubernetes.client.util.generic.dynamic.Dynamics; import java.io.IOException; import java.io.StringWriter; -import java.util.HashMap; +import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.logging.Logger; @@ -41,8 +41,9 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; * Delegee for reconciling the service */ @SuppressWarnings("PMD.DataflowAnomalyAnalysis") -/* default */ class ServiceReconciler { +/* default */ class LoadBalancerReconciler { + private static final String LOAD_BALANCER_SERVICE = "loadBalancerService"; private static final String METADATA = V1APIService.SERIALIZED_NAME_METADATA; private static final String LABELS = V1ObjectMeta.SERIALIZED_NAME_LABELS; @@ -56,7 +57,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; * * @param fmConfig the fm config */ - public ServiceReconciler(Configuration fmConfig) { + public LoadBalancerReconciler(Configuration fmConfig) { this.fmConfig = fmConfig; } @@ -73,41 +74,53 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; public void reconcile(VmDefChanged event, Map model, VmChannel channel) throws IOException, TemplateException, ApiException { - // Get API - DynamicKubernetesApi svcApi = new DynamicKubernetesApi("", "v1", - "services", channel.client()); + // Check if to be generated + @SuppressWarnings("unchecked") + var lbs = Optional.of(model) + .map(m -> (Map) m.get("config")) + .map(c -> c.get(LOAD_BALANCER_SERVICE)).orElse(Boolean.FALSE); + if (lbs instanceof Boolean isOn && !isOn) { + return; + } + if (!(lbs instanceof String)) { + logger.warning(() -> "\"" + LOAD_BALANCER_SERVICE + + "\" in configuration must be boolean or string but is " + + lbs.getClass() + "."); + return; + } // Combine template and data and parse result - var fmTemplate = fmConfig.getTemplate("runnerService.ftl.yaml"); + var fmTemplate = fmConfig.getTemplate("runnerLoadBalancer.ftl.yaml"); StringWriter out = new StringWriter(); fmTemplate.process(model, out); // Avoid Yaml.load due to // https://github.com/kubernetes-client/java/issues/2741 var svcDef = Dynamics.newFromYaml( new Yaml(new SafeConstructor(new LoaderOptions())), out.toString()); - mergeMetadata(svcDef, model, channel); + mergeMetadata(svcDef, lbs, channel); // Apply + DynamicKubernetesApi svcApi = new DynamicKubernetesApi("", "v1", + "services", channel.client()); K8s.apply(svcApi, svcDef, svcDef.getRaw().toString()); } + @SuppressWarnings("unchecked") private void mergeMetadata(DynamicKubernetesObject svcDef, - Map model, VmChannel channel) { + Object lbsConfig, VmChannel channel) { // Get metadata from config - @SuppressWarnings("unchecked") - var asmData = Optional.of(model) - .map(m -> (Map) m.get("config")) - .map(c -> (String) c.get("additionalServiceMetadata")) - .map(y -> (Map) new Yaml( - new SafeConstructor(new LoaderOptions())).load(y)) - .orElseGet(() -> new HashMap()); + Map asmData = Collections.emptyMap(); + if (lbsConfig instanceof String config) { + asmData = (Map) new Yaml( + new SafeConstructor(new LoaderOptions())).load(config); + } var json = channel.client().getJSON(); JsonObject cfgMeta = json.deserialize(json.serialize(asmData), JsonObject.class); // Get metadata from VM definition var vmMeta = GsonPtr.to(channel.vmDefinition()).to("spec") - .get(JsonObject.class, "additionalServiceMetadata") + .get(JsonObject.class, LOAD_BALANCER_SERVICE) .map(JsonObject::deepCopy).orElseGet(() -> new JsonObject()); // Merge Data from VM definition into config data diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index c8ff09b..def68f6 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -60,7 +60,7 @@ public class Reconciler extends Component { private final Configuration fmConfig; private final CmReconciler cmReconciler; private final StsReconciler stsReconciler; - private final ServiceReconciler serviceReconciler; + private final LoadBalancerReconciler lbReconciler; @SuppressWarnings("PMD.UseConcurrentHashMap") private final Map config = new HashMap<>(); @@ -84,7 +84,7 @@ public class Reconciler extends Component { cmReconciler = new CmReconciler(fmConfig); stsReconciler = new StsReconciler(fmConfig); - serviceReconciler = new ServiceReconciler(fmConfig); + lbReconciler = new LoadBalancerReconciler(fmConfig); } /** @@ -137,7 +137,7 @@ public class Reconciler extends Component { var configMap = cmReconciler.reconcile(event, model, channel); model.put("cm", configMap.getRaw()); stsReconciler.reconcile(event, model, channel); - serviceReconciler.reconcile(event, model, channel); + lbReconciler.reconcile(event, model, channel); } private Map prepareModel(JsonObject vmDef)