Skip to content
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

Improve some integration-tests and cleanup /v2/info endpoint. fix #1229 #1249 #1250

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import io.netty.handler.codec.http.HttpHeaders;
import java.util.Optional;
import java.util.Queue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.cloudfoundry.reactor.util.JsonCodec;
Expand Down Expand Up @@ -77,6 +78,12 @@ public final Mono<String> getRoot(String key, ConnectionContext connectionContex
return connectionContext.getCacheDuration().map(cached::cache).orElseGet(cached::cache);
}

@Override
public final Mono<String> getRootKey(Queue<String> key, ConnectionContext connectionContext) {
Mono<String> cached = doGetRootKey(key, connectionContext);
return connectionContext.getCacheDuration().map(cached::cache).orElseGet(cached::cache);
}

@Override
public final Mono<String> getRoot(ConnectionContext connectionContext) {
Mono<String> cached =
Expand All @@ -92,6 +99,9 @@ public final Mono<String> getRoot(ConnectionContext connectionContext) {
protected abstract Mono<UriComponents> doGetRoot(
String key, ConnectionContext connectionContext);

protected abstract Mono<String> doGetRootKey(
Queue<String> key, ConnectionContext connectionContext);

protected final UriComponents getRoot() {
UriComponentsBuilder builder =
UriComponentsBuilder.newInstance().scheme("https").host(getApiHost());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.cloudfoundry.reactor;

import java.util.Queue;
import reactor.core.publisher.Mono;

/**
Expand All @@ -32,11 +33,24 @@ public interface RootProvider {
Mono<String> getRoot(ConnectionContext connectionContext);

/**
* The normalized root for a given key
* The normalized root for a given key.
* The "href" entry for the given key is returned from the root endpoint.
* If the endpoint does not provide a port, it will be added at nomalisation.
*
* @param key the key to look up root from
* @param connectionContext a {@link ConnectionContext} to be used if the roo needs to be retrieved via a network request
* @param key the key to look up from root
* @param connectionContext a {@link ConnectionContext} to be used if the root needs to be retrieved via a network request
* @return the normalized API root
*/
Mono<String> getRoot(String key, ConnectionContext connectionContext);

/**
* The literal String value for a given key. May also access structured fields from the root endpoint,
* like "links.cloud_controller_v2.meta.version".
* Null values from the endpoint are translated to an empty String.
*
* @param keyList the key(s) to look up from root. Nested keys are added at the end of the queue.
* @param connectionContext a {@link ConnectionContext} to be used if the root needs to be retrieved via a network request
* @return the plain value for the given key
*/
Mono<String> getRootKey(Queue<String> keyList, ConnectionContext connectionContext);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,65 +17,79 @@
package org.cloudfoundry.reactor;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.LinkedList;
import java.util.Queue;

import org.immutables.value.Value;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import reactor.core.publisher.Mono;

/**
* A {@link RootProvider} that returns endpoints by delegating to an {@link RootPayloadRootProvider} and then an {@link InfoPayloadRootProvider}.
* A {@link RootProvider} that returns endpoints by delegating to an
* {@link RootPayloadRootProvider} and then an {@link InfoPayloadRootProvider}.
*/
@Value.Immutable
abstract class _DelegatingRootProvider extends AbstractRootProvider {

@Override
protected Mono<UriComponents> doGetRoot(ConnectionContext connectionContext) {
return getRootPayloadRootProvider().doGetRoot(connectionContext)
.onErrorResume(t -> getInfoPayloadRootProvider().doGetRoot(connectionContext));
}
@Override
protected Mono<UriComponents> doGetRoot(ConnectionContext connectionContext) {
return getRootPayloadRootProvider().doGetRoot(connectionContext)
.onErrorResume(t -> getInfoPayloadRootProvider().doGetRoot(connectionContext));
}

@Override
protected Mono<UriComponents> doGetRoot(String key, ConnectionContext connectionContext) {
return getRootPayloadRootProvider().doGetRoot(key, connectionContext).onErrorResume(t -> {
// if root does not return a value, try with info object
if ("cloud_controller_v2".equals(key)) {
return getInfoPayloadRootProvider().doGetRoot(connectionContext)
.map(uri -> UriComponentsBuilder.newInstance().uriComponents(uri).pathSegment("v2").build());
} else if ("cloud_controller_v3".equals(key)) {
return getInfoPayloadRootProvider().doGetRoot(connectionContext)
.map(uri -> UriComponentsBuilder.newInstance().uriComponents(uri).pathSegment("v3").build());
} else if ("logging".equals(key)) {
return getInfoPayloadRootProvider().doGetRoot("doppler_logging_endpoint", connectionContext);
} else if ("routing".equals(key)) {
return getInfoPayloadRootProvider().doGetRoot("routing_endpoint", connectionContext);
} else if ("uaa".equals(key)) {
return getInfoPayloadRootProvider().doGetRoot("token_endpoint", connectionContext);
} else if ("login".equals(key)) {
return getInfoPayloadRootProvider().doGetRoot("authorization_endpoint", connectionContext);
} else {
return getInfoPayloadRootProvider().doGetRoot(key, connectionContext);
}
});
}

@Override
protected Mono<String> doGetRootKey(Queue<String> keyList, ConnectionContext connectionContext) {
Queue<String> keyCopy = new LinkedList<>(keyList);
return getRootPayloadRootProvider().doGetRootKey(keyList, connectionContext).onErrorResume(t -> {
return getInfoV3PayloadRootProvider().doGetRootKey(keyCopy, connectionContext);
});
}

@Override
protected Mono<UriComponents> doGetRoot(String key, ConnectionContext connectionContext) {
return getRootPayloadRootProvider().doGetRoot(key, connectionContext)
.onErrorResume(t -> {
if ("cloud_controller_v2".equals(key)) {
return getInfoPayloadRootProvider().doGetRoot(connectionContext)
.map(uri -> UriComponentsBuilder.newInstance().uriComponents(uri).pathSegment("v2").build());
} else if ("cloud_controller_v3".equals(key)) {
return getInfoPayloadRootProvider().doGetRoot(connectionContext)
.map(uri -> UriComponentsBuilder.newInstance().uriComponents(uri).pathSegment("v3").build());
} else if ("logging".equals(key)) {
return getInfoPayloadRootProvider().doGetRoot("doppler_logging_endpoint", connectionContext);
} else if ("routing".equals(key)) {
return getInfoPayloadRootProvider().doGetRoot("routing_endpoint", connectionContext);
} else if ("uaa".equals(key)) {
return getInfoPayloadRootProvider().doGetRoot("token_endpoint", connectionContext);
} else {
return getInfoPayloadRootProvider().doGetRoot(key, connectionContext);
}
});
}
@Value.Derived
InfoPayloadRootProvider getInfoPayloadRootProvider() {
return InfoPayloadRootProvider.builder().apiHost(getApiHost()).objectMapper(getObjectMapper()).port(getPort())
.secure(getSecure()).build();
}

@Value.Derived
InfoPayloadRootProvider getInfoPayloadRootProvider() {
return InfoPayloadRootProvider.builder()
.apiHost(getApiHost())
.objectMapper(getObjectMapper())
.port(getPort())
.secure(getSecure())
.build();
}
@Value.Derived
InfoV3PayloadRootProvider getInfoV3PayloadRootProvider() {
return InfoV3PayloadRootProvider.builder().apiHost(getApiHost()).objectMapper(getObjectMapper()).port(getPort())
.secure(getSecure()).build();
}

abstract ObjectMapper getObjectMapper();
abstract ObjectMapper getObjectMapper();

@Value.Derived
RootPayloadRootProvider getRootPayloadRootProvider() {
return RootPayloadRootProvider.builder()
.apiHost(getApiHost())
.objectMapper(getObjectMapper())
.port(getPort())
.secure(getSecure())
.build();
}
@Value.Derived
RootPayloadRootProvider getRootPayloadRootProvider() {
return RootPayloadRootProvider.builder().apiHost(getApiHost()).objectMapper(getObjectMapper()).port(getPort())
.secure(getSecure()).build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.Queue;

/**
* A {@link RootProvider} that returns endpoints extracted from the `/v2/info` API for the configured endpoint.
Expand All @@ -45,6 +46,17 @@ protected Mono<UriComponents> doGetRoot(String key, ConnectionContext connection
});
}

protected Mono<String> doGetRootKey(Queue<String> keyList, ConnectionContext connectionContext) {
String key = keyList.poll();
return getInfo(connectionContext)
.map(info -> {
if (!info.containsKey(key)) {
throw new IllegalArgumentException(String.format("Info payload does not contain key '%s'", key));
}
return info.get(key);
});
}

abstract ObjectMapper getObjectMapper();

private UriComponentsBuilder buildInfoUri(UriComponentsBuilder root) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* 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 org.cloudfoundry.reactor;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.immutables.value.Value;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.Queue;

/**
* A {@link RootProvider} that returns endpoints extracted from the `/v3/info` API for the configured endpoint.
*/
@Value.Immutable
abstract class _InfoV3PayloadRootProvider extends AbstractRootProvider {

protected Mono<UriComponents> doGetRoot(ConnectionContext connectionContext) {
return Mono.just(getRoot());
}

protected Mono<UriComponents> doGetRoot(String key, ConnectionContext connectionContext) {
return getInfo(connectionContext)
.map(info -> {
if (!info.containsKey(key)) {
throw new IllegalArgumentException(String.format("InfoV3 payload does not contain key '%s'", key));
}

return normalize(UriComponentsBuilder.fromUriString((String) info.get(key)));
});
}

protected Mono<String> doGetRootKey(Queue<String> keyList, ConnectionContext connectionContext) {
String firstKey = keyList.poll();

@SuppressWarnings("rawtypes")
Mono<Map> payload = getInfo(connectionContext);
return payload
.map(info -> {
if (!info.containsKey(firstKey)) {
throw new IllegalArgumentException(String.format("InfoV3 payload does not contain key '%s'", firstKey));
}
return handleEntry(keyList,info.get(firstKey));
});

}

private String handleEntry(Queue<String> keyList, Object entry) {
if(entry==null) {
return "";
} else if(entry instanceof String) {
if(keyList.isEmpty()) {
return (String) entry;
}else {
throw new IllegalArgumentException(String.format("InfoV3 payload does not contain key '%s'", keyList.peek()));
}
}else if(entry instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> entryMap = (Map<String, Object>) entry;
String key = keyList.poll();
return handleEntry(keyList, entryMap.get(key));
} else{
throw new IllegalArgumentException(String.format("InfoV3 payload does contain unknown type '%s'", entry.getClass().getName()));
}
}

abstract ObjectMapper getObjectMapper();

private UriComponentsBuilder buildInfoUri(UriComponentsBuilder root) {
return root.pathSegment("v3", "info");
}

@SuppressWarnings("rawtypes")
@Value.Derived
private Mono<Map> getInfo(ConnectionContext connectionContext) {
return createOperator(connectionContext)
.flatMap(operator -> operator.get()
.uri(this::buildInfoUri)
.response()
.parseBody(Map.class))
.switchIfEmpty(Mono.error(new IllegalArgumentException("InfoV3 endpoint does not contain a payload")))
.checkpoint();
}

}
Loading