Skip to content

Commit

Permalink
Add tweaks methods for kubernetes (#48)
Browse files Browse the repository at this point in the history
* Add tweaks methods for kubernetes

Signed-off-by: David Kornel <[email protected]>
  • Loading branch information
kornys authored May 3, 2024
1 parent 79d7a04 commit c5f7e63
Show file tree
Hide file tree
Showing 5 changed files with 297 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ private TestFrameConstants() {
*/
public static final long GLOBAL_TIMEOUT = Duration.ofMinutes(10).toMillis();

/**
* Stability timeout in milliseconds
*/
public static final long GLOBAL_STABILITY_TIME = Duration.ofMinutes(1).toMillis();

/**
* OpenShift client type.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,32 @@
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.LabelSelector;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.openshift.client.OpenShiftClient;
import io.skodjob.testframe.LoggerUtils;
import io.skodjob.testframe.TestFrameConstants;
import io.skodjob.testframe.TestFrameEnv;
import io.skodjob.testframe.executor.Exec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Provides functionality to interact with Kubernetes and OpenShift clusters.
* This includes creating clients, reading resources from files, and managing kubeconfig for authentication.
*/
public class KubeClient {
private static final Logger LOGGER = LoggerFactory.getLogger(KubeClient.class);


private KubernetesClient client;
private String kubeconfigPath;
Expand Down Expand Up @@ -98,6 +108,230 @@ public List<HasMetadata> readResourcesFromFile(InputStream is) throws IOExceptio
}
}

/**
* Check if namespace exists in current cluster
*
* @param namespace namespace name
* @return true if namespace exists
*/
public boolean namespaceExists(String namespace) {
return client.namespaces().list().getItems().stream()
.anyMatch(n -> n.getMetadata().getName().equals(namespace));
}

/**
* Creates resource and apply modifier
*
* @param resources resources
* @param modifier modifier
*/
public void create(List<HasMetadata> resources, Function<HasMetadata, HasMetadata> modifier) {
create(null, resources, modifier);
}

/**
* Updates resources and apply modifier
*
* @param resources resources
* @param modifier modifier
*/
public void update(List<HasMetadata> resources, Function<HasMetadata, HasMetadata> modifier) {
update(null, resources, modifier);
}

/**
* Creates resource and apply modifier
*
* @param namespace namespace
* @param resources resources
* @param modifier modifier
*/
public void create(String namespace, List<HasMetadata> resources, Function<HasMetadata, HasMetadata> modifier) {
resources.forEach(res -> {
HasMetadata h = modifier.apply(res);
LOGGER.debug(LoggerUtils.RESOURCE_WITH_NAMESPACE_LOGGER_PATTERN,
"Creating", h.getKind(), h.getMetadata().getName(), namespace);
if (namespace == null) {
client.resource(h).create();
} else {
client.resource(h).inNamespace(namespace).create();
}
});
}

/**
* Updates resources and apply modifier
*
* @param namespace namespace
* @param resources resources
* @param modifier modifier
*/
public void update(String namespace, List<HasMetadata> resources, Function<HasMetadata, HasMetadata> modifier) {
resources.forEach(res -> {
HasMetadata h = modifier.apply(res);
LOGGER.debug(LoggerUtils.RESOURCE_WITH_NAMESPACE_LOGGER_PATTERN,
"Updating", h.getKind(), h.getMetadata().getName(), namespace);
if (namespace == null) {
client.resource(h).update();
} else {
client.resource(h).inNamespace(namespace).update();
}
});
}

/**
* Create or update resources from file and apply modifier
*
* @param resources resources
* @param modifier modifier method
*/
public void createOrUpdate(List<HasMetadata> resources, Function<HasMetadata, HasMetadata> modifier) {
createOrUpdate(null, resources, modifier);
}

/**
* Create or update resources from file and apply modifier
*
* @param ns namespace
* @param resources resources
* @param modifier modifier method
*/
public void createOrUpdate(String ns, List<HasMetadata> resources, Function<HasMetadata, HasMetadata> modifier) {
resources.forEach(i -> {
HasMetadata h = modifier.apply(i);
if (h != null) {
if (client.resource(h).get() == null) {
LOGGER.debug(LoggerUtils.RESOURCE_WITH_NAMESPACE_LOGGER_PATTERN,
"Creating", h.getKind(), h.getMetadata().getName(), h.getMetadata().getNamespace());
if (ns == null) {
client.resource(h).create();
} else {
client.resource(h).inNamespace(ns).create();
}
} else {
LOGGER.debug(LoggerUtils.RESOURCE_WITH_NAMESPACE_LOGGER_PATTERN,
"Updating", h.getKind(), h.getMetadata().getName(), h.getMetadata().getNamespace());
if (ns == null) {
client.resource(h).update();
} else {
client.resource(h).inNamespace(ns).update();
}
}
}
});
}

/**
* Deletes resources
*
* @param resources resources
*/
public void delete(List<HasMetadata> resources) {
resources.forEach(h -> {
if (h != null) {
if (client.resource(h).get() != null) {
LOGGER.debug(LoggerUtils.RESOURCE_WITH_NAMESPACE_LOGGER_PATTERN,
"Deleting", h.getKind(), h.getMetadata().getName(), h.getMetadata().getNamespace());
client.resource(h).delete();
}
}
});
}

/**
* Delete resources
*
* @param resources resources
* @param namespace namespace
*/
public void delete(List<HasMetadata> resources, String namespace) {
resources.forEach(h -> {
if (h != null) {
if (client.resource(h).inNamespace(namespace).get() != null) {
LOGGER.debug(LoggerUtils.RESOURCE_WITH_NAMESPACE_LOGGER_PATTERN,
"Deleting", h.getKind(), h.getMetadata().getName(), namespace);
client.resource(h).inNamespace(namespace).delete();
}
}
});
}

/**
* Get all pods from namespace
*
* @param namespaceName namespace
* @return list of pods
*/
public List<Pod> listPods(String namespaceName) {
return client.pods().inNamespace(namespaceName).list().getItems();
}

/**
* Get all pods with prefix nanme
*
* @param namespaceName namespace
* @param selector prefix
* @return lust of pods
*/
public List<Pod> listPods(String namespaceName, LabelSelector selector) {
return client.pods().inNamespace(namespaceName).withLabelSelector(selector).list().getItems();
}

/**
* Returns list of pods by prefix in pod name
*
* @param namespaceName Namespace name
* @param podNamePrefix prefix with which the name should begin
* @return List of pods
*/
public List<Pod> listPodsByPrefixInName(String namespaceName, String podNamePrefix) {
return listPods(namespaceName)
.stream().filter(p -> p.getMetadata().getName().startsWith(podNamePrefix))
.collect(Collectors.toList());
}

/**
* Return log from pod with one container
*
* @param namespaceName namespace of the pod
* @param podName pod name
* @return logs
*/
public String getLogsFromPod(String namespaceName, String podName) {
return client.pods().inNamespace(namespaceName).withName(podName).getLog();
}

/**
* Return log from pods specific container
*
* @param namespaceName namespace of the pod
* @param podName pod name
* @param containerName container name
* @return logs
*/
public String getLogsFromContainer(String namespaceName, String podName, String containerName) {
return client.pods().inNamespace(namespaceName).withName(podName).inContainer(containerName).getLog();
}

/**
* Returns list of deployments with prefix name
*
* @param namespace namespace
* @param namePrefix prefix
* @return list of deployments
*/
public String getDeploymentNameByPrefix(String namespace, String namePrefix) {
List<Deployment> prefixDeployments = client.apps().deployments()
.inNamespace(namespace).list().getItems().stream().filter(rs ->
rs.getMetadata().getName().startsWith(namePrefix)).toList();

if (!prefixDeployments.isEmpty()) {
return prefixDeployments.get(0).getMetadata().getName();
} else {
return null;
}
}

/**
* Determines the appropriate Kubernetes configuration based on environment variables.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,21 @@ public ExecResult exec(boolean throwError, boolean logToOutput, String... comman
return Exec.exec(null, cmd, 0, logToOutput, throwError);
}

/**
* Executes a command with the option to throw errors and log to output.
*
* @param throwError Whether to throw errors.
* @param logToOutput Whether to log the output.
* @param timeout timeout in milis
* @param command The command to execute.
* @return The execution result.
*/
@Override
public ExecResult exec(boolean throwError, boolean logToOutput, int timeout, String... command) {
List<String> cmd = command(asList(command), false);
return Exec.exec(null, cmd, timeout, logToOutput, throwError);
}

/**
* Executes a command in the current namespace.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,17 @@ default K delete(String... files) {
*/
ExecResult exec(boolean throwError, boolean logToOutput, String... command);

/**
* Execute the given {@code command}. You can specify if potential failure will thrown the exception or not.
*
* @param throwError parameter which control thrown exception in case of failure
* @param command The command
* @param timeout tiemout in ms
* @param logToOutput determines if we want to print whole output of command
* @return The process result.
*/
ExecResult exec(boolean throwError, boolean logToOutput, int timeout, String... command);

/**
* Retrieves the YAML content of a resource.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*/
package io.skodjob.testframe.utils;

import io.fabric8.kubernetes.api.model.Namespace;
import io.fabric8.kubernetes.api.model.NamespaceBuilder;
import io.fabric8.openshift.api.model.operatorhub.v1alpha1.InstallPlan;
import io.fabric8.openshift.api.model.operatorhub.v1alpha1.InstallPlanBuilder;
import io.skodjob.testframe.TestFrameConstants;
Expand Down Expand Up @@ -37,7 +39,7 @@ public static void approveInstallPlan(String namespaceName, String installPlanNa
.getOpenShiftClient().operatorHub().installPlans()
.inNamespace(namespaceName).withName(installPlanName).get())
.editSpec()
.withApproved()
.withApproved()
.endSpec()
.build();

Expand Down Expand Up @@ -65,4 +67,33 @@ public static InstallPlan getNonApprovedInstallPlan(String namespaceName, String
&& installPlan.getSpec().getClusterServiceVersionNames().toString().contains(csvPrefix))
.findFirst().get();
}

/**
* Apply label to namespace and wait for propagation
* @param namespace namespace name
* @param key label key
* @param value label value
*/
public static void labelNamespace(String namespace, String key, String value) {
if (KubeResourceManager.getKubeClient().namespaceExists(namespace)) {
Wait.until(String.format("Namespace %s has label: %s", namespace, key),
TestFrameConstants.GLOBAL_POLL_INTERVAL_1_SEC, TestFrameConstants.GLOBAL_STABILITY_TIME, () -> {
try {
KubeResourceManager.getKubeClient().getClient().namespaces().withName(namespace).edit(n ->
new NamespaceBuilder(n)
.editMetadata()
.addToLabels(key, value)
.endMetadata()
.build());
} catch (Exception ex) {
return false;
}
Namespace n = KubeResourceManager.getKubeClient().getClient().namespaces().withName(namespace).get();
if (n != null) {
return n.getMetadata().getLabels().get(key) != null;
}
return false;
});
}
}
}

0 comments on commit c5f7e63

Please sign in to comment.