Skip to content

Commit

Permalink
NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl …
Browse files Browse the repository at this point in the history
…extends ServerSideApplicable
  • Loading branch information
pan3793 committed Apr 27, 2023
1 parent 85e5dfa commit a8e51e7
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* Fix #5033: port forwarding for clients other than okhttp needs to specify the subprotocol
* Fix #5044: disable Vert.x instance file caching
* Fix #5059: Vert.x InputStreamReader uses an empty Buffer sentinel to avoid NPE
* Fix #5073: `NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl` extends `ServerSideApplicable`

#### Improvements
* Fix #4434: Update CronJobIT to use `batch/v1` CronJob instead
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@

import java.util.List;

public interface ListVisitFromServerWritable<T> extends
DeletableWithOptions, CreateOrReplaceable<List<T>>, FieldValidateable<CreateOrReplaceable<List<T>>> {
public interface ListVisitFromServerWritable<T>
extends DeletableWithOptions,
CreateOrReplaceable<List<T>>,
ServerSideApplicable<List<T>>,
FieldValidateable<CreateOrReplaceable<List<T>>> {

}
Original file line number Diff line number Diff line change
Expand Up @@ -332,4 +332,18 @@ public List<HasMetadata> update() {
return performOperation(Resource::update);
}

@Override
public List<HasMetadata> serverSideApply() {
return performOperation(Resource::serverSideApply);
}

@Override
public ListVisitFromServerGetDeleteRecreateWaitApplicable<HasMetadata> fieldManager(String manager) {
return newInstance(context.withFieldManager(manager));
}

@Override
public ListVisitFromServerGetDeleteRecreateWaitApplicable<HasMetadata> forceConflicts() {
return newInstance(context.withForceConflicts());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,11 @@ public <C extends Client> C clientInWriteContext(Class<C> clazz) {
// operationcontext
OperationContext newContext = HasMetadataOperationsImpl.defaultContext(client).withDryRun(getDryRun())
.withGracePeriodSeconds(getGracePeriodSeconds()).withPropagationPolicy(getPropagationPolicy())
.withFieldValidation(this.fieldValidation);
.withFieldValidation(this.fieldValidation).withFieldManager(this.fieldManager);

if (Boolean.TRUE.equals(this.forceConflicts)) {
newContext = newContext.withForceConflicts();
}

// check before setting to prevent flipping the default flag
if (!Objects.equals(getNamespace(), newContext.getNamespace())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* 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.client.impl;

import io.fabric8.kubernetes.api.model.*;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.base.PatchType;
import io.fabric8.kubernetes.client.http.HttpClient;
import io.fabric8.kubernetes.client.http.HttpRequest;
import io.fabric8.kubernetes.client.http.HttpRequest.Builder;
import io.fabric8.kubernetes.client.http.StandardHttpRequest;
import io.fabric8.kubernetes.client.http.TestHttpResponse;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

class ServerSideApplyTest {

private HttpClient mockClient;
private KubernetesClient kubernetesClient;
private List<Builder> builders;

@BeforeEach
public void setUp() throws IOException {
builders = new ArrayList<>();
this.mockClient = Mockito.mock(HttpClient.class, Mockito.RETURNS_DEEP_STUBS);
when(mockClient.sendAsync(any(), Mockito.eq(byte[].class)))
.thenReturn(CompletableFuture.completedFuture(TestHttpResponse.from(200, "{}")));
Config config = new ConfigBuilder().withMasterUrl("https://localhost:8443/").build();
kubernetesClient = new KubernetesClientImpl(mockClient, config);
when(mockClient.newHttpRequestBuilder()).thenAnswer(answer -> {
HttpRequest.Builder result = Mockito.mock(HttpRequest.Builder.class, Mockito.RETURNS_SELF);
when(result.build()).thenReturn(new StandardHttpRequest.Builder().uri("https://localhost:8443/").build());
builders.add(result);
return result;
});
}

@AfterEach
void tearDown() {
kubernetesClient.close();
kubernetesClient = null;
}

@Test
void testResourceListServerSideApply() {
// Given
Pod pod = withPod("pod1");
Service svc = new ServiceBuilder().withNewMetadata().withName("svc1").endMetadata().build();
// When
kubernetesClient.resourceList(pod, svc).inNamespace("ns1").fieldManager("x")
.forceConflicts().serverSideApply();

// Then
verify(mockClient, times(2)).sendAsync(any(), any());
assertRequest(0, "PATCH", "/api/v1/namespaces/ns1/pods/pod1", "fieldManager=x&force=true",
PatchType.SERVER_SIDE_APPLY.getContentType());
assertRequest(1, "PATCH", "/api/v1/namespaces/ns1/services/svc1", "fieldManager=x&force=true",
PatchType.SERVER_SIDE_APPLY.getContentType());
}

private Pod withPod(String name) {
return new PodBuilder().withNewMetadata().withName(name).withNamespace("default").endMetadata().build();
}

private void assertRequest(int index, String method, String url, String queryParam, String contentType) {
ArgumentCaptor<URL> urlCaptor = ArgumentCaptor.forClass(URL.class);
Builder mock = builders.get(index);
verify(mock).url(urlCaptor.capture());
URL capturedURL = urlCaptor.getValue();
assertEquals(url, capturedURL.getPath());

validateMethod(method, contentType, mock);

assertEquals(queryParam, capturedURL.getQuery());
}

static void validateMethod(String method, String contentType, Builder mock) {
ArgumentCaptor<String> contentTypeCaptor = ArgumentCaptor.forClass(String.class);
switch (method) {
case "DELETE":
Mockito.verify(mock).delete(contentTypeCaptor.capture(), any());
break;
case "POST":
Mockito.verify(mock).post(contentTypeCaptor.capture(), any(String.class));
break;
case "PUT":
Mockito.verify(mock).put(contentTypeCaptor.capture(), any());
break;
case "PATCH":
Mockito.verify(mock).patch(contentTypeCaptor.capture(), any());
break;
default:
break; //TODO: validate GET, but that explicit call was removed
}

if (contentType != null) {
assertEquals(contentType, contentTypeCaptor.getValue());
}
}
}

0 comments on commit a8e51e7

Please sign in to comment.