Skip to content

Commit

Permalink
Add evict(Eviction) DSL method for policy v1 Eviction resource
Browse files Browse the repository at this point in the history
policy/v1 Eviction was introduced in Kubernetes 1.22.1 . There was
already some work done for eviction API in #2249

Right now, we have an evict() DSL method which builds up policy/v1beta1 Eviction
objecti internally.

Adding evict(io.fabric8.kubernetes.api.model.policy.v1.Eviction) method
to Evictable interface in order to allow use of policy/v1 Eviction.

Signed-off-by: Rohan Kumar <rohaan@redhat.com>
  • Loading branch information
rohanKanojia committed Sep 17, 2021
1 parent fc3fc4d commit 00401de
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 9 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ jobs:
strategy:
fail-fast: false
matrix:
kubernetes: [v1.20.1,v1.19.2,v1.12.0]
kubernetes: [v1.22.1,v1.20.1,v1.19.2,v1.14.0]
steps:
- name: Checkout
uses: actions/checkout@v2.3.4
- name: Setup Minikube-Kubernetes
uses: manusa/actions-setup-minikube@v2.4.2
with:
minikube version: v1.16.0
minikube version: v1.23.0
kubernetes version: ${{ matrix.kubernetes }}
github token: ${{ secrets.GITHUB_TOKEN }}
start args: '--addons=metrics-server --force'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,22 @@
*/
package io.fabric8.kubernetes.client.dsl;

import io.fabric8.kubernetes.api.model.policy.v1.Eviction;

public interface Evictable<B> {
/**
* Evicts resource, respecting {@link io.fabric8.kubernetes.api.model.policy.v1beta1.PodDisruptionBudget}
* @return value indicating object was evicted or not
* @throws io.fabric8.kubernetes.client.KubernetesClientException if an error occurs.
*/
B evict();

/**
* Evicts a pod from its node subject to certain policies and safety constraints.
*
* @param eviction Eviction object
* @return value indicating object was evicted or not
* @throws io.fabric8.kubernetes.client.KubernetesClientException if an error occurs.
*/
B evict(Eviction eviction);
}
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,23 @@ protected <T> T handleGet(URL resourceUrl, Class<T> type, Map<String, String> pa
return handleResponse(requestBuilder, type, parameters);
}

/**
* Sends a POST request to /eviction endpoint of specified resource
*
* @param evictionAsStr request body in form of JSON string(serialized form of Eviction)
* @param podUrl URL of Pod
* @throws IOException IOException
* @throws ExecutionException Execution Exception
* @throws InterruptedException Interrupted Exception
*/
protected void handleEviction(String evictionAsStr, URL podUrl) throws IOException, ExecutionException, InterruptedException {
RequestBody requestBody = RequestBody.create(JSON, evictionAsStr);

URL requestUrl = new URL(URLUtils.join(podUrl.toString(), "eviction"));
Request.Builder requestBuilder = new Request.Builder().post(requestBody).url(requestUrl);
handleResponse(requestBuilder, null, Collections.emptyMap());
}

