Skip to content

Commit

Permalink
[issues-k8s-support] - Adding supported execution targets and conditi…
Browse files Browse the repository at this point in the history
…onal OpenShift/Kubernetes JUnit5 extension behavior
  • Loading branch information
fabiobrz committed Sep 30, 2024
1 parent 9fb8400 commit 8a836e2
Show file tree
Hide file tree
Showing 12 changed files with 557 additions and 18 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
Intersmash is a Java library that makes it easy to automate the
provisioning and execution of tests in cloud-native environments.
It helps the user prototype and test complex interoperability scenarios on
kubernetes compliant cloud-native environments and platforms, most notably OpenShift. (*Other Kubernetes implementations will be supported in the future.*)
kubernetes compliant cloud-native environments and platforms, most notably OpenShift.
Kubernetes support is being introduced. Support for other platforms (Bare-metal etc.) could be added
later on demand.



Intersmash is designed with these principles.
* It is integrated with JUnit.
Expand All @@ -16,7 +20,9 @@ the user can easily create an implementation and integrate it into the framework
to test on different Kubernetes compliant cloud implementations. This will
ensure application portability.

For usage examples, see [Getting Started](./docs/GETTING_STARTED.md).
## Next steps
- See how to [configure Intersmash for running tests on OpenShift and Kubernetes, or both](./docs/CLOUD_TARGET.md).
- For usage examples, see [Getting Started](./docs/GETTING_STARTED.md).

## Framework Design

Expand Down
26 changes: 26 additions & 0 deletions core/src/main/java/org/jboss/intersmash/IntersmashConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@
*/
package org.jboss.intersmash;

import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import com.google.common.base.Strings;

