-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Kube API Test #5711
Merged
Merged
feat: Kube API Test #5711
Changes from all commits
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
2f0810e
feat: kube-api-test
csviri 916d643
naming and other improvements
csviri c5bfb11
rename all jenvtest occurence to kubeapitest
csviri 25fe56e
injection fix
csviri e5d9399
wip
csviri be29081
dependencies wip
csviri 76fc8de
fixing dependency issues
csviri eeb10f1
Add licenses and format files
csviri 483709d
missing liceses
csviri f652783
fix kube 1.29 readyness check issue
csviri 62ea870
fixes for review
csviri 27e5df5
remove dependency
csviri f5eeb96
format
csviri 19f29ff
remove commons io from dependency management
csviri 50700ab
docs
csviri dd0b610
docs fixes
csviri 324cb41
add change log
csviri 9fa9fb3
test utils cleanup
csviri 9bf5acf
naming improvement
csviri b6b86e9
feat: kube-api-test
csviri ff6571d
naming and other improvements
csviri 4ee3aa3
injection fix
csviri 7db5da6
fixing dependency issues
csviri c229091
Add licenses and format files
csviri 0e18c62
fixes for review
csviri 6613f53
remove dependency
csviri 571ef78
format
csviri d43fa1e
remove commons io from dependency management
csviri 27510d1
test utils cleanup
csviri 138f774
naming improvement
csviri 9cf5806
Update junit/kube-api-test/core/src/main/java/io/fabric8/kubeapitest/…
csviri 9a85be6
Update doc/kube-api-test.md
csviri d1e87f7
fix links
csviri 06e3c7a
test missing header issue
csviri 0e30f5b
license format
csviri 2a687b5
CertManagerTest improvement
csviri 71b8a5a
find free port improvement
csviri c27b5ca
docs
csviri 133cd10
rebase on main
csviri bd54524
remove openapi json
csviri b284860
kube api not with java 8
csviri File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
# kube-api-test | ||
|
||
`kube-api-test` makes it easy to implement integration tests with Kubernetes API Server in Java. | ||
Inspired by [envtest](https://book.kubebuilder.io/reference/envtest.html) in Kubebuilder (Golang). | ||
|
||
It runs the API Server binaries directly (without nodes and other components, but with etcd). | ||
Linux, Windows, Mac is supported. | ||
|
||
## Usage | ||
|
||
Include dependency: | ||
|
||
```xml | ||
<dependency> | ||
<groupId>io.fabric8</groupId> | ||
<artifactId>kube-api-test</artifactId> | ||
<version>[version]</version> | ||
<scope>test</scope> | ||
</dependency> | ||
``` | ||
|
||
### In Unit Tests | ||
|
||
See sample unit | ||
test [here](https://github.com/fabric8io/kubernetes-client/blob/main/junit/kube-api-test/core/src/test/java/io/fabric8/kubeapitest/sample/JUnitExtensionSimpleCaseTest.java) | ||
|
||
```java | ||
|
||
@EnableKubeAPIServer | ||
class JUnitExtensionSimpleCaseTest { | ||
|
||
// Use @KubeConfig annotation to inject kube config yaml to init any client | ||
@KubeConfig | ||
static String kubeConfigYaml; | ||
|
||
@Test | ||
void simpleTestWithTargetVersion() { | ||
var client = new KubernetesClientBuilder() | ||
.withConfig(Config.fromKubeconfig(kubeConfigYaml)) | ||
.build(); | ||
|
||
client.resource(TestUtils.testConfigMap()).create(); | ||
var cm = client.resource(TestUtils.testConfigMap()).get(); | ||
|
||
Assertions.assertThat(cm).isNotNull(); | ||
} | ||
} | ||
``` | ||
|
||
### Public API | ||
|
||
The underlying API can be used directly. | ||
See [KubeApiServer](https://github.com/fabric8io/kubernetes-client/blob/main/junit/kube-api-test/core/src/main/java/io/fabric8/kubeapitest/KubeAPIServer.java#L28-L28) | ||
|
||
See | ||
it's [usage in a test](https://github.com/fabric8io/kubernetes-client/blob/main/junit/kube-api-test/core/src/test/java/io/fabric8/kubeapitest/sample/KubeApiServerTest.java#L31-L31). | ||
|
||
```java | ||
class KubeApiServerTest { | ||
|
||
@Test | ||
void apiServerTest() { | ||
var kubeApi = new KubeAPIServer(); | ||
kubeApi.start(); | ||
|
||
var client = new KubernetesClientBuilder() | ||
.withConfig(Config.fromKubeconfig(kubeApi.getKubeConfigYaml())) | ||
.build(); | ||
|
||
client.resource(TestUtils.testConfigMap()).create(); | ||
|
||
var cm = client.resource(TestUtils.testConfigMap()).get(); | ||
Assertions.assertThat(cm).isNotNull(); | ||
|
||
kubeApi.stop(); | ||
} | ||
} | ||
``` | ||
|
||
### Fabric8 Client Injection Support | ||
|
||
Use dependency: | ||
|
||
```xml | ||
<dependency> | ||
<groupId>io.fabric8</groupId> | ||
<artifactId>kube-api-test-client-inject</artifactId> | ||
<version>[version]</version> | ||
<scope>test</scope> | ||
</dependency> | ||
``` | ||
|
||
The client can be directly injected to the test. See sample test [here](https://github.com/fabric8io/kubernetes-client/blob/main/junit/kube-api-test/client-inject/src/test/java/io/fabric8/kubeapitest/junit/sample/JUnitFabric8ClientInjectionTest.java#L28-L28). | ||
|
||
```java | ||
|
||
@EnableKubeAPIServer | ||
class JUnitFabric8ClientInjectionTest { | ||
|
||
static KubernetesClient client; | ||
|
||
// emitted code | ||
} | ||
``` | ||
|
||
### Configuration Options | ||
|
||
See available configuration options documented in [KubeAPIServerConfig](https://github.com/fabric8io/kubernetes-client/blob/main/junit/kube-api-test/core/src/main/java/io/fabric8/kubeapitest/KubeAPIServerConfig.java) | ||
|
||
Not all those properties can be overridden using [`@EnableKubeAPIServer`](https://github.com/fabric8io/kubernetes-client/blob/main/junit/kube-api-test/core/src/main/java/io/fabric8/kubeapitest/junit/EnableKubeAPIServer.java) | ||
annotation, since might not make sense to do it for an individual test case. However, those can be passed to | ||
[`KubeApiServer`](https://github.com/fabric8io/kubernetes-client/blob/main/junit/kube-api-test/core/src/main/java/io/fabric8/kubeapitest/KubeAPIServer.java) | ||
and also configured globally using environment variables, see [KubeAPIServerConfigBuilder](https://github.com/fabric8io/kubernetes-client/blob/main/junit/kube-api-test/core/src/main/java/io/fabric8/kubeapitest/KubeAPIServerConfigBuilder.java) | ||
|
||
|
||
### Updating kube config file | ||
|
||
In general, it is not advised but if instructed kube config file (~/kube/config) is updated by the framework. | ||
See related property in [`@EnableKubeAPIServer`](https://github.com/fabric8io/kubernetes-client/blob/main/junit/kube-api-test/core/src/main/java/io/fabric8/kubeapitest/junit/EnableKubeAPIServer.java#L42-L42) | ||
annotation. The config file is automatically cleaned up on stop. | ||
|
||
### How does it work | ||
|
||
In the background Kubernetes and etcd (and kubectl) binaries are downloaded if not found locally. | ||
All the certificates for the Kube API Server and for the client is generated. | ||
The client certificates are generated with group `system:masters`; | ||
|
||
### Downloading binaries | ||
|
||
Binaries are downloaded automatically under ~/.kubeapitest/k8s/[target-platform-and-version] if no binary found locally. | ||
If there are multiple binaries found, the latest if selected (unless a target version is not specified). | ||
|
||
Also [`setup-envtest`](https://pkg.go.dev/sigs.k8s.io/controller-runtime/tools/setup-envtest#section-readme) can be used | ||
to download binaries manually. By executing `setup-envtest use --bin-dir ~/.kubeapitest` will download the latest required | ||
binaries to the default directory. This is useful if always running the tests in offline mode. | ||
|
||
### Support for Parallel Execution in Junit5 | ||
|
||
Parallel test execution is explicitly supported for JUnit5, in fact the project tests are running parallel. | ||
Running a new instance for each test case. This speeds up the tests (in our case >75%) in a way that test cases are also | ||
fully isolated from each other. Although we experienced stability issues on non-Linux platforms. | ||
|
||
### Testing Mutation and Validation Webhooks | ||
|
||
An additional benefit of running K8S API Server this way, is that it makes easy to test | ||
[Conversion Hooks](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#webhook-conversion) | ||
and/or | ||
[Dynamic Admission Controllers](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/) | ||
|
||
In general, it is a best practice to use additional standard frameworks to implement Kubernetes webhooks, | ||
like [kubernetes-webooks-framework](https://github.com/java-operator-sdk/kubernetes-webooks-framework) | ||
with Quarkus or Spring. However, we demonstrate how it works | ||
in [this test](https://github.com/fabric8io/kubernetes-client/blob/main/junit/kube-api-test/core/src/test/java/io/fabric8/kubeapitest/sample/KubernetesMutationHookHandlingTest.java#L72-L72) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!-- | ||
|
||
Copyright (C) 2015 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. | ||
|
||
--> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<groupId>io.fabric8</groupId> | ||
<artifactId>kube-api-test-parent</artifactId> | ||
<version>6.11-SNAPSHOT</version> | ||
</parent> | ||
|
||
<artifactId>kube-api-test-client-inject</artifactId> | ||
<packaging>jar</packaging> | ||
<name>Kube API Test - Client Injecting Support</name> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>io.fabric8</groupId> | ||
<artifactId>kube-api-test</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.junit.jupiter</groupId> | ||
<artifactId>junit-jupiter-api</artifactId> | ||
<scope>compile</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.junit.jupiter</groupId> | ||
<artifactId>junit-jupiter-engine</artifactId> | ||
<scope>compile</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.slf4j</groupId> | ||
<artifactId>slf4j-api</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.fabric8</groupId> | ||
<artifactId>kubernetes-client</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.assertj</groupId> | ||
<artifactId>assertj-core</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.apache.logging.log4j</groupId> | ||
<artifactId>log4j-slf4j2-impl</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.apache.logging.log4j</groupId> | ||
<artifactId>log4j-core</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
</project> |
87 changes: 87 additions & 0 deletions
87
...ient-inject/src/main/java/io/fabric8/kubeapitest/junit/Fabric8ClientInjectionHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
/* | ||
* Copyright (C) 2015 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 io.fabric8.kubeapitest.junit; | ||
|
||
import io.fabric8.kubeapitest.KubeAPIServer; | ||
import io.fabric8.kubeapitest.KubeAPITestException; | ||
import io.fabric8.kubernetes.client.Config; | ||
import io.fabric8.kubernetes.client.KubernetesClient; | ||
import io.fabric8.kubernetes.client.KubernetesClientBuilder; | ||
import org.junit.jupiter.api.extension.ExtensionContext; | ||
|
||
import java.lang.reflect.Field; | ||
import java.util.Arrays; | ||
import java.util.Optional; | ||
import java.util.stream.Collectors; | ||
|
||
public class Fabric8ClientInjectionHandler implements ClientInjectionHandler { | ||
|
||
private KubernetesClient client; | ||
|
||
public boolean isTargetFieldAvailable(ExtensionContext extensionContext, | ||
boolean staticField) { | ||
return getFieldForKubeClientInjection(extensionContext, staticField).isPresent(); | ||
} | ||
|
||
@Override | ||
public void inject(ExtensionContext extensionContext, | ||
boolean staticField, KubeAPIServer kubeApiServer) { | ||
var field = getFieldForKubeClientInjection(extensionContext, staticField).orElseThrow(); | ||
setKubernetesClientToField(extensionContext, field, kubeApiServer); | ||
} | ||
|
||
private void setKubernetesClientToField(ExtensionContext extensionContext, | ||
Field kubeClientField, KubeAPIServer kubeApiServer) { | ||
try { | ||
var target = extensionContext.getTestInstance() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note to self: |
||
.orElseGet(() -> extensionContext.getTestClass().orElseThrow()); | ||
client = new KubernetesClientBuilder() | ||
.withConfig(Config.fromKubeconfig(kubeApiServer.getKubeConfigYaml())).build(); | ||
kubeClientField.setAccessible(true); | ||
kubeClientField.set(target, client); | ||
} catch (IllegalAccessException e) { | ||
throw new KubeAPITestException(e); | ||
} | ||
} | ||
|
||
@Override | ||
public void cleanup(ExtensionContext extensionContext) { | ||
if (client != null) { | ||
client.close(); | ||
} | ||
} | ||
|
||
public static Optional<Field> getFieldForKubeClientInjection(ExtensionContext extensionContext, | ||
boolean staticField) { | ||
Class<?> clazz = extensionContext.getTestClass().orElseThrow(); | ||
var kubeConfigFields = Arrays.stream(clazz.getDeclaredFields()) | ||
.filter(f -> KubernetesClient.class.isAssignableFrom(f.getType())) | ||
.collect(Collectors.toList()); | ||
if (kubeConfigFields.isEmpty()) { | ||
return Optional.empty(); | ||
} | ||
if (kubeConfigFields.size() > 1) { | ||
throw new KubeAPITestException( | ||
"More fields type KubernetesClient found"); | ||
} | ||
var field = kubeConfigFields.get(0); | ||
if (java.lang.reflect.Modifier.isStatic(field.getModifiers()) != staticField) { | ||
return Optional.empty(); | ||
} else { | ||
return Optional.of(field); | ||
} | ||
} | ||
} |
1 change: 1 addition & 0 deletions
1
.../src/main/resources/META-INF/services/io.fabric8.kubeapitest.junit.ClientInjectionHandler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
io.fabric8.kubeapitest.junit.Fabric8ClientInjectionHandler |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This document needs to be linked in root README.md as well . Maybe we should add it below
Mocking Kubernetes
section?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added a section into the docs, pls check if this is ok this way. thx!