Skip to content

Commit

Permalink
Merge pull request #738 from yue9944882/feat/generic-kubernetes-api-i…
Browse files Browse the repository at this point in the history
…nterface

Generic kubernetes api client
  • Loading branch information
k8s-ci-robot authored Jan 22, 2020
2 parents 0924fbf + ea084c4 commit a861a80
Show file tree
Hide file tree
Showing 12 changed files with 1,144 additions and 0 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.kubernetes.client.extended.generic;

import io.kubernetes.client.openapi.models.V1Status;

public class KubernetesApiResponse<DataType> {

private final DataType object;
private final V1Status status;
private final int httpStatusCode;

public KubernetesApiResponse(DataType object) {
this.object = object;
this.status = null;
this.httpStatusCode = -1;
}

public KubernetesApiResponse(V1Status status, int httpStatusCode) {
this.object = null;
this.status = status;
this.httpStatusCode = httpStatusCode;
}

public DataType getObject() {
return object;
}

public V1Status getStatus() {
return status;
}

public int getHttpStatusCode() {
return httpStatusCode;
}

public boolean isSuccess() {
return this.httpStatusCode < 400;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.kubernetes.client.extended.generic.options;

public class CreateOptions {
private Boolean dryRun;
// TODO: structured field-manager for better server-side apply integeration
private Object fieldManager;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.kubernetes.client.extended.generic.options;

import io.kubernetes.client.openapi.models.V1DeleteOptions;

public class DeleteOptions extends V1DeleteOptions {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.kubernetes.client.extended.generic.options;

public class GetOptions {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package io.kubernetes.client.extended.generic.options;

import com.google.gson.annotations.SerializedName;

public class ListOptions {
@SerializedName("fieldSelector")
private String fieldSelector;

@SerializedName("labelSelector")
private String labelSelector;

@SerializedName("resourceVersion")
private String resourceVersion;

@SerializedName("timeoutSeconds")
private Integer timeoutSeconds;

@SerializedName("limit")
private Integer limit;

@SerializedName("continue")
private String _continue;

public String getFieldSelector() {
return fieldSelector;
}

public void setFieldSelector(String fieldSelector) {
this.fieldSelector = fieldSelector;
}

public String getLabelSelector() {
return labelSelector;
}

public void setLabelSelector(String labelSelector) {
this.labelSelector = labelSelector;
}

public String getResourceVersion() {
return resourceVersion;
}

public void setResourceVersion(String resourceVersion) {
this.resourceVersion = resourceVersion;
}

public Integer getLimit() {
return limit;
}

public void setLimit(Integer limit) {
this.limit = limit;
}

public String getContinue() {
return _continue;
}

public void setContinue(String _continue) {
this._continue = _continue;
}

public Integer getTimeoutSeconds() {
return timeoutSeconds;
}

public void setTimeoutSeconds(Integer timeoutSeconds) {
this.timeoutSeconds = timeoutSeconds;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.kubernetes.client.extended.generic.options;

public class PatchOptions {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.kubernetes.client.extended.generic.options;

public class UpdateOptions {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.kubernetes.client.extended.generic;

import io.kubernetes.client.openapi.models.V1ObjectMeta;

public class FooCustomResource {
private V1ObjectMeta metadata;

public V1ObjectMeta getMetadata() {
return metadata;
}

public void setMetadata(V1ObjectMeta metadata) {
this.metadata = metadata;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

FooCustomResource that = (FooCustomResource) o;

return metadata != null ? metadata.equals(that.metadata) : that.metadata == null;
}

@Override
public int hashCode() {
return metadata != null ? metadata.hashCode() : 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.kubernetes.client.extended.generic;

import io.kubernetes.client.openapi.models.V1ListMeta;
import java.util.List;

public class FooCustomResourceList {
private V1ListMeta metadata;
private List<FooCustomResource> items;

public V1ListMeta getMetadata() {
return metadata;
}

public void setMetadata(V1ListMeta metadata) {
this.metadata = metadata;
}

public List<FooCustomResource> getItems() {
return items;
}

public void setItems(List<FooCustomResource> items) {
this.items = items;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

FooCustomResourceList that = (FooCustomResourceList) o;

if (metadata != null ? !metadata.equals(that.metadata) : that.metadata != null) return false;
return items != null ? items.equals(that.items) : that.items == null;
}

@Override
public int hashCode() {
int result = metadata != null ? metadata.hashCode() : 0;
result = 31 * result + (items != null ? items.hashCode() : 0);
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package io.kubernetes.client.extended.generic;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.junit.Assert.*;

import com.github.tomakehurst.wiremock.junit.WireMockRule;
import com.google.gson.Gson;
import io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.models.*;
import io.kubernetes.client.util.ClientBuilder;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

public class GenericKubernetesApiTest {

@Rule public WireMockRule wireMockRule = new WireMockRule(8181);

private GenericKubernetesApi<V1Job, V1JobList> jobClient;

@Before
public void setup() throws IOException {
ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + 8181).build();
jobClient =
new GenericKubernetesApi<>(V1Job.class, V1JobList.class, "batch", "v1", "jobs", apiClient);
}

// test delete
@Test
public void deleteNamespacedJobReturningStatus() {
V1Status status = new V1Status().kind("Status").code(200).message("good!");
stubFor(
delete(urlEqualTo("/apis/batch/v1/namespaces/default/jobs/foo1"))
.willReturn(aResponse().withStatus(200).withBody(new Gson().toJson(status))));

KubernetesApiResponse<V1Job> deleteJobResp = jobClient.delete("default", "foo1", null);
assertTrue(deleteJobResp.isSuccess());
assertEquals(status, deleteJobResp.getStatus());
assertNull(deleteJobResp.getObject());
verify(1, deleteRequestedFor(urlPathEqualTo("/apis/batch/v1/namespaces/default/jobs/foo1")));
}

@Test
public void deleteNamespacedJobReturningDeletedObject() {
V1Job foo1 =
new V1Job().kind("Job").metadata(new V1ObjectMeta().namespace("default").name("foo1"));

stubFor(
delete(urlEqualTo("/apis/batch/v1/namespaces/default/jobs/foo1"))
.willReturn(aResponse().withStatus(200).withBody(new Gson().toJson(foo1))));

KubernetesApiResponse<V1Job> deleteJobResp = jobClient.delete("default", "foo1");
assertTrue(deleteJobResp.isSuccess());
assertEquals(foo1, deleteJobResp.getObject());
assertNull(deleteJobResp.getStatus());
verify(1, deleteRequestedFor(urlPathEqualTo("/apis/batch/v1/namespaces/default/jobs/foo1")));
}

@Test
public void deleteNamespacedJobReturningForbiddenStatus() {
V1Status status = new V1Status().kind("Status").code(403).message("good!");

stubFor(
delete(urlEqualTo("/apis/batch/v1/namespaces/default/jobs/foo1"))
.willReturn(aResponse().withStatus(403).withBody(new Gson().toJson(status))));

KubernetesApiResponse<V1Job> deleteJobResp = jobClient.delete("default", "foo1");
assertFalse(deleteJobResp.isSuccess());
assertEquals(status, deleteJobResp.getStatus());
assertNull(deleteJobResp.getObject());
verify(1, deleteRequestedFor(urlPathEqualTo("/apis/batch/v1/namespaces/default/jobs/foo1")));
}

@Test
public void listNamespacedJobReturningObject() {
V1JobList jobList = new V1JobList().kind("JobList").metadata(new V1ListMeta());

stubFor(
get(urlEqualTo("/apis/batch/v1/namespaces/default/jobs"))
.willReturn(aResponse().withStatus(200).withBody(new Gson().toJson(jobList))));
KubernetesApiResponse<V1JobList> jobListResp = jobClient.list("default");
assertTrue(jobListResp.isSuccess());
assertEquals(jobList, jobListResp.getObject());
assertNull(jobListResp.getStatus());
verify(1, getRequestedFor(urlPathEqualTo("/apis/batch/v1/namespaces/default/jobs")));
}

@Test
public void listClusterJobReturningObject() {
V1JobList jobList = new V1JobList().kind("JobList").metadata(new V1ListMeta());

stubFor(
get(urlEqualTo("/apis/batch/v1/jobs"))
.willReturn(aResponse().withStatus(200).withBody(new Gson().toJson(jobList))));
KubernetesApiResponse<V1JobList> jobListResp = jobClient.list();
assertTrue(jobListResp.isSuccess());
assertEquals(jobList, jobListResp.getObject());
assertNull(jobListResp.getStatus());
verify(1, getRequestedFor(urlPathEqualTo("/apis/batch/v1/jobs")));
}

@Test
public void createNamespacedJobReturningObject() {
V1Job foo1 =
new V1Job().kind("Job").metadata(new V1ObjectMeta().namespace("default").name("foo1"));

stubFor(
post(urlEqualTo("/apis/batch/v1/namespaces/default/jobs"))
.willReturn(aResponse().withStatus(200).withBody(new Gson().toJson(foo1))));
KubernetesApiResponse<V1Job> jobListResp = jobClient.create(foo1);
assertTrue(jobListResp.isSuccess());
assertEquals(foo1, jobListResp.getObject());
assertNull(jobListResp.getStatus());
verify(1, postRequestedFor(urlPathEqualTo("/apis/batch/v1/namespaces/default/jobs")));
}

@Test
public void updateNamespacedJobReturningObject() {
V1Job foo1 =
new V1Job().kind("Job").metadata(new V1ObjectMeta().namespace("default").name("foo1"));

stubFor(
put(urlEqualTo("/apis/batch/v1/namespaces/default/jobs/foo1"))
.willReturn(aResponse().withStatus(200).withBody(new Gson().toJson(foo1))));
KubernetesApiResponse<V1Job> jobListResp = jobClient.update(foo1);
assertTrue(jobListResp.isSuccess());
assertEquals(foo1, jobListResp.getObject());
assertNull(jobListResp.getStatus());
verify(1, putRequestedFor(urlPathEqualTo("/apis/batch/v1/namespaces/default/jobs/foo1")));
}

@Test
public void patchNamespacedJobReturningObject() {
V1Patch v1Patch = new V1Patch("{}");
V1Job foo1 =
new V1Job().kind("Job").metadata(new V1ObjectMeta().namespace("default").name("foo1"));
stubFor(
patch(urlEqualTo("/apis/batch/v1/namespaces/default/jobs/foo1"))
.withHeader("Content-Type", containing(V1Patch.PATCH_FORMAT_STRATEGIC_MERGE_PATCH))
.willReturn(aResponse().withStatus(200).withBody(new Gson().toJson(foo1))));
KubernetesApiResponse<V1Job> jobPatchResp =
jobClient.patch("default", "foo1", V1Patch.PATCH_FORMAT_STRATEGIC_MERGE_PATCH, v1Patch);

assertTrue(jobPatchResp.isSuccess());
assertEquals(foo1, jobPatchResp.getObject());
assertNull(jobPatchResp.getStatus());
verify(1, patchRequestedFor(urlPathEqualTo("/apis/batch/v1/namespaces/default/jobs/foo1")));
}

@Test
public void testReadTimeoutShouldThrowException() {
ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + 8181).build();
apiClient.setHttpClient(
apiClient
.getHttpClient()
.newBuilder()
.readTimeout(1, TimeUnit.MILLISECONDS) // timeout everytime
.build());
jobClient =
new GenericKubernetesApi<>(V1Job.class, V1JobList.class, "batch", "v1", "jobs", apiClient);
try {
KubernetesApiResponse<V1Job> response = jobClient.get("foo", "test");
} catch (Throwable t) {
assertTrue(t.getCause() instanceof SocketTimeoutException);
return;
}
fail("no exception happened");
}
}
Loading

0 comments on commit a861a80

Please sign in to comment.