import cz.xtf.core.config.XTFConfig;
import cz.xtf.core.openshift.OpenShift;
Expand Down Expand Up @@ -112,6 +116,10 @@ public class IntersmashConfig {
private static final String MYSQL_IMAGE_URL = "intersmash.mysql.image";
private static final String PGSQL_IMAGE_URL = "intersmash.postgresql.image";

private static final String JUNIT5_EXECUTION_TARGETS = "intersmash.junit5.execution.targets";
private static final String JUNIT5_EXECUTION_TARGET_OPENSHIFT = "OpenShift";
private static final String JUNIT5_EXECUTION_TARGET_KUBERNETES = "Kubernetes";

public static boolean skipDeploy() {
return XTFConfig.get(SKIP_DEPLOY, "false").equals("true");
}
Expand Down Expand Up @@ -402,4 +410,22 @@ public static String keycloakOperatorChannel() {
public static String keycloakOperatorPackageManifest() {
return XTFConfig.get(KEYCLOAK_OPERATOR_PACKAGE_MANIFEST, DEFAULT_KEYCLOAK_OPERATOR_PACKAGE_MANIFEST);
}

public static String[] getJunit5ExecutionTargets() {
final String propertyValue = XTFConfig.get(JUNIT5_EXECUTION_TARGETS);
if (Strings.isNullOrEmpty(propertyValue)) {
return new String[] { JUNIT5_EXECUTION_TARGET_OPENSHIFT };
}
return XTFConfig.get(JUNIT5_EXECUTION_TARGETS).split(",");
}

public static Boolean testEnvironmentSupportsOpenShift() {
return Arrays.stream(getJunit5ExecutionTargets()).collect(Collectors.toList())
.contains(JUNIT5_EXECUTION_TARGET_OPENSHIFT);
}

public static Boolean testEnvironmentSupportsKubernetes() {
return Arrays.stream(getJunit5ExecutionTargets()).collect(Collectors.toList())
.contains(JUNIT5_EXECUTION_TARGET_KUBERNETES);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Copyright (C) 2023 Red Hat, Inc.
*
* 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.jboss.intersmash.application.k8s;

import org.jboss.intersmash.application.Application;

/**
* Interface representing the Application on Kubernetes.
*
* This interface is not supposed to be implemented by user Applications. See the "Mapping of implemented provisioners"
* section of Intersmash README.md file for the up-to-date list of supported end users Applications.
*/
public interface KubernetesApplication extends Application {

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
package org.jboss.intersmash.junit5;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.jboss.intersmash.IntersmashConfig;
import org.jboss.intersmash.annotations.Intersmash;
Expand All @@ -41,12 +43,33 @@ public class IntersmashExecutionCondition implements ExecutionCondition {

@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
final List<String> targets = Arrays.stream(IntersmashConfig.getJunit5ExecutionTargets())
.collect(Collectors.toList());
// log what the configured JUnit 5 target execution environment names are
log.debug("Configured JUnit 5 execution environments: {}", targets.stream().collect(Collectors.joining(",")));

Intersmash[] intersmashes = context.getRequiredTestClass().getAnnotationsByType(Intersmash.class);
Intersmash intersmash;
if (intersmashes.length > 0) {
intersmash = intersmashes[0];

// Skip tests that don't fit the environments supported by the actual test execution environment
if (IntersmashExtensionHelper.isIntersmashTargetingOpenShift(context)
&& !IntersmashConfig.testEnvironmentSupportsOpenShift()) {
return ConditionEvaluationResult.disabled(
"An @Intersmash service is set to target OpenShift which has not been configured for the current execution.");
}
if (IntersmashExtensionHelper.isIntersmashTargetingKubernetes(context)
&& !IntersmashConfig.testEnvironmentSupportsKubernetes()) {
return ConditionEvaluationResult.disabled(
"An @Intersmash service is set to target Kubernetes which has not been configured for the current execution.");
}

log.debug("Running: {}", context.getRequiredTestClass().getSimpleName());
if (IntersmashConfig.isOcp3x(OpenShifts.admin())

// evaluate peculiar OpenShift/Kubernetes requirements
if (IntersmashExtensionHelper.isIntersmashTargetingOpenShift(context)
&& IntersmashConfig.isOcp3x(OpenShifts.admin())
&& Arrays.stream(intersmash.value()).anyMatch(isOperatorApplication)) {
return ConditionEvaluationResult.disabled("OLM is not available on OCP 3.x clusters, " +
"skip the tests due to OperatorApplication(s) involvement.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -30,7 +29,6 @@
import org.jboss.intersmash.annotations.ServiceProvisioner;
import org.jboss.intersmash.annotations.ServiceUrl;
import org.jboss.intersmash.application.Application;
import org.jboss.intersmash.application.openshift.OpenShiftApplication;
import org.jboss.intersmash.provision.Provisioner;
import org.jboss.intersmash.provision.ProvisionerManager;
import org.jboss.intersmash.provision.openshift.operator.resources.OperatorGroup;
Expand Down Expand Up @@ -62,19 +60,16 @@ public void beforeAll(ExtensionContext extensionContext) throws Exception {
Optional<Throwable> tt = Optional.empty();
try {
log.debug("beforeAll");
// openShiftRecorderService().initFilters(extensionContext);
Intersmash[] intersmashes = extensionContext.getRequiredTestClass().getAnnotationsByType(Intersmash.class);
Intersmash intersmash;
if (intersmashes.length > 0) {
intersmash = intersmashes[0];
} else {
// let's store the Intersmash definition in the extension context store
Intersmash intersmash = IntersmashExtensionHelper.getIntersmash(extensionContext);
if (intersmash == null) {
log.warn("No @Intersmah definition stored by Intersmash extensions");
return;
}

// we don't want to touch anything if the deployment phase is skipped
if (!IntersmashConfig.skipDeploy()) {
if (Arrays.stream(intersmash.value())
.anyMatch(app -> OpenShiftApplication.class.isAssignableFrom(app.value()))) {
if (IntersmashExtensionHelper.isIntersmashTargetingOpenShift(extensionContext)) {
if (!IntersmashConfig.isOcp3x(OpenShifts.admin())) {
operatorCleanup();
log.debug("Deploy operatorgroup [{}] to enable operators subscription into tested namespace",
Expand All @@ -95,7 +90,7 @@ public void beforeAll(ExtensionContext extensionContext) throws Exception {
// store provisioners right now, those might be needed in each phase independently
Provisioner provisioner = ProvisionerManager.getProvisioner(application);
// keep the provisioner in the JUpiter Extension Store
getProvisioners(extensionContext).put(application.getClass().getName(), provisioner);
IntersmashExtensionHelper.getProvisioners(extensionContext).put(application.getClass().getName(), provisioner);
if (!IntersmashConfig.skipDeploy()) {
deployApplication(provisioner);
}
Expand Down Expand Up @@ -145,12 +140,13 @@ public void afterAll(ExtensionContext extensionContext) {
if (IntersmashConfig.skipUndeploy()) {
log.info("Skipping the after test cleanup operations.");
} else {
for (Provisioner provisioner : getProvisioners(extensionContext).values()) {
for (Provisioner provisioner : IntersmashExtensionHelper.getProvisioners(extensionContext).values()) {
undeployApplication(provisioner);
}
// operator group is not bound to a specific product
// no Operator support on OCP3 clusters, OLM doesn't run there
if (!IntersmashConfig.isOcp3x(OpenShifts.admin())) {
if (IntersmashExtensionHelper.isIntersmashTargetingOpenShift(extensionContext)
&& !IntersmashConfig.isOcp3x(OpenShifts.admin())) {
operatorCleanup();
}
// let's cleanup once we're done
Expand Down Expand Up @@ -195,7 +191,8 @@ private void injectServiceUrl(Object o, ExtensionContext extensionContext) throw
List<Field> annotatedFields = AnnotationSupport.findAnnotatedFields(o.getClass(), ServiceUrl.class);
for (Field field : annotatedFields) {
ServiceUrl serviceUrl = field.getAnnotation(ServiceUrl.class);
Provisioner provisioner = getProvisioners(extensionContext).get(serviceUrl.value().getName());
Provisioner provisioner = IntersmashExtensionHelper.getProvisioners(extensionContext)
.get(serviceUrl.value().getName());
URL url = provisioner.getURL();
if (String.class.isAssignableFrom(field.getType())) {
field.setAccessible(true);
Expand All @@ -215,7 +212,8 @@ private void injectServiceProvisioner(Object o, ExtensionContext extensionContex
List<Field> annotatedFields = AnnotationSupport.findAnnotatedFields(o.getClass(), ServiceProvisioner.class);
for (Field field : annotatedFields) {
ServiceProvisioner serviceProvisioner = field.getAnnotation(ServiceProvisioner.class);
Provisioner provisioner = getProvisioners(extensionContext).get(serviceProvisioner.value().getName());
Provisioner provisioner = IntersmashExtensionHelper.getProvisioners(extensionContext)
.get(serviceProvisioner.value().getName());
if (Provisioner.class.isAssignableFrom(field.getType())) {
field.setAccessible(true);
field.set(o, provisioner);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* Copyright (C) 2023 Red Hat, Inc.
*
* 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.jboss.intersmash.junit5;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.jboss.intersmash.annotations.Intersmash;
import org.jboss.intersmash.application.k8s.KubernetesApplication;
import org.jboss.intersmash.application.openshift.OpenShiftApplication;
import org.jboss.intersmash.provision.Provisioner;
import org.junit.jupiter.api.extension.ExtensionContext;

public class IntersmashExtensionHelper {

private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create("org", "jboss", "intersmash",
"IntersmashExtension");
private static final String INTERSMASH_SERVICES = "INTERSMASH_SERVICES";
private static final String INTERSMASH = "INTERSMASH";

public static Map<String, Provisioner> getProvisioners(ExtensionContext extensionContext) {
ExtensionContext.Store store = extensionContext.getStore(NAMESPACE);
Map<String, Provisioner> provisioners = (Map<String, Provisioner>) store.get(INTERSMASH_SERVICES);
if (provisioners != null) {
return provisioners;
} else {
store.put(INTERSMASH_SERVICES, new HashMap<String, Provisioner>());
return (Map<String, Provisioner>) store.get(INTERSMASH_SERVICES);
}
}

public static Intersmash getIntersmash(ExtensionContext extensionContext) {
ExtensionContext.Store store = extensionContext.getStore(NAMESPACE);
Intersmash result = (Intersmash) store.get(INTERSMASH);
if (result != null) {
return result;
} else {
Intersmash[] intersmashes = extensionContext.getRequiredTestClass().getAnnotationsByType(Intersmash.class);
Intersmash intersmash;
if (intersmashes.length > 0) {
store.put(INTERSMASH, intersmashes[0]);
return (Intersmash) store.get(INTERSMASH);
}
return null;
}
}

public static Boolean isIntersmashTargetingOpenShift(ExtensionContext extensionContext) {
return Arrays.stream(getIntersmash(extensionContext).value())
.anyMatch(app -> OpenShiftApplication.class.isAssignableFrom(app.value()));
}

public static Boolean isIntersmashTargetingKubernetes(ExtensionContext extensionContext) {
return Arrays.stream(getIntersmash(extensionContext).value())
.anyMatch(app -> KubernetesApplication.class.isAssignableFrom(app.value()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Copyright (C) 2023 Red Hat, Inc.
*
* 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.jboss.intersmash.provision.k8s;

import java.net.MalformedURLException;
import java.net.URL;

import org.jboss.intersmash.application.k8s.KubernetesApplication;
import org.jboss.intersmash.application.openshift.HasConfigMaps;
import org.jboss.intersmash.application.openshift.HasSecrets;
import org.jboss.intersmash.provision.Provisioner;
import org.jboss.intersmash.provision.openshift.HasPods;
import org.jboss.intersmash.provision.openshift.Scalable;

import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;

/**
* Provisioner that is supposed to deploy an application on Kubernetes.
*/
public interface KubernetesProvisioner<T extends KubernetesApplication> extends Provisioner<T>, Scalable, HasPods {

// TODO - check for aq new class of statics like XTF OpenShifts?
KubernetesClient kubernetes = newKubernetesClient();

static KubernetesClient newKubernetesClient() {
Config config = new ConfigBuilder()
.build();
return new DefaultKubernetesClient(config);
}

@Override
default void preDeploy() {
// create secrets
if (HasSecrets.class.isAssignableFrom(getApplication().getClass())) {
((HasSecrets) getApplication()).getSecrets().forEach(s -> kubernetes.secrets().create(s));
}
// create configMaps
if (HasConfigMaps.class.isAssignableFrom(getApplication().getClass())) {
((HasConfigMaps) getApplication()).getConfigMaps().forEach(c -> kubernetes.configMaps().create(c));
}
}

@Override
default void postUndeploy() {
// delete secrets
if (HasSecrets.class.isAssignableFrom(getApplication().getClass())) {
((HasSecrets) getApplication()).getSecrets().forEach(s -> kubernetes.secrets().delete(s));
}
// delete configMaps
if (HasConfigMaps.class.isAssignableFrom(getApplication().getClass())) {
((HasConfigMaps) getApplication()).getConfigMaps().forEach(c -> kubernetes.configMaps().delete(c));
}
}

// TODO - check (use a static class method like XTF OpenShift::generateHostName ?)
default String getUrl(String routeName, boolean secure) {
String protocol = secure ? "https" : "http";
return protocol + "://" + kubernetes.getMasterUrl() + "-" + routeName + "-"
+ kubernetes.getConfiguration().getNamespace();
}

@Override
default URL getURL() {
try {
return new URL(getUrl(getApplication().getName(), false));
} catch (MalformedURLException ex) {
throw new RuntimeException(
String.format("Failed to get an URL for the \"%s\" route", this.getClass().getSimpleName()), ex);
}
}
}
Loading

0 comments on commit 8a836e2

Please sign in to comment.