From feb8a6eacc63a09239168dbb2ed5c53f2dd46d89 Mon Sep 17 00:00:00 2001 From: Jean Francois Denise Date: Thu, 25 Apr 2024 19:13:57 +0200 Subject: [PATCH 1/2] Use StatefulSet for HA application --- .../openshift/api/OpenShiftSupport.java | 93 ++++++++++++------- 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/openshift-deployment/api/src/main/java/org/wildfly/glow/deployment/openshift/api/OpenShiftSupport.java b/openshift-deployment/api/src/main/java/org/wildfly/glow/deployment/openshift/api/OpenShiftSupport.java index c189e6a8..ba6ffe64 100644 --- a/openshift-deployment/api/src/main/java/org/wildfly/glow/deployment/openshift/api/OpenShiftSupport.java +++ b/openshift-deployment/api/src/main/java/org/wildfly/glow/deployment/openshift/api/OpenShiftSupport.java @@ -29,6 +29,8 @@ import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; +import io.fabric8.kubernetes.api.model.apps.StatefulSet; +import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder; import io.fabric8.kubernetes.client.KubernetesClientBuilder; import io.fabric8.kubernetes.client.Watch; import io.fabric8.kubernetes.client.Watcher; @@ -88,12 +90,15 @@ public class OpenShiftSupport { private static class BuildWatcher implements Watcher, AutoCloseable { + private final CountDownLatch latch = new CountDownLatch(1); private final GlowMessageWriter writer; private boolean failed; + BuildWatcher(GlowMessageWriter writer) { this.writer = writer; } + @Override public void eventReceived(Action action, Build build) { String phase = build.getStatus().getPhase(); @@ -114,9 +119,11 @@ public void eventReceived(Action action, Build build) { @Override public void onClose(WatcherException cause) { } + void await() throws InterruptedException { latch.await(); } + boolean isFailed() { return failed; } @@ -125,10 +132,20 @@ boolean isFailed() { public void close() throws Exception { } } + private static void createAppDeployment(GlowMessageWriter writer, Path target, - OpenShiftClient osClient, String name, Map env, boolean ha, OpenShiftConfiguration config) throws Exception { + OpenShiftClient osClient, String name, Map env, boolean ha, OpenShiftConfiguration config, String deploymentKind) throws Exception { Map matchLabels = new HashMap<>(); matchLabels.put(Deployer.LABEL, name); + IntOrString value = new IntOrString(); + value.setValue(8080); + Service service = new ServiceBuilder().withNewMetadata().withLabels(createCommonLabels(config)).withName(name).endMetadata(). + withNewSpec().withPorts(new ServicePort().toBuilder().withProtocol("TCP"). + withPort(8080). + withTargetPort(value).build()).withType("ClusterIP").withSessionAffinity("None").withSelector(matchLabels).endSpec().build(); + osClient.services().resource(service).createOr(NonDeletingOperation::update); + Utils.persistResource(target, service, name + "-service.yaml"); + ContainerPort port = new ContainerPort(); port.setContainerPort(8080); port.setName("http"); @@ -147,7 +164,7 @@ private static void createAppDeployment(GlowMessageWriter writer, Path target, vars.add(new EnvVar().toBuilder().withName(entry.getKey()).withValue(entry.getValue()).build()); } if (ha) { - writer.info("\n HA enabled, 2 replicas will be started."); + writer.info("\nHA enabled, 2 replicas will be started."); IntOrString v = new IntOrString(); v.setValue(8888); Service pingService = new ServiceBuilder().withNewMetadata().withLabels(createCommonLabels(config)).withName(name + "-ping").endMetadata(). @@ -198,25 +215,28 @@ private static void createAppDeployment(GlowMessageWriter writer, Path target, Map labels = createCommonLabels(config); labels.putAll(matchLabels); - Deployment deployment = new DeploymentBuilder().withNewMetadata().withLabels(labels).withName(name).endMetadata(). - withNewSpec().withReplicas(ha ? 2 : 1). - withNewSelector().withMatchLabels(matchLabels).endSelector(). - withNewTemplate().withNewMetadata().withLabels(labels).endMetadata().withNewSpec(). - withContainers(container).withRestartPolicy("Always"). - endSpec().endTemplate().withNewStrategy().withType("RollingUpdate").endStrategy().endSpec().build(); - osClient.resources(Deployment.class).resource(deployment).createOr(NonDeletingOperation::update); - Utils.persistResource(target, deployment, name + "-deployment.yaml"); - IntOrString v = new IntOrString(); - v.setValue(8080); - Service service = new ServiceBuilder().withNewMetadata().withLabels(createCommonLabels(config)).withName(name).endMetadata(). - withNewSpec().withPorts(new ServicePort().toBuilder().withProtocol("TCP"). - withPort(8080). - withTargetPort(v).build()).withType("ClusterIP").withSessionAffinity("None").withSelector(matchLabels).endSpec().build(); - osClient.services().resource(service).createOr(NonDeletingOperation::update); - Utils.persistResource(target, service, name + "-service.yaml"); - - writer.info("\nWaiting until the application is ready ..."); - osClient.resources(Deployment.class).resource(deployment).waitUntilReady(5, TimeUnit.MINUTES); + writer.info("\nWaiting until the application " + deploymentKind + " is ready ..."); + if (ha) { + StatefulSet deployment = new StatefulSetBuilder().withNewMetadata().withLabels(labels).withName(name).endMetadata(). + withNewSpec().withReplicas(ha ? 2 : 1). + withNewSelector().withMatchLabels(matchLabels).endSelector(). + withNewTemplate().withNewMetadata().withLabels(labels).endMetadata().withNewSpec(). + withContainers(container).withRestartPolicy("Always"). + endSpec().endTemplate().withNewUpdateStrategy().withType("RollingUpdate").endUpdateStrategy().endSpec().build(); + osClient.resources(StatefulSet.class).resource(deployment).createOr(NonDeletingOperation::update); + Utils.persistResource(target, deployment, name + "-statefulset.yaml"); + osClient.resources(StatefulSet.class).resource(deployment).waitUntilReady(5, TimeUnit.MINUTES); + } else { + Deployment deployment = new DeploymentBuilder().withNewMetadata().withLabels(labels).withName(name).endMetadata(). + withNewSpec().withReplicas(ha ? 2 : 1). + withNewSelector().withMatchLabels(matchLabels).endSelector(). + withNewTemplate().withNewMetadata().withLabels(labels).endMetadata().withNewSpec(). + withContainers(container).withRestartPolicy("Always"). + endSpec().endTemplate().withNewStrategy().withType("RollingUpdate").endStrategy().endSpec().build(); + osClient.resources(Deployment.class).resource(deployment).createOr(NonDeletingOperation::update); + Utils.persistResource(target, deployment, name + "-deployment.yaml"); + osClient.resources(Deployment.class).resource(deployment).waitUntilReady(5, TimeUnit.MINUTES); + } } public static ConfigurationResolver.ResolvedEnvs getResolvedEnvs(Layer layer, Set input, Set disabledDeployers) throws Exception { @@ -224,11 +244,11 @@ public static ConfigurationResolver.ResolvedEnvs getResolvedEnvs(Layer layer, Se List deployers = getEnabledDeployers(disabledDeployers); for (Deployer d : deployers) { if (d.getSupportedLayers().contains(layer.getName())) { - Set envs = d.getResolvedEnvs(input); - if (envs != null && !envs.isEmpty()) { - resolved = new ConfigurationResolver.ResolvedEnvs("openshift/"+d.getName(), envs); - break; - } + Set envs = d.getResolvedEnvs(input); + if (envs != null && !envs.isEmpty()) { + resolved = new ConfigurationResolver.ResolvedEnvs("openshift/" + d.getName(), envs); + break; + } } } return resolved; @@ -262,7 +282,7 @@ private static List getEnabledDeployers(Set disabledDeployers) List deployers = new ArrayList<>(); for (Deployer d : existingDeployers.values()) { boolean isDisabled = isDisabled(d.getName(), disabledDeployers); - if(!isDisabled) { + if (!isDisabled) { deployers.add(d); } } @@ -296,7 +316,7 @@ public static void deploy(List deployments, appName += p.getFileName().toString().substring(0, ext); } if (appName.isEmpty()) { - appName = defaultName; + appName = defaultName; } Map env = new HashMap<>(); for (Set envs : scanResults.getSuggestions().getStronglySuggestedConfigurations().values()) { @@ -331,11 +351,11 @@ public static void deploy(List deployments, } else { writer.warn("\nThe deployer " + d.getName() + " has been disabled"); } - actualEnv.putAll(isDisabled ? Collections.emptyMap(): d.deploy(writer, target, osClient, env, host, appName, l.getName(), extraEnv)); - Set buildEnv = requiredBuildTime.get(l); + actualEnv.putAll(isDisabled ? Collections.emptyMap() : d.deploy(writer, target, osClient, env, host, appName, l.getName(), extraEnv)); + Set buildEnv = requiredBuildTime.get(l); if (buildEnv != null) { Set names = new HashSet<>(); - for(Env e : buildEnv) { + for (Env e : buildEnv) { if (!buildExtraEnv.containsKey(e.getName())) { names.add(e.getName()); } @@ -365,10 +385,11 @@ public static void deploy(List deployments, actualEnv.put("SERVER_ARGS", val); } } + String deploymentKind = ha ? "StatefulSet" : "Deployment"; if (!disabledDeployers.isEmpty()) { - writer.warn("The following environment variables will be set in the " + appName + " deployment. Make sure that the required env variables for the disabled deployer(s) have been set:\n"); + writer.warn("The following environment variables will be set in the " + appName + " " + deploymentKind + ". Make sure that the required env variables for the disabled deployer(s) have been set:\n"); } else { - writer.warn("The following environment variables will be set in the " + appName + " deployment:\n"); + writer.warn("The following environment variables will be set in the " + appName + " " + deploymentKind + ":\n"); } if (ha) { actualEnv.put("JGROUPS_PING_PROTOCOL", "openshift.DNS_PING"); @@ -382,7 +403,7 @@ public static void deploy(List deployments, actualBuildEnv.putAll(buildExtraEnv); createBuild(writer, target, osClient, appName, initScript, cliScript, actualBuildEnv, config, serverImageBuildLabels); writer.info("Deploying application image on OpenShift"); - createAppDeployment(writer, target, osClient, appName, actualEnv, ha, config); + createAppDeployment(writer, target, osClient, appName, actualEnv, ha, config, deploymentKind); writer.info("Application route: https://" + host + ("ROOT.war".equals(appName) ? "" : "/" + appName)); } @@ -529,7 +550,7 @@ private static String doServerImageBuild(GlowMessageWriter writer, Path target, writer.warn("\nThe following environment variables have been set in the " + serverImageName + " buildConfig:\n"); for (Map.Entry entry : buildExtraEnv.entrySet()) { String val = buildExtraEnv.get(entry.getKey()); - writer.warn(entry.getKey()+"="+entry.getValue()); + writer.warn(entry.getKey() + "=" + entry.getValue()); vars.add(new EnvVar().toBuilder().withName(entry.getKey()).withValue(val == null ? entry.getValue() : val).build()); } } @@ -553,7 +574,7 @@ private static String doServerImageBuild(GlowMessageWriter writer, Path target, try (Watch watcher = osClient.builds().withName(build.getMetadata().getName()).watch(buildWatcher)) { buildWatcher.await(); } - if(buildWatcher.isFailed()) { + if (buildWatcher.isFailed()) { osClient.imageStreams().resource(stream).delete(); throw new Exception("Server image build has failed. Check the OpenShift build log."); } From a3e078afc8b962d088e2498637ee63150b3f9852 Mon Sep 17 00:00:00 2001 From: Jean Francois Denise Date: Thu, 25 Apr 2024 19:20:38 +0200 Subject: [PATCH 2/2] Fix OpenShift deployment name --- openshift-deployment/api/pom.xml | 5 ++ .../openshift/api/OpenShiftSupport.java | 46 +++++++++++++- .../api/OpenShiftSupportTestCase.java | 63 +++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 openshift-deployment/api/src/test/java/org/wildfly/glow/deployment/openshift/api/OpenShiftSupportTestCase.java diff --git a/openshift-deployment/api/pom.xml b/openshift-deployment/api/pom.xml index 18ba9961..1e37d369 100644 --- a/openshift-deployment/api/pom.xml +++ b/openshift-deployment/api/pom.xml @@ -23,5 +23,10 @@ io.fabric8 openshift-client + + junit + junit + test + \ No newline at end of file diff --git a/openshift-deployment/api/src/main/java/org/wildfly/glow/deployment/openshift/api/OpenShiftSupport.java b/openshift-deployment/api/src/main/java/org/wildfly/glow/deployment/openshift/api/OpenShiftSupport.java index ba6ffe64..27f4e15e 100644 --- a/openshift-deployment/api/src/main/java/org/wildfly/glow/deployment/openshift/api/OpenShiftSupport.java +++ b/openshift-deployment/api/src/main/java/org/wildfly/glow/deployment/openshift/api/OpenShiftSupport.java @@ -289,6 +289,49 @@ private static List getEnabledDeployers(Set disabledDeployers) return deployers; } + static final String generateValidName(String name) { + name = name.toLowerCase(); + StringBuilder validName = new StringBuilder(); + char[] array = name.toCharArray(); + for (int i = 0; i < array.length; i++) { + char c = array[i]; + // start with an alphabetic character + if (i == 0) { + if (c <= 97 || c >= 122) { + validName.append("app-"); + } + validName.append(c); + } else { + // end with an alphabetic character + if (i == array.length - 1) { + if ((c >= 48 && c <= 57) || (c >= 97 && c <= 122)) { + validName.append(c); + } else { + validName.append('0'); + } + } else { + // - allowed in the middle + if (c == '-') { + validName.append(c); + } else { + // a-z or 0-9 + if ((c >= 48 && c <= 57) || (c >= 97 && c <= 122)) { + validName.append(c); + } else { + // Other character are replaced by - + validName.append('-'); + } + } + } + } + } + String ret = validName.toString(); + if (ret.length() > 63) { + ret = ret.substring(0, 63); + } + return ret; + } + public static void deploy(List deployments, String defaultName, GlowMessageWriter writer, @@ -312,8 +355,9 @@ public static void deploy(List deployments, Files.createDirectories(deploymentsDir); for (Path p : deployments) { Files.copy(p, deploymentsDir.resolve(p.getFileName())); - int ext = p.getFileName().toString().indexOf("."); + int ext = p.getFileName().toString().lastIndexOf("."); appName += p.getFileName().toString().substring(0, ext); + appName = generateValidName(appName); } if (appName.isEmpty()) { appName = defaultName; diff --git a/openshift-deployment/api/src/test/java/org/wildfly/glow/deployment/openshift/api/OpenShiftSupportTestCase.java b/openshift-deployment/api/src/test/java/org/wildfly/glow/deployment/openshift/api/OpenShiftSupportTestCase.java new file mode 100644 index 00000000..69e716da --- /dev/null +++ b/openshift-deployment/api/src/test/java/org/wildfly/glow/deployment/openshift/api/OpenShiftSupportTestCase.java @@ -0,0 +1,63 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.glow.deployment.openshift.api; + +import org.junit.Assert; +import org.junit.Test; + +/** + * + * @author jdenise + */ +public class OpenShiftSupportTestCase { + + @Test + public void testAppName() { + { + String name = "foo"; + Assert.assertEquals(name, OpenShiftSupport.generateValidName(name)); + } + { + String name = "web-1.0"; + Assert.assertEquals("web-1-0", OpenShiftSupport.generateValidName(name)); + } + { + String name = "990-web-1.0"; + Assert.assertEquals("app-990-web-1-0", OpenShiftSupport.generateValidName(name)); + } + { + String name = "-web-1.0-foo"; + Assert.assertEquals("app--web-1-0-foo", OpenShiftSupport.generateValidName(name)); + } + { + String name = "web$970*"; + Assert.assertEquals("web-9700", OpenShiftSupport.generateValidName(name)); + } + { + String name = "web_970_456_foo"; + Assert.assertEquals("web-970-456-foo", OpenShiftSupport.generateValidName(name)); + } + { + String name = "ROOT"; + Assert.assertEquals("root", OpenShiftSupport.generateValidName(name)); + } + { + String name = "0123456789012345678901234567890123456789012345678901234567890123456789"; + Assert.assertEquals("app-01234567890123456789012345678901234567890123456789012345678", OpenShiftSupport.generateValidName(name)); + } + } +}