/**
* Send an http request and handle the response.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -75,7 +74,6 @@
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import io.fabric8.kubernetes.client.lib.FilenameUtils;
Expand Down Expand Up @@ -280,6 +278,30 @@ public Boolean evict() {
}
}

@Override
public Boolean evict(io.fabric8.kubernetes.api.model.policy.v1.Eviction eviction) {
try {
if (Utils.isNullOrEmpty(eviction.getMetadata().getNamespace())) {
throw new KubernetesClientException("Namespace not specified, but operation requires it.");
}
if (Utils.isNullOrEmpty(eviction.getMetadata().getName())) {
throw new KubernetesClientException("Name not specified, but operation requires it.");
}
handleEviction(JSON_MAPPER.writeValueAsString(eviction), getResourceUrl());
return true;
} catch (KubernetesClientException e) {
if (e.getCode() != HttpURLConnection.HTTP_NOT_FOUND && e.getCode() != HTTP_TOO_MANY_REQUESTS) {
throw e;
}
return false;
} catch (ExecutionException | IOException exception) {
throw KubernetesClientException.launderThrowable(forOperationType("evict"), exception);
} catch (InterruptedException interruptedException) {
Thread.currentThread().interrupt();
throw KubernetesClientException.launderThrowable(forOperationType("evict"), interruptedException);
}
}

private void evictThis() {
try {
if (Utils.isNullOrEmpty(getNamespace())) {
Expand All @@ -303,11 +325,7 @@ private void handleEvict(URL podUrl, String namespace, String name) throws Execu
.withDeleteOptions(new DeleteOptions())
.build();

RequestBody requestBody = RequestBody.create(JSON, JSON_MAPPER.writeValueAsString(eviction));

URL requestUrl = new URL(URLUtils.join(podUrl.toString(), "eviction"));
Request.Builder requestBuilder = new Request.Builder().post(requestBody).url(requestUrl);
handleResponse(requestBuilder, null, Collections.<String, String>emptyMap());
handleEviction(JSON_MAPPER.writeValueAsString(eviction), podUrl);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* 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.kubernetes;

import io.fabric8.commons.AssumingK8sVersionAtLeast;
import io.fabric8.commons.ClusterEntity;
import io.fabric8.kubernetes.api.model.policy.v1.Eviction;
import io.fabric8.kubernetes.api.model.policy.v1.EvictionBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.arquillian.cube.kubernetes.api.Session;
import org.arquillian.cube.kubernetes.impl.requirement.RequiresKubernetes;
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

@RunWith(ArquillianConditionalRunner.class)
@RequiresKubernetes
public class PodEvictionIT {

@ArquillianResource
public KubernetesClient client;

@ArquillianResource
public Session session;


@ClassRule
public static final AssumingK8sVersionAtLeast assumingK8sVersion = new AssumingK8sVersionAtLeast("1", "22");

@BeforeClass
public static void init() {
ClusterEntity.apply(PodIT.class.getResourceAsStream("/podeviction-it.yml"));
}

@Test
public void evictWithV1PolicyEviction() {
// Given
String podName = "podeviction-it-with-v1-policy-eviction";
Eviction eviction = new EvictionBuilder()
.withNewMetadata()
.withNamespace(session.getNamespace())
.withName(podName)
.endMetadata()
.build();

// When
boolean evicted = client.pods().inNamespace(session.getNamespace()).withName(podName).evict(eviction);

// Then
assertTrue(evicted);
}

@Test
public void evictWithV1PolicyEvictionNonExistentPod() {
// Given
String podName = "podit-evict-non-existent-pod";
Eviction eviction = new EvictionBuilder()
.withNewMetadata()
.withNamespace(session.getNamespace())
.withName(podName)
.endMetadata()
.build();

// When
boolean evicted = client.pods().inNamespace(session.getNamespace()).withName(podName).evict(eviction);

// Then
assertFalse(evicted);
}

@AfterClass
public static void cleanup() {
ClusterEntity.remove(NetworkPolicyIT.class.getResourceAsStream("/podeviction-it.yml"));
}
}
26 changes: 26 additions & 0 deletions kubernetes-itests/src/test/resources/podeviction-it.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#
# 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.
#

---
apiVersion: v1
kind: Pod
metadata:
name: podeviction-it-with-v1-policy-eviction
spec:
containers:
- name: alpine
image: alpine:3.13.6
command: ["sleep", "36000"]
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@

package io.fabric8.kubernetes.client.mock;

import io.fabric8.kubernetes.api.model.DeleteOptionsBuilder;
import io.fabric8.kubernetes.api.model.DeletionPropagation;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodBuilder;
import io.fabric8.kubernetes.api.model.PodList;
import io.fabric8.kubernetes.api.model.PodListBuilder;
import io.fabric8.kubernetes.api.model.WatchEvent;
import io.fabric8.kubernetes.api.model.policy.v1.EvictionBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.LocalPortForward;
Expand Down Expand Up @@ -50,6 +52,7 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
Expand Down Expand Up @@ -250,6 +253,27 @@ void testEvict() {
assertThrows(KubernetesClientException.class, resource::evict);
}

@Test
void testEvictWithPolicyV1Eviction() {
// Given
server.expect().post()
.withPath("/api/v1/namespaces/ns1/pods/foo/eviction")
.andReturn(HttpURLConnection.HTTP_OK, new PodBuilder().build())
.once();

// When
boolean evicted = client.pods().inNamespace("ns1").withName("foo").evict(new EvictionBuilder()
.withNewMetadata()
.withName("foo")
.withNamespace("ns1")
.endMetadata()
.withDeleteOptions(new DeleteOptionsBuilder().build())
.build());

// Then
assertTrue(evicted);
}

@Test
void testCreateWithNameMismatch() {
Pod pod1 = new PodBuilder().withNewMetadata().withName("pod1").withNamespace("test").and().build();
Expand Down

0 comments on commit 00401de

Please sign in to comment.