-
Notifications
You must be signed in to change notification settings - Fork 4
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
Service integration #7
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,166 @@ | ||||||
package com.github.jeanbaptistewatenberg.junit5kubernetes.core; | ||||||
|
||||||
import com.github.jeanbaptistewatenberg.junit5kubernetes.core.wait.WaitStrategy; | ||||||
import com.google.gson.JsonSyntaxException; | ||||||
import com.google.gson.reflect.TypeToken; | ||||||
import io.kubernetes.client.openapi.ApiClient; | ||||||
import io.kubernetes.client.openapi.ApiException; | ||||||
import io.kubernetes.client.openapi.Configuration; | ||||||
import io.kubernetes.client.openapi.apis.CoreV1Api; | ||||||
import io.kubernetes.client.openapi.models.V1Service; | ||||||
import io.kubernetes.client.util.Config; | ||||||
import io.kubernetes.client.util.Watch; | ||||||
import okhttp3.OkHttpClient; | ||||||
import okhttp3.Protocol; | ||||||
|
||||||
import java.io.IOException; | ||||||
import java.util.Collections; | ||||||
import java.util.concurrent.TimeUnit; | ||||||
import java.util.logging.Logger; | ||||||
|
||||||
public class Service extends KubernetesGenericObject<Service> { | ||||||
protected static final String DEBUG = System.getProperty("junitKubernetesDebug"); | ||||||
protected static final String DISABLE_HTTP2 = System.getProperty("junitKubernetesDisableHttp2"); | ||||||
protected static final String SYSTEM_NAMESPACE = System.getProperty("kubernetesNamespace"); | ||||||
protected static final String NAMESPACE = SYSTEM_NAMESPACE != null && !SYSTEM_NAMESPACE.trim().equals("") ? SYSTEM_NAMESPACE : "default"; | ||||||
private static final Logger LOGGER = Logger.getLogger(Service.class.getName()); | ||||||
|
||||||
protected final V1Service serviceToCreate; | ||||||
protected final CoreV1Api coreV1Api; | ||||||
protected WaitStrategy<V1Service> waitStrategy; | ||||||
|
||||||
protected final ThreadLocal<V1Service> createdService = new ThreadLocal<>(); | ||||||
|
||||||
public Service(V1Service serviceToCreate) { | ||||||
this.serviceToCreate = serviceToCreate; | ||||||
this.coreV1Api = initiateCoreV1Api(); | ||||||
} | ||||||
|
||||||
private static CoreV1Api initiateCoreV1Api() { | ||||||
CoreV1Api coreV1Api; | ||||||
try { | ||||||
ApiClient client = Config.defaultClient(); | ||||||
if (DEBUG != null && DEBUG.equalsIgnoreCase("true")) { | ||||||
client.setDebugging(true); | ||||||
} | ||||||
// infinite timeout | ||||||
OkHttpClient.Builder builder = client.getHttpClient().newBuilder() | ||||||
.readTimeout(0, TimeUnit.SECONDS); | ||||||
|
||||||
if (DISABLE_HTTP2 != null && DISABLE_HTTP2.equalsIgnoreCase("true")) { | ||||||
builder.protocols(Collections.singletonList(Protocol.HTTP_1_1)); | ||||||
} | ||||||
|
||||||
client.setVerifyingSsl(false); | ||||||
|
||||||
OkHttpClient httpClient = builder.build(); | ||||||
client.setHttpClient(httpClient); | ||||||
Configuration.setDefaultApiClient(client); | ||||||
|
||||||
coreV1Api = new CoreV1Api(client); | ||||||
|
||||||
} catch (IOException e) { | ||||||
throw new RuntimeException(e); | ||||||
} | ||||||
return coreV1Api; | ||||||
} | ||||||
|
||||||
@Override | ||||||
public String getObjectName() { | ||||||
V1Service v1Service = this.createdService.get(); | ||||||
if (v1Service != null) { | ||||||
return v1Service.getMetadata().getName(); | ||||||
} else { | ||||||
throw new RuntimeException("Can't get name of a non running object."); | ||||||
} | ||||||
} | ||||||
|
||||||
@Override | ||||||
public String getObjectHostIp() { | ||||||
V1Service v1Service = this.createdService.get(); | ||||||
if (v1Service != null) { | ||||||
try { | ||||||
V1Service retrievedService = coreV1Api.readNamespacedService(v1Service.getMetadata().getName(), NAMESPACE, null, null, null); | ||||||
if (retrievedService.getStatus() == null) { | ||||||
throw new RuntimeException("Can't get ip of a non running object."); | ||||||
} | ||||||
//Anhand der Umgebungsvariable entscheiden, ob der Test im Cluster oder lokal ausgeführt wird | ||||||
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.
Suggested change
|
||||||
if(System.getenv("KUBERNETES_PORT") != null) { | ||||||
return retrievedService.getSpec().getClusterIP(); | ||||||
}else{ | ||||||
return retrievedService.getSpec().getExternalIPs().get(0); | ||||||
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. In some clusters a service may not have any |
||||||
} | ||||||
} catch (ApiException e) { | ||||||
throw logAndThrowApiException(e); | ||||||
} | ||||||
} else { | ||||||
throw new RuntimeException("Can't get ip of a non running object."); | ||||||
} | ||||||
} | ||||||
|
||||||
@Override | ||||||
public void create() { | ||||||
try{ | ||||||
createdService.set(coreV1Api.createNamespacedService(NAMESPACE, serviceToCreate, null, null, null)); | ||||||
final String serviceName = createdService.get().getMetadata().getName(); | ||||||
|
||||||
if (this.waitStrategy != null) { | ||||||
try (Watch<V1Service> watch = Watch.createWatch( | ||||||
coreV1Api.getApiClient(), | ||||||
coreV1Api.listNamespacedServiceCall(NAMESPACE, null, null, null, | ||||||
null, null, null, null, null, true, null), | ||||||
new TypeToken<Watch.Response<V1Service>>() { | ||||||
}.getType())) { | ||||||
|
||||||
this.waitStrategy.apply(watch, createdService.get()); | ||||||
} catch (IOException | ApiException e) { | ||||||
if (e instanceof ApiException) { | ||||||
throw logAndThrowApiException((ApiException) e); | ||||||
} | ||||||
throw new RuntimeException(e); | ||||||
} | ||||||
} | ||||||
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> removeService(serviceName, coreV1Api))); | ||||||
onKubernetesObjectReady(); | ||||||
} catch (ApiException e) { | ||||||
throw logAndThrowApiException(e); | ||||||
} | ||||||
} | ||||||
|
||||||
@Override | ||||||
public void remove() { | ||||||
V1Service v1Service = this.createdService.get(); | ||||||
if (v1Service != null) { | ||||||
String serviceName = v1Service.getMetadata().getName(); | ||||||
removeService(serviceName, coreV1Api); | ||||||
} | ||||||
} | ||||||
|
||||||
private static void removeService(String serviceName, CoreV1Api coreV1Api) { | ||||||
try { | ||||||
coreV1Api.deleteNamespacedService(serviceName, NAMESPACE, null, null, null, null, null, null); | ||||||
} catch (ApiException e) { | ||||||
throw logAndThrowApiException(e); | ||||||
} catch (JsonSyntaxException e) { | ||||||
if (e.getCause() instanceof IllegalStateException) { | ||||||
IllegalStateException ise = (IllegalStateException) e.getCause(); | ||||||
if (ise.getMessage() != null && ise.getMessage().contains("Expected a string but was BEGIN_OBJECT")) { | ||||||
// Catching exception because of issue https://github.com/kubernetes-client/java/issues/86 | ||||||
} else throw e; | ||||||
} else throw e; | ||||||
} | ||||||
} | ||||||
|
||||||
@Override | ||||||
public Service withWaitStrategy(WaitStrategy waitStrategy) { | ||||||
this.waitStrategy = waitStrategy; | ||||||
return this; | ||||||
} | ||||||
|
||||||
protected static RuntimeException logAndThrowApiException(ApiException e) { | ||||||
LOGGER.severe("Kubernetes API replied with " + e.getCode() + " status code and body " + e.getResponseBody()); | ||||||
System.out.println("Kubernetes API replied with " + e.getCode() + " status code and body " + e.getResponseBody()); | ||||||
return new RuntimeException(e.getResponseBody(), e); | ||||||
} | ||||||
Comment on lines
+161
to
+165
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. I believe we should factorize this as well in the abstract class 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. thank you for your quick response! Hopefully this week I will find some time to look closer at your Points. I needed to create a more specific service for the pods. Instead of extending the service config over a pod, I thougth making an explictit service would be clearer. Best regards |
||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package com.github.jeanbaptistewatenberg.junit5kubernetes.core.impl; | ||
|
||
import com.github.jeanbaptistewatenberg.junit5kubernetes.core.Service; | ||
import com.github.jeanbaptistewatenberg.junit5kubernetes.core.wait.WaitStrategy; | ||
import io.kubernetes.client.fluent.VisitableBuilder; | ||
import io.kubernetes.client.openapi.models.V1Service; | ||
import io.kubernetes.client.openapi.models.V1ServiceBuilder; | ||
import io.kubernetes.client.openapi.models.V1ServiceFluent; | ||
import io.kubernetes.client.openapi.models.V1ServiceFluentImpl; | ||
|
||
public class GenericServiceBuilder extends V1ServiceFluentImpl<GenericServiceBuilder> implements VisitableBuilder<Service, GenericServiceBuilder> { | ||
|
||
private WaitStrategy waitStrategy; | ||
V1ServiceFluent<?> fluent; | ||
|
||
public GenericServiceBuilder() { | ||
this(new V1Service()); | ||
} | ||
|
||
public GenericServiceBuilder(V1ServiceFluent<?> fluent, V1Service instance) { | ||
|
||
this.fluent = fluent; | ||
fluent.withApiVersion(instance.getApiVersion()); | ||
fluent.withKind(instance.getKind()); | ||
fluent.withMetadata(instance.getMetadata()); | ||
fluent.withSpec(instance.getSpec()); | ||
fluent.withStatus(instance.getStatus()); | ||
} | ||
|
||
public GenericServiceBuilder(V1Service instance) { | ||
|
||
this.fluent = this; | ||
this.withApiVersion(instance.getApiVersion()); | ||
this.withKind(instance.getKind()); | ||
this.withMetadata(instance.getMetadata()); | ||
this.withSpec(instance.getSpec()); | ||
this.withStatus(instance.getStatus()); | ||
} | ||
|
||
@Override | ||
public Service build() { | ||
|
||
V1Service serviceToCreate = new V1ServiceBuilder() | ||
.withApiVersion(fluent.getApiVersion()) | ||
.withKind(fluent.getKind()) | ||
.withStatus(fluent.buildStatus()) | ||
.withMetadata(fluent.buildMetadata()) | ||
.withSpec(fluent.buildSpec()) | ||
.build(); | ||
|
||
Service buildable = new Service(serviceToCreate); | ||
return buildable.withWaitStrategy(waitStrategy); | ||
} | ||
|
||
public GenericServiceBuilder withWaitStrategy(WaitStrategy<V1Service> waitStrategy) { | ||
this.waitStrategy = waitStrategy; | ||
return this; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,46 @@ | ||||||
package com.github.jeanbaptistewatenberg.junit5kubernetes.core.wait.impl.service; | ||||||
|
||||||
import com.github.jeanbaptistewatenberg.junit5kubernetes.core.wait.WaitStrategy; | ||||||
import io.kubernetes.client.openapi.ApiException; | ||||||
import io.kubernetes.client.openapi.Configuration; | ||||||
import io.kubernetes.client.openapi.apis.CoreV1Api; | ||||||
import io.kubernetes.client.openapi.models.V1Service; | ||||||
import io.kubernetes.client.util.Watch; | ||||||
|
||||||
import java.time.LocalDateTime; | ||||||
|
||||||
public class ServiceWaitReadyStrategy extends WaitStrategy<V1Service> { | ||||||
|
||||||
private final CoreV1Api coreClient; | ||||||
|
||||||
public ServiceWaitReadyStrategy() { | ||||||
this.coreClient = new CoreV1Api(Configuration.getDefaultApiClient()); | ||||||
} | ||||||
|
||||||
@Override | ||||||
public void apply(Watch<V1Service> resourceWatch, V1Service createdResource) throws ApiException { | ||||||
|
||||||
boolean serviceSuccessfullyStarted = false; | ||||||
LocalDateTime startTime = LocalDateTime.now(); | ||||||
for (Watch.Response<V1Service> item : resourceWatch) { | ||||||
String name = item.object.getMetadata().getName(); | ||||||
if (name.equals(createdResource.getMetadata().getName())) { | ||||||
|
||||||
while (LocalDateTime.now().isBefore(startTime.plus(this.getTimeout()))) { | ||||||
|
||||||
V1Service retrievedService = coreClient.readNamespacedService(createdResource.getMetadata().getName(), | ||||||
createdResource.getMetadata().getNamespace(), null, null, null); | ||||||
if (retrievedService.getSpec().getExternalIPs() != null && retrievedService.getSpec().getExternalIPs().size() > 0) { | ||||||
serviceSuccessfullyStarted = true; | ||||||
break; | ||||||
} | ||||||
} | ||||||
break; | ||||||
} | ||||||
} | ||||||
|
||||||
if (!serviceSuccessfullyStarted) { | ||||||
throw new RuntimeException("Failed to run pod " + this + " before timeout " + this.getTimeout()); | ||||||
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.
Suggested change
|
||||||
} | ||||||
} | ||||||
} |
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.
We may want to factorize this client initiation in
kubernetesGenericObject
to avoid duplicating it fromPod.java
.